Skip to content

Commit

Permalink
feat: Improve settings editor
Browse files Browse the repository at this point in the history
  • Loading branch information
hverlin committed Dec 27, 2024
1 parent e89d529 commit 968da8d
Show file tree
Hide file tree
Showing 12 changed files with 347 additions and 131 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,10 @@
{
"command": "mise.fmt",
"title": "Mise: Run mise fmt"
},
{
"command": "mise.editSetting",
"title": "Mise: Edit Setting"
}
],
"menus": {
Expand Down
1 change: 1 addition & 0 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ export const MISE_SET_ENV_VARIABLE = "mise.setEnvVariable";
export const MISE_USE_TOOL = "mise.useTool";
export const MISE_WATCH_TASK = "mise.watchTask";
export const MISE_FMT = "mise.fmt";
export const MISE_EDIT_SETTING = "mise.editSetting";
85 changes: 85 additions & 0 deletions src/miseExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createCache } from "async-cache-dedupe";
import vscode, { MarkdownString } from "vscode";
import {
MISE_CONFIGURE_ALL_SKD_PATHS,
MISE_EDIT_SETTING,
MISE_FMT,
MISE_INSTALL_ALL,
MISE_LIST_ALL_TOOLS,
Expand Down Expand Up @@ -332,6 +333,90 @@ export class MiseExtension {
}),
);

context.subscriptions.push(
vscode.commands.registerCommand(
MISE_EDIT_SETTING,
async (settingName: string) => {
const [schema, allSettings] = await Promise.all([
this.miseService.getSettingsSchema(),
this.miseService.getSettings(),
]);
let setting = settingName;
if (!setting) {
const selectedSetting = await vscode.window.showQuickPick(
schema.map((s) => ({
label: s.key,
description: [
s.type ? `${s.type}` : "",
s.deprecated ? "Deprecated" : "",
]
.filter(Boolean)
.join(" | "),
detail: s.description,
})),
{ placeHolder: "Select a setting" },
);
if (!selectedSetting) {
return;
}
setting = selectedSetting.label;
}

const settingSchema = schema.find((s) => s.key === setting);
if (!settingSchema) {
logger.warn(`Setting ${setting} not found in schema`);
return;
}

const currentValue = allSettings[setting]?.value;

const value =
settingSchema.type === "boolean"
? await vscode.window.showQuickPick(["true", "false"], {
placeHolder: `Select new value for ${setting}. Current value: ${currentValue}`,
})
: settingSchema.enum?.length
? await vscode.window.showQuickPick(settingSchema.enum, {
placeHolder: `Select new value for ${setting}. Current value: ${currentValue}`,
})
: settingSchema.type === "array"
? await vscode.window.showInputBox({
prompt: `Enter new value for ${setting} (comma separated). Current value: ${currentValue}`,
value:
typeof currentValue === "string"
? JSON.parse(currentValue)?.join(",")
: "",
})
: await vscode.window.showInputBox({
prompt: `Enter new value for ${setting}`,
value:
settingSchema.type === "string"
? (currentValue?.toString() ?? "")
: JSON.stringify(currentValue),
});

if (value === undefined) {
return;
}

const file =
await this.miseService.getMiseTomlConfigFilePathsEvenIfMissing();
const selectedFilePath = await vscode.window.showQuickPick(file, {
placeHolder: "Select a configuration file",
});

if (!selectedFilePath) {
return;
}

await this.miseService.editSetting(setting, {
filePath: selectedFilePath,
value,
});
},
),
);

await vscode.commands.executeCommand(MISE_RELOAD);

