Skip to content

Commit d5ac379

Browse files
committed
feat: implement UCD Explorer commands and initialize view
1 parent dddda22 commit d5ac379

9 files changed

Lines changed: 190 additions & 142 deletions

File tree

src/commands/browse-ucd-files.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { executeCommand, useCommand } from "reactive-vscode";
2+
import * as Meta from "../generated/meta";
3+
4+
export function useBrowseUcdFilesCommand() {
5+
useCommand(Meta.commands.browseUcdFiles, async () => {
6+
executeCommand("ucd:explorer.focus");
7+
});
8+
}

src/commands/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { useBrowseUcdFilesCommand } from "./browse-ucd-files";
2+
import { useOpenEntryCommand } from "./open-entry";
3+
import { useRefreshExplorerCommand } from "./refresh-explorer";
4+
import { useVisualizeFileCommand } from "./visualize-file";
5+
6+
export function registerCommands() {
7+
useBrowseUcdFilesCommand();
8+
useRefreshExplorerCommand();
9+
useVisualizeFileCommand();
10+
useOpenEntryCommand();
11+
}

src/commands/open-entry.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { TreeViewNode } from "reactive-vscode";
2+
import type { UCDTreeItem } from "../composables/useUCDExplorer";
3+
import { hasUCDFolderPath } from "@luxass/unicode-utils";
4+
import { executeCommand, useCommand } from "reactive-vscode";
5+
import { Uri, window } from "vscode";
6+
import * as Meta from "../generated/meta";
7+
import { logger } from "../logger";
8+
9+
export function useOpenEntryCommand() {
10+
useCommand(Meta.commands.openEntry, async (versionOrTreeView: string | TreeViewNode, filePath?: string) => {
11+
if (versionOrTreeView == null) {
12+
logger.error("No entry provided to openEntry command.");
13+
return;
14+
}
15+
16+
if (typeof versionOrTreeView === "object" && "treeItem" in versionOrTreeView) {
17+
const treeView = versionOrTreeView;
18+
19+
if (!treeView.treeItem || !(treeView.treeItem as UCDTreeItem).__ucd) {
20+
logger.error("Invalid entry provided to openEntry command.");
21+
return;
22+
}
23+
24+
const ucdItem = (treeView.treeItem as UCDTreeItem).__ucd;
25+
if (!ucdItem) {
26+
logger.error("UCD item is undefined or null.");
27+
return;
28+
}
29+
30+
if (!ucdItem?.ucdUrl) {
31+
logger.error("UCD item does not have a valid URL.");
32+
return;
33+
}
34+
35+
executeCommand("vscode.open", Uri.parse(ucdItem.ucdUrl));
36+
return;
37+
}
38+
39+
const version = versionOrTreeView;
40+
if (!filePath) {
41+
logger.error("File path is required when version is provided as string.");
42+
return;
43+
}
44+
45+
await window.showTextDocument(Uri.parse(`ucd:${version}/${hasUCDFolderPath(version) ? "ucd/" : ""}${filePath}`));
46+
});
47+
}

src/commands/refresh-explorer.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { useCommand } from "reactive-vscode";
2+
import { useUCDExplorer } from "../composables/useUCDExplorer";
3+
import * as Meta from "../generated/meta";
4+
import { logger } from "../logger";
5+
6+
export function useRefreshExplorerCommand() {
7+
useCommand(Meta.commands.refreshExplorer, async () => {
8+
logger.info("Refreshing UCD Explorer...");
9+
10+
useUCDExplorer().refresh();
11+
12+
logger.info("UCD Explorer refreshed.");
13+
});
14+
}

src/commands/visualize-file.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { useCommand } from "reactive-vscode";
2+
import { window } from "vscode";
3+
import * as Meta from "../generated/meta";
4+
5+
export function useVisualizeFileCommand() {
6+
useCommand(Meta.commands.visualizeFile, () => {
7+
window.showErrorMessage("This command is not implemented yet.");
8+
});
9+
}

