提交 27c5f644 编写于 作者: D Daniel Imms 提交者: GitHub

Merge pull request #20582 from Microsoft/tyriar/7321

Terminal links
......@@ -425,9 +425,9 @@
"resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.0.tgz"
},
"xterm": {
"version": "2.2.3",
"version": "2.3.0",
"from": "Tyriar/xterm.js#vscode-release/1.10",
"resolved": "git+https://github.com/Tyriar/xterm.js.git#90cd66bf353b86ad52d7b650760d8d879dd1c7b8"
"resolved": "git+https://github.com/Tyriar/xterm.js.git#5513303451202b0135601a2f026602ed391b3906"
},
"yauzl": {
"version": "2.3.1",
......
......@@ -58,6 +58,16 @@
opacity: 0 !important;
}
.monaco-workbench .panel.integrated-terminal .xterm a {
color: inherit;
text-decoration: none;
}
.monaco-workbench .panel.integrated-terminal .xterm a:hover {
cursor: pointer;
text-decoration: underline;
}
.monaco-workbench .panel.integrated-terminal .xterm:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar).focus .reverse-video,
.monaco-workbench .panel.integrated-terminal .xterm:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar):focus .reverse-video { color: #CCC; }
.vs-dark .monaco-workbench .panel.integrated-terminal .xterm:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar).focus .reverse-video,
......
......@@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import DOM = require('vs/base/browser/dom');
import Event, { Emitter } from 'vs/base/common/event';
import URI from 'vs/base/common/uri';
......@@ -10,7 +11,6 @@ import cp = require('child_process');
import lifecycle = require('vs/base/common/lifecycle');
import nls = require('vs/nls');
import os = require('os');
import path = require('path');
import platform = require('vs/base/common/platform');
import xterm = require('xterm');
import { Dimension } from 'vs/base/browser/builder';
......@@ -22,9 +22,11 @@ import { IStringDictionary } from 'vs/base/common/collections';
import { ITerminalInstance, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, IShellLaunchConfig } from 'vs/workbench/parts/terminal/common/terminal';
import { ITerminalProcessFactory } from 'vs/workbench/parts/terminal/electron-browser/terminal';
import { IWorkspace, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { TabFocus } from 'vs/editor/common/config/commonEditorConfig';
import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper';
import { TerminalLinkHandler } from 'vs/workbench/parts/terminal/electron-browser/terminalLinkHandler';
/** The amount of time to consider terminal errors to be related to the launch */
const LAUNCHING_DURATION = 500;
......@@ -74,13 +76,15 @@ export class TerminalInstance implements ITerminalInstance {
public constructor(
private _terminalFocusContextKey: IContextKey<boolean>,
private _configHelper: TerminalConfigHelper,
private _linkHandler: TerminalLinkHandler,
private _container: HTMLElement,
private _shellLaunchConfig: IShellLaunchConfig,
@IContextKeyService private _contextKeyService: IContextKeyService,
@IKeybindingService private _keybindingService: IKeybindingService,
@IMessageService private _messageService: IMessageService,
@IPanelService private _panelService: IPanelService,
@IWorkspaceContextService private _contextService: IWorkspaceContextService
@IWorkspaceContextService private _contextService: IWorkspaceContextService,
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService
) {
this._instanceDisposables = [];
this._processDisposables = [];
......@@ -139,6 +143,7 @@ export class TerminalInstance implements ITerminalInstance {
this._xtermElement = document.createElement('div');
this._xterm.open(this._xtermElement);
this._xterm.registerLinkMatcher(this._linkHandler.localLinkRegex, (url) => this._linkHandler.handleLocalLink(url), 1);
this._xterm.attachCustomKeydownHandler((event: KeyboardEvent) => {
// Disable all input if the terminal is exiting
if (this._isExiting) {
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import * as pfs from 'vs/base/node/pfs';
import Uri from 'vs/base/common/uri';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { Platform } from 'vs/base/common/platform';
import { TPromise } from 'vs/base/common/winjs.base';
const pathPrefix = '(\\.\\.?|\\~)';
const pathSeparatorClause = '\\/';
const excludedPathCharactersClause = '[^\\0\\s!$`&*()+\'":;]'; // '":; are allowed in paths but they are often separators so ignore them
const escapedExcludedPathCharactersClause = '(\\\\s|\\\\!|\\\\$|\\\\`|\\\\&|\\\\*|(|)|\\+)';
/** A regex that matches paths in the form /path, ~/path, ./path, ../path */
const UNIX_LIKE_LOCAL_LINK_REGEX = new RegExp('(' + pathPrefix + '?(' + pathSeparatorClause + '(' + excludedPathCharactersClause + '|' + escapedExcludedPathCharactersClause + ')+)+)');
const winPathPrefix = '([a-zA-Z]:|\\.\\.?|\\~)';
const winPathSeparatorClause = '(\\\\|\\/)';
const winExcludedPathCharactersClause = '[^\\0<>\\?\\|\\/\\s!$`&*()+\'":;]';
/** A regex that matches paths in the form c:\path, ~\path, .\path */
const WINDOWS_LOCAL_LINK_REGEX = new RegExp('(' + winPathPrefix + '?(' + winPathSeparatorClause + '(' + winExcludedPathCharactersClause + ')+)+)');
export class TerminalLinkHandler {
constructor(
private _platform: Platform,
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService,
@IWorkspaceContextService private _contextService: IWorkspaceContextService
) {
}
public get localLinkRegex(): RegExp {
if (this._platform === Platform.Windows) {
return WINDOWS_LOCAL_LINK_REGEX;
}
return UNIX_LIKE_LOCAL_LINK_REGEX;
}
public handleLocalLink(link: string): TPromise<void> {
if (this._platform === Platform.Windows) {
return this._handleWindowsLocalLink(link);
}
return this._handleUnixLikeLocalLink(link);
}
private _handleUnixLikeLocalLink(link: string): TPromise<void> {
// Resolve ~ -> $HOME
if (link.charAt(0) === '~') {
if (!process.env.HOME) {
return TPromise.as(void 0);
}
link = process.env.HOME + link.substring(1);
}
return this._handleCommonLocalLink(link);
}
private _handleWindowsLocalLink(link: string): TPromise<void> {
// Resolve ~ -> %HOMEDRIVE%\%HOMEPATH%
if (link.charAt(0) === '~') {
if (!process.env.HOMEDRIVE || !process.env.HOMEPATH) {
return TPromise.as(void 0);
}
link = `${process.env.HOMEDRIVE}\\${process.env.HOMEPATH + link.substring(1)}`;
}
return this._handleCommonLocalLink(link);
}
private _handleCommonLocalLink(link: string): TPromise<void> {
// Resolve workspace path . / .. -> <path>/. / <path/..
if (link.charAt(0) === '.') {
if (!this._contextService.hasWorkspace) {
// Abort if no workspace is open
return TPromise.as(void 0);
}
link = path.join(this._contextService.getWorkspace().resource.fsPath, link);
}
// Clean up the path
const resource = Uri.file(path.normalize(path.resolve(link)));
// Open an editor if the path exists
return pfs.fileExists(link).then(isFile => {
if (!isFile) {
return void 0;
}
return this._editorService.openEditor({ resource }).then(() => void 0);
});
}
}
......@@ -15,12 +15,14 @@ import { ITerminalInstance, ITerminalService, IShellLaunchConfig, KEYBINDING_CON
import { TPromise } from 'vs/base/common/winjs.base';
import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper';
import { TerminalInstance } from 'vs/workbench/parts/terminal/electron-browser/terminalInstance';
import { TerminalLinkHandler } from 'vs/workbench/parts/terminal/electron-browser/terminalLinkHandler';
export class TerminalService implements ITerminalService {
public _serviceBrand: any;
private _activeTerminalInstanceIndex: number;
private _configHelper: TerminalConfigHelper;
private _linkHandler: TerminalLinkHandler;
private _onActiveInstanceChanged: Emitter<string>;
private _onInstanceDisposed: Emitter<ITerminalInstance>;
private _onInstanceProcessIdReady: Emitter<ITerminalInstance>;
......@@ -57,7 +59,8 @@ export class TerminalService implements ITerminalService {
this._configurationService.onDidUpdateConfiguration(() => this.updateConfig());
this._terminalFocusContextKey = KEYBINDING_CONTEXT_TERMINAL_FOCUS.bindTo(this._contextKeyService);
this._configHelper = <TerminalConfigHelper>this._instantiationService.createInstance(TerminalConfigHelper, platform.platform);
this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper, platform.platform);
this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, platform.platform);
this.onInstanceDisposed((terminalInstance) => { this._removeInstance(terminalInstance); });
}
......@@ -65,6 +68,7 @@ export class TerminalService implements ITerminalService {
let terminalInstance = this._instantiationService.createInstance(TerminalInstance,
this._terminalFocusContextKey,
this._configHelper,
this._linkHandler,
this._terminalContainer,
shell);
terminalInstance.addDisposable(terminalInstance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged));
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import { Platform } from 'vs/base/common/platform';
import { TerminalLinkHandler } from 'vs/workbench/parts/terminal/electron-browser/terminalLinkHandler';
suite('Workbench - TerminalLinkHandler', () => {
suite('localLinkRegex', () => {
test('Windows', () => {
const regex = new TerminalLinkHandler(Platform.Windows, null, null).localLinkRegex;
function testLink(link: string) {
assert.equal(` ${link} `.match(regex)[1], link);
assert.equal(`:${link}:`.match(regex)[1], link);
assert.equal(`;${link};`.match(regex)[1], link);
assert.equal(`(${link})`.match(regex)[1], link);
}
testLink('c:\\foo');
testLink('c:/foo');
testLink('.\\foo');
testLink('./foo');
testLink('..\\foo');
testLink('../foo');
testLink('~\\foo');
testLink('~/foo');
testLink('c:/a/long/path');
testLink('c:\\a\\long\\path');
testLink('c:\\mixed/slash\\path');
});
test('Linux', () => {
const regex = new TerminalLinkHandler(Platform.Linux, null, null).localLinkRegex;
function testLink(link: string) {
assert.equal(` ${link} `.match(regex)[1], link);
assert.equal(`:${link}:`.match(regex)[1], link);
assert.equal(`;${link};`.match(regex)[1], link);
assert.equal(`(${link})`.match(regex)[1], link);
}
testLink('/foo');
testLink('~/foo');
testLink('./foo');
testLink('../foo');
testLink('/a/long/path');
});
});
});
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册