diff --git a/CHANGELOG.md b/CHANGELOG.md index 1699ad4..2ba9696 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,13 @@ # Changelog +### v1.1.0 + +- Add code lens for editing files from settings + + Add ability to open editor for file added to `dotfiles.files` settings using a code lens, to make it faster to update the files in settings.json. Changes made in the editor automatically update the file's content in settings.json if `dotfiles.autoUpdate` is enabled. + + +- Simplify Makefile + ### v1.0.1 - Refactor Configuration to a separate class @@ -58,4 +67,3 @@ - Initial commit - diff --git a/README.md b/README.md index 8cb2966..f386538 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,10 @@ If `dotfiles.autoUpdate` is enabled, automatically apply config from settings to writes file `$XDG_CONFIG_HOME/path/to/file.txt` with the content of the key. +A code lens is added to keys in settings.json under `dotfiles.files` to open the file in an editor. Saving the file in the editor will apply changes back to settings.json if `dotfiles.autoUpdate` is enabled. + + + ## Extension Settings ||Description|Default| diff --git a/images/code-lens.png b/images/code-lens.png new file mode 100644 index 0000000..35e6bf2 Binary files /dev/null and b/images/code-lens.png differ diff --git a/package-lock.json b/package-lock.json index 628c621..78baf4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "dotfiles", - "version": "1.0.1", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dotfiles", - "version": "1.0.1", + "version": "1.1.0", "license": "MIT", "devDependencies": { "@types/mocha": "^10.0.6", @@ -17,6 +17,7 @@ "@vscode/test-cli": "^0.0.6", "@vscode/test-electron": "^2.3.9", "eslint": "^8.56.0", + "jsonc-parser": "^3.2.1", "typescript": "^5.3.3" }, "engines": { @@ -1777,6 +1778,12 @@ "dev": true, "license": "MIT" }, + "node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true + }, "node_modules/jszip": { "version": "3.10.1", "dev": true, diff --git a/package.json b/package.json index f37ce40..4ce29cb 100644 --- a/package.json +++ b/package.json @@ -8,15 +8,19 @@ }, "description": "Apply dotfiles from settings", "icon": "images/icon-small.png", - "version": "1.0.1", + "version": "1.1.0", "license": "MIT", - "preview": true, "engines": { "vscode": "^1.74.0" }, "categories": [ "Other" ], + "author": { + "name": "grg", + "url": "https://grg.app", + "email": "vscode@grg.app" + }, "homepage": "https://github.com/grgar/vscode-dotfiles", "qna": "https://github.com/grgar/vscode-dotfiles/discussions", "bugs": { @@ -68,14 +72,15 @@ "test": "vscode-test" }, "devDependencies": { - "@types/vscode": "^1.74.0", "@types/mocha": "^10.0.6", "@types/node": "18.x", + "@types/vscode": "^1.74.0", "@typescript-eslint/eslint-plugin": "^7.0.2", "@typescript-eslint/parser": "^7.0.2", - "eslint": "^8.56.0", - "typescript": "^5.3.3", "@vscode/test-cli": "^0.0.6", - "@vscode/test-electron": "^2.3.9" + "@vscode/test-electron": "^2.3.9", + "eslint": "^8.56.0", + "jsonc-parser": "^3.2.1", + "typescript": "^5.3.3" } } diff --git a/src/Configuration.ts b/src/Configuration.ts index 2b07ec3..ae31fa2 100644 --- a/src/Configuration.ts +++ b/src/Configuration.ts @@ -1,6 +1,14 @@ import path from 'path'; import * as vscode from "vscode"; +export const namespace = "dotfiles"; + +export enum Section { + directory = "directory", + files = "files", + autoUpdate = "autoUpdate", +} + export default class Configuration { readonly namespace: string; readonly logger?: (msg: string) => void; @@ -11,7 +19,7 @@ export default class Configuration { } getDirectoryPath() { - let directory = vscode.workspace.getConfiguration(this.namespace).get("directory") || process.env["XDG_CONFIG_HOME"] || path.join(process.env["HOME"]!, ".config"); + let directory = vscode.workspace.getConfiguration(this.namespace).get(Section.directory) || process.env["XDG_CONFIG_HOME"] || path.join(process.env["HOME"]!, ".config"); if (directory.endsWith("/")) { return directory.slice(directory.length - 1); } @@ -19,12 +27,12 @@ export default class Configuration { } getFiles() { - return vscode.workspace.getConfiguration(this.namespace).get<{ [key: string]: string; }>("files", {}); + return vscode.workspace.getConfiguration(this.namespace).get<{ [key: string]: string; }>(Section.files, {}); } async setFiles(files: { [key: string]: string; }) { try { - await vscode.workspace.getConfiguration(this.namespace).update("files", files, vscode.ConfigurationTarget.Global); + await vscode.workspace.getConfiguration(this.namespace).update(Section.files, files, vscode.ConfigurationTarget.Global); } catch (err) { this.logger?.(`${new Date().toLocaleString()}: failed to update files: ${err}`); vscode.window.showErrorMessage("Unable to update dotfiles.files: " + err); @@ -32,6 +40,6 @@ export default class Configuration { } shouldAutoUpdate() { - return vscode.workspace.getConfiguration(this.namespace).get("autoUpdate", false); + return vscode.workspace.getConfiguration(this.namespace).get(Section.autoUpdate, false); } } diff --git a/src/SettingsLensProvider.ts b/src/SettingsLensProvider.ts new file mode 100644 index 0000000..6d726ca --- /dev/null +++ b/src/SettingsLensProvider.ts @@ -0,0 +1,59 @@ +import { JSONVisitor, visit } from "jsonc-parser"; +import path from "path"; +import * as vscode from "vscode"; + +export class SettingsLensProvider implements vscode.CodeLensProvider { + private key: string; + private basePath: string; + + constructor(key: string, basePath: string) { + this.key = key; + this.basePath = basePath; + } + + provideCodeLenses(document: vscode.TextDocument): vscode.ProviderResult { + return parseJSONForFileLocations(this.key, document).map( + ({ name, range }) => + new vscode.CodeLens( + range, + { + title: "open file", + command: "vscode.open", + arguments: [path.join(this.basePath, name)], + }, + ), + ); + } +} + +export interface FileLocation { + name: string; + range: vscode.Range; +} + +const parseJSONForFileLocations = (filesProperty: String, document: vscode.TextDocument, buffer = document.getText()): FileLocation[] => { + let level = 0; + let inFiles = false; + const files: FileLocation[] = []; + const visitor: JSONVisitor = { + onObjectBegin() { + level++; + }, + onObjectEnd() { + inFiles = false; + level--; + }, + onObjectProperty(property: string, offset: number, length: number) { + if (level === 1 && property === filesProperty) { + inFiles = true; + } else if (inFiles) { + files.push({ + name: property, + range: new vscode.Range(document.positionAt(offset), document.positionAt(offset + length)) + }); + } + }, + }; + visit(buffer, visitor); + return files; +}; diff --git a/src/extension.ts b/src/extension.ts index 9ca7b53..9729624 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,9 +1,9 @@ import * as fs from "fs/promises"; import path from 'path'; import * as vscode from 'vscode'; -import Configuration from "./Configuration"; +import Configuration, { namespace as configNamespace, Section } from "./Configuration"; +import { SettingsLensProvider } from "./SettingsLensProvider"; -export const configNamespace = "dotfiles"; const outputChannel = vscode.window.createOutputChannel(configNamespace); async function apply() { @@ -45,11 +45,18 @@ function didSave(doc: vscode.TextDocument) { } export function activate(context: vscode.ExtensionContext) { + const configuration = new Configuration(configNamespace, outputChannel.appendLine); context.subscriptions.push( vscode.commands.registerCommand("dotfiles.apply", apply), - vscode.workspace.onDidSaveTextDocument(didSave) + vscode.workspace.onDidSaveTextDocument(didSave), + vscode.languages.registerCodeLensProvider( + { + language: 'jsonc', + pattern: '**/settings.json', + }, + new SettingsLensProvider(`${configNamespace}.${Section.files}`, configuration.getDirectoryPath()), + ) ); - const configuration = new Configuration(configNamespace, outputChannel.appendLine); if (configuration.shouldAutoUpdate()) { apply(); }