src/composables/useUCDExplorer.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import type { TreeViewNode } from "reactive-vscode";
2+
import type { TreeItem } from "vscode";
3+
import { UNICODE_VERSION_METADATA } from "@luxass/unicode-utils";
4+
import { computed, createSingletonComposable, ref } from "reactive-vscode";
5+
import { ThemeIcon, TreeItemCollapsibleState } from "vscode";
6+
import { getFilesByVersion } from "../lib/files";
7+
import { logger } from "../logger";
8+
import { useUCDStore } from "./useUCDStore";
9+
10+
export interface UCDTreeItem extends TreeItem {
11+
__ucd?: {
12+
ucdUrl: string;
13+
version: string;
14+
};
15+
}
16+
17+
export const useUCDExplorer = createSingletonComposable(() => {
18+
const store = useUCDStore();
19+
20+
const childrenCache = ref<Map<string, TreeViewNode[]>>(new Map());
21+
const loadingPromises = ref<Map<string, Promise<TreeViewNode[]>>>(new Map());
22+
23+
function refresh() {
24+
childrenCache.value.clear();
25+
loadingPromises.value.clear();
26+
}
27+
28+
async function loadChildrenForVersion(version: string): Promise<TreeViewNode[]> {
29+
const cached = childrenCache.value.get(version);
30+
if (cached) {
31+
return cached;
32+
}
33+
34+
// return existing promise if already loading (prevents duplicate requests)
35+
const existingPromise = loadingPromises.value.get(version);
36+
if (existingPromise) {
37+
return existingPromise;
38+
}
39+
40+
// create new loading promise
41+
const loadingPromise = (async () => {
42+
try {
43+
const files = await getFilesByVersion(store.value!, version);
44+
childrenCache.value.set(version, files);
45+
return files;
46+
} catch (error) {
47+
logger.error(`Failed to load files for version ${version}:`, error);
48+
return [];
49+
} finally {
50+
// clean up loading promise regardless of success/failure
51+
loadingPromises.value.delete(version);
52+
}
53+
})();
54+
55+
// store the promise to prevent duplicate requests
56+
loadingPromises.value.set(version, loadingPromise);
57+
return loadingPromise;
58+
}
59+
60+
const nodes = computed<TreeViewNode[]>(() =>
61+
UNICODE_VERSION_METADATA.map((metadata) => {
62+
const version = metadata.version;
63+
return {
64+
treeItem: {
65+
iconPath: new ThemeIcon("folder"),
66+
label: metadata.version + (metadata.status === "draft" ? " (Draft)" : ""),
67+
description: metadata.date ? `Released in ${metadata.date}` : "",
68+
tooltip: `Documentation: ${metadata.documentationUrl}\nUCD URL: ${metadata.ucdUrl}`,
69+
collapsibleState: TreeItemCollapsibleState.Collapsed,
70+
contextValue: "ucd:version-folder",
71+
__ucd: metadata,
72+
} as UCDTreeItem,
73+
children: childrenCache.value.get(version) || [],
74+
};
75+
}),
76+
);
77+
78+
return {
79+
nodes,
80+
loadChildrenForVersion,
81+
refresh,
82+
};
83+
});

src/extension.ts

Lines changed: 8 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,17 @@
1-
import type { TreeViewNode } from "reactive-vscode";
2-
import type { UCDTreeItem } from "./views/ucd-explorer";
3-
import { hasUCDFolderPath } from "@luxass/unicode-utils";
4-
import { defineExtension, executeCommand, useActiveTextEditor, useCommand, useEditorDecorations } from "reactive-vscode";
5-
import { Range, Uri, window } from "vscode";
1+
import { defineExtension } from "reactive-vscode";
2+
import { registerCommands } from "./commands";
63
import { useUCDContentProvider } from "./composables/useUCDContentProvider";
7-
import { useUCDStore } from "./composables/useUCDStore";
8-
import * as Meta from "./generated/meta";
94
import { logger } from "./logger";
10-
import { useUCDExplorer } from "./views/ucd-explorer";
5+
import { initializeUCDExplorerView } from "./views/ucd-explorer";
116

127
const { activate, deactivate } = defineExtension(async () => {
13-
useCommand(Meta.commands.browseUcdFiles, async () => {
14-
executeCommand("ucd:explorer.focus");
15-
});
16-
17-
useCommand(Meta.commands.refreshExplorer, async () => {
18-
logger.info("Refreshing UCD Explorer...");
19-
logger.info("UCD Explorer refreshed.");
20-
21-
const store = useUCDStore();
22-
if (!store.value) {
23-
logger.error("UCD Store is not initialized.");
24-
return;
25-
}
26-
27-
store.refresh();
28-
});
29-
30-
useCommand(Meta.commands.visualizeFile, () => {
31-
window.showErrorMessage("This command is not implemented yet.");
32-
});
33-
34-
useCommand(Meta.commands.openEntry, async (versionOrTreeView: string | TreeViewNode, filePath?: string) => {
35-
if (versionOrTreeView == null) {
36-
logger.error("No entry provided to openEntry command.");
37-
return;
38-
}
39-
40-
if (typeof versionOrTreeView === "object" && "treeItem" in versionOrTreeView) {
41-
const treeView = versionOrTreeView;
42-
43-
if (!treeView.treeItem || !(treeView.treeItem as UCDTreeItem).__ucd) {
44-
logger.error("Invalid entry provided to openEntry command.");
45-
return;
46-
}
47-
48-
const ucdItem = (treeView.treeItem as UCDTreeItem).__ucd;
49-
if (!ucdItem) {
50-
logger.error("UCD item is undefined or null.");
51-
return;
52-
}
53-
54-
if (!ucdItem?.ucdUrl) {
55-
logger.error("UCD item does not have a valid URL.");
56-
return;
57-
}
58-
59-
executeCommand("vscode.open", Uri.parse(ucdItem.ucdUrl));
60-
return;
61-
}
62-
63-
const version = versionOrTreeView;
64-
if (!filePath) {
65-
logger.error("File path is required when version is provided as string.");
66-
return;
67-
}
68-
69-
await window.showTextDocument(Uri.parse(`ucd:${version}/${hasUCDFolderPath(version) ? "ucd/" : ""}${filePath}`));
70-
});
71-
72-
useUCDExplorer();
8+
logger.info("Activating UCD Explorer extension...");
9+
initializeUCDExplorerView();
7310

11+
registerCommands();
7412
useUCDContentProvider();
13+
14+
logger.info("UCD Explorer extension activated successfully.");
7515
});
7616

