From 75601aa58d0a7715fc08ff83484a01e31b0459cf Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Long <43560378+nguyenngoclongdev@users.noreply.github.com> Date: Thu, 17 Oct 2024 09:47:17 +0000 Subject: [PATCH] fix: quick navigate to configuration file from activity --- .changeset/green-llamas-provide.md | 5 ++ esbuild.js | 54 +++++++++++++ package-lock.json | 119 ++++++++++++++++++++++------- package.json | 12 ++- src/commands/navigateAsync.ts | 54 +++++++++++++ src/configuration/configuration.ts | 2 +- src/explorer/tree-provider.ts | 19 +++++ src/extension.ts | 4 + src/utils/constants.ts | 1 + 9 files changed, 240 insertions(+), 30 deletions(-) create mode 100644 .changeset/green-llamas-provide.md create mode 100644 esbuild.js create mode 100644 src/commands/navigateAsync.ts diff --git a/.changeset/green-llamas-provide.md b/.changeset/green-llamas-provide.md new file mode 100644 index 0000000..f6ee104 --- /dev/null +++ b/.changeset/green-llamas-provide.md @@ -0,0 +1,5 @@ +--- +"terminal-keeper": patch +--- + +fix: quick navigate to configuration file from activity diff --git a/esbuild.js b/esbuild.js new file mode 100644 index 0000000..0fcfc78 --- /dev/null +++ b/esbuild.js @@ -0,0 +1,54 @@ +const esbuild = require('esbuild'); + +const production = process.argv.includes('--production'); +const watch = process.argv.includes('--watch'); + +async function main() { + const ctx = await esbuild.context({ + entryPoints: ['src/extension.ts'], + bundle: true, + format: 'cjs', + minify: production, + sourcemap: !production, + sourcesContent: false, + platform: 'node', + outfile: 'dist/extension.js', + external: ['vscode'], + logLevel: 'silent', + mainFields: ['module', 'main'], + plugins: [ + /* add to the end of plugins array */ + esbuildProblemMatcherPlugin + ] + }); + if (watch) { + await ctx.watch(); + } else { + await ctx.rebuild(); + await ctx.dispose(); + } +} + +/** + * @type {import('esbuild').Plugin} + */ +const esbuildProblemMatcherPlugin = { + name: 'esbuild-problem-matcher', + setup(build) { + build.onStart(() => { + console.log('Build started...'); + }); + build.onEnd((result) => { + result.errors.forEach(({ text, location }) => { + console.error(`✘ [ERROR] ${text}`); + console.error(` ${location.file}:${location.line}:${location.column}:`); + }); + console.info('Build finished!'); + }); + } +}; + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/package-lock.json b/package-lock.json index 3db03ef..4c24228 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "terminal-keeper", - "version": "1.1.48", + "version": "1.1.49", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "terminal-keeper", - "version": "1.1.48", + "version": "1.1.49", "license": "MIT", "dependencies": { "@sgarciac/bombadil": "^2.3.0", @@ -427,6 +427,16 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/@eslint/eslintrc/node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -440,6 +450,18 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/js": { "version": "8.41.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.41.0.tgz", @@ -464,6 +486,28 @@ "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -2238,8 +2282,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/constants-browserify": { "version": "1.0.0", @@ -2887,6 +2930,16 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2980,6 +3033,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint/node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -4809,30 +4874,6 @@ "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", "dev": true }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimatch/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/minimist-options": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", @@ -6065,6 +6106,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/rimraf/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -6086,6 +6137,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/ripemd160": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", diff --git a/package.json b/package.json index be14f65..87a271a 100644 --- a/package.json +++ b/package.json @@ -150,6 +150,11 @@ "title": "Refresh", "icon": "$(refresh)" }, + { + "command": "terminal-keeper.navigate-activity", + "title": "Edit", + "icon": "$(search)" + }, { "command": "terminal-keeper.collapse-all-activity", "title": "Collapse All", @@ -333,6 +338,11 @@ "command": "terminal-keeper.copy-command-activity", "when": "viewItem == terminal-context", "group": "terminal@3" + }, + { + "command": "terminal-keeper.navigate-activity", + "when": "viewItem == terminal-context", + "group": "terminal@4" } ], "explorer/context": [ @@ -401,7 +411,7 @@ "cs": "changeset", "test": "node ./dist/__test__/runTest.js", "clean": "rm -rf dist node_modules web/build web/node_modules *.vsix", - "build": "esbuild ./src/extension.ts --bundle --outfile=dist/extension.js --external:vscode --format=cjs --platform=node", + "build": "node esbuild.js", "build:webext": "webpack", "watch": "tsc -watch -p ./", "watch:webext": "webpack --watch" diff --git a/src/commands/navigateAsync.ts b/src/commands/navigateAsync.ts new file mode 100644 index 0000000..4fe0851 --- /dev/null +++ b/src/commands/navigateAsync.ts @@ -0,0 +1,54 @@ +import path from 'path'; +import { Selection, TextDocument, Uri, window, workspace } from 'vscode'; +import { Configuration } from '../configuration/configuration'; +import { TKTreeItem } from '../explorer/tree-provider'; +import { constants } from '../utils/constants'; +import { showErrorMessageWithDetail, showGenerateConfiguration } from '../utils/utils'; + +const getFileUriBySource = (source: string | undefined): Uri => { + if (source === 'settings.json') { + return Uri.file(path.join(Configuration.vscodeDirPath, source)); + } + return Uri.file(Configuration.sessionFilePath); +}; + +const getKeywordRegex = (treeItem: TKTreeItem): RegExp => { + const { source, keyword } = treeItem; + let keywordFull = keyword || ''; + if (source === 'settings.json') { + keywordFull = `terminal-keeper.${keyword}`; + } + return new RegExp(keywordFull, 'gm'); +}; + +export const navigateAsync = async (treeItem: TKTreeItem): Promise => { + try { + // Write configuration file + const isDefinedSessionFile = await Configuration.isDefinedSessionFile(); + if (!isDefinedSessionFile) { + await showGenerateConfiguration(); + return; + } + + // Get source include keyword + const { source } = treeItem; + const sessionFileUri = getFileUriBySource(source); + const document: TextDocument = await workspace.openTextDocument(sessionFileUri); + const content = document.getText(); + + // Navigate to the session configuration + const regex = getKeywordRegex(treeItem); + const matches = [...content.matchAll(regex)]; + let selections: Selection[] = []; + matches.forEach((match) => { + if (match.index) { + const startPosition = document.positionAt(match.index); + const endPosition = document.positionAt(match.index + match[0].length); + selections.push(new Selection(startPosition, endPosition)); + } + }); + await window.showTextDocument(document, { selection: selections?.[0] }); + } catch (error) { + showErrorMessageWithDetail(constants.openConfigurationFailed, error); + } +}; diff --git a/src/configuration/configuration.ts b/src/configuration/configuration.ts index 69d3396..b30010a 100644 --- a/src/configuration/configuration.ts +++ b/src/configuration/configuration.ts @@ -7,7 +7,7 @@ import { SessionConfiguration } from './interface'; export class Configuration { private static workSpaceConfigurationSpace: string = 'terminal-keeper'; - private static vscodeDirPath: string = ''; + public static vscodeDirPath: string = ''; public static sessionFilePath: string = ''; public static userConfigKeys: string[] = []; diff --git a/src/explorer/tree-provider.ts b/src/explorer/tree-provider.ts index a6c5b46..63f39da 100644 --- a/src/explorer/tree-provider.ts +++ b/src/explorer/tree-provider.ts @@ -11,12 +11,17 @@ import { } from 'vscode'; import { Configuration } from '../configuration/configuration'; import { SessionItem } from '../configuration/interface'; +import { extCommands } from '../utils/constants'; export class TKTreeItem extends TreeItem { sessionId: string | undefined; terminalArrayIndex: number | undefined; children: TKTreeItem[] | undefined; + // Use to navigate to json + source: 'settings.json' | 'sessions.json' | undefined; + keyword: string | undefined; + constructor(label: string, children?: TKTreeItem[]) { super(label, children === undefined ? TreeItemCollapsibleState.None : TreeItemCollapsibleState.Collapsed); this.children = children; @@ -192,6 +197,13 @@ export class TreeProvider implements TreeDataProvider { .appendCodeblock(`Config Source: ${source}`); item.contextValue = 'overview-context'; item.iconPath = new ThemeIcon(id || 'circle-filled', color); + item.source = source; + item.keyword = label; + item.command = { + title: 'Navigate to configuration', + command: extCommands.navigateActivity, + arguments: [item] + }; return item; }; @@ -264,6 +276,13 @@ export class TreeProvider implements TreeDataProvider { item.iconPath = new ThemeIcon(icon?.id || 'terminal', color); item.sessionId = sessionId; item.terminalArrayIndex = terminalArrayIndex; + item.source = 'sessions.json'; + item.keyword = terminalName; + item.command = { + title: 'Navigate to configuration', + command: extCommands.navigateActivity, + arguments: [item] + }; return item; }; } diff --git a/src/extension.ts b/src/extension.ts index af3f247..2566e82 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -8,6 +8,7 @@ import { generateAsync } from './commands/generateAsync'; import { importAsync } from './commands/importAsync'; import { killAllAsync } from './commands/killAllAsync'; import { migrateAsync } from './commands/migrateAsync'; +import { navigateAsync } from './commands/navigateAsync'; import { openAsync } from './commands/openAsync'; import { removeAsync } from './commands/removeAsync'; import { saveAsync } from './commands/saveAsync'; @@ -74,6 +75,9 @@ export async function activate(context: ExtensionContext) { commands.registerCommand(extCommands.collapseAllActivity, async () => { await commands.executeCommand(sysCommands.activityCollapseAll); }), + commands.registerCommand(extCommands.navigateActivity, async (sessionTreeItem: TKTreeItem) => { + await navigateAsync(sessionTreeItem); + }), commands.registerCommand(extCommands.helpAndFeedbackActivity, async () => { await env.openExternal(Uri.parse(constants.helpAndFeedbackUrl)); }), diff --git a/src/utils/constants.ts b/src/utils/constants.ts index c2d7028..19ba429 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -22,6 +22,7 @@ export const extCommands = { sendToCurrentTerminalActivity: 'terminal-keeper.send-to-current-terminal-activity', copyCommandActivity: 'terminal-keeper.copy-command-activity', collapseAllActivity: 'terminal-keeper.collapse-all-activity', + navigateActivity: 'terminal-keeper.navigate-activity', helpAndFeedbackActivity: 'terminal-keeper.help-and-feedback-activity' };