Skip to content

Commit

Permalink
feat: show track configuration files
Browse files Browse the repository at this point in the history
  • Loading branch information
hverlin committed Nov 26, 2024
1 parent c276217 commit e068e52
Show file tree
Hide file tree
Showing 13 changed files with 342 additions and 64 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,7 @@
"dependencies": {
"@iarna/toml": "^2.2.5",
"@std/collections": "npm:@jsr/std__collections@^1.0.9",
"@tanstack/match-sorter-utils": "^8.19.4",
"@tanstack/react-query": "^5.60.5",
"@tanstack/react-table": "^8.20.5",
"@vscode-elements/react-elements": "^0.7.0",
Expand Down
16 changes: 16 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 50 additions & 0 deletions src/miseService.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { readlink } from "node:fs/promises";
import * as os from "node:os";
import path from "node:path";
import * as toml from "@iarna/toml";
Expand All @@ -12,13 +13,21 @@ import {
updateBinPath,
} from "./configuration";
import { expandPath } from "./utils/fileUtils";
import { uniqBy } from "./utils/fn";
import { logger } from "./utils/logger";
import { resolveMisePath } from "./utils/miseBinLocator";
import { type MiseConfig, parseMiseConfig } from "./utils/miseDoctorParser";
import { showSettingsNotification } from "./utils/notify";
import { execAsync, execAsyncMergeOutput } from "./utils/shell";
import { type MiseTaskInfo, parseTaskInfo } from "./utils/taskInfoParser";

// https://github.com/jdx/mise/blob/main/src/env.rs
const XDG_STATE_HOME =
process.env.XDG_STATE_HOME ?? path.join(os.homedir(), ".local", "state");
const STATE_DIR =
process.env.MISE_STATE_DIR ?? path.join(XDG_STATE_HOME, "mise");
const TRACKED_CONFIG_DIR = path.join(STATE_DIR, "tracked-configs");

const MIN_MISE_VERSION = [2024, 11, 4] as const;

function ensureMiseCommand(
Expand Down Expand Up @@ -644,6 +653,47 @@ export class MiseService {
return toml.parse(stdout);
}

async getTrackedConfigFiles() {
const trackedConfigFiles = await vscode.workspace.fs.readDirectory(
vscode.Uri.file(TRACKED_CONFIG_DIR),
);

const parsedTrackedConfigs = await Promise.all(
trackedConfigFiles.map(async ([n]) => {
try {
const trackedConfigPath = await readlink(
path.join(TRACKED_CONFIG_DIR, n),
).catch(() => "");
if (!trackedConfigPath) {
return {};
}

const stats = await vscode.workspace.fs.stat(
vscode.Uri.file(trackedConfigPath),
);

if (stats.type === vscode.FileType.File) {
const content = await vscode.workspace.fs.readFile(
vscode.Uri.file(trackedConfigPath),
);
const config = toml.parse(content.toString());
return { path: trackedConfigPath, tools: config.tools ?? {} };
}
} catch {
return {};
}
}),
);

const validConfigs = parsedTrackedConfigs.filter(
(trackedConfigPath) => trackedConfigPath?.path,
) as Array<{ path: string; tools: object }>;

return uniqBy(validConfigs, (c) => c.path).sort((a, b) =>
a.path.localeCompare(b.path),
);
}

dispose() {
if (this.terminal) {
this.terminal.dispose();
Expand Down
16 changes: 16 additions & 0 deletions src/utils/fn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export function uniqBy<T>(array: T[], iteratee: (value: T) => string): T[] {
if (!Array.isArray(array)) {
throw new TypeError("Expected an array as first argument");
}

const seen = new Map<string, T>();

for (const item of array) {
const key = iteratee(item);
if (!seen.has(key)) {
seen.set(key, item);
}
}

return Array.from(seen.values());
}
31 changes: 29 additions & 2 deletions src/webviewPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export default class WebViewPanel {
private readonly _extContext: vscode.ExtensionContext;
private _disposables: vscode.Disposable[] = [];
private readonly miseService: MiseService;
private _currentPath = "tools";

public static createOrShow(
extContext: vscode.ExtensionContext,
Expand All @@ -38,16 +39,22 @@ export default class WebViewPanel {
_extContext: vscode.ExtensionContext,
column: vscode.ViewColumn,
miseService: MiseService,
initialPath?: string,
) {
this._extContext = _extContext;
this._extensionUri = _extContext.extensionUri;
this.miseService = miseService;
this._currentPath = initialPath ?? "tools";

this._panel = vscode.window.createWebviewPanel(
WebViewPanel.viewType,
"Mise",
column,
{ enableScripts: true, localResourceRoots: [this._extensionUri] },
{
retainContextWhenHidden: true,
enableScripts: true,
localResourceRoots: [this._extensionUri],
},
);

this._panel.webview.html = this._getHtmlForWebview(this._panel.webview);
Expand Down Expand Up @@ -77,6 +84,10 @@ export default class WebViewPanel {
this._panel.webview.onDidReceiveMessage(
async (message) => {
switch (message.type) {
case "updateState": {
this._currentPath = message.path;
break;
}
case "query":
switch (message.queryKey[0]) {
case "tools": {
Expand All @@ -94,6 +105,11 @@ export default class WebViewPanel {
this.miseService.getSettings(),
);
}
case "trackedConfigs": {
return executeAction(message, () =>
this.miseService.getTrackedConfigFiles(),
);
}
}
break;
case "mutation":
Expand All @@ -106,6 +122,14 @@ export default class WebViewPanel {
),
);
}
case "openFile": {
return executeAction(message, async () =>
vscode.window.showTextDocument(
vscode.Uri.file(message.variables?.path as string),
{ preview: true, viewColumn: vscode.ViewColumn.One },
),
);
}
}
break;
}
Expand Down Expand Up @@ -174,6 +198,9 @@ export default class WebViewPanel {
}
});

const $head = $("head");
$head.append(`<meta name="initial-path" content="${this._currentPath}">`);

const codiconsUri = webview.asWebviewUri(
vscode.Uri.joinPath(
this._extensionUri,
Expand All @@ -183,7 +210,7 @@ export default class WebViewPanel {
"codicon.css",
),
);
$("head").append(
$head.append(
`<link rel="stylesheet" type="text/css" href="${codiconsUri}">`,
);

Expand Down
13 changes: 13 additions & 0 deletions src/webviews/App.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import { Settings } from "./Settings";
import { Tools } from "./Tools";
import { TrackedConfigs } from "./TrackedConfigs";
import { Tabs } from "./components/Tabs";

export function App() {
const initialPathMeta = document.querySelector('meta[name="initial-path"]');

return (
<div>
<Tabs
initialTab={
initialPathMeta?.getAttribute("content") === "settings"
? "Settings"
: "Tools"
}
tabs={[
{
name: "Tools",
Expand All @@ -17,6 +25,11 @@ export function App() {
content: <Settings />,
icon: "codicon codicon-settings",
},
{
name: "Config files",
content: <TrackedConfigs />,
icon: "codicon codicon-file",
},
]}
/>
</div>
Expand Down
16 changes: 8 additions & 8 deletions src/webviews/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const Settings = () => {
});

const schemaQuery = useQuery({
queryKey: ["settingsSchema2"],
queryKey: ["settingsSchema"],
queryFn: async () => {
const res = await fetch(
"https://raw.githubusercontent.com/jdx/mise/refs/heads/main/schema/mise.json",
Expand Down Expand Up @@ -60,14 +60,14 @@ export const Settings = () => {

return (
<div>
<div style={{ padding: "10px" }}>
<VscodeCheckbox
onChange={() => setShowModifiedOnly(!showModifiedOnly)}
label="Show modified only"
checked={showModifiedOnly ? true : undefined}
/>
</div>
<CustomTable
filterRowElement={
<VscodeCheckbox
onChange={() => setShowModifiedOnly(!showModifiedOnly)}
label="Show modified only"
checked={showModifiedOnly ? true : undefined}
/>
}
isLoading={settingsQuery.isLoading}
data={settingValues.filter(
(value) =>
Expand Down
4 changes: 3 additions & 1 deletion src/webviews/Tools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ export const Tools = () => {
return (
<div>
<CustomTable
filterRowElement={
outdatedToolsQuery.isLoading ? "Loading outdated tools..." : ""
}
isLoading={toolsQuery.isLoading}
columns={[
{
Expand Down Expand Up @@ -121,7 +124,6 @@ export const Tools = () => {
]}
data={toolsQuery?.data || []}
/>
{outdatedToolsQuery.isLoading ? "Loading outdated tools..." : ""}
</div>
);
};
68 changes: 68 additions & 0 deletions src/webviews/TrackedConfigs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { useMutation, useQuery } from "@tanstack/react-query";
import CustomTable from "./components/CustomTable";
import { vscodeClient } from "./webviewVsCodeApi";

export const TrackedConfigs = () => {
const settingsQuery = useQuery({
queryKey: ["trackedConfigs"],
queryFn: ({ queryKey }) =>
vscodeClient.request({ queryKey }) as Promise<
Array<{ path: string; tools: object }>
>,
});

const openFileMutation = useMutation({
mutationKey: ["openFile"],
mutationFn: (path: string) =>
vscodeClient.request({ mutationKey: ["openFile"], variables: { path } }),
});

if (settingsQuery.isError) {
return <div>Error: {settingsQuery.error.message}</div>;
}

return (
<div>
<CustomTable
filterRowElement={"Showing tracked config files"}
isLoading={settingsQuery.isLoading}
data={settingsQuery.data || []}
columns={[
{
id: "path",
header: "File",
accessorKey: "path",
cell: ({ row }) => {
return (
<a
href={`#${row.original.path}`}
onClick={(e) => {
e.preventDefault();
openFileMutation.mutate(row.original.path);
}}
title={row.original.path}
>
{row.original.path}
</a>
);
},
},
{
id: "tools",
header: "Tools",
accessorKey: "tools",
cell: ({ row }) => {
return Object.entries(row.original.tools).map(
([toolName, toolVersion]) => (
<div key={toolName}>
{toolName} = {JSON.stringify(toolVersion)}
</div>
),
);
},
},
]}
/>
</div>
);
};
Loading

0 comments on commit e068e52

Please sign in to comment.