setTimeout(async () => {
Expand Down
42 changes: 37 additions & 5 deletions src/miseService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ import { uniqBy } from "./utils/fn";
import { logger } from "./utils/logger";
import { resolveMisePath } from "./utils/miseBinLocator";
import { type MiseConfig, parseMiseConfig } from "./utils/miseDoctorParser";
import { idiomaticFileToTool, idiomaticFiles } from "./utils/miseUtilts";
import {
flattenJsonSchema,
idiomaticFileToTool,
idiomaticFiles,
} from "./utils/miseUtilts";
import { showSettingsNotification } from "./utils/notify";
import {
execAsync,
Expand Down Expand Up @@ -110,9 +114,20 @@ export class MiseService {
private longCache = createCache({
ttl: 60,
storage: { type: "memory" },
}).define("execCmd", ({ command, setMiseEnv } = {}) =>
this.execMiseCommand(command, { setMiseEnv }),
);
})
.define("execCmd", ({ command, setMiseEnv } = {}) =>
this.execMiseCommand(command, { setMiseEnv }),
)
.define("fetchSchema", async () => {
const res = await fetch(
"https://raw.githubusercontent.com/jdx/mise/refs/heads/main/schema/mise.json",
);
if (!res.ok) {
return [];
}
const json = await res.json();
return flattenJsonSchema(json.$defs.settings);
});

async invalidateCache() {
await Promise.all([this.dedupeCache.clear(), this.cache.clear()]);
Expand Down Expand Up @@ -833,7 +848,7 @@ export class MiseService {

async getSettings() {
if (!this.getMiseBinaryPath()) {
return [];
return {};
}

const { stdout } = await this.execMiseCommand(
Expand All @@ -842,6 +857,10 @@ export class MiseService {
return flattenSettings(JSON.parse(stdout));
}

async getSettingsSchema() {
return this.longCache.fetchSchema();
}

async getTrackedConfigFiles() {
const trackedConfigFiles = await vscode.workspace.fs.readDirectory(
vscode.Uri.file(TRACKED_CONFIG_DIR),
Expand Down Expand Up @@ -947,4 +966,17 @@ export class MiseService {

await this.runMiseToolActionInConsole(`install ${toolName}@${version}`);
}

async editSetting(
setting: string,
{ value, filePath }: { value: string; filePath: string },
) {
if (!this.getMiseBinaryPath()) {
return;
}

await this.runMiseToolActionInConsole(
`config set settings.${setting} "${value}" --file "${filePath}"`,
);
}
}
4 changes: 3 additions & 1 deletion src/providers/envProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,9 @@ function updateTerminalsEnvs(variablesToRemove: [string, string][]) {
const commands = variablesToRemove.map(([name]) => `;unset ${name}`).join("");
const isTerminalFocused = vscode.window.activeTerminal !== undefined;

if (isTerminalFocused) {
const isMiseTask =
vscode.window.activeTerminal?.creationOptions?.name?.includes("mise");
if (isTerminalFocused && !isMiseTask) {
return vscode.commands.executeCommand("workbench.action.terminal.relaunch");
}

Expand Down
4 changes: 2 additions & 2 deletions src/providers/miseTomlCodeLensProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ function addListToolsCodeLens(range: vscode.Range): vscode.CodeLens {

function addSettingsListCodeLens(range: vscode.Range): vscode.CodeLens {
return new vscode.CodeLens(range, {
title: "$(gear) View all settings",
tooltip: "Show all settings",
title: "$(gear) Manage settings",
tooltip: "Manage settings",
command: MISE_SHOW_SETTINGS,
arguments: [],
});
Expand Down
73 changes: 73 additions & 0 deletions src/utils/miseUtilts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,76 @@ export const getCleanedToolName = (toolName: string) => {
.replace("nodejs", "node")
.replace("golang", "go");
};

type JSONType = "string" | "boolean" | "number" | "object" | "array";

export type FlattenedProperty = {
key: string;
type: JSONType;
itemsType: JSONType | undefined;
enum: string[] | undefined;
description: string | undefined;
defaultValue: unknown;
deprecated?: string;
};

type PropertyValue = {
type?: JSONType;
description?: string;
default?: unknown;
deprecated?: string;
items?: { type: JSONType };
enum?: string[];
properties?: Record<string, PropertyValue>;
};

type SchemaType = {
properties: Record<string, PropertyValue>;
};

export function flattenJsonSchema(
schema: SchemaType,
parentKey = "",
result: FlattenedProperty[] = [],
): FlattenedProperty[] {
if (!schema.properties) {
return result;
}

for (const [key, value] of Object.entries(schema.properties)) {
const currentKey = parentKey ? `${parentKey}.${key}` : key;

if (value.properties) {
flattenJsonSchema({ properties: value.properties }, currentKey, result);
} else {
result.push({
key: currentKey,
type: value.type ?? "string",
itemsType: value.items?.type,
description: value.description,
defaultValue: value.default,
enum: value.enum,
...(value.deprecated && { deprecated: value.deprecated }),
});
}
}

return result;
}

export function getDefaultForType(type?: string): unknown {
switch (type) {
case "string":
return "";
case "boolean":
return false;
case "number":
return 0;
case "object":
return {};
case "array":
return [];
default:
return "";
}
}
16 changes: 15 additions & 1 deletion src/webviewPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as os from "node:os";
import path from "node:path";
import * as cheerio from "cheerio";
import * as vscode from "vscode";
import { MISE_EDIT_SETTING } from "./commands";
import type { MiseService } from "./miseService";
import { logger } from "./utils/logger";

Expand Down Expand Up @@ -57,7 +58,7 @@ export default class WebViewPanel {
`Mise: ${this.view === "TOOLS" ? "Tools" : this.view === "SETTINGS" ? "Settings" : "Tracked Configs"}`,
column,
{
retainContextWhenHidden: true,
retainContextWhenHidden: false,
enableScripts: true,
localResourceRoots: [this._extensionUri],
},
Expand Down Expand Up @@ -107,6 +108,11 @@ export default class WebViewPanel {
this.miseService.getSettings(),
);
}
case "settingsSchema": {
return executeAction(message, () =>
this.miseService.getSettingsSchema(),
);
}
case "trackedConfigs": {
return executeAction(message, () =>
this.miseService.getTrackedConfigFiles(),
Expand Down Expand Up @@ -150,6 +156,14 @@ export default class WebViewPanel {
),
);
}
case "editSetting": {
return executeAction(message, async () =>
vscode.commands.executeCommand(
MISE_EDIT_SETTING,
message.variables?.key,
),
);
}
}
break;
}
Expand Down
Loading

0 comments on commit 968da8d

Please sign in to comment.