7717
export { activate, deactivate };

src/lib/files.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { UCDStore, UnicodeVersionFile } from "@ucdjs/ucd-store";
22
import type { TreeViewNode } from "reactive-vscode";
3-
import type { UCDTreeItem } from "../views/ucd-explorer";
3+
import type { UCDTreeItem } from "../composables/useUCDExplorer";
44
import { hasUCDFolderPath } from "@luxass/unicode-utils";
55
import { ThemeIcon, TreeItemCollapsibleState } from "vscode";
66
import * as Meta from "../generated/meta";

src/views/ucd-explorer.ts

Lines changed: 9 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,12 @@
1-
import type { TreeViewNode } from "reactive-vscode";
2-
import type { TreeItem } from "vscode";
3-
import { UNICODE_VERSION_METADATA } from "@luxass/unicode-utils";
4-
import { computed, createSingletonComposable, ref, useTreeView } from "reactive-vscode";
5-
import { ThemeIcon, TreeItemCollapsibleState } from "vscode";
6-
import { useUCDStore } from "../composables/useUCDStore";
7-
import { getFilesByVersion } from "../lib/files";
1+
import type { UCDTreeItem } from "../composables/useUCDExplorer";
2+
import { useTreeView } from "reactive-vscode";
3+
import { useUCDExplorer } from "../composables/useUCDExplorer";
84
import { logger } from "../logger";
95

10-
export interface UCDTreeItem extends TreeItem {
11-
__ucd?: {
12-
ucdUrl: string;
13-
version: string;
14-
};
15-
}
16-
17-
export const useUCDExplorer = createSingletonComposable(() => {
18-
const store = useUCDStore();
19-
20-
const childrenCache = ref<Map<string, TreeViewNode[]>>(new Map());
21-
const loadingPromises = ref<Map<string, Promise<TreeViewNode[]>>>(new Map());
22-
23-
async function loadChildrenForVersion(version: string): Promise<TreeViewNode[]> {
24-
const cached = childrenCache.value.get(version);
25-
if (cached) {
26-
return cached;
27-
}
28-
29-
// return existing promise if already loading (prevents duplicate requests)
30-
const existingPromise = loadingPromises.value.get(version);
31-
if (existingPromise) {
32-
return existingPromise;
33-
}
34-
35-
// create new loading promise
36-
const loadingPromise = (async () => {
37-
try {
38-
const files = await getFilesByVersion(store.value!, version);
39-
childrenCache.value.set(version, files);
40-
return files;
41-
} catch (error) {
42-
logger.error(`Failed to load files for version ${version}:`, error);
43-
return [];
44-
} finally {
45-
// clean up loading promise regardless of success/failure
46-
loadingPromises.value.delete(version);
47-
}
48-
})();
6+
export function initializeUCDExplorerView() {
7+
const explorer = useUCDExplorer();
498

50-
// store the promise to prevent duplicate requests
51-
loadingPromises.value.set(version, loadingPromise);
52-
return loadingPromise;
53-
}
54-
55-
const nodes = computed<TreeViewNode[]>(() =>
56-
UNICODE_VERSION_METADATA.map((metadata) => {
57-
const version = metadata.version;
58-
return {
59-
treeItem: {
60-
iconPath: new ThemeIcon("folder"),
61-
label: metadata.version + (metadata.status === "draft" ? " (Draft)" : ""),
62-
description: metadata.date ? `Released in ${metadata.date}` : "",
63-
tooltip: `Documentation: ${metadata.documentationUrl}\nUCD URL: ${metadata.ucdUrl}`,
64-
collapsibleState: TreeItemCollapsibleState.Collapsed,
65-
contextValue: "ucd:version-folder",
66-
__ucd: metadata,
67-
} as UCDTreeItem,
68-
children: childrenCache.value.get(version) || [],
69-
};
70-
}),
71-
);
72-
73-
const view = useTreeView("ucd:explorer", nodes, {
9+
const view = useTreeView("ucd:explorer", explorer.nodes, {
7410
showCollapseAll: true,
7511
});
7612

@@ -84,13 +20,13 @@ export const useUCDExplorer = createSingletonComposable(() => {
8420
if (treeItem.contextValue === "ucd:version-folder") {
8521
const version = (treeItem as UCDTreeItem).__ucd?.version;
8622
if (version) {
87-
await loadChildrenForVersion(version);
23+
await explorer.loadChildrenForVersion(version);
8824
}
8925
}
9026
} catch (err) {
9127
logger.error("An error occurred while expanding entry in UCD Explorer", err);
9228
}
9329
});
9430

95-
return view;
96-
});
31+
return { view, explorer };
32+
}

0 commit comments

Comments
 (0)