From b38a0f2242219db1abfe24ecae1ab16fb5c4eac4 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 13 Nov 2020 14:41:35 +0100 Subject: [PATCH] Use vscode.open in NPM scripts view Part of #110497 --- extensions/npm/src/npmMain.ts | 9 +++-- extensions/npm/src/npmView.ts | 39 +++++++++++------- extensions/npm/src/tasks.ts | 76 +++++++++++++++++++---------------- 3 files changed, 71 insertions(+), 53 deletions(-) diff --git a/extensions/npm/src/npmMain.ts b/extensions/npm/src/npmMain.ts index 67cdf56c2d3e7..91936da62574d 100644 --- a/extensions/npm/src/npmMain.ts +++ b/extensions/npm/src/npmMain.ts @@ -31,6 +31,7 @@ export async function activate(context: vscode.ExtensionContext): Promise const canRunNPM = canRunNpmInCurrentWorkspace(); context.subscriptions.push(addJSONProviders(httpRequest.xhr, canRunNPM)); + registerTaskProvider(context); treeDataProvider = registerExplorer(context); @@ -48,7 +49,6 @@ export async function activate(context: vscode.ExtensionContext): Promise } })); - registerTaskProvider(context); registerHoverProvider(context); context.subscriptions.push(vscode.commands.registerCommand('npm.runSelectedScript', runSelectedScript)); @@ -71,6 +71,7 @@ function canRunNpmInCurrentWorkspace() { return false; } +let taskProvider: NpmTaskProvider; function registerTaskProvider(context: vscode.ExtensionContext): vscode.Disposable | undefined { if (vscode.workspace.workspaceFolders) { let watcher = vscode.workspace.createFileSystemWatcher('**/package.json'); @@ -82,8 +83,8 @@ function registerTaskProvider(context: vscode.ExtensionContext): vscode.Disposab let workspaceWatcher = vscode.workspace.onDidChangeWorkspaceFolders((_e) => invalidateScriptCaches()); context.subscriptions.push(workspaceWatcher); - let provider: vscode.TaskProvider = new NpmTaskProvider(); - let disposable = vscode.tasks.registerTaskProvider('npm', provider); + taskProvider = new NpmTaskProvider(); + let disposable = vscode.tasks.registerTaskProvider('npm', taskProvider); context.subscriptions.push(disposable); return disposable; } @@ -92,7 +93,7 @@ function registerTaskProvider(context: vscode.ExtensionContext): vscode.Disposab function registerExplorer(context: vscode.ExtensionContext): NpmScriptsTreeDataProvider | undefined { if (vscode.workspace.workspaceFolders) { - let treeDataProvider = new NpmScriptsTreeDataProvider(context); + let treeDataProvider = new NpmScriptsTreeDataProvider(context, taskProvider!); const view = vscode.window.createTreeView('npm', { treeDataProvider: treeDataProvider, showCollapseAll: true }); context.subscriptions.push(view); return treeDataProvider; diff --git a/extensions/npm/src/npmView.ts b/extensions/npm/src/npmView.ts index 5667cee5438b5..e7eec0e84fa51 100644 --- a/extensions/npm/src/npmView.ts +++ b/extensions/npm/src/npmView.ts @@ -7,14 +7,18 @@ import { JSONVisitor, visit } from 'jsonc-parser'; import * as path from 'path'; import { commands, Event, EventEmitter, ExtensionContext, + Range, Selection, Task, - TaskGroup, tasks, TextDocument, ThemeIcon, TreeDataProvider, TreeItem, TreeItemCollapsibleState, Uri, + TaskGroup, tasks, TextDocument, TextDocumentShowOptions, ThemeIcon, TreeDataProvider, TreeItem, TreeItemCollapsibleState, Uri, window, workspace, WorkspaceFolder } from 'vscode'; import * as nls from 'vscode-nls'; import { createTask, getTaskName, isAutoDetectionEnabled, isWorkspaceFolder, NpmTaskDefinition, - startDebugging + NpmTaskProvider, + startDebugging, + TaskLocation, + TaskWithLocation } from './tasks'; const localize = nls.loadMessageBundle(); @@ -74,15 +78,20 @@ class NpmScript extends TreeItem { task: Task; package: PackageJSON; - constructor(_context: ExtensionContext, packageJson: PackageJSON, task: Task) { + constructor(_context: ExtensionContext, packageJson: PackageJSON, task: Task, public taskLocation?: TaskLocation) { super(task.name, TreeItemCollapsibleState.None); const command: ExplorerCommands = workspace.getConfiguration('npm').get('scriptExplorerAction') || 'open'; const commandList = { 'open': { title: 'Edit Script', - command: 'npm.openScript', - arguments: [this] + command: 'vscode.open', + arguments: [ + taskLocation?.document, + taskLocation ? { + selection: new Range(taskLocation.line, taskLocation.line) + } : undefined + ] }, 'run': { title: 'Run Script', @@ -123,7 +132,7 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider { private _onDidChangeTreeData: EventEmitter = new EventEmitter(); readonly onDidChangeTreeData: Event = this._onDidChangeTreeData.event; - constructor(context: ExtensionContext) { + constructor(context: ExtensionContext, public taskProvider: NpmTaskProvider) { const subscriptions = context.subscriptions; this.extensionContext = context; subscriptions.push(commands.registerCommand('npm.runScript', this.runScript, this)); @@ -228,7 +237,7 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider { async getChildren(element?: TreeItem): Promise { if (!this.taskTree) { - let taskItems = await tasks.fetchTasks({ type: 'npm' }); + const taskItems = await this.taskProvider.tasksWithLocation; if (taskItems) { this.taskTree = this.buildTaskTree(taskItems); if (this.taskTree.length === 0) { @@ -265,7 +274,7 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider { return fullName === task.name; } - private buildTaskTree(tasks: Task[]): Folder[] | PackageJSON[] | NoScripts[] { + private buildTaskTree(tasks: TaskWithLocation[]): Folder[] | PackageJSON[] | NoScripts[] { let folders: Map = new Map(); let packages: Map = new Map(); @@ -273,22 +282,22 @@ export class NpmScriptsTreeDataProvider implements TreeDataProvider { let packageJson = null; tasks.forEach(each => { - if (isWorkspaceFolder(each.scope) && !this.isInstallTask(each)) { - folder = folders.get(each.scope.name); + if (isWorkspaceFolder(each.task.scope) && !this.isInstallTask(each.task)) { + folder = folders.get(each.task.scope.name); if (!folder) { - folder = new Folder(each.scope); - folders.set(each.scope.name, folder); + folder = new Folder(each.task.scope); + folders.set(each.task.scope.name, folder); } - let definition: NpmTaskDefinition = each.definition; + let definition: NpmTaskDefinition = each.task.definition; let relativePath = definition.path ? definition.path : ''; - let fullPath = path.join(each.scope.name, relativePath); + let fullPath = path.join(each.task.scope.name, relativePath); packageJson = packages.get(fullPath); if (!packageJson) { packageJson = new PackageJSON(folder, relativePath); folder.addPackage(packageJson); packages.set(fullPath, packageJson); } - let script = new NpmScript(this.extensionContext, packageJson, each); + let script = new NpmScript(this.extensionContext, packageJson, each.task, each.location); packageJson.addScript(script); } }); diff --git a/extensions/npm/src/tasks.ts b/extensions/npm/src/tasks.ts index eef43a19d2f99..41e8cf45492b7 100644 --- a/extensions/npm/src/tasks.ts +++ b/extensions/npm/src/tasks.ts @@ -5,7 +5,7 @@ import { TaskDefinition, Task, TaskGroup, WorkspaceFolder, RelativePattern, ShellExecution, Uri, workspace, - DebugConfiguration, debug, TaskProvider, TextDocument, tasks, TaskScope, QuickPickItem, window + DebugConfiguration, debug, TaskProvider, TextDocument, tasks, TaskScope, QuickPickItem, window, Position } from 'vscode'; import * as path from 'path'; import * as fs from 'fs'; @@ -28,19 +28,34 @@ export interface FolderTaskItem extends QuickPickItem { type AutoDetect = 'on' | 'off'; -let cachedTasks: Task[] | undefined = undefined; +let cachedTasks: TaskWithLocation[] | undefined = undefined; const INSTALL_SCRIPT = 'install'; +export interface TaskLocation { + document: Uri, + line: Position +} + +export interface TaskWithLocation { + task: Task, + location?: TaskLocation +} + export class NpmTaskProvider implements TaskProvider { constructor() { } - public provideTasks() { + get tasksWithLocation(): Promise { return provideNpmScripts(); } + public async provideTasks() { + const tasks = await provideNpmScripts(); + return tasks.map(task => task.task); + } + public resolveTask(_task: Task): Promise | undefined { const npmTask = (_task.definition).script; if (npmTask) { @@ -145,10 +160,10 @@ export async function hasNpmScripts(): Promise { } } -async function detectNpmScripts(): Promise { +async function detectNpmScripts(): Promise { - let emptyTasks: Task[] = []; - let allTasks: Task[] = []; + let emptyTasks: TaskWithLocation[] = []; + let allTasks: TaskWithLocation[] = []; let visitedPackageJsonFiles: Set = new Set(); let folders = workspace.workspaceFolders; @@ -189,7 +204,7 @@ export async function detectNpmScriptsForFolder(folder: Uri): Promise ({ label: t.name, task: t }))); + folderTasks.push(...tasks.map(t => ({ label: t.task.name, task: t.task }))); } } return folderTasks; @@ -198,7 +213,7 @@ export async function detectNpmScriptsForFolder(folder: Uri): Promise { +export async function provideNpmScripts(): Promise { if (!cachedTasks) { cachedTasks = await detectNpmScripts(); } @@ -236,8 +251,8 @@ function isDebugScript(script: string): boolean { return match !== null; } -async function provideNpmScriptsForFolder(packageJsonUri: Uri): Promise { - let emptyTasks: Task[] = []; +async function provideNpmScriptsForFolder(packageJsonUri: Uri): Promise { + let emptyTasks: TaskWithLocation[] = []; let folder = workspace.getWorkspaceFolder(packageJsonUri); if (!folder) { @@ -248,12 +263,13 @@ async function provideNpmScriptsForFolder(packageJsonUri: Uri): Promise return emptyTasks; } - const result: Task[] = []; + const result: TaskWithLocation[] = []; const prePostScripts = getPrePostScripts(scripts); - for (const each of Object.keys(scripts)) { - const task = await createTask(each, `run ${each}`, folder!, packageJsonUri, scripts![each]); + for (const each of scripts.keys()) { + const scriptValue = scripts.get(each)!; + const task = await createTask(each, `run ${each}`, folder!, packageJsonUri, scriptValue.script); const lowerCaseTaskName = each.toLowerCase(); if (isBuildTask(lowerCaseTaskName)) { task.group = TaskGroup.Build; @@ -265,14 +281,14 @@ async function provideNpmScriptsForFolder(packageJsonUri: Uri): Promise } // todo@connor4312: all scripts are now debuggable, what is a 'debug script'? - if (isDebugScript(scripts![each])) { + if (isDebugScript(scriptValue.script)) { task.group = TaskGroup.Rebuild; // hack: use Rebuild group to tag debug scripts } - result.push(task); + result.push({ task, location: scriptValue.location }); } // always add npm install (without a problem matcher) - result.push(await createTask(INSTALL_SCRIPT, INSTALL_SCRIPT, folder, packageJsonUri, 'install dependencies from package', [])); + result.push({ task: await createTask(INSTALL_SCRIPT, INSTALL_SCRIPT, folder, packageJsonUri, 'install dependencies from package', []) }); return result; } @@ -352,17 +368,6 @@ async function exists(file: string): Promise { }); } -async function readFile(file: string): Promise { - return new Promise((resolve, reject) => { - fs.readFile(file, (err, data) => { - if (err) { - reject(err); - } - resolve(data.toString()); - }); - }); -} - export async function runScript(script: string, document: TextDocument) { let uri = document.uri; let folder = workspace.getWorkspaceFolder(uri); @@ -393,10 +398,11 @@ export async function startDebugging(scriptName: string, cwd: string, folder: Wo export type StringMap = { [s: string]: string; }; -async function findAllScripts(buffer: string): Promise { - let scripts: StringMap = {}; +async function findAllScripts(document: TextDocument, buffer: string): Promise> { + let scripts: Map = new Map(); let script: string | undefined = undefined; let inScripts = false; + let scriptOffset = 0; let visitor: JSONVisitor = { onError(_error: ParseErrorCode, _offset: number, _length: number) { @@ -410,17 +416,18 @@ async function findAllScripts(buffer: string): Promise { onLiteralValue(value: any, _offset: number, _length: number) { if (script) { if (typeof value === 'string') { - scripts[script] = value; + scripts.set(script, { script: value, location: { document: document.uri, line: document.positionAt(scriptOffset) } }); } script = undefined; } }, - onObjectProperty(property: string, _offset: number, _length: number) { + onObjectProperty(property: string, offset: number, _length: number) { if (property === 'scripts') { inScripts = true; } else if (inScripts && !script) { script = property; + scriptOffset = offset; } else { // nested object which is invalid, ignore the script script = undefined; } @@ -508,7 +515,7 @@ export function findScriptAtPosition(buffer: string, offset: number): string | u return foundScript; } -export async function getScripts(packageJsonUri: Uri): Promise { +export async function getScripts(packageJsonUri: Uri): Promise | undefined> { if (packageJsonUri.scheme !== 'file') { return undefined; @@ -520,8 +527,9 @@ export async function getScripts(packageJsonUri: Uri): Promise