diff --git a/resources/web/code-web.js b/resources/web/code-web.js index 68b88385f1e..6773985431f 100644 --- a/resources/web/code-web.js +++ b/resources/web/code-web.js @@ -54,7 +54,8 @@ const args = minimist(process.argv, { 'local_port', 'extension', 'extensionId', - 'github-auth' + 'github-auth', + 'open-file' ], }); @@ -71,6 +72,7 @@ if (args.help) { ' --secondary-port Secondary port\n' + ' --extension Path of an extension to include\n' + ' --extensionId Id of an extension to include\n' + + ' --open-file uri of the file to open. Also support selections in the file. Eg: scheme://authority/path#L1:2-L10:3\n' + ' --github-auth Github authentication token\n' + ' --verbose Print out more information\n' + ' --help\n' + @@ -420,10 +422,32 @@ async function handleRoot(req, res) { ? req.headers['host'].replace(':' + PORT, ':' + SECONDARY_PORT) : `${HOST}:${SECONDARY_PORT}` ); + const openFileUrl = args['open-file'] ? url.parse(args['open-file'], true) : undefined; + let selection; + if (openFileUrl.hash) { + const rangeMatch = /L(?\d+)(?::(?\d+))?((?:-L(?\d+))(?::(?\d+))?)?/.exec(openFileUrl.hash); + if (rangeMatch?.groups) { + const { startLineNumber, startColumn, endLineNumber, endColumn } = rangeMatch.groups; + const start = { line: parseInt(startLineNumber), column: startColumn ? (parseInt(startColumn) || 1) : 1 }; + const end = endLineNumber ? { line: parseInt(endLineNumber), column: endColumn ? (parseInt(endColumn) || 1) : 1 } : start; + selection = { start, end } + } + } const webConfigJSON = { folderUri: folderUri, additionalBuiltinExtensions, - webWorkerExtensionHostIframeSrc: `${SCHEME}://${secondaryHost}/static/out/vs/workbench/services/extensions/worker/httpWebWorkerExtensionHostIframe.html` + webWorkerExtensionHostIframeSrc: `${SCHEME}://${secondaryHost}/static/out/vs/workbench/services/extensions/worker/httpWebWorkerExtensionHostIframe.html`, + defaultLayout: openFileUrl ? { + force: true, + editors: [{ + uri: { + scheme: openFileUrl.protocol.substring(0, openFileUrl.protocol.length - 1), + authority: openFileUrl.host, + path: openFileUrl.path, + }, + selection, + }] + } : undefined }; if (args['wrap-iframe']) { webConfigJSON._wrapWebWorkerExtHostInIframe = true; diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index bca994efa1e..e2cde9f3926 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -166,11 +166,15 @@ export interface IPathData { // the file path to open within the instance readonly fileUri?: UriComponents; - // the line number in the file path to open - readonly lineNumber?: number; - - // the column number in the file path to open - readonly columnNumber?: number; + /** + * An optional selection to apply in the file + */ + readonly selection?: { + readonly startLineNumber: number; + readonly startColumn: number; + readonly endLineNumber?: number; + readonly endColumn?: number; + } // a hint that the file exists. if true, the // file exists, if false it does not. with diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 8a775d273d1..f673af7f71b 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -914,7 +914,11 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic if (options.gotoLineMode) { const { path, line, column } = parseLineAndColumnAware(uri.path); - return { fileUri: uri.with({ path }), lineNumber: line, columnNumber: column, remoteAuthority }; + return { + fileUri: uri.with({ path }), + selection: line ? { startLineNumber: line, startColumn: column || 1 } : undefined, + remoteAuthority + }; } return { fileUri: uri, remoteAuthority }; @@ -973,7 +977,11 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic } // File - return { fileUri: URI.file(path), lineNumber, columnNumber, exists: true }; + return { + fileUri: URI.file(path), + selection: lineNumber ? { startLineNumber: lineNumber, startColumn: columnNumber || 1 } : undefined, + exists: true + }; } // Folder (we check for isDirectory() because e.g. paths like /dev/null @@ -1029,7 +1037,11 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // file name ends with .code-workspace if (hasWorkspaceFileExtension(path)) { if (options.forceOpenWorkspaceAsFile) { - return { fileUri: uri, lineNumber, columnNumber, remoteAuthority: options.remoteAuthority }; + return { + fileUri: uri, + selection: lineNumber ? { startLineNumber: lineNumber, startColumn: columnNumber || 1 } : undefined, + remoteAuthority: options.remoteAuthority + }; } return { workspace: getWorkspaceIdentifier(uri), remoteAuthority }; @@ -1037,7 +1049,11 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // file name starts with a dot or has an file extension else if (options.gotoLineMode || posix.basename(path).indexOf('.') !== -1) { - return { fileUri: uri, lineNumber, columnNumber, remoteAuthority }; + return { + fileUri: uri, + selection: lineNumber ? { startLineNumber: lineNumber, startColumn: columnNumber || 1 } : undefined, + remoteAuthority + }; } } diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 3f91a43463c..547d962d97c 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -37,7 +37,7 @@ import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/a import { IFileService } from 'vs/platform/files/common/files'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { coalesce } from 'vs/base/common/arrays'; -import { assertIsDefined } from 'vs/base/common/types'; +import { assertIsDefined, isNumber } from 'vs/base/common/types'; import { INotificationService, NotificationsFilter } from 'vs/platform/notification/common/notification'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { WINDOW_ACTIVE_BORDER, WINDOW_INACTIVE_BORDER } from 'vs/workbench/common/theme'; @@ -644,7 +644,17 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return { filesToOpenOrCreate: defaultLayout.editors.map(file => { - return { fileUri: URI.revive(file.uri), openOnlyIfExists: file.openOnlyIfExists, editorOverrideId: file.openWith }; + return { + fileUri: URI.revive(file.uri), + selection: file.selection && file.selection.start && isNumber(file.selection.start.line) ? { + startLineNumber: file.selection.start.line, + startColumn: isNumber(file.selection.start.column) ? file.selection.start.column : 1, + endLineNumber: isNumber(file.selection.end.line) ? file.selection.end.line : undefined, + endColumn: isNumber(file.selection.end.line) ? (isNumber(file.selection.end.column) ? file.selection.end.column : 1) : undefined, + } : undefined, + openOnlyIfExists: file.openOnlyIfExists, + editorOverrideId: file.openWith + }; }) }; } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 241e412eb4b..8bf13afeca2 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { Event } from 'vs/base/common/event'; -import { assertIsDefined } from 'vs/base/common/types'; +import { assertIsDefined, isUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IEditor, IEditorViewState, IDiffEditor } from 'vs/editor/common/editorCommon'; @@ -1109,11 +1109,8 @@ export async function pathsToEditors(paths: IPathData[] | undefined, fileService return; } - const options: ITextEditorOptions = (exists && typeof path.lineNumber === 'number') ? { - selection: { - startLineNumber: path.lineNumber, - startColumn: path.columnNumber || 1 - }, + const options: ITextEditorOptions = (exists && !isUndefined(path.selection)) ? { + selection: path.selection, pinned: true, override: path.editorOverrideId } : { diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 5f12e6d395e..2b32eb71c2b 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -17,6 +17,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { parseLineAndColumnAware } from 'vs/base/common/extpath'; import { LogLevelToString } from 'vs/platform/log/common/log'; import { ExtensionKind } from 'vs/platform/extensions/common/extensions'; +import { isUndefined } from 'vs/base/common/types'; class BrowserWorkbenchConfiguration implements IWindowConfiguration { @@ -44,8 +45,7 @@ class BrowserWorkbenchConfiguration implements IWindowConfiguration { return [{ fileUri: fileUri.with({ path: pathColumnAware.path }), - lineNumber: pathColumnAware.line, - columnNumber: pathColumnAware.column + selection: !isUndefined(pathColumnAware.line) ? { startLineNumber: pathColumnAware.line, startColumn: pathColumnAware.column || 1 } : undefined }]; } diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index d98a5111249..1af62ce0041 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -30,6 +30,7 @@ import { localize } from 'vs/nls'; import Severity from 'vs/base/common/severity'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { DomEmitter } from 'vs/base/browser/event'; +import { isUndefined } from 'vs/base/common/types'; /** * A workspace to open in the workbench can either be: @@ -286,8 +287,7 @@ export class BrowserHostService extends Disposable implements IHostService { const pathColumnAware = parseLineAndColumnAware(openable.fileUri.path); openables = [{ fileUri: openable.fileUri.with({ path: pathColumnAware.path }), - lineNumber: pathColumnAware.line, - columnNumber: pathColumnAware.column + selection: !isUndefined(pathColumnAware.line) ? { startLineNumber: pathColumnAware.line, startColumn: pathColumnAware.column || 1 } : undefined }]; } else { openables = [openable]; diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index dff7ef966b4..59faebbffa1 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -205,8 +205,25 @@ interface IDefaultView { readonly id: string; } +interface IPosition { + readonly line: number; + readonly column: number; +} + +interface IRange { + /** + * The start position. It is before or equal to end position. + */ + readonly start: IPosition; + /** + * The end position. It is after or equal to start position. + */ + readonly end: IPosition; +} + interface IDefaultEditor { readonly uri: UriComponents; + readonly selection?: IRange; readonly openOnlyIfExists?: boolean; readonly openWith?: string; } @@ -691,6 +708,8 @@ export { IDefaultView, IDefaultEditor, IDefaultLayout, + IPosition, + IRange as ISelection, // Env IPerformanceMark,