Merge branch 'master' into isidorn/centeredLayout

This commit is contained in:
isidor 2018-06-19 09:41:20 +02:00
commit 906007ff9f
113 changed files with 2743 additions and 1419 deletions

View file

@ -1,10 +1,10 @@
---
name: Bug report
about: Create a report to help us improve
---
<!-- Please search existing issues to avoid creating duplicates. -->
<!-- Please search existing issues to avoid creating duplicates. -->
<!-- Also please test using the latest insiders build to make sure your issue has not already been fixed: https://code.visualstudio.com/insiders/ -->
<!-- Use Help > Report Issue to prefill these. -->
- VSCode Version:

1
.vscode/launch.json vendored
View file

@ -202,6 +202,7 @@
"runtimeExecutable": "${workspaceFolder}/.build/electron/code-oss"
},
"stopOnEntry": false,
"outputCapture": "std",
"args": [
"--delay",
"--timeout",

View file

@ -3,7 +3,10 @@
"compounds": [
{
"name": "Debug Extension and Language Server",
"configurations": ["Launch Extension", "Attach Language Server"]
"configurations": [
"Launch Extension",
"Attach Language Server"
]
}
],
"configurations": [
@ -17,7 +20,9 @@
],
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": ["${workspaceFolder}/client/out/**/*.js"],
"outFiles": [
"${workspaceFolder}/client/out/**/*.js"
],
"smartStep": true,
"preLaunchTask": "npm: compile"
},
@ -26,10 +31,15 @@
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceFolder}", "--extensionTestsPath=${workspaceFolder}/client/out/test" ],
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/client/out/test"
],
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": ["${workspaceFolder}/client/out/test/**/*.js"],
"outFiles": [
"${workspaceFolder}/client/out/test/**/*.js"
],
"preLaunchTask": "npm: compile"
},
{
@ -39,7 +49,9 @@
"protocol": "inspector",
"port": 6044,
"sourceMaps": true,
"outFiles": ["${workspaceFolder}/server/out/**/*.js"],
"outFiles": [
"${workspaceFolder}/server/out/**/*.js"
],
"smartStep": true,
"restart": true
}

View file

@ -243,6 +243,17 @@
"default": "ignore",
"description": "%css.lint.idSelector.desc%"
},
"css.lint.unknownAtRules": {
"type": "string",
"scope": "resource",
"enum": [
"ignore",
"warning",
"error"
],
"default": "warning",
"description": "%css.lint.unknownAtRules.desc%"
},
"css.trace.server": {
"type": "string",
"scope": "window",
@ -704,4 +715,4 @@
"@types/node": "7.0.43",
"mocha": "^5.2.0"
}
}
}

View file

@ -16,6 +16,7 @@
"css.lint.importStatement.desc": "Import statements do not load in parallel",
"css.lint.propertyIgnoredDueToDisplay.desc": "Property is ignored due to the display. E.g. with 'display: inline', the width, height, margin-top, margin-bottom, and float properties have no effect",
"css.lint.universalSelector.desc": "The universal selector (*) is known to be slow",
"css.lint.unknownAtRules.desc": "Unknown at-rule.",
"css.lint.unknownProperties.desc": "Unknown property.",
"css.lint.unknownVendorSpecificProperties.desc": "Unknown vendor specific property.",
"css.lint.vendorPrefix.desc": "When using a vendor-specific prefix also include the standard property",

14
extensions/css/.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,14 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Grammar",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceRoot}"
]
}
]
}

View file

@ -5,55 +5,55 @@
"configuration.typescript": "TypeScript",
"typescript.useCodeSnippetsOnMethodSuggest.dec": "Complete functions with their parameter signature.",
"typescript.tsdk.desc": "Specifies the folder path containing the tsserver and lib*.d.ts files to use.",
"typescript.disableAutomaticTypeAcquisition": "Disables automatic type acquisition. Requires TypeScript >= 2.0.6.",
"typescript.disableAutomaticTypeAcquisition": "Disables automatic type acquisition.",
"typescript.tsserver.log": "Enables logging of the TS server to a file. This log can be used to diagnose TS Server issues. The log may contain file paths, source code, and other potentially sensitive information from your project.",
"typescript.tsserver.pluginPaths": "Additional paths to discover Typescript Language Service plugins. Requires TypeScript >= 2.3.0.",
"typescript.tsserver.pluginPaths": "Additional paths to discover Typescript Language Service plugins. Requires using TypeScript 2.3.0 or newer in the workspace.",
"typescript.tsserver.pluginPaths.item": "Either an absolute or relative path. Relative path will be resolved against workspace folder(s).",
"typescript.tsserver.trace": "Enables tracing of messages sent to the TS server. This trace can be used to diagnose TS Server issues. The trace may contain file paths, source code, and other potentially sensitive information from your project.",
"typescript.validate.enable": "Enable/disable TypeScript validation.",
"typescript.format.enable": "Enable/disable default TypeScript formatter.",
"javascript.format.enable": "Enable/disable default JavaScript formatter.",
"format.insertSpaceAfterCommaDelimiter": "Defines space handling after a comma delimiter.",
"format.insertSpaceAfterConstructor": "Defines space handling after the constructor keyword. Requires TypeScript >= 2.3.0.",
"format.insertSpaceAfterConstructor": "Defines space handling after the constructor keyword. Requires using TypeScript 2.3.0 or newer in the workspace.",
"format.insertSpaceAfterSemicolonInForStatements": " Defines space handling after a semicolon in a for statement.",
"format.insertSpaceBeforeAndAfterBinaryOperators": "Defines space handling after a binary operator.",
"format.insertSpaceAfterKeywordsInControlFlowStatements": "Defines space handling after keywords in a control flow statement.",
"format.insertSpaceAfterFunctionKeywordForAnonymousFunctions": "Defines space handling after function keyword for anonymous functions.",
"format.insertSpaceBeforeFunctionParenthesis": "Defines space handling before function argument parentheses. Requires TypeScript >= 2.1.5.",
"format.insertSpaceBeforeFunctionParenthesis": "Defines space handling before function argument parentheses.",
"format.insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": "Defines space handling after opening and before closing non-empty parenthesis.",
"format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": "Defines space handling after opening and before closing non-empty brackets.",
"format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": "Defines space handling after opening and before closing non-empty braces. Requires TypeScript >= 2.3.0.",
"format.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": "Defines space handling after opening and before closing template string braces. Requires TypeScript >= 2.0.6.",
"format.insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces": "Defines space handling after opening and before closing JSX expression braces. Requires TypeScript >= 2.0.6.",
"format.insertSpaceAfterTypeAssertion": "Defines space handling after type assertions in TypeScript. Requires TypeScript >= 2.4.",
"format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": "Defines space handling after opening and before closing non-empty braces. Requires using TypeScript 2.3.0 or newer in the workspace.",
"format.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": "Defines space handling after opening and before closing template string braces.",
"format.insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces": "Defines space handling after opening and before closing JSX expression braces.",
"format.insertSpaceAfterTypeAssertion": "Defines space handling after type assertions in TypeScript. Requires using TypeScript 2.4 or newer in the workspace.",
"format.placeOpenBraceOnNewLineForFunctions": "Defines whether an open brace is put onto a new line for functions or not.",
"format.placeOpenBraceOnNewLineForControlBlocks": "Defines whether an open brace is put onto a new line for control blocks or not.",
"javascript.validate.enable": "Enable/disable JavaScript validation.",
"goToProjectConfig.title": "Go to Project Configuration",
"javascript.referencesCodeLens.enabled": "Enable/disable references CodeLens in JavaScript files.",
"typescript.referencesCodeLens.enabled": "Enable/disable references CodeLens in TypeScript files. Requires TypeScript >= 2.0.6.",
"typescript.implementationsCodeLens.enabled": "Enable/disable implementations CodeLens. Requires TypeScript >= 2.2.0.",
"typescript.referencesCodeLens.enabled": "Enable/disable references CodeLens in TypeScript files.",
"typescript.implementationsCodeLens.enabled": "Enable/disable implementations CodeLens.",
"typescript.openTsServerLog.title": "Open TS Server log",
"typescript.restartTsServer": "Restart TS server",
"typescript.selectTypeScriptVersion.title": "Select TypeScript Version",
"typescript.reportStyleChecksAsWarnings": "Report style checks as warnings",
"jsDocCompletion.enabled": "Enable/disable auto JSDoc comments",
"javascript.implicitProjectConfig.checkJs": "Enable/disable semantic checking of JavaScript files. Existing jsconfig.json or tsconfig.json files override this setting. Requires TypeScript >= 2.3.1.",
"typescript.npm": "Specifies the path to the NPM executable used for Automatic Type Acquisition. Requires TypeScript >= 2.3.4.",
"javascript.implicitProjectConfig.checkJs": "Enable/disable semantic checking of JavaScript files. Existing jsconfig.json or tsconfig.json files override this setting. Requires using TypeScript 2.3.1 or newer in the workspace.",
"typescript.npm": "Specifies the path to the NPM executable used for Automatic Type Acquisition. Requires using TypeScript 2.3.4 or newer in the workspace.",
"typescript.check.npmIsInstalled": "Check if NPM is installed for Automatic Type Acquisition.",
"javascript.nameSuggestions": "Enable/disable including unique names from the file in JavaScript suggestion lists.",
"typescript.tsc.autoDetect": "Controls auto detection of tsc tasks. 'off' disables this feature. 'build' only creates single run compile tasks. 'watch' only creates compile and watch tasks. 'on' creates both build and watch tasks. Default is 'on'.",
"typescript.problemMatchers.tsc.label": "TypeScript problems",
"typescript.problemMatchers.tscWatch.label": "TypeScript problems (watch mode)",
"typescript.quickSuggestionsForPaths": "Enable/disable quick suggestions when typing out an import path.",
"typescript.locale": "Sets the locale used to report JavaScript and TypeScript errors. Requires TypeScript >= 2.6.0. Default of 'null' uses VS Code's locale.",
"javascript.implicitProjectConfig.experimentalDecorators": "Enable/disable 'experimentalDecorators' for JavaScript files that are not part of a project. Existing jsconfig.json or tsconfig.json files override this setting. Requires TypeScript >= 2.3.1.",
"typescript.autoImportSuggestions.enabled": "Enable/disable auto import suggestions. Requires TypeScript >= 2.6.1",
"typescript.locale": "Sets the locale used to report JavaScript and TypeScript errors. Requires using TypeScript 2.6.0. Default of 'null' uses VS Code's locale. or newer in the workspace.",
"javascript.implicitProjectConfig.experimentalDecorators": "Enable/disable 'experimentalDecorators' for JavaScript files that are not part of a project. Existing jsconfig.json or tsconfig.json files override this setting. Requires using TypeScript 2.3.1 or newer in the workspace.",
"typescript.autoImportSuggestions.enabled": "Enable/disable auto import suggestions. Requires using TypeScript 2.6.1 or newer in the workspace.",
"taskDefinition.tsconfig.description": "The tsconfig file that defines the TS build.",
"javascript.suggestionActions.enabled": "Enable/disable suggestion diagnostics for JavaScript files in the editor. Requires TypeScript >= 2.8",
"typescript.suggestionActions.enabled": "Enable/disable suggestion diagnostics for TypeScript files in the editor. Requires TypeScript >= 2.8",
"typescript.preferences.quoteStyle": "Preferred quote style to use for quick fixes: 'single' quotes, 'double' quotes, or 'auto' infer quote type from existing imports. Requires TypeScript >= 2.9",
"typescript.preferences.importModuleSpecifier": "Preferred path style for auto imports:\n- \"relative\" to the file location.\n- \"non-relative\" based on the 'baseUrl' configured in your 'jsconfig.json' / 'tsconfig.json'.\n- \"auto\" infer the shortest path type.\nRequires TypeScript >= 2.9",
"typescript.showUnused": "Enable/disable highlighting of unused variables in code. Requires TypeScript >= 2.9",
"typescript.updateImportsOnFileMove.enabled": "Enable/disable automatic updating of import paths when you rename or move a file in VS Code. Possible values are: 'prompt' on each rename, 'always' update paths automatically, and 'never' rename paths and don't prompt me. Requires TypeScript >= 2.9"
"javascript.suggestionActions.enabled": "Enable/disable suggestion diagnostics for JavaScript files in the editor. Requires using TypeScript 2.8 or newer in the workspace.",
"typescript.suggestionActions.enabled": "Enable/disable suggestion diagnostics for TypeScript files in the editor. Requires using TypeScript 2.8 or newer in the workspace.",
"typescript.preferences.quoteStyle": "Preferred quote style to use for quick fixes: 'single' quotes, 'double' quotes, or 'auto' infer quote type from existing imports. Requires using TypeScript 2.9 or newer in the workspace.",
"typescript.preferences.importModuleSpecifier": "Preferred path style for auto imports:\n- \"relative\" to the file location.\n- \"non-relative\" based on the 'baseUrl' configured in your 'jsconfig.json' / 'tsconfig.json'.\n- \"auto\" infer the shortest path type.\nRequires using TypeScript 2.9 or newer in the workspace.",
"typescript.showUnused": "Enable/disable highlighting of unused variables in code. Requires using TypeScript 2.9 or newer in the workspace.",
"typescript.updateImportsOnFileMove.enabled": "Enable/disable automatic updating of import paths when you rename or move a file in VS Code. Possible values are: 'prompt' on each rename, 'always' update paths automatically, and 'never' rename paths and don't prompt me. Requires using TypeScript 2.9 or newer in the workspace."
}

View file

@ -37,7 +37,8 @@ class MyCompletionItem extends vscode.CompletionItem {
if (tsEntry.isRecommended) {
// Make sure isRecommended property always comes first
// https://github.com/Microsoft/vscode/issues/40325
this.sortText = '\0' + tsEntry.sortText;
this.sortText = tsEntry.sortText;
this.preselect = true;
} else if (tsEntry.source) {
// De-prioritze auto-imports
// https://github.com/Microsoft/vscode/issues/40311
@ -594,4 +595,4 @@ export function register(
return vscode.languages.registerCompletionItemProvider(selector,
new TypeScriptCompletionItemProvider(client, typingsStatus, fileConfigurationManager, commandManager),
...TypeScriptCompletionItemProvider.triggerCharacters);
}
}

View file

@ -155,7 +155,7 @@ export class DiagnosticsManager {
return this._diagnostics.get(DiagnosticKind.Suggestion)!.get(file).filter(x => {
if (!this._enableSuggestions) {
// Still show unused
return x.customTags && x.customTags.indexOf(vscode.DiagnosticTag.Unnecessary) !== -1;
return x.tags && x.tags.indexOf(vscode.DiagnosticTag.Unnecessary) !== -1;
}
return true;
});

View file

@ -35,7 +35,7 @@ class TypeScriptDocumentSymbolProvider implements vscode.DocumentSymbolProvider
public constructor(
private readonly client: ITypeScriptServiceClient) { }
public async provideDocumentSymbols(resource: vscode.TextDocument, token: vscode.CancellationToken): Promise<any> { // todo@joh `any[]` temporary hack to make typescript happy...
public async provideDocumentSymbols(resource: vscode.TextDocument, token: vscode.CancellationToken): Promise<vscode.DocumentSymbol[] | vscode.SymbolInformation[]> {
const filepath = this.client.toPath(resource.uri);
if (!filepath) {
return [];

View file

@ -14,6 +14,7 @@ import * as languageIds from '../utils/languageModeIds';
import * as typeConverters from '../utils/typeConverters';
import FileConfigurationManager from './fileConfigurationManager';
import * as fileSchemes from '../utils/fileSchemes';
import { escapeRegExp } from '../utils/regexp';
const localize = nls.loadMessageBundle();
@ -222,29 +223,45 @@ export class UpdateImportsOnFileRenameHandler {
return;
}
return typeConverters.WorkspaceEdit.fromFromFileCodeEdits(this.client, response.body.map((edit: Proto.FileCodeEdits) => this.fixEdit(edit, isDirectoryRename)));
const edits: Proto.FileCodeEdits[] = [];
for (const edit of response.body) {
edits.push(await this.fixEdit(edit, isDirectoryRename, oldFile, newFile));
}
return typeConverters.WorkspaceEdit.fromFromFileCodeEdits(this.client, edits);
}
private fixEdit(
private async fixEdit(
edit: Proto.FileCodeEdits,
isDirectoryRename: boolean
): Proto.FileCodeEdits {
isDirectoryRename: boolean,
oldFile: string,
newFile: string,
): Promise<Proto.FileCodeEdits> {
if (!isDirectoryRename || this.client.apiVersion.gte(API.v300)) {
return edit;
}
const document = await vscode.workspace.openTextDocument(edit.fileName);
const oldFileRe = new RegExp('/' + escapeRegExp(path.basename(oldFile)) + '/');
// Workaround for https://github.com/Microsoft/TypeScript/issues/24968
const textChanges = edit.textChanges.map((change): Proto.CodeEdit => {
const match = change.newText.match(/\/[^\/]+$/g);
const existingText = document.getText(typeConverters.Range.fromTextSpan(change));
const existingMatch = existingText.match(oldFileRe);
if (!existingMatch) {
return change;
}
const match = new RegExp('/' + escapeRegExp(path.basename(newFile)) + '/(.+)$', 'g').exec(change.newText);
if (!match) {
return change;
}
return {
newText: change.newText.slice(0, -match[0].length),
newText: change.newText.slice(0, -match[1].length),
start: change.start,
end: {
line: change.end.line,
offset: change.end.offset - match[0].length
offset: change.end.offset - match[1].length
}
};
});

View file

@ -154,7 +154,7 @@ export default class LanguageProvider {
const reportUnnecessary = config.get<boolean>('showUnused', true);
this.diagnosticsManager.diagnosticsReceived(diagnosticsKind, file, diagnostics.filter(diag => {
if (!reportUnnecessary) {
diag.customTags = undefined;
diag.tags = undefined;
if (diag.reportUnnecessary && diag.severity === vscode.DiagnosticSeverity.Hint) {
return false;
}

View file

@ -298,7 +298,7 @@ export default class TypeScriptServiceClientHost {
}).filter((x: any) => !!x) as DiagnosticRelatedInformation[];
}
if (diagnostic.reportsUnnecessary) {
converted.customTags = [DiagnosticTag.Unnecessary];
converted.tags = [DiagnosticTag.Unnecessary];
}
(converted as Diagnostic & { reportUnnecessary: any }).reportUnnecessary = diagnostic.reportsUnnecessary;
return converted as Diagnostic & { reportUnnecessary: any };

View file

@ -9,9 +9,8 @@ import { isSupportedLanguageMode } from './languageModeIds';
/**
* When clause context set when the current file is managed by vscode's built-in typescript extension.
*/
const isManagedFile_contextName = 'typescript.isManagedFile';
export default class ManagedFileContextManager {
private static readonly contextName = 'typescript.isManagedFile';
private isInManagedFileContext: boolean = false;
@ -41,8 +40,7 @@ export default class ManagedFileContextManager {
return;
}
vscode.commands.executeCommand('setContext', isManagedFile_contextName, newValue);
vscode.commands.executeCommand('setContext', ManagedFileContextManager.contextName, newValue);
this.isInManagedFileContext = newValue;
}
}

View file

@ -6,7 +6,7 @@
'use strict';
import * as assert from 'assert';
import { workspace, window, commands, ViewColumn, TextEditorViewColumnChangeEvent, Uri, Selection, Position, CancellationTokenSource, TextEditorSelectionChangeKind } from 'vscode';
import { workspace, window, commands, ViewColumn, TextEditorViewColumnChangeEvent, Uri, Selection, Position, CancellationTokenSource, TextEditorSelectionChangeKind, Terminal } from 'vscode';
import { join } from 'path';
import { closeAllEditors, pathEquals, createRandomFile } from '../utils';
@ -557,37 +557,143 @@ suite('window namespace tests', () => {
});
});
test('createTerminal, Terminal.name', () => {
const terminal = window.createTerminal('foo');
assert.equal(terminal.name, 'foo');
suite('Terminal', () => {
test('createTerminal, Terminal.name', () => {
const terminal = window.createTerminal('foo');
assert.equal(terminal.name, 'foo');
assert.throws(() => {
(<any>terminal).name = 'bar';
}, 'Terminal.name should be readonly');
});
test('terminal, sendText immediately after createTerminal should not throw', () => {
const terminal = window.createTerminal();
assert.doesNotThrow(terminal.sendText.bind(terminal, 'echo "foo"'));
});
test('terminal, onDidCloseTerminal event fires when terminal is disposed', (done) => {
const terminal = window.createTerminal();
window.onDidCloseTerminal((eventTerminal) => {
assert.equal(terminal, eventTerminal);
done();
assert.throws(() => {
(<any>terminal).name = 'bar';
}, 'Terminal.name should be readonly');
terminal.dispose();
});
terminal.dispose();
});
test('terminal, processId immediately after createTerminal should fetch the pid', (done) => {
window.createTerminal().processId.then(id => {
assert.ok(id > 0);
done();
test('sendText immediately after createTerminal should not throw', () => {
const terminal = window.createTerminal();
assert.doesNotThrow(terminal.sendText.bind(terminal, 'echo "foo"'));
terminal.dispose();
});
});
test('terminal, name should set terminal.name', () => {
assert.equal(window.createTerminal('foo').name, 'foo');
test('onDidCloseTerminal event fires when terminal is disposed', (done) => {
const terminal = window.createTerminal();
const reg = window.onDidCloseTerminal((eventTerminal) => {
assert.equal(terminal, eventTerminal);
reg.dispose();
done();
});
terminal.dispose();
});
test('processId immediately after createTerminal should fetch the pid', (done) => {
const terminal = window.createTerminal();
terminal.processId.then(id => {
assert.ok(id > 0);
terminal.dispose();
done();
});
});
test('name in constructor should set terminal.name', () => {
const terminal = window.createTerminal('a');
assert.equal(terminal.name, 'a');
terminal.dispose();
});
test('onDidOpenTerminal should fire when a terminal is created', (done) => {
const reg1 = window.onDidOpenTerminal(term => {
assert.equal(term.name, 'b');
reg1.dispose();
const reg2 = window.onDidCloseTerminal(() => {
reg2.dispose();
done();
});
terminal.dispose();
});
const terminal = window.createTerminal('b');
});
test('createTerminalRenderer should fire onDidOpenTerminal and onDidCloseTerminal', (done) => {
const reg1 = window.onDidOpenTerminal(term => {
assert.equal(term.name, 'c');
reg1.dispose();
const reg2 = window.onDidCloseTerminal(() => {
reg2.dispose();
done();
});
term.dispose();
});
window.createTerminalRenderer('c');
});
test('terminal renderers should get maximum dimensions set when shown', (done) => {
let terminal: Terminal;
const reg1 = window.onDidOpenTerminal(term => {
reg1.dispose();
term.show();
terminal = term;
});
const renderer = window.createTerminalRenderer('foo');
const reg2 = renderer.onDidChangeMaximumDimensions(dimensions => {
assert.ok(dimensions.cols > 0);
assert.ok(dimensions.rows > 0);
reg2.dispose();
const reg3 = window.onDidCloseTerminal(() => {
reg3.dispose();
done();
});
terminal.dispose();
});
});
test('TerminalRenderer.write should fire Terminal.onData', (done) => {
const reg1 = window.onDidOpenTerminal(terminal => {
reg1.dispose();
const reg2 = terminal.onData(data => {
assert.equal(data, 'bar');
reg2.dispose();
const reg3 = window.onDidCloseTerminal(() => {
reg3.dispose();
done();
});
terminal.dispose();
});
renderer.write('bar');
});
const renderer = window.createTerminalRenderer('foo');
});
test('Terminal.sendText should fire Termnial.onInput', (done) => {
const reg1 = window.onDidOpenTerminal(terminal => {
reg1.dispose();
const reg2 = renderer.onInput(data => {
assert.equal(data, 'bar');
reg2.dispose();
const reg3 = window.onDidCloseTerminal(() => {
reg3.dispose();
done();
});
terminal.dispose();
});
terminal.sendText('bar', false);
});
const renderer = window.createTerminalRenderer('foo');
});
test('onDidChangeActiveTerminal should fire when new terminals are created', (done) => {
const reg1 = window.onDidChangeActiveTerminal((active: Terminal | undefined) => {
assert.equal(active, terminal);
assert.equal(active, window.activeTerminal);
reg1.dispose();
const reg2 = window.onDidChangeActiveTerminal((active: Terminal | undefined) => {
assert.equal(active, undefined);
assert.equal(active, window.activeTerminal);
reg2.dispose();
done();
});
terminal.dispose();
});
const terminal = window.createTerminal();
terminal.show();
});
});
});

View file

@ -520,42 +520,51 @@ suite('workspace-namespace', () => {
});
// test('applyEdit should fail when editing deleted resource', async () => {
// const resource = await createRandomFile();
test('applyEdit should fail when editing deleted resource', async () => {
const resource = await createRandomFile();
// const edit = new vscode.WorkspaceEdit();
// edit.deleteResource(resource);
// try {
// edit.insert(resource, new vscode.Position(0, 0), '');
// assert.fail(false, 'Should disallow edit of deleted resource');
// } catch {
// // noop
// }
// });
const edit = new vscode.WorkspaceEdit();
edit.deleteFile(resource);
edit.insert(resource, new vscode.Position(0, 0), '');
// test('applyEdit should fail when renaming deleted resource', async () => {
// const resource = await createRandomFile();
let success = await vscode.workspace.applyEdit(edit);
assert.equal(success, false);
});
// const edit = new vscode.WorkspaceEdit();
// edit.deleteResource(resource);
// try {
// edit.renameResource(resource, resource);
// assert.fail(false, 'Should disallow rename of deleted resource');
// } catch {
// // noop
// }
// });
test('applyEdit should fail when renaming deleted resource', async () => {
const resource = await createRandomFile();
// test('applyEdit should fail when editing renamed from resource', async () => {
// const resource = await createRandomFile();
// const newResource = vscode.Uri.parse(resource.fsPath + '.1');
// const edit = new vscode.WorkspaceEdit();
// edit.renameResource(resource, newResource);
// try {
// edit.insert(resource, new vscode.Position(0, 0), '');
// assert.fail(false, 'Should disallow editing renamed file');
// } catch {
// // noop
// }
// });
const edit = new vscode.WorkspaceEdit();
edit.deleteFile(resource);
edit.renameFile(resource, resource);
let success = await vscode.workspace.applyEdit(edit);
assert.equal(success, false);
});
test('applyEdit should fail when editing renamed from resource', async () => {
const resource = await createRandomFile();
const newResource = vscode.Uri.parse(resource.fsPath + '.1');
const edit = new vscode.WorkspaceEdit();
edit.renameFile(resource, newResource);
edit.insert(resource, new vscode.Position(0, 0), '');
let success = await vscode.workspace.applyEdit(edit);
assert.equal(success, false);
});
test('applyEdit "edit A -> rename A to B -> edit B"', async () => {
const oldUri = await createRandomFile();
const newUri = oldUri.with({ path: oldUri.path + 'NEW' });
const edit = new vscode.WorkspaceEdit();
edit.insert(oldUri, new vscode.Position(0, 0), 'BEFORE');
edit.renameFile(oldUri, newUri);
edit.insert(newUri, new vscode.Position(0, 0), 'AFTER');
let success = await vscode.workspace.applyEdit(edit);
assert.equal(success, true);
// let doc = await vscode.workspace.openTextDocument(newUri);
// assert.equal(doc.getText(), 'AFTERBEFORE');
});
});

View file

@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.25.0",
"distro": "5f30bb6859641677a2676f623f994f923e609469",
"distro": "e781cd4cf9c4c77fa3c0fcb04017a3ba57cde7a2",
"author": {
"name": "Microsoft Corporation"
},
@ -48,7 +48,7 @@
"vscode-debugprotocol": "1.28.0",
"vscode-nsfw": "1.0.17",
"vscode-ripgrep": "^1.0.1",
"vscode-textmate": "^3.3.3",
"vscode-textmate": "^4.0.0-next.2",
"vscode-xterm": "3.5.0-beta12",
"yauzl": "^2.9.1"
},
@ -137,4 +137,4 @@
"windows-mutex": "^0.2.0",
"windows-process-tree": "0.2.2"
}
}
}

View file

@ -3,6 +3,39 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Error.stackTraceLimit = 100; // increase number of stack frames (from 10, https://github.com/v8/v8/wiki/Stack-Trace-API)
const fs = require('fs');
const path = require('path');
const product = require('../product.json');
const appRoot = path.dirname(__dirname);
function getApplicationPath() {
if (process.env['VSCODE_DEV']) {
return appRoot;
} else if (process.platform === 'darwin') {
return path.dirname(path.dirname(path.dirname(appRoot)));
} else {
return path.dirname(path.dirname(appRoot));
}
}
const portableDataName = product.portable || `${product.applicationName}-portable-data`;
const portableDataPath = path.join(path.dirname(getApplicationPath()), portableDataName);
const isPortable = fs.existsSync(portableDataPath);
const portableTempPath = path.join(portableDataPath, 'tmp');
const isTempPortable = isPortable && fs.existsSync(portableTempPath);
if (isPortable) {
process.env['VSCODE_PORTABLE'] = portableDataPath;
} else {
delete process.env['VSCODE_PORTABLE'];
}
if (isTempPortable) {
process.env[process.platform === 'win32' ? 'TEMP' : 'TMPDIR'] = portableTempPath;
}
//#region Add support for using node_modules.asar
(function () {
const path = require('path');

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
let perf = require('./vs/base/common/performance');
const perf = require('./vs/base/common/performance');
perf.mark('main:started');
// Perf measurements
@ -12,6 +12,37 @@ global.perfStartTime = Date.now();
Error.stackTraceLimit = 100; // increase number of stack frames (from 10, https://github.com/v8/v8/wiki/Stack-Trace-API)
const fs = require('fs');
const path = require('path');
const product = require('../product.json');
const appRoot = path.dirname(__dirname);
function getApplicationPath() {
if (process.env['VSCODE_DEV']) {
return appRoot;
} else if (process.platform === 'darwin') {
return path.dirname(path.dirname(path.dirname(appRoot)));
} else {
return path.dirname(path.dirname(appRoot));
}
}
const portableDataName = product.portable || `${product.applicationName}-portable-data`;
const portableDataPath = process.env['VSCODE_PORTABLE'] || path.join(path.dirname(getApplicationPath()), portableDataName);
const isPortable = fs.existsSync(portableDataPath);
const portableTempPath = path.join(portableDataPath, 'tmp');
const isTempPortable = isPortable && fs.existsSync(portableTempPath);
if (isPortable) {
process.env['VSCODE_PORTABLE'] = portableDataPath;
} else {
delete process.env['VSCODE_PORTABLE'];
}
if (isTempPortable) {
process.env[process.platform === 'win32' ? 'TEMP' : 'TMPDIR'] = portableTempPath;
}
//#region Add support for using node_modules.asar
(function () {
const path = require('path');
@ -36,20 +67,27 @@ Error.stackTraceLimit = 100; // increase number of stack frames (from 10, https:
})();
//#endregion
let app = require('electron').app;
const app = require('electron').app;
// TODO@Ben Electron 2.0.x: prevent localStorage migration from SQLite to LevelDB due to issues
app.commandLine.appendSwitch('disable-mojo-local-storage');
let fs = require('fs');
let path = require('path');
let minimist = require('minimist');
let paths = require('./paths');
// TODO@Ben Electron 2.0.x: force srgb color profile (for https://github.com/Microsoft/vscode/issues/51791)
app.commandLine.appendSwitch('force-color-profile', 'srgb');
let args = minimist(process.argv, {
string: ['user-data-dir', 'locale']
const minimist = require('minimist');
const paths = require('./paths');
const args = minimist(process.argv, {
string: [
'user-data-dir',
'locale',
'js-flags',
'max-memory'
]
});
//#region NLS
function stripComments(content) {
let regexp = /("(?:[^\\\"]*(?:\\.)?)*")|('(?:[^\\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g;
let result = content.replace(regexp, function (match, m1, m2, m3, m4) {
@ -74,113 +112,38 @@ function stripComments(content) {
return result;
}
let _commit;
function getCommit() {
if (_commit) {
return _commit;
}
if (_commit === null) {
return undefined;
}
try {
let productJson = require(path.join(__dirname, '../product.json'));
if (productJson.commit) {
_commit = productJson.commit;
} else {
_commit = null;
}
} catch (exp) {
_commit = null;
}
return _commit || undefined;
}
const mkdir = dir => new Promise((c, e) => fs.mkdir(dir, err => (err && err.code !== 'EEXIST') ? e(err) : c()));
const exists = file => new Promise(c => fs.exists(file, c));
const readFile = file => new Promise((c, e) => fs.readFile(file, 'utf8', (err, data) => err ? e(err) : c(data)));
const writeFile = (file, content) => new Promise((c, e) => fs.writeFile(file, content, 'utf8', err => err ? e(err) : c()));
const touch = file => new Promise((c, e) => { const d = new Date(); fs.utimes(file, d, d, err => err ? e(err) : c()); });
function mkdirp(dir) {
return mkdir(dir)
.then(null, (err) => {
if (err && err.code === 'ENOENT') {
let parent = path.dirname(dir);
if (parent !== dir) { // if not arrived at root
return mkdirp(parent)
.then(() => {
return mkdir(dir);
});
}
}
throw err;
});
}
return mkdir(dir).then(null, err => {
if (err && err.code === 'ENOENT') {
const parent = path.dirname(dir);
function mkdir(dir) {
return new Promise((resolve, reject) => {
fs.mkdir(dir, (err) => {
if (err && err.code !== 'EEXIST') {
reject(err);
} else {
resolve(dir);
if (parent !== dir) { // if not arrived at root
return mkdirp(parent).then(() => mkdir(dir));
}
});
});
}
}
function exists(file) {
return new Promise((resolve) => {
fs.exists(file, (result) => {
resolve(result);
});
});
}
function readFile(file) {
return new Promise((resolve, reject) => {
fs.readFile(file, 'utf8', (err, data) => {
if (err) {
reject(err);
return;
}
resolve(data);
});
});
}
function writeFile(file, content) {
return new Promise((resolve, reject) => {
fs.writeFile(file, content, 'utf8', (err) => {
if (err) {
reject(err);
return;
}
resolve(undefined);
});
});
}
function touch(file) {
return new Promise((resolve, reject) => {
let d = new Date();
fs.utimes(file, d, d, (err) => {
if (err) {
reject(err);
return;
}
resolve(undefined);
});
throw err;
});
}
function resolveJSFlags() {
let jsFlags = [];
const jsFlags = [];
if (args['js-flags']) {
jsFlags.push(args['js-flags']);
}
if (args['max-memory'] && !/max_old_space_size=(\d+)/g.exec(args['js-flags'])) {
jsFlags.push(`--max_old_space_size=${args['max-memory']}`);
}
if (jsFlags.length > 0) {
return jsFlags.join(' ');
} else {
return null;
}
return jsFlags.length > 0 ? jsFlags.join(' ') : null;
}
// Language tags are case insensitve however an amd loader is case sensitive
@ -285,7 +248,7 @@ function getNLSConfiguration(locale) {
}
perf.mark('nlsGeneration:start');
let defaultResult = function(locale) {
let defaultResult = function (locale) {
let isCoreLanguage = true;
if (locale) {
isCoreLanguage = ['de', 'es', 'fr', 'it', 'ja', 'ko', 'ru', 'zh-cn', 'zh-tw'].some((language) => {
@ -296,13 +259,13 @@ function getNLSConfiguration(locale) {
let result = resolveLocale(locale);
perf.mark('nlsGeneration:end');
return Promise.resolve(result);
} else {
} else {
perf.mark('nlsGeneration:end');
return Promise.resolve({ locale: locale, availableLanguages: {} });
}
};
try {
let commit = getCommit();
let commit = product.commit;
if (!commit) {
return defaultResult(locale);
}
@ -339,7 +302,7 @@ function getNLSConfiguration(locale) {
return exists(coreLocation).then((fileExists) => {
if (fileExists) {
// We don't wait for this. No big harm if we can't touch
touch(coreLocation).catch(() => {});
touch(coreLocation).catch(() => { });
perf.mark('nlsGeneration:end');
return result;
}
@ -392,7 +355,9 @@ function getNLSConfiguration(locale) {
return defaultResult(locale);
}
}
//#endregion
//#region Cached Data Dir
function getNodeCachedDataDir() {
// flag to disable cached data support
if (process.argv.indexOf('--no-cached-data') > 0) {
@ -405,7 +370,7 @@ function getNodeCachedDataDir() {
}
// find commit id
let commit = getCommit();
let commit = product.commit;
if (!commit) {
return Promise.resolve(undefined);
}
@ -414,10 +379,18 @@ function getNodeCachedDataDir() {
return mkdirp(dir).then(undefined, function () { /*ignore*/ });
}
//#endregion
function getUserDataPath() {
if (isPortable) {
return path.join(portableDataPath, 'user-data');
}
return path.resolve(args['user-data-dir'] || paths.getDefaultUserDataPath(process.platform));
}
// Set userData path before app 'ready' event and call to process.chdir
let userData = path.resolve(args['user-data-dir'] || paths.getDefaultUserDataPath(process.platform));
app.setPath('userData', userData);
app.setPath('userData', getUserDataPath());
// Update cwd based on environment and platform
try {

View file

@ -23,13 +23,16 @@ declare module "vscode-textmate" {
readonly name?: string;
readonly settings: IRawThemeSetting[];
}
export interface Thenable<T> extends PromiseLike<T> {
}
/**
* A registry helper that can locate grammar file paths given scope names.
*/
export interface RegistryOptions {
theme?: IRawTheme;
getFilePath(scopeName: string): string;
loadGrammar(scopeName: string): Thenable<IRawGrammar>;
getInjections?(scopeName: string): string[];
getOnigLib?(): Thenable<IOnigLib>;
}
/**
* A map from scope name to a language id. Please do not use language id 0.
@ -38,10 +41,10 @@ declare module "vscode-textmate" {
[scopeName: string]: number;
}
/**
* A map from scope name to a token type.
* A map from selectors to token types.
*/
export interface ITokenTypeMap {
[scopeName: string]: StandardTokenType;
[selector: string]: StandardTokenType;
}
export const enum StandardTokenType {
Other = 0,
@ -72,25 +75,25 @@ declare module "vscode-textmate" {
* Load the grammar for `scopeName` and all referenced included grammars asynchronously.
* Please do not use language id 0.
*/
loadGrammarWithEmbeddedLanguages(initialScopeName: string, initialLanguage: number, embeddedLanguages: IEmbeddedLanguagesMap, callback: (err: any, grammar: IGrammar) => void): void;
loadGrammarWithEmbeddedLanguages(initialScopeName: string, initialLanguage: number, embeddedLanguages: IEmbeddedLanguagesMap): Thenable<IGrammar>;
/**
* Load the grammar for `scopeName` and all referenced included grammars asynchronously.
* Please do not use language id 0.
*/
loadGrammarWithConfiguration(initialScopeName: string, initialLanguage: number, configuration: IGrammarConfiguration, callback: (err: any, grammar: IGrammar) => void): void;
loadGrammarWithConfiguration(initialScopeName: string, initialLanguage: number, configuration: IGrammarConfiguration): Thenable<IGrammar>;
/**
* Load the grammar for `scopeName` and all referenced included grammars asynchronously.
*/
loadGrammar(initialScopeName: string, callback: (err: any, grammar: IGrammar) => void): void;
private _loadGrammar(initialScopeName, callback);
loadGrammar(initialScopeName: string): Thenable<IGrammar>;
private _loadGrammar(initialScopeName, initialLanguage, embeddedLanguages, tokenTypes);
/**
* Load the grammar at `path` synchronously.
* Adds a rawGrammar.
*/
loadGrammarFromPathSync(path: string, initialLanguage?: number, embeddedLanguages?: IEmbeddedLanguagesMap): IGrammar;
addGrammar(rawGrammar: IRawGrammar, injections?: string[], initialLanguage?: number, embeddedLanguages?: IEmbeddedLanguagesMap): Thenable<IGrammar>;
/**
* Get the grammar for `scopeName`. The grammar must first be created via `loadGrammar` or `loadGrammarFromPathSync`.
* Get the grammar for `scopeName`. The grammar must first be created via `loadGrammar` or `addGrammar`.
*/
grammarForScopeName(scopeName: string, initialLanguage?: number, embeddedLanguages?: IEmbeddedLanguagesMap, tokenTypes?: ITokenTypeMap): IGrammar;
grammarForScopeName(scopeName: string, initialLanguage?: number, embeddedLanguages?: IEmbeddedLanguagesMap, tokenTypes?: ITokenTypeMap): Thenable<IGrammar>;
}
/**
* A grammar
@ -179,4 +182,75 @@ declare module "vscode-textmate" {
equals(other: StackElement): boolean;
}
export const INITIAL: StackElement;
export const parseRawGrammar: (content: string, filePath: string) => IRawGrammar;
export interface ILocation {
readonly filename: string;
readonly line: number;
readonly char: number;
}
export interface ILocatable {
readonly $vscodeTextmateLocation?: ILocation;
}
export interface IRawGrammar extends ILocatable {
repository: IRawRepository;
readonly scopeName: string;
readonly patterns: IRawRule[];
readonly injections?: {
[expression: string]: IRawRule;
};
readonly injectionSelector?: string;
readonly fileTypes?: string[];
readonly name?: string;
readonly firstLineMatch?: string;
}
export interface IRawRepositoryMap {
[name: string]: IRawRule;
$self: IRawRule;
$base: IRawRule;
}
export type IRawRepository = IRawRepositoryMap & ILocatable;
export interface IRawRule extends ILocatable {
id?: number;
readonly include?: string;
readonly name?: string;
readonly contentName?: string;
readonly match?: string;
readonly captures?: IRawCaptures;
readonly begin?: string;
readonly beginCaptures?: IRawCaptures;
readonly end?: string;
readonly endCaptures?: IRawCaptures;
readonly while?: string;
readonly whileCaptures?: IRawCaptures;
readonly patterns?: IRawRule[];
readonly repository?: IRawRepository;
readonly applyEndPatternLast?: boolean;
}
export interface IRawCapturesMap {
[captureId: string]: IRawRule;
}
export type IRawCaptures = IRawCapturesMap & ILocatable;
export interface IOnigLib {
createOnigScanner(sources: string[]): OnigScanner;
createOnigString(sources: string): OnigString;
}
export interface IOnigCaptureIndex {
start: number;
end: number;
length: number;
}
export interface IOnigMatch {
index: number;
captureIndices: IOnigCaptureIndex[];
scanner: OnigScanner;
}
export interface OnigScanner {
findNextMatchSync(string: string | OnigString, startPosition: number): IOnigMatch;
}
export interface OnigString {
readonly content: string;
readonly dispose?: () => void;
}
}

View file

@ -11,7 +11,7 @@ import { Event, mapEvent, Emitter } from 'vs/base/common/event';
import * as types from 'vs/base/common/types';
import * as dom from 'vs/base/browser/dom';
import { clamp } from 'vs/base/common/numbers';
import { range, firstIndex } from 'vs/base/common/arrays';
import { range, firstIndex, pushToStart, pushToEnd } from 'vs/base/common/arrays';
import { Sash, Orientation, ISashEvent as IBaseSashEvent, SashState } from 'vs/base/browser/ui/sash/sash';
import { Color } from 'vs/base/common/color';
import { domEvent } from 'vs/base/browser/event';
@ -77,44 +77,6 @@ enum State {
Busy
}
function pushToStart<T>(arr: T[], value: T): T[] {
let didFindValue = false;
const result = arr.filter(v => {
if (v === value) {
didFindValue = true;
return false;
}
return true;
});
if (didFindValue) {
result.unshift(value);
}
return result;
}
function pushToEnd<T>(arr: T[], value: T): T[] {
let didFindValue = false;
const result = arr.filter(v => {
if (v === value) {
didFindValue = true;
return false;
}
return true;
});
if (didFindValue) {
result.push(value);
}
return result;
}
export type DistributeSizing = { type: 'distribute' };
export type SplitSizing = { type: 'split', index: number };
export type Sizing = DistributeSizing | SplitSizing;
@ -346,13 +308,18 @@ export class SplitView implements IDisposable {
const toSize = this.getViewSize(to);
const toView = this.removeView(to);
const fromView = this.removeView(from);
this.addView(toView, fromSize, from);
this.addView(fromView, toSize, to);
}
private relayout(lowPriorityIndex?: number, highPriorityIndex?: number): void {
const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
this.resizeAndLayout(this.viewItems.length - 1, this.size - contentSize, undefined, lowPriorityIndex, highPriorityIndex);
this.resize(this.viewItems.length - 1, this.size - contentSize, undefined, lowPriorityIndex, highPriorityIndex);
this.distributeEmptySpace();
this.layoutViews();
this.saveProportions();
}
layout(size: number): void {
@ -360,14 +327,21 @@ export class SplitView implements IDisposable {
this.size = size;
if (!this.proportions) {
this.resizeAndLayout(this.viewItems.length - 1, size - previousSize);
this.resize(this.viewItems.length - 1, size - previousSize);
} else {
for (let i = 0; i < this.viewItems.length; i++) {
const item = this.viewItems[i];
item.size = clamp(Math.round(this.proportions[i] * size), item.view.minimumSize, item.view.maximumSize);
}
}
this.layoutViews();
this.distributeEmptySpace();
this.layoutViews();
}
private saveProportions(): void {
if (this.contentSize > 0) {
this.proportions = this.viewItems.map(i => i.size / this.contentSize);
}
}
@ -382,10 +356,9 @@ export class SplitView implements IDisposable {
const resetSashDragState = (start: number, alt: boolean) => {
const sizes = this.viewItems.map(i => i.size);
// TODO@Joao rename these guys
let minDelta = Number.POSITIVE_INFINITY;
let minDelta = Number.NEGATIVE_INFINITY;
let maxDelta = Number.POSITIVE_INFINITY;
if (this.inverseAltBehavior) {
alt = !alt;
}
@ -398,11 +371,11 @@ export class SplitView implements IDisposable {
if (isLastSash) {
const viewItem = this.viewItems[index];
minDelta = (viewItem.size - viewItem.view.minimumSize) / 2;
minDelta = (viewItem.view.minimumSize - viewItem.size) / 2;
maxDelta = (viewItem.view.maximumSize - viewItem.size) / 2;
} else {
const viewItem = this.viewItems[index + 1];
minDelta = (viewItem.view.maximumSize - viewItem.size) / 2;
minDelta = (viewItem.size - viewItem.view.maximumSize) / 2;
maxDelta = (viewItem.size - viewItem.view.minimumSize) / 2;
}
}
@ -425,19 +398,21 @@ export class SplitView implements IDisposable {
const newSizes = this.viewItems.map(i => i.size);
const viewItemIndex = isLastSash ? index : index + 1;
const viewItem = this.viewItems[viewItemIndex];
const newMinDelta = (viewItem.view.maximumSize - viewItem.size);
const newMaxDelta = (viewItem.size - viewItem.view.minimumSize);
const newMinDelta = viewItem.size - viewItem.view.maximumSize;
const newMaxDelta = viewItem.size - viewItem.view.minimumSize;
const resizeIndex = isLastSash ? index - 1 : index + 1;
this.resize(resizeIndex, -newDelta, newSizes, undefined, undefined, newMinDelta, newMaxDelta);
}
this.distributeEmptySpace();
this.layoutViews();
}
private onSashEnd(index: number): void {
this._onDidSashChange.fire(index);
this.sashDragState.disposable.dispose();
this.saveProportions();
}
private onViewChange(item: IViewItem, size: number | undefined): void {
@ -475,7 +450,7 @@ export class SplitView implements IDisposable {
const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0);
const deltaDown = clamp(delta, -expandDown, collapseDown);
this.resizeAndLayout(index, deltaDown);
this.resize(index, deltaDown);
delta -= deltaDown;
}
@ -485,9 +460,12 @@ export class SplitView implements IDisposable {
const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0);
const deltaUp = clamp(-delta, -collapseUp, expandUp);
this.resizeAndLayout(index - 1, deltaUp);
this.resize(index - 1, deltaUp);
}
this.distributeEmptySpace();
this.layoutViews();
this.saveProportions();
this.state = State.Idle;
}
@ -513,24 +491,24 @@ export class SplitView implements IDisposable {
sizes = this.viewItems.map(i => i.size),
lowPriorityIndex?: number,
highPriorityIndex?: number,
overloadMinDelta: number = Number.POSITIVE_INFINITY,
overloadMinDelta: number = Number.NEGATIVE_INFINITY,
overloadMaxDelta: number = Number.POSITIVE_INFINITY
): number {
if (index < 0 || index >= this.viewItems.length) {
return 0;
}
let upIndexes = range(index, -1);
let downIndexes = range(index + 1, this.viewItems.length);
const upIndexes = range(index, -1);
const downIndexes = range(index + 1, this.viewItems.length);
if (typeof highPriorityIndex === 'number') {
upIndexes = pushToStart(upIndexes, highPriorityIndex);
downIndexes = pushToStart(downIndexes, highPriorityIndex);
pushToStart(upIndexes, highPriorityIndex);
pushToStart(downIndexes, highPriorityIndex);
}
if (typeof lowPriorityIndex === 'number') {
upIndexes = pushToEnd(upIndexes, lowPriorityIndex);
downIndexes = pushToEnd(downIndexes, lowPriorityIndex);
pushToEnd(upIndexes, lowPriorityIndex);
pushToEnd(downIndexes, lowPriorityIndex);
}
const upItems = upIndexes.map(i => this.viewItems[i]);
@ -539,14 +517,12 @@ export class SplitView implements IDisposable {
const downItems = downIndexes.map(i => this.viewItems[i]);
const downSizes = downIndexes.map(i => sizes[i]);
const collapseUp = upIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0);
const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0);
const collapseDown = downIndexes.length === 0 ? Number.POSITIVE_INFINITY : downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0);
const expandDown = downIndexes.length === 0 ? Number.POSITIVE_INFINITY : downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0);
const minDelta = -Math.min(collapseUp, expandDown, overloadMinDelta);
const maxDelta = Math.min(collapseDown, expandUp, overloadMaxDelta);
const minDeltaUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.minimumSize - sizes[i]), 0);
const maxDeltaUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0);
const maxDeltaDown = downIndexes.length === 0 ? Number.POSITIVE_INFINITY : downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0);
const minDeltaDown = downIndexes.length === 0 ? Number.NEGATIVE_INFINITY : downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.maximumSize), 0);
const minDelta = Math.max(minDeltaUp, minDeltaDown, overloadMinDelta);
const maxDelta = Math.min(maxDeltaDown, maxDeltaUp, overloadMaxDelta);
delta = clamp(delta, minDelta, maxDelta);
@ -571,23 +547,7 @@ export class SplitView implements IDisposable {
return delta;
}
private resizeAndLayout(
index: number,
delta: number,
sizes = this.viewItems.map(i => i.size),
lowPriorityIndex?: number,
highPriorityIndex?: number
): void {
this.resize(index, delta, sizes, lowPriorityIndex, highPriorityIndex);
this.layoutViews();
if (this.contentSize > 0) {
this.proportions = this.viewItems.map(i => i.size / this.contentSize);
}
}
private layoutViews(): void {
// Rebalance empty space
private distributeEmptySpace(): void {
let contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
let emptyDelta = this.size - contentSize;
@ -599,7 +559,9 @@ export class SplitView implements IDisposable {
emptyDelta -= viewDelta;
item.size = size;
}
}
private layoutViews(): void {
// Save new content size
this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);

View file

@ -463,3 +463,27 @@ export function shuffle<T>(array: T[]): void {
array[j] = temp;
}
}
/**
* Pushes an element to the start of the array, if found.
*/
export function pushToStart<T>(arr: T[], value: T): void {
const index = arr.indexOf(value);
if (index > -1) {
arr.splice(index, 1);
arr.unshift(value);
}
}
/**
* Pushes an element to the end of the array, if found.
*/
export function pushToEnd<T>(arr: T[], value: T): void {
const index = arr.indexOf(value);
if (index > -1) {
arr.splice(index, 1);
arr.push(value);
}
}

View file

@ -387,4 +387,48 @@ suite('Splitview', () => {
view2.dispose();
view1.dispose();
});
test('split sizing', () => {
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
const view3 = new TestView(20, Number.POSITIVE_INFINITY);
const splitview = new SplitView(container);
splitview.layout(200);
splitview.addView(view1, Sizing.Distribute);
assert.equal(view1.size, 200);
splitview.addView(view2, Sizing.Split(0));
assert.deepEqual([view1.size, view2.size], [100, 100]);
splitview.addView(view3, Sizing.Split(1));
assert.deepEqual([view1.size, view2.size, view3.size], [100, 50, 50]);
splitview.dispose();
view3.dispose();
view2.dispose();
view1.dispose();
});
test('split sizing 2', () => {
const view1 = new TestView(20, Number.POSITIVE_INFINITY);
const view2 = new TestView(20, Number.POSITIVE_INFINITY);
const view3 = new TestView(20, Number.POSITIVE_INFINITY);
const splitview = new SplitView(container);
splitview.layout(200);
splitview.addView(view1, Sizing.Distribute);
assert.equal(view1.size, 200);
splitview.addView(view2, Sizing.Split(0));
assert.deepEqual([view1.size, view2.size], [100, 100]);
splitview.addView(view3, Sizing.Split(0));
assert.deepEqual([view1.size, view2.size, view3.size], [50, 100, 50]);
splitview.dispose();
view3.dispose();
view2.dispose();
view1.dispose();
});
});

View file

@ -323,6 +323,11 @@ function main() {
VSCODE_NLS_CONFIG: process.env['VSCODE_NLS_CONFIG'],
VSCODE_LOGS: process.env['VSCODE_LOGS']
};
if (process.env['VSCODE_PORTABLE']) {
instanceEnv['VSCODE_PORTABLE'] = process.env['VSCODE_PORTABLE'];
}
assign(process.env, instanceEnv);
// Startup

View file

@ -510,7 +510,6 @@ export class WindowsManager implements IWindowsMainService {
reuseWindow: openConfig.forceReuseWindow,
context: openConfig.context,
filePath: fileToCheck && fileToCheck.filePath,
userHome: this.environmentService.userHome,
workspaceResolver: workspace => this.workspacesMainService.resolveWorkspaceSync(workspace.configPath)
});

View file

@ -30,7 +30,7 @@ export interface IBestWindowOrFolderOptions<W extends ISimpleWindow> {
workspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace;
}
export function findBestWindowOrFolderForFile<W extends ISimpleWindow>({ windows, newWindow, reuseWindow, context, filePath, userHome, codeSettingsFolder, workspaceResolver }: IBestWindowOrFolderOptions<W>): W | string {
export function findBestWindowOrFolderForFile<W extends ISimpleWindow>({ windows, newWindow, reuseWindow, context, filePath, workspaceResolver }: IBestWindowOrFolderOptions<W>): W | string {
if (!newWindow && filePath && (context === OpenContext.DESKTOP || context === OpenContext.CLI || context === OpenContext.DOCK)) {
const windowOnFilePath = findWindowOnFilePath(windows, filePath, workspaceResolver);
if (windowOnFilePath) {

View file

@ -293,7 +293,7 @@ export interface ISuggestion {
documentation?: string | IMarkdownString;
filterText?: string;
sortText?: string;
autoSelect?: boolean;
preselect?: boolean;
noAutoAccept?: boolean;
commitCharacters?: string[];
overwriteBefore?: number;
@ -648,8 +648,8 @@ export interface DocumentSymbol {
detail: string;
kind: SymbolKind;
containerName?: string;
fullRange: IRange;
identifierRange: IRange;
range: IRange;
selectionRange: IRange;
children?: DocumentSymbol[];
}

View file

@ -131,7 +131,7 @@ class ModelMarkerHandler {
switch (marker.severity) {
case MarkerSeverity.Hint:
if (marker.customTags && marker.customTags.indexOf(MarkerTag.Unnecessary) >= 0) {
if (marker.tags && marker.tags.indexOf(MarkerTag.Unnecessary) >= 0) {
className = ClassName.EditorUnnecessaryDecoration;
} else {
className = ClassName.EditorHintDecoration;
@ -159,8 +159,8 @@ class ModelMarkerHandler {
break;
}
if (marker.customTags) {
if (marker.customTags.indexOf(MarkerTag.Unnecessary) !== -1) {
if (marker.tags) {
if (marker.tags.indexOf(MarkerTag.Unnecessary) !== -1) {
inlineClassName = ClassName.EditorUnnecessaryInlineDecoration;
}
}

View file

@ -53,7 +53,7 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
const { lineHeight } = this._editor.getConfiguration();
let pad = Math.floor(lineHeight / 3);
if (this._position.position.lineNumber < this._model.position.lineNumber) {
if (this._position && this._position.position.lineNumber < this._model.position.lineNumber) {
pad += lineHeight;
}

View file

@ -113,8 +113,8 @@ export class CursorUndo extends EditorAction {
constructor() {
super({
id: 'cursorUndo',
label: nls.localize('cursor.undo', "Remove Selection of Last Find Match"),
alias: 'Remove Selection of Last Find Match',
label: nls.localize('cursor.undo', "Soft Undo"),
alias: 'Soft Undo',
precondition: null,
kbOpts: {
kbExpr: EditorContextKeys.textInputFocus,

View file

@ -32,7 +32,7 @@ export abstract class TreeElement {
} else {
candidateId = `${container.id}/${candidate.name}`;
if (container.children[candidateId] !== void 0) {
candidateId = `${container.id}/${candidate.name}_${candidate.fullRange.startLineNumber}_${candidate.fullRange.startColumn}`;
candidateId = `${container.id}/${candidate.name}_${candidate.range.startLineNumber}_${candidate.range.startColumn}`;
}
}
@ -131,7 +131,7 @@ export class OutlineGroup extends TreeElement {
private _getItemEnclosingPosition(position: IPosition, children: { [id: string]: OutlineElement }): OutlineElement {
for (let key in children) {
let item = children[key];
if (!Range.containsPosition(item.symbol.fullRange, position)) {
if (!Range.containsPosition(item.symbol.range, position)) {
continue;
}
return this._getItemEnclosingPosition(position, item.children) || item;
@ -150,11 +150,11 @@ export class OutlineGroup extends TreeElement {
item.marker = undefined;
// find the proper start index to check for item/marker overlap.
let idx = binarySearch<IRange>(markers, item.symbol.fullRange, Range.compareRangesUsingStarts);
let idx = binarySearch<IRange>(markers, item.symbol.range, Range.compareRangesUsingStarts);
let start: number;
if (idx < 0) {
start = ~idx;
if (start > 0 && Range.areIntersecting(markers[start - 1], item.symbol.fullRange)) {
if (start > 0 && Range.areIntersecting(markers[start - 1], item.symbol.range)) {
start -= 1;
}
} else {
@ -164,7 +164,7 @@ export class OutlineGroup extends TreeElement {
let myMarkers: IMarker[] = [];
let myTopSev: MarkerSeverity;
while (start < markers.length && Range.areIntersecting(markers[start], item.symbol.fullRange)) {
while (start < markers.length && Range.areIntersecting(markers[start], item.symbol.range)) {
// remove markers intersecting with this outline element
// and store them in a 'private' array.
let marker = markers.splice(start, 1)[0];

View file

@ -49,7 +49,7 @@ export class OutlineItemComparator implements ISorter {
return a.symbol.name.localeCompare(b.symbol.name);
case OutlineItemCompareType.ByPosition:
default:
return Range.compareRangesUsingStarts(a.symbol.fullRange, b.symbol.fullRange);
return Range.compareRangesUsingStarts(a.symbol.range, b.symbol.range);
}
}
@ -310,7 +310,7 @@ export class OutlineController extends WorkbenchTreeController {
protected onLeftClick(tree: ITree, element: any, event: IMouseEvent, origin: string = 'mouse'): boolean {
const payload = { origin: origin, originalEvent: event };
const payload = { origin: origin, originalEvent: event, didClickElement: false };
if (tree.getInput() === element) {
tree.clearFocus(payload);
@ -322,13 +322,13 @@ export class OutlineController extends WorkbenchTreeController {
}
event.stopPropagation();
payload.didClickElement = element instanceof OutlineElement && !this.isClickOnTwistie(event);
tree.domFocus();
tree.setSelection([element], payload);
tree.setFocus(element, payload);
const didClickElement = element instanceof OutlineElement && !this.isClickOnTwistie(event);
if (!didClickElement) {
if (!payload.didClickElement) {
if (tree.isExpanded(element)) {
tree.collapse(element).then(null, onUnexpectedError);
} else {

View file

@ -75,8 +75,8 @@ suite('OutlineModel', function () {
name,
detail: 'fake',
kind: SymbolKind.Boolean,
identifierRange: range,
fullRange: range
selectionRange: range,
range: range
};
}

View file

@ -195,12 +195,12 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
}
private findDefinition(target: IMouseTarget): TPromise<Location[]> {
let model = this.editor.getModel();
const model = this.editor.getModel();
if (!model) {
return TPromise.as(null);
}
return getDefinitionsAtPosition(this.editor.getModel(), target.position);
return getDefinitionsAtPosition(model, target.position);
}
private gotoDefinition(target: IMouseTarget, sideBySide: boolean): TPromise<any> {

View file

@ -39,7 +39,7 @@ export function getDocumentSymbols(model: ITextModel): TPromise<DocumentSymbol[]
}
function compareEntriesUsingStart(a: DocumentSymbol, b: DocumentSymbol): number {
return Range.compareRangesUsingStarts(a.fullRange, b.fullRange);
return Range.compareRangesUsingStarts(a.range, b.range);
}
function flatten(bucket: DocumentSymbol[], entries: DocumentSymbol[], overrideContainerLabel: string): void {
@ -49,8 +49,8 @@ function flatten(bucket: DocumentSymbol[], entries: DocumentSymbol[], overrideCo
name: entry.name,
detail: entry.detail,
containerName: entry.containerName || overrideContainerLabel,
fullRange: entry.fullRange,
identifierRange: entry.identifierRange,
range: entry.range,
selectionRange: entry.selectionRange,
children: undefined, // we flatten it...
});
if (entry.children) {

View file

@ -24,7 +24,7 @@ export abstract class Memory {
// stop when leaving the group of top matches
break;
}
if (suggestion.autoSelect) {
if (suggestion.preselect) {
// stop when seeing an auto-select-item
return i;
}

View file

@ -45,9 +45,9 @@ suite('SuggestMemories', function () {
let item2 = createSuggestItem('bazz', 0);
let item3 = createSuggestItem('bazz', 0);
let item4 = createSuggestItem('bazz', 0);
item1.suggestion.autoSelect = false;
item2.suggestion.autoSelect = true;
item3.suggestion.autoSelect = true;
item1.suggestion.preselect = false;
item2.suggestion.preselect = true;
item3.suggestion.preselect = true;
assert.equal(mem.select(buffer, pos, [item1, item2, item3, item4]), 1);
});
@ -57,9 +57,9 @@ suite('SuggestMemories', function () {
let item2 = createSuggestItem('bazz', 0);
let item3 = createSuggestItem('bazz', 0);
let item4 = createSuggestItem('bazz', 0);
item1.suggestion.autoSelect = false;
item2.suggestion.autoSelect = true;
item3.suggestion.autoSelect = true;
item1.suggestion.preselect = false;
item2.suggestion.preselect = true;
item3.suggestion.preselect = true;
let items = [item1, item2, item3, item4];

View file

@ -197,7 +197,7 @@ export class QuickOutlineAction extends BaseEditorQuickOpenAction {
}
// Add
results.push(this.symbolEntry(label, symbolKindToCssClass(element.kind), description, element.fullRange, highlights, editor, controller));
results.push(this.symbolEntry(label, symbolKindToCssClass(element.kind), description, element.range, highlights, editor, controller));
}
}

8
src/vs/monaco.d.ts vendored
View file

@ -1093,7 +1093,7 @@ declare namespace monaco.editor {
endLineNumber: number;
endColumn: number;
relatedInformation?: IRelatedInformation[];
customTags?: MarkerTag[];
tags?: MarkerTag[];
}
/**
@ -1109,7 +1109,7 @@ declare namespace monaco.editor {
endLineNumber: number;
endColumn: number;
relatedInformation?: IRelatedInformation[];
customTags?: MarkerTag[];
tags?: MarkerTag[];
}
/**
@ -4876,8 +4876,8 @@ declare namespace monaco.languages {
detail: string;
kind: SymbolKind;
containerName?: string;
fullRange: IRange;
identifierRange: IRange;
range: IRange;
selectionRange: IRange;
children?: DocumentSymbol[];
}

View file

@ -146,7 +146,7 @@ const optionsHelp: { [name: string]: string; } = {
'-a, --add <dir>': localize('add', "Add folder(s) to the last active window."),
'-g, --goto <file:line[:character]>': localize('goto', "Open a file at the path on the specified line and character position."),
'-n, --new-window': localize('newWindow', "Force to open a new window."),
'-r, --reuse-window': localize('reuseWindow', "Force to open a file or folder in the last active window."),
'-r, --reuse-window': localize('reuseWindow', "Force to open a file or folder in an already opened window."),
'-w, --wait': localize('wait', "Wait for the files to be closed before returning."),
'--locale <locale>': localize('locale', "The locale to use (e.g. en-US or zh-TW)."),
'--user-data-dir <dir>': localize('userDataDir', "Specifies the directory that user data is kept in. Can be used to open multiple distinct instances of Code."),

View file

@ -90,7 +90,13 @@ export class EnvironmentService implements IEnvironmentService {
get userHome(): string { return os.homedir(); }
@memoize
get userDataPath(): string { return parseUserDataDir(this._args, process); }
get userDataPath(): string {
if (process.env['VSCODE_PORTABLE']) {
return path.join(process.env['VSCODE_PORTABLE'], 'user-data');
}
return parseUserDataDir(this._args, process);
}
get appNameLong(): string { return product.nameLong; }
@ -127,7 +133,19 @@ export class EnvironmentService implements IEnvironmentService {
get installSourcePath(): string { return path.join(this.userDataPath, 'installSource'); }
@memoize
get extensionsPath(): string { return parsePathArg(this._args['extensions-dir'], process) || process.env['VSCODE_EXTENSIONS'] || path.join(this.userHome, product.dataFolderName, 'extensions'); }
get extensionsPath(): string {
const fromArgs = parsePathArg(this._args['extensions-dir'], process);
if (fromArgs) {
return fromArgs;
} else if (process.env['VSCODE_EXTENSIONS']) {
return process.env['VSCODE_EXTENSIONS'];
} else if (process.env['VSCODE_PORTABLE']) {
return path.join(process.env['VSCODE_PORTABLE'], 'extensions');
} else {
return path.join(this.userHome, product.dataFolderName, 'extensions');
}
}
@memoize
get extensionDevelopmentPath(): string { return this._args.extensionDevelopmentPath ? path.normalize(this._args.extensionDevelopmentPath) : this._args.extensionDevelopmentPath; }

View file

@ -13,6 +13,7 @@ import { toErrorMessage } from 'vs/base/common/errorMessage';
import { join } from 'vs/base/common/paths';
import { Limiter } from 'vs/base/common/async';
import { fromNodeEventEmitter, anyEvent, mapEvent, debounceEvent } from 'vs/base/common/event';
import { Schemas } from 'vs/base/common/network';
export class ExtensionsLifecycle extends Disposable {
@ -36,7 +37,7 @@ export class ExtensionsLifecycle extends Disposable {
}
private parseUninstallScript(extension: ILocalExtension): { uninstallHook: string, args: string[] } {
if (extension.manifest && extension.manifest['scripts'] && typeof extension.manifest['scripts']['vscode:uninstall'] === 'string') {
if (extension.location.scheme === Schemas.file && extension.manifest && extension.manifest['scripts'] && typeof extension.manifest['scripts']['vscode:uninstall'] === 'string') {
const uninstallScript = (<string>extension.manifest['scripts']['vscode:uninstall']).split(' ');
if (uninstallScript.length < 2 || uninstallScript[0] !== 'node' || !uninstallScript[1]) {
this.logService.warn(extension.identifier.id, 'Uninstall script should be a node script');

View file

@ -658,7 +658,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
}
private preUninstallExtension(extension: ILocalExtension): TPromise<void> {
return pfs.exists(extension.location.path)
return pfs.exists(extension.location.fsPath)
.then(exists => exists ? null : TPromise.wrapError(new Error(nls.localize('notExists', "Could not find extension"))))
.then(() => {
this.logService.info('Uninstalling extension:', extension.identifier.id);
@ -788,7 +788,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
private removeExtension(extension: ILocalExtension, type: string): TPromise<void> {
this.logService.trace(`Deleting ${type} extension from disk`, extension.identifier.id);
return pfs.rimraf(extension.location.path).then(() => this.logService.info('Deleted from disk', extension.identifier.id));
return pfs.rimraf(extension.location.fsPath).then(() => this.logService.info('Deleted from disk', extension.identifier.id));
}
private isUninstalled(id: string): TPromise<boolean> {

View file

@ -127,12 +127,6 @@ export interface IFileService {
*/
createFolder(resource: URI): TPromise<IFileStat>;
/**
* Renames the provided file to use the new name. The returned promise
* will have the stat model object as a result.
*/
rename(resource: URI, newName: string): TPromise<IFileStat>;
/**
* Deletes the provided file. The optional useTrash parameter allows to
* move the file to trash.

View file

@ -17,6 +17,7 @@ import { isValidLocalization, ILocalizationsService, LanguageType } from 'vs/pla
import product from 'vs/platform/node/product';
import { distinct, equals } from 'vs/base/common/arrays';
import { Event, Emitter } from 'vs/base/common/event';
import { Schemas } from 'vs/base/common/network';
interface ILanguagePack {
hash: string;
@ -138,7 +139,7 @@ class LanguagePacksCache extends Disposable {
private createLanguagePacksFromExtension(languagePacks: { [language: string]: ILanguagePack }, extension: ILocalExtension): void {
const extensionIdentifier = { id: getGalleryExtensionIdFromLocal(extension), uuid: extension.identifier.uuid };
for (const localizationContribution of extension.manifest.contributes.localizations) {
if (isValidLocalization(localizationContribution)) {
if (extension.location.scheme === Schemas.file && isValidLocalization(localizationContribution)) {
let languagePack = languagePacks[localizationContribution.languageId];
if (!languagePack) {
languagePack = { hash: '', extensions: [], translations: {} };

View file

@ -184,7 +184,7 @@ export class MarkerService implements IMarkerService {
message, source,
startLineNumber, startColumn, endLineNumber, endColumn,
relatedInformation,
customTags,
tags,
} = data;
if (!message) {
@ -210,7 +210,7 @@ export class MarkerService implements IMarkerService {
endLineNumber,
endColumn,
relatedInformation,
customTags,
tags,
};
}

View file

@ -87,7 +87,7 @@ export interface IMarkerData {
endLineNumber: number;
endColumn: number;
relatedInformation?: IRelatedInformation[];
customTags?: MarkerTag[];
tags?: MarkerTag[];
}
export interface IResourceMarker {
@ -107,7 +107,7 @@ export interface IMarker {
endLineNumber: number;
endColumn: number;
relatedInformation?: IRelatedInformation[];
customTags?: MarkerTag[];
tags?: MarkerTag[];
}
export interface MarkerStatistics {

View file

@ -73,6 +73,7 @@ export interface IProductConfiguration {
'darwin': string;
};
logUploaderUrl: string;
portable?: string;
}
export interface ISurveyData {

81
src/vs/vscode.d.ts vendored
View file

@ -2466,6 +2466,56 @@ declare module 'vscode' {
constructor(name: string, kind: SymbolKind, range: Range, uri?: Uri, containerName?: string);
}
/**
* Represents programming constructs like variables, classes, interfaces etc. that appear in a document. Document
* symbols can be hierarchical and they have two ranges: one that encloses its definition and one that points to
* its most interesting range, e.g. the range of an identifier.
*/
export class DocumentSymbol {
/**
* The name of this symbol.
*/
name: string;
/**
* More detail for this symbol, e.g the signature of a function.
*/
detail: string;
/**
* The kind of this symbol.
*/
kind: SymbolKind;
/**
* The range enclosing this symbol not including leading/trailing whitespace but everything else, e.g comments and code.
*/
range: Range;
/**
* The range that should be selected and reveal when this symbol is being picked, e.g the name of a function.
* Must be contained by the [`range`](#DocumentSymbol.range).
*/
selectionRange: Range;
/**
* Children of this symbol, e.g. properties of a class.
*/
children: DocumentSymbol[];
/**
* Creates a new document symbol.
*
* @param name The name of the symbol.
* @param detail Details for the symbol.
* @param kind The kind of the symbol.
* @param range The full range of the symbol.
* @param selectionRange The range that should be reveal.
*/
constructor(name: string, detail: string, kind: SymbolKind, range: Range, selectionRange: Range);
}
/**
* The document symbol provider interface defines the contract between extensions and
* the [go to symbol](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-symbol)-feature.
@ -2480,7 +2530,7 @@ declare module 'vscode' {
* @return An array of document highlights or a thenable that resolves to such. The lack of a result can be
* signaled by returning `undefined`, `null`, or an empty array.
*/
provideDocumentSymbols(document: TextDocument, token: CancellationToken): ProviderResult<SymbolInformation[]>;
provideDocumentSymbols(document: TextDocument, token: CancellationToken): ProviderResult<SymbolInformation[] | DocumentSymbol[]>;
}
/**
@ -3055,6 +3105,13 @@ declare module 'vscode' {
*/
filterText?: string;
/**
* Select this item when showing. *Note* that only one completion item can be selected and
* that the editor decides which item that is. The rule is that the *first* item of those
* that match best is selected.
*/
preselect?: boolean;
/**
* A string or snippet that should be inserted in a document when selecting
* this completion. When `falsy` the [label](#CompletionItem.label)
@ -3881,6 +3938,23 @@ declare module 'vscode' {
constructor(location: Location, message: string);
}
/**
* Additional metadata about the type of a diagnostic.
*/
export enum DiagnosticTag {
/**
* Unused or unnecessary code.
*
* Diagnostics with this tag are rendered faded out. The amount of fading
* is controlled by the `"editorUnnecessaryCode.opacity"` theme color. For
* example, `"editorUnnecessaryCode.opacity": "#000000c0" will render the
* code with 75% opacity. For high contrast themes, use the
* `"editorUnnecessaryCode.border"` the color to underline unnecessary code
* instead of fading it out.
*/
Unnecessary = 1,
}
/**
* Represents a diagnostic, such as a compiler error or warning. Diagnostic objects
* are only valid in the scope of a file.
@ -3921,6 +3995,11 @@ declare module 'vscode' {
*/
relatedInformation?: DiagnosticRelatedInformation[];
/**
* Additional metadata about the diagnostic.
*/
tags?: DiagnosticTag[];
/**
* Creates a new diagnostic object.
*

View file

@ -339,11 +339,123 @@ declare module 'vscode' {
/**
* Fires when the terminal's pty slave pseudo-device is written to. In other words, this
* provides access to the raw data stream from the process running within the terminal,
* including ANSI sequences.
* including VT sequences.
*/
onData: Event<string>;
}
/**
* Represents the dimensions of a terminal.
*/
export interface TerminalDimensions {
/**
* The number of columns in the terminal.
*/
cols: number;
/**
* The number of rows in the terminal.
*/
rows: number;
}
/**
* Represents a terminal without a process where all interaction and output in the terminal is
* controlled by an extension. This is similar to an output window but has the same VT sequence
* compatility as the regular terminal.
*
* Note that an instance of [Terminal](#Terminal) will be created when a TerminalRenderer is
* created with all its APIs available for use by extensions. When using the Terminal object
* of a TerminalRenderer it acts just like normal only the extension that created the
* TerminalRenderer essentially acts as a process. For example when an
* [Terminal.onData](#Terminal.onData) listener is registered, that will fire when
* [TerminalRenderer.write](#TerminalRenderer.write) is called. Similarly when
* [Terminal.sendText](#Terminal.sendText) is triggered that will fire the
* [TerminalRenderer.onInput](#TerminalRenderer.onInput) event.
*
* **Example:** Create a terminal renderer, show it and write hello world in red
* ```typescript
* const renderer = window.createTerminalRenderer('foo');
* renderer.terminal.then(t => t.show());
* renderer.write('\x1b[31mHello world\x1b[0m');
* ```
*/
export interface TerminalRenderer {
/**
* The name of the terminal, this will appear in the terminal selector.
*/
name: string;
/**
* The dimensions of the terminal, the rows and columns of the terminal can only be set to
* a value smaller than the maximum value, if this is undefined the terminal will auto fit
* to the maximum value [maximumDimensions](TerminalRenderer.maximumDimensions).
*
* **Example:** Override the dimensions of a TerminalRenderer to 20 columns and 10 rows
* ```typescript
* terminalRenderer.dimensions = {
* cols: 20,
* rows: 10
* };
* ```
*/
dimensions: TerminalDimensions;
/**
* The maximum dimensions of the terminal, this will be undefined immediately after a
* terminal renderer is created and also until the terminal becomes visible in the UI.
* Listen to [onDidChangeMaximumDimensions](TerminalRenderer.onDidChangeMaximumDimensions)
* to get notified when this value changes.
*/
readonly maximumDimensions: TerminalDimensions;
/**
* The corressponding [Terminal](#Terminal) for this TerminalRenderer.
*/
readonly terminal: Thenable<Terminal>;
/**
* Write text to the terminal. Unlike [Terminal.sendText](#Terminal.sendText) which sends
* text to the underlying _process_, this will write the text to the terminal itself.
*
* **Example:** Write red text to the terminal
* ```typescript
* terminalRenderer.write('\x1b[31mHello world\x1b[0m');
* ```
*
* **Example:** Move the cursor to the 10th row and 20th column and write an asterisk
* ```typescript
* terminalRenderer.write('\x1b[10;20H*');
* ```
*
* @param text The text to write.
*/
write(text: string): void;
/**
* An event which fires on keystrokes in the terminal or when an extension calls
* [Terminal.sendText](#Terminal.sendText). Keystrokes are converted into their
* corresponding VT sequence representation.
*
* **Example:** Simulate interaction with the terminal from an outside extension or a
* workbench command such as `workbench.action.terminal.runSelectedText`
* ```typescript
* const terminalRenderer = window.createTerminalRenderer('test');
* terminalRenderer.onInput(data => {
* cosole.log(data); // 'Hello world'
* });
* terminalRenderer.terminal.then(t => t.sendText('Hello world'));
* ```
*/
onInput: Event<string>;
/**
* An event which fires when the [maximum dimensions](#TerminalRenderer.maimumDimensions) of
* the terminal renderer change.
*/
onDidChangeMaximumDimensions: Event<TerminalDimensions>;
}
export namespace window {
/**
* The currently opened terminals or an empty array.
@ -352,11 +464,33 @@ declare module 'vscode' {
*/
export let terminals: Terminal[];
/**
* The currently active terminal or `undefined`. The active terminal is the one that
* currently has focus or most recently had focus.
*
* @readonly
*/
export let activeTerminal: Terminal | undefined;
/**
* An [event](#Event) which fires when the [active terminal](#window.activeTerminal)
* has changed. *Note* that the event also fires when the active editor changes
* to `undefined`.
*/
export const onDidChangeActiveTerminal: Event<Terminal | undefined>;
/**
* An [event](#Event) which fires when a terminal has been created, either through the
* [createTerminal](#window.createTerminal) API or commands.
*/
export const onDidOpenTerminal: Event<Terminal>;
/**
* Create a [TerminalRenderer](#TerminalRenderer).
*
* @param name The name of the terminal renderer, this shows up in the terminal selector.
*/
export function createTerminalRenderer(name: string): TerminalRenderer;
}
//#endregion
@ -377,59 +511,6 @@ declare module 'vscode' {
//#endregion
//#region Joh: hierarchical document symbols, https://github.com/Microsoft/vscode/issues/34968
export class DocumentSymbol {
/**
* The name of this symbol.
*/
name: string;
/**
* More detail for this symbol, e.g the signature of a function.
*/
detail: string;
/**
* The kind of this symbol.
*/
kind: SymbolKind;
/**
* The full range of this symbol not including leading/trailing whitespace but everything else.
*/
fullRange: Range;
/**
* The range that should be revealed when this symbol is being selected, e.g the name of a function.
* Must be contained by the [`fullRange`](#DocumentSymbol.fullRange).
*/
gotoRange: Range;
/**
* Children of this symbol, e.g. properties of a class.
*/
children: DocumentSymbol[];
/**
* Creates a new document symbol.
*
* @param name The name of the symbol.
* @param detail Details for the symbol.
* @param kind The kind of the symbol.
* @param fullRange The full range of the symbol.
* @param gotoRange The range that should be reveal.
*/
constructor(name: string, detail: string, kind: SymbolKind, fullRange: Range, gotoRange: Range);
}
export interface DocumentSymbolProvider {
provideDocumentSymbols(document: TextDocument, token: CancellationToken): ProviderResult<SymbolInformation[] | DocumentSymbol[]>;
}
//#endregion
//#region Joh -> exclusive document filters
export interface DocumentFilter {
@ -535,22 +616,12 @@ declare module 'vscode' {
//#endregion
//#region mjbvz: Unused diagnostics
/**
* Additional metadata about the type of diagnostic.
*/
export enum DiagnosticTag {
/**
* Unused or unnecessary code.
*/
Unnecessary = 1,
}
//#region joh: https://github.com/Microsoft/vscode/issues/10659
export interface Diagnostic {
/**
* Additional metadata about the type of the diagnostic.
*/
customTags?: DiagnosticTag[];
export interface WorkspaceEdit {
createFile(uri: Uri): void;
deleteFile(uri: Uri): void;
renameFile(oldUri: Uri, newUri: Uri): void;
}
//#endregion

View file

@ -494,7 +494,9 @@ export class MainThreadTask implements MainThreadTaskShape {
}
this._taskService.registerTaskSystem(key, {
platform: platform,
fileSystemScheme: key,
uriProvider: (path: string): URI => {
return URI.parse(`${info.scheme}://${info.host}:${info.port}${path}`);
},
context: this._extHostContext,
resolveVariables: (workspaceFolder: IWorkspaceFolder, variables: Set<string>): TPromise<Map<string, string>> => {
let vars: string[] = [];

View file

@ -5,7 +5,7 @@
'use strict';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest } from 'vs/workbench/parts/terminal/common/terminal';
import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY } from 'vs/workbench/parts/terminal/common/terminal';
import { TPromise } from 'vs/base/common/winjs.base';
import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, ShellLaunchConfigDto } from '../node/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
@ -16,22 +16,23 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
private _proxy: ExtHostTerminalServiceShape;
private _toDispose: IDisposable[] = [];
private _terminalProcesses: { [id: number]: ITerminalProcessExtHostProxy } = {};
private _dataListeners: { [id: number]: IDisposable } = {};
constructor(
extHostContext: IExtHostContext,
@ITerminalService private terminalService: ITerminalService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalService);
this._toDispose.push(terminalService.onInstanceCreated((terminalInstance) => {
this._toDispose.push(terminalService.onInstanceCreated((instance) => {
// Delay this message so the TerminalInstance constructor has a chance to finish and
// return the ID normally to the extension host. The ID that is passed here will be used
// to register non-extension API terminals in the extension host.
setTimeout(() => this._onTerminalOpened(terminalInstance), 100);
setTimeout(() => this._onTerminalOpened(instance), EXT_HOST_CREATION_DELAY);
}));
this._toDispose.push(terminalService.onInstanceDisposed(terminalInstance => this._onTerminalDisposed(terminalInstance)));
this._toDispose.push(terminalService.onInstanceProcessIdReady(terminalInstance => this._onTerminalProcessIdReady(terminalInstance)));
this._toDispose.push(terminalService.onInstanceDisposed(instance => this._onTerminalDisposed(instance)));
this._toDispose.push(terminalService.onInstanceProcessIdReady(instance => this._onTerminalProcessIdReady(instance)));
this._toDispose.push(terminalService.onInstanceDimensionsChanged(instance => this._onInstanceDimensionsChanged(instance)));
this._toDispose.push(terminalService.onInstanceRequestExtHostProcess(request => this._onTerminalRequestExtHostProcess(request)));
this._toDispose.push(terminalService.onActiveInstanceChanged(instance => this._onActiveTerminalChanged(instance ? instance.id : undefined)));
// Set initial ext host state
this.terminalService.terminalInstances.forEach(t => {
@ -60,8 +61,13 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
return TPromise.as(this.terminalService.createTerminal(shellLaunchConfig).id);
}
public $createTerminalRenderer(name: string): TPromise<number> {
const instance = this.terminalService.createTerminalRenderer(name);
return TPromise.as(instance.id);
}
public $show(terminalId: number, preserveFocus: boolean): void {
let terminalInstance = this.terminalService.getInstanceFromId(terminalId);
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance) {
this.terminalService.setActiveInstance(terminalInstance);
this.terminalService.showPanel(!preserveFocus);
@ -75,31 +81,68 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
}
public $dispose(terminalId: number): void {
let terminalInstance = this.terminalService.getInstanceFromId(terminalId);
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance) {
terminalInstance.dispose();
}
}
public $terminalRendererWrite(terminalId: number, text: string): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) {
terminalInstance.write(text);
}
}
public $terminalRendererSetName(terminalId: number, name: string): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) {
terminalInstance.setTitle(name, false);
}
}
public $terminalRendererSetDimensions(terminalId: number, dimensions: ITerminalDimensions): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) {
terminalInstance.setDimensions(dimensions);
}
}
public $terminalRendererRegisterOnInputListener(terminalId: number): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance) {
terminalInstance.addDisposable(terminalInstance.onRendererInput(data => this._onTerminalRendererInput(terminalId, data)));
}
}
public $sendText(terminalId: number, text: string, addNewLine: boolean): void {
let terminalInstance = this.terminalService.getInstanceFromId(terminalId);
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance) {
terminalInstance.sendText(text, addNewLine);
}
}
public $registerOnDataListener(terminalId: number): void {
let terminalInstance = this.terminalService.getInstanceFromId(terminalId);
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance) {
this._dataListeners[terminalId] = terminalInstance.onData(data => this._onTerminalData(terminalId, data));
terminalInstance.onDisposed(instance => delete this._dataListeners[terminalId]);
terminalInstance.addDisposable(terminalInstance.onData(data => {
this._onTerminalData(terminalId, data);
}));
}
}
private _onActiveTerminalChanged(terminalId: number | undefined): void {
this._proxy.$acceptActiveTerminalChanged(terminalId);
}
private _onTerminalData(terminalId: number, data: string): void {
this._proxy.$acceptTerminalProcessData(terminalId, data);
}
private _onTerminalRendererInput(terminalId: number, data: string): void {
this._proxy.$acceptTerminalRendererInput(terminalId, data);
}
private _onTerminalDisposed(terminalInstance: ITerminalInstance): void {
this._proxy.$acceptTerminalClosed(terminalInstance.id);
}
@ -112,6 +155,14 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
this._proxy.$acceptTerminalProcessId(terminalInstance.id, terminalInstance.processId);
}
private _onInstanceDimensionsChanged(instance: ITerminalInstance): void {
// Only send the dimensions if the terminal is a renderer only as there is no API to access
// dimensions on a plain Terminal.
if (instance.shellLaunchConfig.isRendererOnly) {
this._proxy.$acceptTerminalRendererDimensions(instance.id, instance.cols, instance.rows);
}
}
private _onTerminalRequestExtHostProcess(request: ITerminalProcessExtHostRequest): void {
this._terminalProcesses[request.proxy.terminalId] = request.proxy;
const shellLaunchConfigDto: ShellLaunchConfigDto = {

View file

@ -7,7 +7,6 @@ import * as map from 'vs/base/common/map';
import URI, { UriComponents } from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { localize } from 'vs/nls';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { EditorViewColumn, viewColumnToEditorGroup, editorGroupToViewColumn } from 'vs/workbench/api/shared/editor';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IOpenerService } from 'vs/platform/opener/common/opener';
@ -40,9 +39,8 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv
constructor(
context: IExtHostContext,
@IContextKeyService contextKeyService: IContextKeyService,
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
@ILifecycleService lifecycleService: ILifecycleService,
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
@IEditorService private readonly _editorService: IEditorService,
@IWebviewEditorService private readonly _webviewService: IWebviewEditorService,
@IOpenerService private readonly _openerService: IOpenerService,
@ -60,11 +58,11 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv
}, this, this._toDispose);
}
dispose(): void {
public dispose(): void {
this._toDispose = dispose(this._toDispose);
}
$createWebviewPanel(
public $createWebviewPanel(
handle: WebviewPanelHandle,
viewType: string,
title: string,
@ -78,10 +76,7 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv
mainThreadShowOptions.group = viewColumnToEditorGroup(this._editorGroupService, showOptions.viewColumn);
}
const webview = this._webviewService.createWebview(MainThreadWebviews.viewType, title, mainThreadShowOptions, {
...options,
localResourceRoots: Array.isArray(options.localResourceRoots) ? options.localResourceRoots.map(URI.revive) : undefined
}, URI.revive(extensionLocation), this.createWebviewEventDelegate(handle));
const webview = this._webviewService.createWebview(MainThreadWebviews.viewType, title, mainThreadShowOptions, reviveWebviewOptions(options), URI.revive(extensionLocation), this.createWebviewEventDelegate(handle));
webview.state = {
viewType: viewType,
state: undefined
@ -91,30 +86,27 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv
this._activeWebview = handle;
}
$disposeWebview(handle: WebviewPanelHandle): void {
public $disposeWebview(handle: WebviewPanelHandle): void {
const webview = this.getWebview(handle);
webview.dispose();
}
$setTitle(handle: WebviewPanelHandle, value: string): void {
public $setTitle(handle: WebviewPanelHandle, value: string): void {
const webview = this.getWebview(handle);
webview.setName(value);
}
$setHtml(handle: WebviewPanelHandle, value: string): void {
public $setHtml(handle: WebviewPanelHandle, value: string): void {
const webview = this.getWebview(handle);
webview.html = value;
}
$setOptions(handle: WebviewPanelHandle, options: vscode.WebviewOptions): void {
public $setOptions(handle: WebviewPanelHandle, options: vscode.WebviewOptions): void {
const webview = this.getWebview(handle);
webview.setOptions({
...options,
localResourceRoots: Array.isArray(options.localResourceRoots) ? options.localResourceRoots.map(URI.revive) : undefined
});
webview.setOptions(reviveWebviewOptions(options));
}
$reveal(handle: WebviewPanelHandle, viewColumn: EditorViewColumn | null, preserveFocus: boolean): void {
public $reveal(handle: WebviewPanelHandle, viewColumn: EditorViewColumn | null, preserveFocus: boolean): void {
const webview = this.getWebview(handle);
if (webview.isDisposed()) {
return;
@ -125,7 +117,7 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv
this._webviewService.revealWebview(webview, targetGroup || this._editorGroupService.activeGroup, preserveFocus);
}
async $postMessage(handle: WebviewPanelHandle, message: any): TPromise<boolean> {
public async $postMessage(handle: WebviewPanelHandle, message: any): TPromise<boolean> {
const webview = this.getWebview(handle);
const editors = this._editorService.visibleControls
.filter(e => e instanceof WebviewEditor)
@ -139,15 +131,15 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv
return (editors.length > 0);
}
$registerSerializer(viewType: string): void {
public $registerSerializer(viewType: string): void {
this._revivers.add(viewType);
}
$unregisterSerializer(viewType: string): void {
public $unregisterSerializer(viewType: string): void {
this._revivers.delete(viewType);
}
reviveWebview(webview: WebviewEditorInput): TPromise<void> {
public reviveWebview(webview: WebviewEditorInput): TPromise<void> {
const viewType = webview.state.viewType;
return this._extensionService.activateByEvent(`onWebviewPanel:${viewType}`).then(() => {
const handle = 'revival-' + MainThreadWebviews.revivalPool++;
@ -170,7 +162,7 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv
});
}
canRevive(webview: WebviewEditorInput): boolean {
public canRevive(webview: WebviewEditorInput): boolean {
if (webview.isDisposed() || !webview.state) {
return false;
}
@ -200,14 +192,6 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv
};
}
private getWebview(handle: WebviewPanelHandle): WebviewEditorInput {
const webview = this._webviews.get(handle);
if (!webview) {
throw new Error('Unknown webview handle:' + handle);
}
return webview;
}
private onActiveEditorChanged() {
const activeEditor = this._editorService.activeControl;
let newActiveWebview: { input: WebviewEditorInput, handle: WebviewPanelHandle } | undefined = undefined;
@ -286,6 +270,14 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv
}
}
private getWebview(handle: WebviewPanelHandle): WebviewEditorInput {
const webview = this._webviews.get(handle);
if (!webview) {
throw new Error('Unknown webview handle:' + handle);
}
return webview;
}
private static getDeserializationFailedContents(viewType: string) {
return `<!DOCTYPE html>
<html>
@ -297,4 +289,11 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv
<body>${localize('errorMessage', "An error occurred while restoring view:{0}", viewType)}</body>
</html>`;
}
}
}
function reviveWebviewOptions(options: WebviewInputOptions): WebviewInputOptions {
return {
...options,
localResourceRoots: Array.isArray(options.localResourceRoots) ? options.localResourceRoots.map(URI.revive) : undefined
};
}

View file

@ -331,6 +331,9 @@ export function createApiFactory(
get visibleTextEditors() {
return extHostEditors.getVisibleTextEditors();
},
get activeTerminal() {
return proposedApiFunction(extension, extHostTerminalService.activeTerminal);
},
get terminals() {
return proposedApiFunction(extension, extHostTerminalService.terminals);
},
@ -372,6 +375,9 @@ export function createApiFactory(
onDidOpenTerminal: proposedApiFunction(extension, (listener, thisArg?, disposables?) => {
return extHostTerminalService.onDidOpenTerminal(listener, thisArg, disposables);
}),
onDidChangeActiveTerminal: proposedApiFunction(extension, (listener, thisArg?, disposables?) => {
return extHostTerminalService.onDidChangeActiveTerminal(listener, thisArg, disposables);
}),
get state() {
return extHostWindow.state;
},
@ -427,6 +433,9 @@ export function createApiFactory(
}
return extHostTerminalService.createTerminal(<string>nameOrOptions, shellPath, shellArgs);
},
createTerminalRenderer(name: string): vscode.TerminalRenderer {
return extHostTerminalService.createTerminalRenderer(name);
},
registerTreeDataProvider(viewId: string, treeDataProvider: vscode.TreeDataProvider<any>): vscode.Disposable {
return extHostTreeViews.registerTreeDataProvider(viewId, treeDataProvider);
},

View file

@ -49,6 +49,7 @@ import { ISingleEditOperation } from 'vs/editor/common/model';
import { IPatternInfo, IRawSearchQuery, IRawFileMatch2, ISearchCompleteStats } from 'vs/platform/search/common/search';
import { LogLevel } from 'vs/platform/log/common/log';
import { TaskExecutionDTO, TaskDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO } from 'vs/workbench/api/shared/tasks';
import { ITerminalDimensions } from 'vs/workbench/parts/terminal/common/terminal';
export interface IEnvironment {
isExtensionDevelopmentDebug: boolean;
@ -324,16 +325,24 @@ export interface MainThreadProgressShape extends IDisposable {
export interface MainThreadTerminalServiceShape extends IDisposable {
$createTerminal(name?: string, shellPath?: string, shellArgs?: string[], cwd?: string, env?: { [key: string]: string }, waitOnExit?: boolean): TPromise<number>;
$createTerminalRenderer(name: string): TPromise<number>;
$dispose(terminalId: number): void;
$hide(terminalId: number): void;
$sendText(terminalId: number, text: string, addNewLine: boolean): void;
$show(terminalId: number, preserveFocus: boolean): void;
$registerOnDataListener(terminalId: number): void;
// Process
$sendProcessTitle(terminalId: number, title: string): void;
$sendProcessData(terminalId: number, data: string): void;
$sendProcessPid(terminalId: number, pid: number): void;
$sendProcessExit(terminalId: number, exitCode: number): void;
// Renderer
$terminalRendererSetName(terminalId: number, name: string): void;
$terminalRendererSetDimensions(terminalId: number, dimensions: ITerminalDimensions): void;
$terminalRendererWrite(terminalId: number, text: string): void;
$terminalRendererRegisterOnInputListener(terminalId: number): void;
}
export interface TransferQuickPickItems extends IQuickPickItem {
@ -841,8 +850,11 @@ export interface ShellLaunchConfigDto {
export interface ExtHostTerminalServiceShape {
$acceptTerminalClosed(id: number): void;
$acceptTerminalOpened(id: number, name: string): void;
$acceptActiveTerminalChanged(id: number | null): void;
$acceptTerminalProcessId(id: number, processId: number): void;
$acceptTerminalProcessData(id: number, data: string): void;
$acceptTerminalRendererInput(id: number, data: string): void;
$acceptTerminalRendererDimensions(id: number, cols: number, rows: number): void;
$createProcess(id: number, shellLaunchConfig: ShellLaunchConfigDto, cols: number, rows: number): void;
$acceptProcessInput(id: number, data: string): void;
$acceptProcessResize(id: number, cols: number, rows: number): void;

View file

@ -17,7 +17,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments';
import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands';
import { ExtHostDiagnostics } from 'vs/workbench/api/node/extHostDiagnostics';
import { asWinJsPromise } from 'vs/base/common/async';
import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, WorkspaceSymbolDto, SuggestResultDto, WorkspaceSymbolsDto, SuggestionDto, CodeActionDto, ISerializedDocumentFilter } from './extHost.protocol';
import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, WorkspaceSymbolDto, SuggestResultDto, WorkspaceSymbolsDto, SuggestionDto, CodeActionDto, ISerializedDocumentFilter, WorkspaceEditDto } from './extHost.protocol';
import { regExpLeadsToEndlessLoop } from 'vs/base/common/strings';
import { IPosition } from 'vs/editor/common/core/position';
import { IRange, Range as EditorRange } from 'vs/editor/common/core/range';
@ -69,8 +69,8 @@ class OutlineAdapter {
name: info[i].name,
kind: typeConvert.SymbolKind.from(info[i].kind),
containerName: info[i].containerName,
fullRange: typeConvert.Range.from(info[i].location.range),
identifierRange: typeConvert.Range.from(info[i].location.range),
range: typeConvert.Range.from(info[i].location.range),
selectionRange: typeConvert.Range.from(info[i].location.range),
children: []
};
@ -81,7 +81,7 @@ class OutlineAdapter {
break;
}
let parent = parentStack[parentStack.length - 1];
if (EditorRange.containsRange(parent.fullRange, element.fullRange) && !EditorRange.equalsRange(parent.fullRange, element.fullRange)) {
if (EditorRange.containsRange(parent.range, element.range) && !EditorRange.equalsRange(parent.range, element.range)) {
parent.children.push(element);
parentStack.push(element);
break;
@ -483,7 +483,7 @@ class RenameAdapter {
private readonly _provider: vscode.RenameProvider
) { }
provideRenameEdits(resource: URI, position: IPosition, newName: string): TPromise<modes.WorkspaceEdit> {
provideRenameEdits(resource: URI, position: IPosition, newName: string): TPromise<WorkspaceEditDto> {
let doc = this._documents.getDocumentData(resource).document;
let pos = typeConvert.Position.to(position);
@ -662,6 +662,7 @@ class SuggestAdapter {
documentation: item.documentation,
filterText: item.filterText,
sortText: item.sortText,
preselect: item.preselect,
//
insertText: undefined,
additionalTextEdits: item.additionalTextEdits && item.additionalTextEdits.map(typeConvert.TextEdit.from),
@ -1106,7 +1107,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
return this._createDisposable(handle);
}
$provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string): TPromise<modes.WorkspaceEdit> {
$provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string): TPromise<WorkspaceEditDto> {
return this._withAdapter(handle, RenameAdapter, adapter => adapter.provideRenameEdits(URI.revive(resource), position, newName));
}

View file

@ -15,13 +15,64 @@ import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShap
import { IMessageFromTerminalProcess } from 'vs/workbench/parts/terminal/node/terminal';
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
import { ILogService } from 'vs/platform/log/common/log';
import { EXT_HOST_CREATION_DELAY } from 'vs/workbench/parts/terminal/common/terminal';
export class ExtHostTerminal implements vscode.Terminal {
private _name: string;
private _id: number;
private _proxy: MainThreadTerminalServiceShape;
private _disposed: boolean;
private _queuedRequests: ApiRequest[];
const RENDERER_NO_PROCESS_ID = -1;
export class BaseExtHostTerminal {
public _id: number;
protected _idPromise: Promise<number>;
private _idPromiseComplete: (value: number) => any;
private _disposed: boolean = false;
private _queuedRequests: ApiRequest[] = [];
constructor(
protected _proxy: MainThreadTerminalServiceShape,
id?: number
) {
this._idPromise = new Promise<number>(c => {
if (id !== undefined) {
this._id = id;
c(id);
} else {
this._idPromiseComplete = c;
}
});
}
public dispose(): void {
if (!this._disposed) {
this._disposed = true;
this._queueApiRequest(this._proxy.$dispose, []);
}
}
protected _checkDisposed() {
if (this._disposed) {
throw new Error('Terminal has already been disposed');
}
}
protected _queueApiRequest(callback: (...args: any[]) => void, args: any[]): void {
const request: ApiRequest = new ApiRequest(callback, args);
if (!this._id) {
this._queuedRequests.push(request);
return;
}
request.run(this._proxy, this._id);
}
protected _runQueuedRequests(id: number): void {
this._id = id;
this._idPromiseComplete(id);
this._queuedRequests.forEach((r) => {
r.run(this._proxy, this._id);
});
this._queuedRequests.length = 0;
}
}
export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Terminal {
private _pidPromise: Promise<number>;
private _pidPromiseComplete: (value: number) => any;
@ -34,17 +85,17 @@ export class ExtHostTerminal implements vscode.Terminal {
constructor(
proxy: MainThreadTerminalServiceShape,
name: string = '',
id?: number
private _name: string,
id?: number,
pid?: number
) {
this._proxy = proxy;
this._name = name;
if (id) {
this._id = id;
}
this._queuedRequests = [];
super(proxy, id);
this._pidPromise = new Promise<number>(c => {
this._pidPromiseComplete = c;
if (pid === RENDERER_NO_PROCESS_ID) {
c(undefined);
} else {
this._pidPromiseComplete = c;
}
});
}
@ -56,11 +107,7 @@ export class ExtHostTerminal implements vscode.Terminal {
waitOnExit?: boolean
): void {
this._proxy.$createTerminal(this._name, shellPath, shellArgs, cwd, env, waitOnExit).then((id) => {
this._id = id;
this._queuedRequests.forEach((r) => {
r.run(this._proxy, this._id);
});
this._queuedRequests = [];
this._runQueuedRequests(id);
});
}
@ -87,13 +134,6 @@ export class ExtHostTerminal implements vscode.Terminal {
this._queueApiRequest(this._proxy.$hide, []);
}
public dispose(): void {
if (!this._disposed) {
this._disposed = true;
this._queueApiRequest(this._proxy.$dispose, []);
}
}
public _setProcessId(processId: number): void {
// The event may fire 2 times when the panel is restored
if (this._pidPromiseComplete) {
@ -105,34 +145,95 @@ export class ExtHostTerminal implements vscode.Terminal {
public _fireOnData(data: string): void {
this._onData.fire(data);
}
}
private _queueApiRequest(callback: (...args: any[]) => void, args: any[]) {
let request: ApiRequest = new ApiRequest(callback, args);
if (!this._id) {
this._queuedRequests.push(request);
return;
}
request.run(this._proxy, this._id);
export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vscode.TerminalRenderer {
public get name(): string { return this._name; }
public set name(newName: string) {
this._name = newName;
this._checkDisposed();
this._queueApiRequest(this._proxy.$terminalRendererSetName, [this._name]);
}
private _checkDisposed() {
if (this._disposed) {
throw new Error('Terminal has already been disposed');
private readonly _onInput: Emitter<string> = new Emitter<string>();
public get onInput(): Event<string> {
this._checkDisposed();
this._queueApiRequest(this._proxy.$terminalRendererRegisterOnInputListener, [this._id]);
// Tell the main side to start sending data if it's not already
// this._proxy.$terminalRendererRegisterOnDataListener(this._id);
return this._onInput && this._onInput.event;
}
private _dimensions: vscode.TerminalDimensions | undefined;
public get dimensions(): vscode.TerminalDimensions { return this._dimensions; }
public set dimensions(dimensions: vscode.TerminalDimensions) {
this._checkDisposed();
this._dimensions = dimensions;
this._queueApiRequest(this._proxy.$terminalRendererSetDimensions, [dimensions]);
}
private _maximumDimensions: vscode.TerminalDimensions;
public get maximumDimensions(): vscode.TerminalDimensions {
if (!this._maximumDimensions) {
return undefined;
}
return {
rows: this._maximumDimensions.rows,
cols: this._maximumDimensions.cols
};
}
private readonly _onDidChangeMaximumDimensions: Emitter<vscode.TerminalDimensions> = new Emitter<vscode.TerminalDimensions>();
public get onDidChangeMaximumDimensions(): Event<vscode.TerminalDimensions> {
return this._onDidChangeMaximumDimensions && this._onDidChangeMaximumDimensions.event;
}
public get terminal(): Promise<ExtHostTerminal> {
return this._idPromise.then(id => this._fetchTerminal(id));
}
constructor(
proxy: MainThreadTerminalServiceShape,
private _name: string,
private _fetchTerminal: (id: number) => Promise<ExtHostTerminal>
) {
super(proxy);
this._proxy.$createTerminalRenderer(this._name).then(id => {
this._runQueuedRequests(id);
});
}
public write(data: string): void {
this._checkDisposed();
this._queueApiRequest(this._proxy.$terminalRendererWrite, [data]);
}
public _fireOnInput(data: string): void {
this._onInput.fire(data);
}
public _setMaximumDimensions(cols: number, rows: number): void {
this._maximumDimensions = { cols, rows };
this._onDidChangeMaximumDimensions.fire(this.maximumDimensions);
}
}
export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
private _proxy: MainThreadTerminalServiceShape;
private _activeTerminal: ExtHostTerminal;
private _terminals: ExtHostTerminal[] = [];
private _terminalProcesses: { [id: number]: cp.ChildProcess } = {};
private _terminalRenderers: ExtHostTerminalRenderer[] = [];
public get activeTerminal(): ExtHostTerminal { return this._activeTerminal; }
public get terminals(): ExtHostTerminal[] { return this._terminals; }
private readonly _onDidCloseTerminal: Emitter<vscode.Terminal> = new Emitter<vscode.Terminal>();
public get onDidCloseTerminal(): Event<vscode.Terminal> { return this._onDidCloseTerminal && this._onDidCloseTerminal.event; }
private readonly _onDidOpenTerminal: Emitter<vscode.Terminal> = new Emitter<vscode.Terminal>();
public get onDidOpenTerminal(): Event<vscode.Terminal> { return this._onDidOpenTerminal && this._onDidOpenTerminal.event; }
private readonly _onDidChangeActiveTerminal: Emitter<vscode.Terminal | undefined> = new Emitter<vscode.Terminal | undefined>();
public get onDidChangeActiveTerminal(): Event<vscode.Terminal | undefined> { return this._onDidChangeActiveTerminal && this._onDidChangeActiveTerminal.event; }
constructor(
mainContext: IMainContext,
@ -143,53 +244,94 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
}
public createTerminal(name?: string, shellPath?: string, shellArgs?: string[]): vscode.Terminal {
let terminal = new ExtHostTerminal(this._proxy, name);
const terminal = new ExtHostTerminal(this._proxy, name);
terminal.create(shellPath, shellArgs);
this._terminals.push(terminal);
return terminal;
}
public createTerminalFromOptions(options: vscode.TerminalOptions): vscode.Terminal {
let terminal = new ExtHostTerminal(this._proxy, options.name);
const terminal = new ExtHostTerminal(this._proxy, options.name);
terminal.create(options.shellPath, options.shellArgs, options.cwd, options.env /*, options.waitOnExit*/);
this._terminals.push(terminal);
return terminal;
}
public $acceptTerminalProcessData(id: number, data: string): void {
let index = this._getTerminalIndexById(id);
if (index === null) {
return;
public createTerminalRenderer(name: string): vscode.TerminalRenderer {
const renderer = new ExtHostTerminalRenderer(this._proxy, name, (id) => this._getTerminalByIdEventually(id));
this._terminalRenderers.push(renderer);
return renderer;
}
public $acceptActiveTerminalChanged(id: number | null): void {
const original = this._activeTerminal;
if (id === null) {
this._activeTerminal = undefined;
} else {
const terminal = this._getTerminalById(id);
if (terminal) {
this._activeTerminal = terminal;
}
}
if (original !== this._activeTerminal) {
this._onDidChangeActiveTerminal.fire(this._activeTerminal);
}
}
public $acceptTerminalProcessData(id: number, data: string): void {
const terminal = this._getTerminalById(id);
if (terminal) {
terminal._fireOnData(data);
}
}
public $acceptTerminalRendererDimensions(id: number, cols: number, rows: number): void {
const renderer = this._getTerminalRendererById(id);
if (renderer) {
renderer._setMaximumDimensions(cols, rows);
}
}
public $acceptTerminalRendererInput(id: number, data: string): void {
const renderer = this._getTerminalRendererById(id);
if (renderer) {
renderer._fireOnInput(data);
}
const terminal = this._terminals[index];
terminal._fireOnData(data);
}
public $acceptTerminalClosed(id: number): void {
let index = this._getTerminalIndexById(id);
const index = this._getTerminalObjectIndexById(this.terminals, id);
if (index === null) {
return;
}
let terminal = this._terminals.splice(index, 1)[0];
const terminal = this._terminals.splice(index, 1)[0];
this._onDidCloseTerminal.fire(terminal);
}
public $acceptTerminalOpened(id: number, name: string): void {
let index = this._getTerminalIndexById(id);
const index = this._getTerminalObjectIndexById(this._terminals, id);
if (index !== null) {
// The terminal has already been created (via createTerminal*), only fire the event
this._onDidOpenTerminal.fire(this.terminals[index]);
return;
}
let terminal = new ExtHostTerminal(this._proxy, name, id);
const renderer = this._getTerminalRendererById(id);
const terminal = new ExtHostTerminal(this._proxy, name, id, renderer ? RENDERER_NO_PROCESS_ID : undefined);
this._terminals.push(terminal);
this._onDidOpenTerminal.fire(terminal);
}
public $acceptTerminalProcessId(id: number, processId: number): void {
let terminal = this._getTerminalById(id);
if (terminal) {
terminal._setProcessId(processId);
} else {
// Retry one more time in case the terminal has not yet been initialized.
setTimeout(() => {
terminal = this._getTerminalById(id);
terminal._setProcessId(processId);
}, EXT_HOST_CREATION_DELAY);
}
}
@ -232,7 +374,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
// Continue env initialization, merging in the env from the launch
// config and adding keys that are needed to create the process
const env = terminalEnvironment.createTerminalEnv(parentEnv, shellLaunchConfig, initialCwd, locale, cols, rows);
let cwd = Uri.parse(require.toUrl('../../parts/terminal/node')).fsPath;
const cwd = Uri.parse(require.toUrl('../../parts/terminal/node')).fsPath;
const options = { env, cwd, execArgv: [] };
// Fork the process and listen for messages
@ -286,16 +428,43 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
this._proxy.$sendProcessExit(id, exitCode);
}
private _getTerminalById(id: number): ExtHostTerminal {
let index = this._getTerminalIndexById(id);
return index !== null ? this._terminals[index] : null;
private _getTerminalByIdEventually(id: number, retries: number = 5): Promise<ExtHostTerminal> {
return new Promise(c => {
if (retries === 0) {
c(undefined);
return;
}
const terminal = this._getTerminalById(id);
if (terminal) {
c(terminal);
} else {
// This should only be needed immediately after createTerminalRenderer is called as
// the ExtHostTerminal has not yet been iniitalized
setTimeout(() => {
c(this._getTerminalByIdEventually(id, retries - 1));
}, 200);
}
});
}
private _getTerminalIndexById(id: number): number {
private _getTerminalById(id: number): ExtHostTerminal {
return this._getTerminalObjectById(this._terminals, id);
}
private _getTerminalRendererById(id: number): ExtHostTerminalRenderer {
return this._getTerminalObjectById(this._terminalRenderers, id);
}
private _getTerminalObjectById<T extends ExtHostTerminal | ExtHostTerminalRenderer>(array: T[], id: number): T {
const index = this._getTerminalObjectIndexById(array, id);
return index !== null ? array[index] : null;
}
private _getTerminalObjectIndexById<T extends ExtHostTerminal | ExtHostTerminalRenderer>(array: T[], id: number): number {
let index: number = null;
this._terminals.some((terminal, i) => {
// TODO: This shouldn't be cas
let thisId = (<any>terminal)._id;
array.some((item, i) => {
const thisId = item._id;
if (thisId === id) {
index = i;
return true;

View file

@ -11,7 +11,7 @@ import { TextEditorSelectionChangeKind } from './extHostTypes';
import * as TypeConverters from './extHostTypeConverters';
import { TextEditorDecorationType, ExtHostTextEditor } from './extHostTextEditor';
import { ExtHostDocumentsAndEditors } from './extHostDocumentsAndEditors';
import { MainContext, MainThreadTextEditorsShape, ExtHostEditorsShape, ITextDocumentShowOptions, ITextEditorPositionData, IMainContext, WorkspaceEditDto, IEditorPropertiesChangeData } from './extHost.protocol';
import { MainContext, MainThreadTextEditorsShape, ExtHostEditorsShape, ITextDocumentShowOptions, ITextEditorPositionData, IMainContext, IEditorPropertiesChangeData } from './extHost.protocol';
import * as vscode from 'vscode';
export class ExtHostEditors implements ExtHostEditorsShape {
@ -91,23 +91,7 @@ export class ExtHostEditors implements ExtHostEditorsShape {
}
applyWorkspaceEdit(edit: vscode.WorkspaceEdit): TPromise<boolean> {
const dto: WorkspaceEditDto = { edits: [] };
for (let entry of edit.entries()) {
let [uri, uriOrEdits] = entry;
if (Array.isArray(uriOrEdits)) {
let doc = this._extHostDocumentsAndEditors.getDocument(uri.toString());
dto.edits.push({
resource: uri,
modelVersionId: doc && doc.version,
edits: uriOrEdits.map(TypeConverters.TextEdit.from)
});
// } else {
// dto.edits.push({ oldUri: uri, newUri: uriOrEdits });
}
}
const dto = TypeConverters.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors);
return this._proxy.$tryApplyWorkspaceEdit(dto);
}

View file

@ -21,9 +21,10 @@ import { ISelection } from 'vs/editor/common/core/selection';
import * as htmlContent from 'vs/base/common/htmlContent';
import { IRelativePattern } from 'vs/base/common/glob';
import * as languageSelector from 'vs/editor/common/modes/languageSelector';
import { WorkspaceEditDto, ResourceTextEditDto } from 'vs/workbench/api/node/extHost.protocol';
import { WorkspaceEditDto, ResourceTextEditDto, ResourceFileEditDto } from 'vs/workbench/api/node/extHost.protocol';
import { MarkerSeverity, IRelatedInformation, IMarkerData, MarkerTag } from 'vs/platform/markers/common/markers';
import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors';
export interface PositionLike {
line: number;
@ -110,7 +111,7 @@ export namespace Diagnostic {
code: String(value.code),
severity: DiagnosticSeverity.from(value.severity),
relatedInformation: value.relatedInformation && value.relatedInformation.map(DiagnosticRelatedInformation.from),
customTags: Array.isArray(value.customTags) ? value.customTags.map(DiagnosticTag.from) : undefined,
tags: Array.isArray(value.tags) ? value.tags.map(DiagnosticTag.from) : undefined,
};
}
}
@ -269,15 +270,16 @@ export const TextEdit = {
};
export namespace WorkspaceEdit {
export function from(value: vscode.WorkspaceEdit): modes.WorkspaceEdit {
export function from(value: vscode.WorkspaceEdit, documents?: ExtHostDocumentsAndEditors): WorkspaceEditDto {
const result: modes.WorkspaceEdit = {
edits: []
};
for (const entry of value.entries()) {
for (const entry of (value as types.WorkspaceEdit).allEntries()) {
const [uri, uriOrEdits] = entry;
if (Array.isArray(uriOrEdits)) {
// text edits
result.edits.push({ resource: uri, edits: uriOrEdits.map(TextEdit.from) });
let doc = documents ? documents.getDocument(uri.toString()) : undefined;
result.edits.push({ resource: uri, modelVersionId: doc && doc.version, edits: uriOrEdits.map(TextEdit.from) });
} else {
// resource edits
result.edits.push({ oldUri: uri, newUri: uriOrEdits });
@ -294,11 +296,11 @@ export namespace WorkspaceEdit {
URI.revive((<ResourceTextEditDto>edit).resource),
<types.TextEdit[]>(<ResourceTextEditDto>edit).edits.map(TextEdit.to)
);
// } else {
// result.renameResource(
// URI.revive((<ResourceFileEditDto>edit).oldUri),
// URI.revive((<ResourceFileEditDto>edit).newUri)
// );
} else {
result.renameFile(
URI.revive((<ResourceFileEditDto>edit).oldUri),
URI.revive((<ResourceFileEditDto>edit).newUri)
);
}
}
return result;
@ -374,8 +376,8 @@ export namespace DocumentSymbol {
let result: modes.DocumentSymbol = {
name: info.name,
detail: info.detail,
fullRange: Range.from(info.fullRange),
identifierRange: Range.from(info.gotoRange),
range: Range.from(info.range),
selectionRange: Range.from(info.selectionRange),
kind: SymbolKind.from(info.kind)
};
if (info.children) {
@ -388,8 +390,8 @@ export namespace DocumentSymbol {
info.name,
info.detail,
SymbolKind.to(info.kind),
Range.to(info.fullRange),
Range.to(info.identifierRange),
Range.to(info.range),
Range.to(info.selectionRange),
);
if (info.children) {
result.children = info.children.map(to) as any;

View file

@ -499,17 +499,17 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
private _resourceEdits: { seq: number, from: URI, to: URI }[] = [];
private _textEdits = new Map<string, { seq: number, uri: URI, edits: TextEdit[] }>();
// createResource(uri: vscode.Uri): void {
// this.renameResource(undefined, uri);
// }
createFile(uri: vscode.Uri): void {
this.renameFile(undefined, uri);
}
// deleteResource(uri: vscode.Uri): void {
// this.renameResource(uri, undefined);
// }
deleteFile(uri: vscode.Uri): void {
this.renameFile(uri, undefined);
}
// renameResource(from: vscode.Uri, to: vscode.Uri): void {
// this._resourceEdits.push({ seq: this._seqPool++, from, to });
// }
renameFile(from: vscode.Uri, to: vscode.Uri): void {
this._resourceEdits.push({ seq: this._seqPool++, from, to });
}
// resourceEdits(): [vscode.Uri, vscode.Uri][] {
// return this._resourceEdits.map(({ from, to }) => (<[vscode.Uri, vscode.Uri]>[from, to]));
@ -566,20 +566,19 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
}
allEntries(): ([URI, TextEdit[]] | [URI, URI])[] {
return this.entries();
// // use the 'seq' the we have assigned when inserting
// // the operation and use that order in the resulting
// // array
// const res: ([URI, TextEdit[]] | [URI, URI])[] = [];
// this._textEdits.forEach(value => {
// const { seq, uri, edits } = value;
// res[seq] = [uri, edits];
// });
// this._resourceEdits.forEach(value => {
// const { seq, from, to } = value;
// res[seq] = [from, to];
// });
// return res;
// use the 'seq' the we have assigned when inserting
// the operation and use that order in the resulting
// array
const res: ([URI, TextEdit[]] | [URI, URI])[] = [];
this._textEdits.forEach(value => {
const { seq, uri, edits } = value;
res[seq] = [uri, edits];
});
this._resourceEdits.forEach(value => {
const { seq, from, to } = value;
res[seq] = [from, to];
});
return res;
}
get size(): number {
@ -751,7 +750,7 @@ export class Diagnostic {
code: string | number;
severity: DiagnosticSeverity;
relatedInformation: DiagnosticRelatedInformation[];
customTags?: DiagnosticTag[];
tags?: DiagnosticTag[];
constructor(range: Range, message: string, severity: DiagnosticSeverity = DiagnosticSeverity.Error) {
this.range = range;
@ -885,20 +884,20 @@ export class DocumentSymbol {
name: string;
detail: string;
kind: SymbolKind;
fullRange: Range;
gotoRange: Range;
range: Range;
selectionRange: Range;
children: DocumentSymbol[];
constructor(name: string, detail: string, kind: SymbolKind, fullRange: Range, gotoRange: Range) {
constructor(name: string, detail: string, kind: SymbolKind, range: Range, selectionRange: Range) {
this.name = name;
this.detail = detail;
this.kind = kind;
this.fullRange = fullRange;
this.gotoRange = gotoRange;
this.range = range;
this.selectionRange = selectionRange;
this.children = [];
if (!this.fullRange.contains(this.gotoRange)) {
throw new Error('gotoRange must be contained in fullRange');
if (!this.range.contains(this.selectionRange)) {
throw new Error('selectionRange must be contained in fullRange');
}
}
}
@ -1073,7 +1072,7 @@ export enum CompletionItemKind {
TypeParameter = 24
}
export class CompletionItem {
export class CompletionItem implements vscode.CompletionItem {
label: string;
kind: CompletionItemKind;
@ -1081,6 +1080,7 @@ export class CompletionItem {
documentation: string | MarkdownString;
sortText: string;
filterText: string;
preselect: boolean;
insertText: string | SnippetString;
range: Range;
textEdit: TextEdit;
@ -1100,6 +1100,7 @@ export class CompletionItem {
documentation: this.documentation,
sortText: this.sortText,
filterText: this.filterText,
preselect: this.preselect,
insertText: this.insertText,
textEdit: this.textEdit
};

View file

@ -106,5 +106,8 @@ export interface TaskFilterDTO {
}
export interface TaskSystemInfoDTO {
scheme: string;
host: string;
port: number;
platform: string;
}

View file

@ -41,30 +41,6 @@
padding-left: 10px;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top::before {
content: '';
position: absolute;
top: 0;
left: 0;
z-index: 5;
pointer-events: none;
background-color: var(--tab-border-top-color);
width: 100%;
height: 1px;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-bottom::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
z-index: 5;
pointer-events: none;
background-color: var(--tab-border-bottom-color);
width: 100%;
height: 1px;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon-theme.close-button-right,
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon-theme.close-button-off {
padding-left: 5px; /* reduce padding when we show icons and are in shrinking mode and tab close button is not left */
@ -109,6 +85,39 @@
padding-right: 10px;
}
/* Tab border top/bottom */
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-border-top-container,
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-border-bottom-container {
display: none; /* hidden by default until a color is provided (see below) */
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top > .tab-border-top-container {
display: block;
content: '';
position: absolute;
top: 0;
left: 0;
z-index: 5;
pointer-events: none;
background-color: var(--tab-border-top-color);
width: 100%;
height: 1px;
}
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container {
display: block;
content: '';
position: absolute;
bottom: 0;
left: 0;
z-index: 5;
pointer-events: none;
background-color: var(--tab-border-bottom-color);
width: 100%;
height: 1px;
}
/* Tab Label */
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab .tab-label {

View file

@ -383,6 +383,11 @@ export class TabsTitleControl extends TitleControl {
// Gesture Support
Gesture.addTarget(tabContainer);
// Tab Border Top
const tabBorderTopContainer = document.createElement('div');
addClass(tabBorderTopContainer, 'tab-border-top-container');
tabContainer.appendChild(tabBorderTopContainer);
// Tab Editor Label
const editorLabel = this.instantiationService.createInstance(ResourceLabel, tabContainer, void 0);
this.tabLabelWidgets.push(editorLabel);
@ -392,6 +397,11 @@ export class TabsTitleControl extends TitleControl {
addClass(tabCloseContainer, 'tab-close');
tabContainer.appendChild(tabCloseContainer);
// Tab Border Bottom
const tabBorderBottomContainer = document.createElement('div');
addClass(tabBorderBottomContainer, 'tab-border-bottom-container');
tabContainer.appendChild(tabBorderBottomContainer);
const tabActionRunner = new EditorCommandsContextActionRunner({ groupId: this.group.id, editorIndex: index });
const tabActionBar = new ActionBar(tabCloseContainer, { ariaLabel: localize('araLabelTabActions', "Tab actions"), actionRunner: tabActionRunner });

View file

@ -5,6 +5,7 @@
.monaco-workbench > .part.statusbar {
box-sizing: border-box;
cursor: default;
width: 100%;
height: 22px;
font-size: 12px;
@ -52,7 +53,6 @@
}
.monaco-workbench > .part.statusbar > .statusbar-entry > span {
cursor: default;
height: 100%;
}

View file

@ -0,0 +1 @@
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><style>.st0{fill:#f6f6f6;fill-opacity:0}.st1{fill:#fff}.st2{fill:#167abf}</style><path class="st0" d="M1024 1024H0V0h1024v1024z"/><path class="st1" d="M1024 85.333v853.333H0V85.333h1024z"/><path class="st2" d="M0 85.333h298.667v853.333H0V85.333zm1024 0v853.333H384V85.333h640zm-554.667 160h341.333v-64H469.333v64zm341.334 533.334H469.333v64h341.333l.001-64zm128-149.334H597.333v64h341.333l.001-64zm0-149.333H597.333v64h341.333l.001-64zm0-149.333H597.333v64h341.333l.001-64z"/></svg>

After

Width:  |  Height:  |  Size: 559 B

View file

@ -54,14 +54,16 @@
}
.monaco-workbench > .part.titlebar > .window-appicon {
width: 15px;
padding-left: 10px;
width: 35px;
margin-right: 113px;
-webkit-app-region: no-drag;
position: relative;
z-index: 99;
order: 1;
image-rendering: crisp-edges;
background-image: url('code-icon.svg');
background-repeat: no-repeat;
background-position: center center;
background-size: 16px;
}
.monaco-workbench > .part.titlebar > .window-controls-container {

View file

@ -57,8 +57,8 @@ export class TitlebarPart extends Part implements ITitleService {
titleFontSize?: number;
titlebarHeight?: number;
controlsWidth?: number;
appIconSize?: number;
appIconWidth?: number;
appIconLeftPadding?: number;
} = Object.create(null);
private isInactive: boolean;
@ -251,9 +251,8 @@ export class TitlebarPart extends Part implements ITitleService {
// App Icon (Windows/Linux)
if (!isMacintosh) {
this.appIcon = $(this.titleContainer).img({
this.appIcon = $(this.titleContainer).div({
class: 'window-appicon',
src: paths.join(this.environmentService.appRoot, isWindows ? 'resources/win32/code.ico' : 'resources/linux/code.png')
}).on(EventType.DBLCLICK, e => {
EventHelper.stop(e, true);
@ -457,14 +456,14 @@ export class TitlebarPart extends Part implements ITitleService {
this.initialSizing.appIconWidth = parseInt(this.appIcon.getComputedStyle().width, 10);
}
if (typeof this.initialSizing.appIconLeftPadding !== 'number') {
this.initialSizing.appIconLeftPadding = parseInt(this.appIcon.getComputedStyle().paddingLeft, 10);
if (typeof this.initialSizing.appIconSize !== 'number') {
this.initialSizing.appIconSize = parseInt(this.appIcon.getComputedStyle().backgroundSize, 10);
}
const currentAppIconHeight = parseInt(this.appIcon.getComputedStyle().height, 10);
const newControlsWidth = this.initialSizing.controlsWidth / getZoomFactor();
const newAppIconWidth = this.initialSizing.appIconWidth / getZoomFactor();
const newAppIconPaddingLeft = this.initialSizing.appIconLeftPadding / getZoomFactor();
const newAppIconSize = this.initialSizing.appIconSize / getZoomFactor();
if (!this.menubarWidth) {
this.menubarWidth = 0;
@ -483,9 +482,9 @@ export class TitlebarPart extends Part implements ITitleService {
// Adjust app icon mimic menubar
this.appIcon.style({
width: `${newAppIconWidth}px`,
'padding-left': `${newAppIconPaddingLeft}px`,
'margin-right': `${newControlsWidth - newAppIconWidth - newAppIconPaddingLeft + bufferWidth}px`,
'width': `${newAppIconWidth}px`,
'background-size': `${newAppIconSize}px`,
'margin-right': `${newControlsWidth - newAppIconWidth + bufferWidth}px`,
'padding-top': `${(newHeight - currentAppIconHeight) / 2.0}px`,
'padding-bottom': `${(newHeight - currentAppIconHeight) / 2.0}px`
});

View file

@ -496,7 +496,11 @@ export class WorkbenchShell {
private registerListeners(): void {
// Resize
this.toUnbind.push(addDisposableListener(window, EventType.RESIZE, () => this.layout()));
this.toUnbind.push(addDisposableListener(window, EventType.RESIZE, e => {
if (e.target === window) {
this.layout();
}
}));
}
public onUnexpectedError(error: any): void {

View file

@ -1184,7 +1184,7 @@ export class Workbench extends Disposable implements IPartService {
case Parts.TITLEBAR_PART:
return this.getCustomTitleBarStyle() === 'custom' && !browser.isFullscreen();
case Parts.MENUBAR_PART:
return this.getCustomTitleBarStyle() === 'custom' && !this.menubarHidden;
return this.isVisible(Parts.TITLEBAR_PART) && !this.menubarHidden;
case Parts.SIDEBAR_PART:
return !this.sideBarHidden;
case Parts.PANEL_PART:

View file

@ -27,6 +27,8 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
import { getPathLabel } from 'vs/base/common/labels';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
const $ = dom.$;
@ -397,6 +399,7 @@ class CallStackRenderer implements IRenderer {
constructor(
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IEnvironmentService private environmentService: IEnvironmentService
) {
// noop
}
@ -476,7 +479,7 @@ class CallStackRenderer implements IRenderer {
} else if (templateId === CallStackRenderer.ERROR_TEMPLATE_ID) {
this.renderError(element, templateData);
} else if (templateId === CallStackRenderer.LOAD_MORE_TEMPLATE_ID) {
this.renderLoadMore(element, templateData);
this.renderLoadMore(templateData);
}
}
@ -506,7 +509,7 @@ class CallStackRenderer implements IRenderer {
data.label.title = element;
}
private renderLoadMore(element: any, data: ILoadMoreTemplateData): void {
private renderLoadMore(data: ILoadMoreTemplateData): void {
data.label.textContent = nls.localize('loadMoreStackFrames', "Load More Stack Frames");
}
@ -515,7 +518,8 @@ class CallStackRenderer implements IRenderer {
dom.toggleClass(data.stackFrame, 'label', stackFrame.presentationHint === 'label');
dom.toggleClass(data.stackFrame, 'subtle', stackFrame.presentationHint === 'subtle');
data.file.title = stackFrame.source.raw.path || stackFrame.source.name;
const path = stackFrame.source.raw.path || stackFrame.source.name;
data.file.title = getPathLabel(path, this.environmentService);
if (stackFrame.source.raw.origin) {
data.file.title += `\n${stackFrame.source.raw.origin}`;
}

View file

@ -8,7 +8,6 @@
import * as nls from 'vs/nls';
import { readFile } from 'vs/base/node/pfs';
import * as semver from 'semver';
import * as path from 'path';
import { Event, Emitter } from 'vs/base/common/event';
import { index } from 'vs/base/common/arrays';
import { assign } from 'vs/base/common/objects';
@ -40,6 +39,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { groupBy } from 'vs/base/common/collections';
import { Schemas } from 'vs/base/common/network';
import { join } from 'vs/base/common/paths';
interface IExtensionStateProvider<T> {
(extension: Extension): T;
@ -138,7 +138,7 @@ class Extension implements IExtension {
private get localIconUrl(): string {
if (this.local && this.local.manifest.icon) {
return this.local.location.with({ path: path.join(this.local.location.path, this.local.manifest.icon) }).toString();
return this.local.location.with({ path: join(this.local.location.path, this.local.manifest.icon) }).toString();
}
return null;
}

View file

@ -36,6 +36,7 @@ import { IWindowService } from 'vs/platform/windows/common/windows';
import { URLService } from 'vs/platform/url/common/urlService';
import URI from 'vs/base/common/uri';
import { ExtensionManagementServerService } from 'vs/workbench/services/extensions/node/extensionManagementServerService';
import { Schemas } from 'vs/base/common/network';
suite('ExtensionsActions Test', () => {
@ -94,14 +95,14 @@ suite('ExtensionsActions Test', () => {
});
test('Install action is disabled when there is no extension', () => {
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: 'vscode-local', authority: 'local' }) });
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: Schemas.file }) });
assert.ok(!testObject.enabled);
});
test('Test Install action when state is installed', () => {
const workbenchService = instantiationService.get(IExtensionsWorkbenchService);
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: 'vscode-local', authority: 'local' }) });
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: Schemas.file }) });
const local = aLocalExtension('a');
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
return workbenchService.queryLocal()
@ -119,7 +120,7 @@ suite('ExtensionsActions Test', () => {
test('Test Install action when state is installing', () => {
const workbenchService = instantiationService.get(IExtensionsWorkbenchService);
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: 'vscode-local', authority: 'local' }) });
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: Schemas.file }) });
const gallery = aGalleryExtension('a');
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery));
return workbenchService.queryGallery()
@ -135,7 +136,7 @@ suite('ExtensionsActions Test', () => {
test('Test Install action when state is uninstalled', () => {
const workbenchService = instantiationService.get(IExtensionsWorkbenchService);
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: 'vscode-local', authority: 'local' }) });
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: Schemas.file }) });
const gallery = aGalleryExtension('a');
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery));
return workbenchService.queryGallery()
@ -147,7 +148,7 @@ suite('ExtensionsActions Test', () => {
});
test('Test Install action when extension is system action', () => {
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: 'vscode-local', authority: 'local' }) });
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: Schemas.file }) });
const local = aLocalExtension('a', {}, { type: LocalExtensionType.System });
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
@ -161,7 +162,7 @@ suite('ExtensionsActions Test', () => {
});
test('Test Install action when extension doesnot has gallery', () => {
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: 'vscode-local', authority: 'local' }) });
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: Schemas.file }) });
const local = aLocalExtension('a');
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
@ -242,14 +243,14 @@ suite('ExtensionsActions Test', () => {
});
test('Test CombinedInstallAction when there is no extension', () => {
const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: 'vscode-local', authority: 'local' }) });
const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: Schemas.file }) });
assert.ok(!testObject.enabled);
assert.equal('extension-action prominent install no-extension', testObject.class);
});
test('Test CombinedInstallAction when extension is system extension', () => {
const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: 'vscode-local', authority: 'local' }) });
const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: Schemas.file }) });
const local = aLocalExtension('a', {}, { type: LocalExtensionType.System });
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
@ -263,7 +264,7 @@ suite('ExtensionsActions Test', () => {
test('Test CombinedInstallAction when installAction is enabled', () => {
const workbenchService = instantiationService.get(IExtensionsWorkbenchService);
const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: 'vscode-local', authority: 'local' }) });
const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: Schemas.file }) });
const gallery = aGalleryExtension('a');
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery));
@ -277,7 +278,7 @@ suite('ExtensionsActions Test', () => {
});
test('Test CombinedInstallAction when unInstallAction is enabled', () => {
const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: 'vscode-local', authority: 'local' }) });
const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: Schemas.file }) });
const local = aLocalExtension('a');
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
@ -291,7 +292,7 @@ suite('ExtensionsActions Test', () => {
});
test('Test CombinedInstallAction when state is installing', () => {
const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: 'vscode-local', authority: 'local' }) });
const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: Schemas.file }) });
const workbenchService = instantiationService.get(IExtensionsWorkbenchService);
const gallery = aGalleryExtension('a');
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery));
@ -307,7 +308,7 @@ suite('ExtensionsActions Test', () => {
});
test('Test CombinedInstallAction when state is uninstalling', () => {
const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: 'vscode-local', authority: 'local' }) });
const testObject: ExtensionsActions.CombinedInstallAction = instantiationService.createInstance(ExtensionsActions.CombinedInstallAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: Schemas.file }) });
const local = aLocalExtension('a');
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
@ -322,13 +323,13 @@ suite('ExtensionsActions Test', () => {
});
test('Test UpdateAction when there is no extension', () => {
const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: 'vscode-local', authority: 'local' }) });
const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: Schemas.file }) });
assert.ok(!testObject.enabled);
});
test('Test UpdateAction when extension is uninstalled', () => {
const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: 'vscode-local', authority: 'local' }) });
const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: Schemas.file }) });
const gallery = aGalleryExtension('a', { version: '1.0.0' });
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery));
return instantiationService.get(IExtensionsWorkbenchService).queryGallery()
@ -339,7 +340,7 @@ suite('ExtensionsActions Test', () => {
});
test('Test UpdateAction when extension is installed and not outdated', () => {
const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: 'vscode-local', authority: 'local' }) });
const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: Schemas.file }) });
const local = aLocalExtension('a', { version: '1.0.0' });
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
@ -353,7 +354,7 @@ suite('ExtensionsActions Test', () => {
});
test('Test UpdateAction when extension is installed outdated and system extension', () => {
const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: 'vscode-local', authority: 'local' }) });
const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: Schemas.file }) });
const local = aLocalExtension('a', { version: '1.0.0' }, { type: LocalExtensionType.System });
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
@ -367,7 +368,7 @@ suite('ExtensionsActions Test', () => {
});
test('Test UpdateAction when extension is installed outdated and user extension', () => {
const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: 'vscode-local', authority: 'local' }) });
const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: Schemas.file }) });
const local = aLocalExtension('a', { version: '1.0.0' });
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
@ -381,7 +382,7 @@ suite('ExtensionsActions Test', () => {
});
test('Test UpdateAction when extension is installing and outdated and user extension', () => {
const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: 'vscode-local', authority: 'local' }) });
const testObject: ExtensionsActions.UpdateAction = instantiationService.createInstance(ExtensionsActions.UpdateAction, <IExtensionManagementServer>{ extensionManagementService: null, location: URI.from({ scheme: Schemas.file }) });
const local = aLocalExtension('a', { version: '1.0.0' });
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);

View file

@ -8,7 +8,6 @@
import * as sinon from 'sinon';
import * as assert from 'assert';
import * as fs from 'fs';
import * as path from 'path';
import { assign } from 'vs/base/common/objects';
import { TPromise } from 'vs/base/common/winjs.base';
import { generateUuid } from 'vs/base/common/uuid';
@ -194,8 +193,8 @@ suite('ExtensionsWorkbenchService Test', () => {
assert.equal('1.1.0', actual.version);
assert.equal('1.1.0', actual.latestVersion);
assert.equal('localDescription1', actual.description);
assert.equal(expected1.location.with({ path: path.join(expected1.location.path, 'localIcon1') }).toString(), actual.iconUrl);
assert.equal(expected1.location.with({ path: path.join(expected1.location.path, 'localIcon1') }).toString(), actual.iconUrlFallback);
assert.equal('file:///localPath1/localIcon1', actual.iconUrl);
assert.equal('file:///localPath1/localIcon1', actual.iconUrlFallback);
assert.equal(null, actual.licenseUrl);
assert.equal(ExtensionState.Installed, actual.state);
assert.equal(null, actual.installCount);

View file

@ -36,7 +36,6 @@ import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IInstantiationService, ServicesAccessor, IConstructorSignature2 } from 'vs/platform/instantiation/common/instantiation';
import { ITextModel } from 'vs/editor/common/model';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, SAVE_ALL_COMMAND_ID, SAVE_ALL_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID } from 'vs/workbench/parts/files/electron-browser/fileCommands';
import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService';
@ -289,8 +288,7 @@ class RenameFileAction extends BaseRenameAction {
element: ExplorerItem,
@IFileService fileService: IFileService,
@INotificationService notificationService: INotificationService,
@ITextFileService textFileService: ITextFileService,
@IBackupFileService private backupFileService: IBackupFileService
@ITextFileService textFileService: ITextFileService
) {
super(RenameFileAction.ID, nls.localize('rename', "Rename"), element, fileService, notificationService, textFileService);
@ -298,43 +296,10 @@ class RenameFileAction extends BaseRenameAction {
}
public runAction(newName: string): TPromise<any> {
const dirty = this.textFileService.getDirty().filter(d => resources.isEqualOrParent(d, this.element.resource, !isLinux /* ignorecase */));
const dirtyRenamed: URI[] = [];
return TPromise.join(dirty.map(d => {
let renamed: URI;
const parentResource = this.element.parent.resource;
const targetResource = parentResource.with({ path: paths.join(parentResource.path, newName) });
// If the dirty file itself got moved, just reparent it to the target folder
const targetPath = paths.join(this.element.parent.resource.path, newName);
if (this.element.resource.toString() === d.toString()) {
renamed = this.element.parent.resource.with({ path: targetPath });
}
// Otherwise, a parent of the dirty resource got moved, so we have to reparent more complicated
else {
renamed = this.element.parent.resource.with({ path: paths.join(targetPath, d.path.substr(this.element.resource.path.length + 1)) });
}
dirtyRenamed.push(renamed);
const model = this.textFileService.models.get(d);
return this.backupFileService.backupResource(renamed, model.createSnapshot(), model.getVersionId());
}))
// 2. soft revert all dirty since we have backed up their contents
.then(() => this.textFileService.revertAll(dirty, { soft: true /* do not attempt to load content from disk */ }))
// 3.) run the rename operation
.then(() => this.fileService.rename(this.element.resource, newName).then(null, (error: Error) => {
return TPromise.join(dirtyRenamed.map(d => this.backupFileService.discardResourceBackup(d))).then(() => {
this.onErrorWithRetry(error, () => this.runAction(newName));
});
}))
// 4.) resolve those that were dirty to load their previous dirty contents from disk
.then(() => {
return TPromise.join(dirtyRenamed.map(t => this.textFileService.models.loadOrCreate(t)));
});
return this.textFileService.move(this.element.resource, targetResource);
}
}

View file

@ -56,7 +56,6 @@ import { IDialogService, IConfirmationResult, IConfirmation, getConfirmMessage }
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
export class FileDataSource implements IDataSource {
constructor(
@ -759,7 +758,6 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop {
@IConfigurationService private configurationService: IConfigurationService,
@IInstantiationService instantiationService: IInstantiationService,
@ITextFileService private textFileService: ITextFileService,
@IBackupFileService private backupFileService: IBackupFileService,
@IWindowService private windowService: IWindowService,
@IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService
) {
@ -1033,91 +1031,48 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop {
}
private doHandleExplorerDrop(tree: ITree, source: ExplorerItem, target: ExplorerItem | Model, isCopy: boolean): TPromise<void> {
if (!(target instanceof ExplorerItem)) {
return TPromise.as(void 0);
}
return tree.expand(target).then(() => {
// Reuse duplicate action if user copies
if (isCopy) {
return this.instantiationService.createInstance(DuplicateFileAction, tree, source, target).run();
}
const dirtyMoved: URI[] = [];
// Otherwise move
const targetResource = target.resource.with({ path: paths.join(target.resource.path, source.name) });
// Success: load all files that are dirty again to restore their dirty contents
// Error: discard any backups created during the process
const onSuccess = () => TPromise.join(dirtyMoved.map(t => this.textFileService.models.loadOrCreate(t)));
const onError = (error?: Error, showError?: boolean) => {
if (showError) {
return this.textFileService.move(source.resource, targetResource).then(null, error => {
// Conflict
if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_MOVE_CONFLICT) {
const confirm: IConfirmation = {
message: nls.localize('confirmOverwriteMessage', "'{0}' already exists in the destination folder. Do you want to replace it?", source.name),
detail: nls.localize('irreversible', "This action is irreversible!"),
primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"),
type: 'warning'
};
// Move with overwrite if the user confirms
return this.dialogService.confirm(confirm).then(res => {
if (res.confirmed) {
return this.textFileService.move(source.resource, targetResource, true /* overwrite */).then(null, error => this.notificationService.error(error));
}
return void 0;
});
}
// Any other error
else {
this.notificationService.error(error);
}
return TPromise.join(dirtyMoved.map(d => this.backupFileService.discardResourceBackup(d)));
};
if (!(target instanceof ExplorerItem)) {
return TPromise.as(void 0);
}
// 1. check for dirty files that are being moved and backup to new target
const dirty = this.textFileService.getDirty().filter(d => resources.isEqualOrParent(d, source.resource, !isLinux /* ignorecase */));
return TPromise.join(dirty.map(d => {
let moved: URI;
// If the dirty file itself got moved, just reparent it to the target folder
if (source.resource.toString() === d.toString()) {
moved = target.resource.with({ path: paths.join(target.resource.path, source.name) });
}
// Otherwise, a parent of the dirty resource got moved, so we have to reparent more complicated. Example:
else {
moved = target.resource.with({ path: paths.join(target.resource.path, d.path.substr(source.parent.resource.path.length + 1)) });
}
dirtyMoved.push(moved);
const model = this.textFileService.models.get(d);
return this.backupFileService.backupResource(moved, model.createSnapshot(), model.getVersionId());
}))
// 2. soft revert all dirty since we have backed up their contents
.then(() => this.textFileService.revertAll(dirty, { soft: true /* do not attempt to load content from disk */ }))
// 3.) run the move operation
.then(() => {
const targetResource = target.resource.with({ path: paths.join(target.resource.path, source.name) });
return this.fileService.moveFile(source.resource, targetResource).then(null, error => {
// Conflict
if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_MOVE_CONFLICT) {
const confirm: IConfirmation = {
message: nls.localize('confirmOverwriteMessage', "'{0}' already exists in the destination folder. Do you want to replace it?", source.name),
detail: nls.localize('irreversible', "This action is irreversible!"),
primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"),
type: 'warning'
};
// Move with overwrite if the user confirms
return this.dialogService.confirm(confirm).then(res => {
if (res.confirmed) {
const targetDirty = this.textFileService.getDirty().filter(d => resources.isEqualOrParent(d, targetResource, !isLinux /* ignorecase */));
// Make sure to revert all dirty in target first to be able to overwrite properly
return this.textFileService.revertAll(targetDirty, { soft: true /* do not attempt to load content from disk */ }).then(() => {
// Then continue to do the move operation
return this.fileService.moveFile(source.resource, targetResource, true).then(onSuccess, error => onError(error, true));
});
}
return onError();
});
}
return onError(error, true);
});
})
// 4.) resolve those that were dirty to load their previous dirty contents from disk
.then(onSuccess, onError);
return void 0;
});
}, errors.onUnexpectedError);
}
}

View file

@ -267,6 +267,7 @@ export class OutlinePanel extends ViewletPanel {
dispose(): void {
dispose(this._disposables);
dispose(this._requestOracle);
dispose(this._editorDisposables);
super.dispose();
}
@ -548,7 +549,7 @@ export class OutlinePanel extends ViewletPanel {
// feature: reveal outline selection in editor
// on change -> reveal/select defining range
this._editorDisposables.push(this._tree.onDidChangeSelection(e => {
if (e.payload === this) {
if (e.payload === this || e.payload && !e.payload.didClickElement) {
return;
}
let [first] = e.selection;
@ -630,11 +631,11 @@ export class OutlinePanel extends ViewletPanel {
private async _revealTreeSelection(model: OutlineModel, element: OutlineElement, focus: boolean, aside: boolean): TPromise<void> {
let input = this._editorService.createInput({ resource: model.textModel.uri });
await this._editorService.openEditor(input, { preserveFocus: !focus, selection: Range.collapseToStart(element.symbol.identifierRange), revealInCenterIfOutsideViewport: true, forceOpen: true }, aside ? SIDE_GROUP : ACTIVE_GROUP);
await this._editorService.openEditor(input, { preserveFocus: !focus, selection: Range.collapseToStart(element.symbol.selectionRange), revealInCenterIfOutsideViewport: true, forceOpen: true }, aside ? SIDE_GROUP : ACTIVE_GROUP);
}
private async _revealEditorSelection(model: OutlineModel, selection: Selection): TPromise<void> {
if (!this._outlineViewState.followCursor && !this._tree.getInput()) {
if (!this._outlineViewState.followCursor || !this._tree.getInput()) {
return;
}
let item = model.getItemEnclosingPosition({

View file

@ -5,40 +5,45 @@
.settings-editor {
padding-top: 11px;
display: flex;
margin: auto;
max-width: 1000px;
}
.settings-editor > .settings-editor-right {
flex: 1;
}
/* header styling */
.settings-editor > .settings-header {
padding-left: 15px;
.settings-editor > .settings-editor-right > .settings-header {
padding-left: 5px;
padding-right: 5px;
max-width: 800px;
max-width: 1000px;
box-sizing: border-box;
margin: auto;
}
.settings-editor > .settings-header > .settings-preview-header {
.settings-editor > .settings-editor-right > .settings-header > .settings-preview-header {
margin-bottom: 5px;
}
.settings-editor > .settings-header > .settings-preview-header .settings-preview-label {
.settings-editor > .settings-editor-right > .settings-header > .settings-preview-header .settings-preview-label {
opacity: .7;
}
.settings-editor > .settings-header > .settings-advanced-customization .open-settings-button,
.settings-editor > .settings-header > .settings-advanced-customization .open-settings-button:hover,
.settings-editor > .settings-header > .settings-advanced-customization .open-settings-button:active {
.settings-editor > .settings-editor-right > .settings-header > .settings-advanced-customization .open-settings-button,
.settings-editor > .settings-editor-right > .settings-header > .settings-advanced-customization .open-settings-button:hover,
.settings-editor > .settings-editor-right > .settings-header > .settings-advanced-customization .open-settings-button:active {
padding: 0;
text-decoration: underline;
display: inline;
}
.settings-editor > .settings-header > .settings-advanced-customization {
.settings-editor > .settings-editor-right > .settings-header > .settings-advanced-customization {
opacity: .7;
margin-top: 10px;
}
.settings-editor > .settings-header > .settings-preview-header > .settings-preview-warning {
.settings-editor > .settings-editor-right > .settings-header > .settings-preview-header > .settings-preview-warning {
text-align: right;
text-transform: uppercase;
background: rgba(136, 136, 136, 0.3);
@ -48,228 +53,242 @@
margin-right: 7px;
}
.settings-editor > .settings-header > .search-container {
.settings-editor > .settings-editor-right > .settings-header > .search-container {
position: relative;
}
.settings-editor > .settings-header .search-container > .settings-search-input {
.settings-editor > .settings-editor-right > .settings-header .search-container > .settings-search-input {
vertical-align: middle;
}
.settings-editor > .settings-header .search-container > .settings-search-input > .monaco-inputbox {
.settings-editor > .settings-editor-right > .settings-header .search-container > .settings-search-input > .monaco-inputbox {
height: 30px;
width: 100%;
}
.settings-editor > .settings-header .search-container > .settings-search-input > .monaco-inputbox .input {
.settings-editor > .settings-editor-right > .settings-header .search-container > .settings-search-input > .monaco-inputbox .input {
font-size: 14px;
padding-left: 10px;
}
.settings-editor > .settings-header > .settings-header-controls {
.settings-editor > .settings-editor-right > .settings-header > .settings-header-controls {
margin-top: 2px;
height: 30px;
display: flex;
}
.settings-editor > .settings-header .settings-tabs-widget > .monaco-action-bar .action-item:not(:first-child) .action-label {
.settings-editor > .settings-editor-right > .settings-header .settings-tabs-widget > .monaco-action-bar .action-item:not(:first-child) .action-label {
margin-left: 14px;
}
.settings-editor > .settings-header .settings-tabs-widget .monaco-action-bar .action-item .dropdown-icon {
.settings-editor > .settings-editor-right > .settings-header .settings-tabs-widget .monaco-action-bar .action-item .dropdown-icon {
/** The tab widget container height is shorter than elsewhere, need to tweak this */
padding-top: 3px;
}
.settings-editor > .settings-header > .settings-header-controls .settings-header-controls-right {
.settings-editor > .settings-editor-right > .settings-header > .settings-header-controls .settings-header-controls-right {
margin-left: auto;
padding-top: 3px;
display: flex;
}
.settings-editor > .settings-header > .settings-header-controls .settings-header-controls-right #configured-only-checkbox {
.settings-editor > .settings-editor-right > .settings-header > .settings-header-controls .settings-header-controls-right #configured-only-checkbox {
flex-shrink: 0;
}
.settings-editor > .settings-header > .settings-header-controls .settings-header-controls-right .configured-only-label {
.settings-editor > .settings-editor-right > .settings-header > .settings-header-controls .settings-header-controls-right .configured-only-label {
white-space: nowrap;
margin-right: 10px;
margin-left: 2px;
opacity: 0.7;
}
.settings-editor > .settings-body .settings-tree-container .monaco-tree-wrapper {
max-width: 800px;
margin: auto;
.settings-editor > .settings-editor-right > .settings-body {
display: flex;
max-width: 1000px;
}
.settings-editor > .settings-body .settings-tree-container .monaco-scrollable-element .shadow.top-left-corner {
.settings-editor > .settings-editor-right > .settings-body .settings-tree-container .monaco-tree-wrapper {
max-width: 800px;
}
.settings-editor > .settings-editor-right > .settings-body .settings-tree-container .monaco-scrollable-element .shadow.top-left-corner {
left: calc((100% - 800px)/2);
}
.settings-editor > .settings-body .settings-tree-container .monaco-scrollable-element .shadow {
.settings-editor > .settings-editor-right > .settings-body .settings-tree-container .monaco-scrollable-element .shadow {
left: calc((100% - 794px)/2);
width: 800px;
}
.settings-editor > .settings-body .settings-tree-container .monaco-tree::before {
.settings-editor > .settings-editor-right > .settings-body .settings-tree-container .monaco-tree::before {
outline: none !important;
}
.settings-editor > .settings-body .settings-tree-container {
.settings-editor > .settings-toc-container {
width: 175px;
margin-top: 114px;
margin-right: 5px;
}
.settings-editor.search-mode > .settings-toc-container .monaco-tree {
display: none;
}
.settings-editor > .settings-toc-container .monaco-tree-row .settings-toc-entry {
overflow: hidden;
text-overflow: ellipsis;
line-height: 22px;
}
.settings-editor > .settings-editor-right > .settings-body .settings-tree-container {
flex: 1;
border-spacing: 0;
border-collapse: separate;
position: relative;
}
.settings-editor > .settings-body > .settings-tree-container .setting-item {
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item {
padding-top: 4px;
padding-left: 5px;
cursor: default;
white-space: normal;
height: 100%;
min-height: 75px;
}
.settings-editor > .settings-body > .settings-tree-container .setting-item.odd:not(.focused):not(.selected):not(:hover),
.settings-editor > .settings-body > .settings-tree-container .monaco-tree:not(:focus) .setting-item.focused.odd:not(.selected):not(:hover),
.settings-editor > .settings-body > .settings-tree-container .monaco-tree:not(.focused) .setting-item.focused.odd:not(.selected):not(:hover) {
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item.odd:not(.focused):not(.selected):not(:hover),
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .monaco-tree:not(:focus) .setting-item.focused.odd:not(.selected):not(:hover),
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .monaco-tree:not(.focused) .setting-item.focused.odd:not(.selected):not(:hover) {
background-color: rgba(130, 130, 130, 0.04);
}
.settings-editor > .settings-body > .settings-tree-container .setting-item > .setting-item-left {
flex: 1;
padding-top: 3px;
padding-bottom: 12px;
}
.settings-editor > .settings-body > .settings-tree-container .setting-item > .setting-item-right {
min-width: 180px;
margin: 21px 10px 0px 5px;
}
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-title {
margin-top: 2px;
line-height: initial;
}
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-title .setting-item-is-configured-label {
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-title .setting-item-is-configured-label {
font-style: italic;
opacity: 0.8;
margin-right: 7px;
}
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-title .setting-item-overrides {
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-title .setting-item-overrides {
opacity: 0.5;
font-style: italic;
}
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-label {
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-label {
margin-right: 7px;
}
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-label,
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-category {
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-label,
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-category {
font-weight: bold;
}
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-category {
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-category {
opacity: 0.7;
}
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description {
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-description {
opacity: 0.7;
margin-top: 3px;
overflow: hidden;
white-space: nowrap;
white-space: pre;
text-overflow: ellipsis;
height: 18px;
}
.settings-editor > .settings-body > .settings-tree-container .setting-measure-container.monaco-tree-row {
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-measure-container.monaco-tree-row {
padding-left: 15px;
opacity: 0;
max-width: 800px;
}
.settings-editor > .settings-body > .settings-tree-container .setting-item.is-expanded .setting-item-description,
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-measure-helper .setting-item-description {
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item.is-expanded .setting-item-description,
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item.setting-measure-helper .setting-item-description {
height: initial;
white-space: pre-wrap;
}
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value > *:first-child {
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-value {
margin-top: 5px;
margin-bottom: 7px; /* Needed when measuring an expanded row */
display: flex;
}
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-item-control.setting-type-number {
min-width: 200px;
}
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-item-control.setting-type-enum,
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-item-control.setting-type-string {
flex: 1;
min-width: initial;
}
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-item-control.setting-type-enum > *:first-child {
width: 100%;
min-width: 250px;
display: inline-block;
}
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value {
margin-top: 10px;
margin-bottom: 8px;
display: inline-block;
}
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value > .edit-in-settings-button,
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value > .edit-in-settings-button:hover,
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value > .edit-in-settings-button:active {
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-item-control > .edit-in-settings-button,
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-item-control > .edit-in-settings-button:hover,
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-item-control > .edit-in-settings-button:active {
margin: auto;
text-align: left;
text-decoration: underline;
}
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value > .edit-in-settings-button + .setting-reset-button.monaco-button {
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-item-control > .edit-in-settings-button + .setting-reset-button.monaco-button {
display: none;
}
.settings-editor > .settings-body > .settings-tree-container .setting-item .monaco-select-box {
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .monaco-select-box {
width: initial;
font: inherit;
height: 26px;
}
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-reset-button.monaco-button {
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-reset-button.monaco-button {
text-align: left;
display: inline-block;
visibility: hidden;
width: initial;
padding-top: 0px; /* So focus outline doesn't overlap the control above */
padding-top: 2px;
}
.settings-editor > .settings-body > .settings-tree-container .setting-item.is-configured .setting-item-value > .setting-reset-button.monaco-button {
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item.is-configured .setting-item-value > .setting-reset-button.monaco-button {
visibility: visible;
}
.settings-editor > .settings-body > .settings-tree-container .all-settings {
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .all-settings {
display: flex;
}
.settings-editor > .settings-body > .settings-tree-container .all-settings .all-settings-button {
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .all-settings .all-settings-button {
margin: auto;
}
.settings-editor > .settings-body > .settings-tree-container .all-settings .all-settings-button .monaco-button {
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .all-settings .all-settings-button .monaco-button {
padding-left: 10px;
padding-right: 10px;
}
/*
Ensure the is-configured indicators can appear outside of the list items themselves:
- Disable overflow: hidden on the listrow
- Allocate some space with a margin on the list-row
- Make up for that space with a negative margin on the settings-body
This is risky, consider a different approach
*/
.settings-editor .settings-tree-container .setting-item {
overflow: visible;
}
.settings-editor > .settings-body > .settings-tree-container .settings-group-title-label {
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .settings-group-title-label {
margin: 0px;
padding: 5px 0px;
font-size: 13px;
padding-left: 5px !important;
}
.settings-editor > .settings-body .settings-feedback-button {
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .settings-group-level-1 {
padding-top: 16px;
font-size: 24px;
}
.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .settings-group-level-2 {
padding-top: 16px;
font-size: 20px;
}
.settings-editor > .settings-editor-right > .settings-body .settings-feedback-button {
color: rgb(255, 255, 255);
background-color: rgb(14, 99, 156);
position: absolute;

View file

@ -7,15 +7,18 @@ import * as DOM from 'vs/base/browser/dom';
import { Button } from 'vs/base/browser/ui/button/button';
import * as arrays from 'vs/base/common/arrays';
import { Delayer, ThrottledDelayer } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Color } from 'vs/base/common/color';
import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors';
import { KeyCode } from 'vs/base/common/keyCodes';
import { TPromise } from 'vs/base/common/winjs.base';
import { ITreeConfiguration } from 'vs/base/parts/tree/browser/tree';
import { ITree, ITreeConfiguration } from 'vs/base/parts/tree/browser/tree';
import { DefaultTreestyler } from 'vs/base/parts/tree/browser/treeDefaults';
import 'vs/css!./media/settingsEditor2';
import { localize } from 'vs/nls';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { WorkbenchTree } from 'vs/platform/list/browser/listService';
import { ILogService } from 'vs/platform/log/common/log';
@ -26,14 +29,13 @@ import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant }
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { EditorOptions, IEditor } from 'vs/workbench/common/editor';
import { SearchWidget, SettingsTarget, SettingsTargetsWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets';
import { ISettingsEditorViewState, SearchResultIdx, SearchResultModel, SettingsAccessibilityProvider, SettingsDataSource, SettingsRenderer, SettingsTreeController, SettingsTreeFilter, TreeElement } from 'vs/workbench/parts/preferences/browser/settingsTree';
import { IPreferencesSearchService, ISearchProvider, CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS } from 'vs/workbench/parts/preferences/common/preferences';
import { tocData, commonlyUsedData } from 'vs/workbench/parts/preferences/browser/settingsLayout';
import { ISettingsEditorViewState, SearchResultIdx, SearchResultModel, SettingsAccessibilityProvider, SettingsDataSource, SettingsRenderer, SettingsTreeController, SettingsTreeElement, SettingsTreeFilter, SettingsTreeModel, SettingsTreeSettingElement, SettingsTreeGroupElement, resolveSettingsTree, NonExpandableTree } from 'vs/workbench/parts/preferences/browser/settingsTree';
import { TOCDataSource, TOCRenderer } from 'vs/workbench/parts/preferences/browser/tocTree';
import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, IPreferencesSearchService, ISearchProvider } from 'vs/workbench/parts/preferences/common/preferences';
import { IPreferencesService, ISearchResult, ISettingsEditorModel } from 'vs/workbench/services/preferences/common/preferences';
import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput';
import { DefaultSettingsEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
const $ = DOM.$;
@ -43,26 +45,30 @@ export class SettingsEditor2 extends BaseEditor {
private defaultSettingsEditorModel: DefaultSettingsEditorModel;
private rootElement: HTMLElement;
private headerContainer: HTMLElement;
private searchWidget: SearchWidget;
private settingsTargetsWidget: SettingsTargetsWidget;
private showConfiguredSettingsOnlyCheckbox: HTMLInputElement;
private savedExpandedGroups: any[];
private settingsTreeContainer: HTMLElement;
private settingsTree: WorkbenchTree;
private treeDataSource: SettingsDataSource;
private settingsTreeModel: SettingsTreeModel;
private tocTreeContainer: HTMLElement;
private tocTree: WorkbenchTree;
private delayedModifyLogging: Delayer<void>;
private delayedFilterLogging: Delayer<void>;
private localSearchDelayer: Delayer<void>;
private remoteSearchThrottle: ThrottledDelayer<void>;
private searchInProgress: TPromise<void>;
private pendingSettingModifiedReport: { key: string, value: any };
private settingUpdateDelayer: Delayer<void>;
private pendingSettingUpdate: { key: string, value: any };
private selectedElement: TreeElement;
private selectedElement: SettingsTreeElement;
private viewState: ISettingsEditorViewState;
private searchResultModel: SearchResultModel;
@ -81,23 +87,26 @@ export class SettingsEditor2 extends BaseEditor {
@IContextKeyService contextKeyService: IContextKeyService
) {
super(SettingsEditor2.ID, telemetryService, themeService);
this.delayedModifyLogging = new Delayer<void>(1000);
this.delayedFilterLogging = new Delayer<void>(1000);
this.localSearchDelayer = new Delayer(100);
this.remoteSearchThrottle = new ThrottledDelayer(200);
this.viewState = { settingsTarget: ConfigurationTarget.USER };
this.settingUpdateDelayer = new Delayer<void>(500);
this.inSettingsEditorContextKey = CONTEXT_SETTINGS_EDITOR.bindTo(contextKeyService);
this.searchFocusContextKey = CONTEXT_SETTINGS_SEARCH_FOCUS.bindTo(contextKeyService);
this._register(configurationService.onDidChangeConfiguration(() => this.refreshTreeAndMaintainFocus()));
this._register(configurationService.onDidChangeConfiguration(() => this.onConfigUpdate()));
}
createEditor(parent: HTMLElement): void {
const prefsEditorElement = DOM.append(parent, $('div', { class: 'settings-editor' }));
this.rootElement = DOM.append(parent, $('.settings-editor'));
this.createTOC(this.rootElement);
this.createHeader(prefsEditorElement);
this.createBody(prefsEditorElement);
const settingsEditorRightElement = DOM.append(this.rootElement, $('.settings-editor-right'));
this.createHeader(settingsEditorRightElement);
this.createBody(settingsEditorRightElement);
}
setInput(input: SettingsEditor2Input, options: EditorOptions, token: CancellationToken): Thenable<void> {
@ -208,14 +217,53 @@ export class SettingsEditor2 extends BaseEditor {
private createBody(parent: HTMLElement): void {
const bodyContainer = DOM.append(parent, $('.settings-body'));
this.createList(bodyContainer);
this.createSettingsTree(bodyContainer);
if (this.environmentService.appQuality !== 'stable') {
this.createFeedbackButton(bodyContainer);
}
}
private createList(parent: HTMLElement): void {
private createTOC(parent: HTMLElement): void {
this.tocTreeContainer = DOM.append(parent, $('.settings-toc-container'));
const tocTreeDataSource = this.instantiationService.createInstance(TOCDataSource);
const renderer = this.instantiationService.createInstance(TOCRenderer);
this.tocTree = this.instantiationService.createInstance(WorkbenchTree, this.tocTreeContainer,
<ITreeConfiguration>{
dataSource: tocTreeDataSource,
renderer,
filter: this.instantiationService.createInstance(SettingsTreeFilter, this.viewState)
},
{
showLoading: false
});
this._register(this.tocTree.onDidChangeSelection(e => {
if (this.settingsTreeModel) {
const element = e.selection[0];
const currentSelection = this.settingsTree.getSelection()[0];
const isEqualOrParent = (element: SettingsTreeElement, candidate: SettingsTreeElement) => {
do {
if (element === candidate) {
return true;
}
} while (element = element.parent);
return false;
};
if (element && (!currentSelection || !isEqualOrParent(currentSelection, element))) {
this.settingsTree.reveal(element, 0);
this.settingsTree.setSelection([element]);
this.settingsTree.setFocus(element);
}
}
}));
}
private createSettingsTree(parent: HTMLElement): void {
this.settingsTreeContainer = DOM.append(parent, $('.settings-tree-container'));
this.treeDataSource = this.instantiationService.createInstance(SettingsDataSource, this.viewState);
@ -224,10 +272,10 @@ export class SettingsEditor2 extends BaseEditor {
this._register(renderer.onDidOpenSettings(() => this.openSettingsFile()));
const treeClass = 'settings-editor-tree';
this.settingsTree = this.instantiationService.createInstance(WorkbenchTree, this.settingsTreeContainer,
this.settingsTree = this.instantiationService.createInstance(NonExpandableTree, this.settingsTreeContainer,
<ITreeConfiguration>{
dataSource: this.treeDataSource,
renderer: renderer,
renderer,
controller: this.instantiationService.createInstance(SettingsTreeController),
accessibilityProvider: this.instantiationService.createInstance(SettingsAccessibilityProvider),
filter: this.instantiationService.createInstance(SettingsTreeFilter, this.viewState),
@ -237,24 +285,24 @@ export class SettingsEditor2 extends BaseEditor {
ariaLabel: localize('treeAriaLabel', "Settings"),
showLoading: false,
indentPixels: 0,
twistiePixels: 15,
twistiePixels: 0,
});
this._register(registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
const activeBorderColor = theme.getColor(listActiveSelectionBackground);
if (activeBorderColor) {
collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .monaco-tree:focus .monaco-tree-row.focused {outline: solid 1px ${activeBorderColor}; outline-offset: -1px; }`);
collector.addRule(`.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .monaco-tree:focus .monaco-tree-row.focused {outline: solid 1px ${activeBorderColor}; outline-offset: -1px; }`);
}
const inactiveBorderColor = theme.getColor(listInactiveSelectionBackground);
if (inactiveBorderColor) {
collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .monaco-tree .monaco-tree-row.focused {outline: solid 1px ${inactiveBorderColor}; outline-offset: -1px; }`);
collector.addRule(`.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .monaco-tree .monaco-tree-row.focused {outline: solid 1px ${inactiveBorderColor}; outline-offset: -1px; }`);
}
}));
this.settingsTree.getHTMLElement().classList.add(treeClass);
attachStyler(this.themeService, {
this._register(attachStyler(this.themeService, {
listActiveSelectionBackground: editorBackground,
listActiveSelectionForeground: foreground,
listFocusAndSelectionBackground: editorBackground,
@ -267,9 +315,9 @@ export class SettingsEditor2 extends BaseEditor {
listInactiveSelectionForeground: foreground
}, colors => {
this.settingsTree.style(colors);
});
}));
this.settingsTree.onDidChangeFocus(e => {
this._register(this.settingsTree.onDidChangeFocus(e => {
this.settingsTree.setSelection([e.focus]);
if (this.selectedElement) {
this.settingsTree.refresh(this.selectedElement);
@ -280,7 +328,19 @@ export class SettingsEditor2 extends BaseEditor {
}
this.selectedElement = e.focus;
});
}));
this._register(this.settingsTree.onDidChangeSelection(e => {
const element = e.selection[0] instanceof SettingsTreeSettingElement ? e.selection[0].parent :
e.selection[0] instanceof SettingsTreeGroupElement ? e.selection[0] :
null;
if (element && this.tocTree.getSelection()[0] !== element) {
this.tocTree.reveal(element, 0);
this.tocTree.setSelection([element]);
this.tocTree.setFocus(element);
}
}));
}
private createFeedbackButton(parent: HTMLElement): void {
@ -298,53 +358,42 @@ export class SettingsEditor2 extends BaseEditor {
private onShowConfiguredOnlyClicked(): void {
this.viewState.showConfiguredOnly = this.showConfiguredSettingsOnlyCheckbox.checked;
this.refreshTreeAndMaintainFocus();
// TODO@roblou - This is slow
if (this.viewState.showConfiguredOnly) {
this.savedExpandedGroups = this.settingsTree.getExpandedElements();
const nav = this.settingsTree.getNavigator();
let element;
while (element = nav.next()) {
this.settingsTree.expand(element);
}
} else if (this.savedExpandedGroups) {
const nav = this.settingsTree.getNavigator();
let element;
while (element = nav.next()) {
this.settingsTree.collapse(element);
}
this.settingsTree.expandAll(this.savedExpandedGroups);
this.savedExpandedGroups = null;
}
this.tocTree.refresh();
this.settingsTree.setScrollPosition(0);
this.expandAll(this.settingsTree);
}
private onDidChangeSetting(key: string, value: any): void {
// ConfigurationService displays the error if this fails.
// Force a render afterwards because onDidConfigurationUpdate doesn't fire if the update doesn't result in an effective setting value change
this.configurationService.updateValue(key, value, <ConfigurationTarget>this.settingsTargetsWidget.settingsTarget)
.then(() => this.refreshTreeAndMaintainFocus());
const reportModifiedProps = {
key,
query: this.searchWidget.getValue(),
searchResults: this.searchResultModel && this.searchResultModel.getUniqueResults(),
rawResults: this.searchResultModel && this.searchResultModel.getRawResults(),
showConfiguredOnly: this.viewState.showConfiguredOnly,
isReset: typeof value === 'undefined',
settingsTarget: this.settingsTargetsWidget.settingsTarget as SettingsTarget
};
if (this.pendingSettingModifiedReport && key !== this.pendingSettingModifiedReport.key) {
this.reportModifiedSetting(reportModifiedProps);
if (this.pendingSettingUpdate && this.pendingSettingUpdate.key !== key) {
this.updateChangedSetting(key, value);
}
this.pendingSettingModifiedReport = { key, value };
this.delayedModifyLogging.trigger(() => this.reportModifiedSetting(reportModifiedProps));
this.pendingSettingUpdate = { key, value };
this.settingUpdateDelayer.trigger(() => this.updateChangedSetting(key, value));
}
private updateChangedSetting(key: string, value: any): TPromise<void> {
// ConfigurationService displays the error if this fails.
// Force a render afterwards because onDidConfigurationUpdate doesn't fire if the update doesn't result in an effective setting value change
return this.configurationService.updateValue(key, value, <ConfigurationTarget>this.settingsTargetsWidget.settingsTarget)
.then(() => this.refreshTreeAndMaintainFocus())
.then(() => {
const reportModifiedProps = {
key,
query: this.searchWidget.getValue(),
searchResults: this.searchResultModel && this.searchResultModel.getUniqueResults(),
rawResults: this.searchResultModel && this.searchResultModel.getRawResults(),
showConfiguredOnly: this.viewState.showConfiguredOnly,
isReset: typeof value === 'undefined',
settingsTarget: this.settingsTargetsWidget.settingsTarget as SettingsTarget
};
return this.reportModifiedSetting(reportModifiedProps);
});
}
private reportModifiedSetting(props: { key: string, query: string, searchResults: ISearchResult[], rawResults: ISearchResult[], showConfiguredOnly: boolean, isReset: boolean, settingsTarget: SettingsTarget }): void {
this.pendingSettingModifiedReport = null;
this.pendingSettingUpdate = null;
const remoteResult = props.searchResults && props.searchResults[SearchResultIdx.Remote];
const localResult = props.searchResults && props.searchResults[SearchResultIdx.Local];
@ -410,15 +459,33 @@ export class SettingsEditor2 extends BaseEditor {
}
this.defaultSettingsEditorModel = model;
if (!this.settingsTree.getInput()) {
this.settingsTree.setInput(this.defaultSettingsEditorModel);
this.expandCommonlyUsedSettings();
}
this.onConfigUpdate();
});
}
return TPromise.as(null);
}
private toggleSearchMode(): void {
DOM.toggleClass(this.rootElement, 'search-mode', !!this.searchResultModel);
}
private onConfigUpdate(): TPromise<void> {
const groups = this.defaultSettingsEditorModel.settingsGroups.slice(1); // Without commonlyUsed
const resolvedSettingsRoot = resolveSettingsTree(tocData, groups);
const commonlyUsed = resolveSettingsTree(commonlyUsedData, groups);
resolvedSettingsRoot.children.unshift(commonlyUsed);
if (this.settingsTreeModel) {
this.settingsTreeModel.update(resolvedSettingsRoot);
} else {
this.settingsTreeModel = this.instantiationService.createInstance(SettingsTreeModel, this.viewState, resolvedSettingsRoot);
this.settingsTree.setInput(this.settingsTreeModel.root);
this.tocTree.setInput(this.settingsTreeModel.root);
}
return this.refreshTreeAndMaintainFocus();
}
private refreshTreeAndMaintainFocus(): TPromise<any> {
// Sort of a hack to maintain focus on the focused control across a refresh
const focusedRowItem = DOM.findParentWithClass(<HTMLElement>document.activeElement, 'setting-item');
@ -427,18 +494,22 @@ export class SettingsEditor2 extends BaseEditor {
(<HTMLInputElement>document.activeElement).selectionStart :
null;
return this.settingsTree.refresh().then(() => {
if (focusedRowId) {
const rowSelector = `.setting-item#${focusedRowId}`;
const inputElementToFocus: HTMLElement = this.settingsTreeContainer.querySelector(`${rowSelector} input, ${rowSelector} select, ${rowSelector} a`);
if (inputElementToFocus) {
inputElementToFocus.focus();
if (typeof selection === 'number') {
(<HTMLInputElement>inputElementToFocus).setSelectionRange(selection, selection);
return this.settingsTree.refresh()
.then(() => {
if (focusedRowId) {
const rowSelector = `.setting-item#${focusedRowId}`;
const inputElementToFocus: HTMLElement = this.settingsTreeContainer.querySelector(`${rowSelector} input, ${rowSelector} select, ${rowSelector} a`);
if (inputElementToFocus) {
inputElementToFocus.focus();
if (typeof selection === 'number') {
(<HTMLInputElement>inputElementToFocus).setSelectionRange(selection, selection);
}
}
}
}
});
})
.then(() => {
return this.tocTree.refresh();
});
}
private onSearchInputChanged(): void {
@ -467,16 +538,19 @@ export class SettingsEditor2 extends BaseEditor {
}
this.searchResultModel = null;
this.settingsTree.setInput(this.defaultSettingsEditorModel);
this.expandCommonlyUsedSettings();
this.toggleSearchMode();
this.settingsTree.setInput(this.settingsTreeModel.root);
return TPromise.wrap(null);
}
}
private expandCommonlyUsedSettings(): void {
const commonlyUsedGroup = this.defaultSettingsEditorModel.settingsGroups[0];
this.settingsTree.expand(this.treeDataSource.getGroupElement(commonlyUsedGroup, 0));
private expandAll(tree: ITree): void {
const nav = tree.getNavigator();
let cur;
while (cur = nav.next()) {
tree.expand(cur);
}
}
private reportFilteringUsed(query: string, results: ISearchResult[]): void {
@ -539,6 +613,7 @@ export class SettingsEditor2 extends BaseEditor {
const [result] = results;
if (!this.searchResultModel) {
this.searchResultModel = new SearchResultModel();
this.toggleSearchMode();
this.settingsTree.setInput(this.searchResultModel);
}
@ -577,6 +652,8 @@ export class SettingsEditor2 extends BaseEditor {
private layoutSettingsList(dimension: DOM.Dimension): void {
const listHeight = dimension.height - (DOM.getDomNodePagePosition(this.headerContainer).height + 12 /*padding*/);
this.settingsTreeContainer.style.height = `${listHeight}px`;
this.tocTreeContainer.style.height = `${listHeight}px`;
this.settingsTree.layout(listHeight, 800);
this.tocTree.layout(listHeight, 200);
}
}
}

View file

@ -0,0 +1,193 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ISetting } from 'vs/workbench/services/preferences/common/preferences';
export interface ITOCEntry {
id: string;
label: string;
children?: ITOCEntry[];
settings?: (string | ISetting)[];
}
export const commonlyUsedData: ITOCEntry = {
id: 'commonlyUsed',
label: 'Commonly Used',
settings: ['files.autoSave', 'editor.fontSize', 'editor.fontFamily', 'editor.tabSize', 'editor.renderWhitespace', 'editor.cursorStyle', 'editor.multiCursorModifier', 'editor.insertSpaces', 'editor.wordWrap', 'files.exclude', 'files.associations']
};
export const tocData: ITOCEntry = {
id: 'root',
label: 'root',
children: [
{
id: 'editor',
label: 'Text Editor',
children: [
{
id: 'editor/cursor',
label: 'Cursor',
settings: ['editor.cursor*']
},
{
id: 'editor/find',
label: 'Find',
settings: ['editor.find.*']
},
{
id: 'editor/font',
label: 'Font',
settings: ['editor.font*']
},
{
id: 'editor/format',
label: 'Format',
settings: ['editor.format*']
},
{
id: 'editor/diffEditor',
label: 'Diff Editor',
settings: ['diffEditor.*']
},
{
id: 'editor/minimap',
label: 'Minimap',
settings: ['editor.minimap.*']
},
{
id: 'editor/suggestions',
label: 'Suggestions',
settings: ['editor.*suggestion*']
},
{
id: 'editor/files',
label: 'Files',
settings: ['files.*']
},
{
id: 'editor/editor',
label: 'Editor',
settings: ['editor.*']
}
]
},
{
id: 'workbench',
label: 'Workbench',
children: [
{
id: 'workbench/appearance',
label: 'Appearance',
settings: ['workbench.activityBar.*', 'workbench.*color*', 'workbench.fontAliasing', 'workbench.iconTheme', 'workbench.sidebar.location', 'workbench.*.visible', 'workbench.tips.enabled', 'workbench.tree.*', 'workbench.view.*']
},
{
id: 'workbench/editor',
label: 'Editor Management',
settings: ['workbench.editor.*']
},
{
id: 'workbench/zenmode',
label: 'Zen Mode',
settings: ['zenmode.*']
},
{
id: 'workbench/workbench',
label: 'Workbench',
settings: ['workbench.*']
}
]
},
{
id: 'window',
label: 'Window',
children: [
{
id: 'window/newWindow',
label: 'New Window',
settings: ['window.*newwindow*']
},
{
id: 'window/window',
label: 'Window',
settings: ['window.*']
}
]
},
{
id: 'features',
label: 'Features',
children: [
{
id: 'features/explorer',
label: 'File Explorer',
settings: ['explorer.*', 'outline.*']
},
{
id: 'features/search',
label: 'Search',
settings: ['search.*']
}
,
{
id: 'features/debug',
label: 'Debug',
settings: ['debug.*', 'launch']
},
{
id: 'features/scm',
label: 'SCM',
settings: ['scm.*']
},
{
id: 'features/extensions',
label: 'Extension Viewlet',
settings: ['extensions.*']
},
{
id: 'features/terminal',
label: 'Terminal',
settings: ['terminal.*']
},
{
id: 'features/problems',
label: 'Problems',
settings: ['problems.*']
}
]
},
{
id: 'application',
label: 'Application',
children: [
{
id: 'application/http',
label: 'Proxy',
settings: ['http.*']
},
{
id: 'application/keyboard',
label: 'Keyboard',
settings: ['keyboard.*']
},
{
id: 'application/update',
label: 'Update',
settings: ['update.*']
},
{
id: 'application/telemetry',
label: 'Telemetry',
settings: ['telemetry.*']
}
]
},
{
id: 'extensions',
label: 'Extensions',
settings: ['*']
}
]
};

View file

@ -9,24 +9,26 @@ import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { Button } from 'vs/base/browser/ui/button/button';
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { SelectBox } from 'vs/base/browser/ui/selectBox/selectBox';
import * as arrays from 'vs/base/common/arrays';
import { Color } from 'vs/base/common/color';
import { Emitter, Event } from 'vs/base/common/event';
import { KeyCode } from 'vs/base/common/keyCodes';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import * as objects from 'vs/base/common/objects';
import { escapeRegExpCharacters } from 'vs/base/common/strings';
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { IAccessibilityProvider, IDataSource, IFilter, IRenderer, ITree } from 'vs/base/parts/tree/browser/tree';
import { localize } from 'vs/nls';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { WorkbenchTreeController } from 'vs/platform/list/browser/listService';
import { WorkbenchTreeController, WorkbenchTree } from 'vs/platform/list/browser/listService';
import { editorActiveLinkForeground, registerColor } from 'vs/platform/theme/common/colorRegistry';
import { attachButtonStyler, attachInputBoxStyler, attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { SettingsTarget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets';
import { ITOCEntry } from 'vs/workbench/parts/preferences/browser/settingsLayout';
import { ISearchResult, ISetting, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences';
import { DefaultSettingsEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels';
const $ = DOM.$;
@ -39,24 +41,25 @@ export const modifiedItemForeground = registerColor('settings.modifiedItemForegr
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
const modifiedItemForegroundColor = theme.getColor(modifiedItemForeground);
if (modifiedItemForegroundColor) {
collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item.is-configured .setting-item-is-configured-label { color: ${modifiedItemForegroundColor}; }`);
collector.addRule(`.settings-editor > .settings-editor-right > .settings-body > .settings-tree-container .setting-item.is-configured .setting-item-is-configured-label { color: ${modifiedItemForegroundColor}; }`);
}
});
export interface ITreeItem {
export abstract class SettingsTreeElement {
id: string;
parent: any; // SearchResultModel or group element... TODO search should be more similar to the normal case
}
export enum TreeItemType {
setting,
groupTitle
export class SettingsTreeGroupElement extends SettingsTreeElement {
children: (SettingsTreeGroupElement | SettingsTreeSettingElement)[];
label: string;
level: number;
}
export interface ISettingElement extends ITreeItem {
type: TreeItemType.setting;
parent: ISettingsGroup;
export class SettingsTreeSettingElement extends SettingsTreeElement {
setting: ISetting;
isExpanded: boolean;
displayCategory: string;
displayLabel: string;
value: any;
@ -67,15 +70,110 @@ export interface ISettingElement extends ITreeItem {
enum?: string[];
}
export interface IGroupElement extends ITreeItem {
type: TreeItemType.groupTitle;
parent: DefaultSettingsEditorModel;
group: ISettingsGroup;
index: number;
export interface ITOCEntry {
id: string;
label: string;
children?: ITOCEntry[];
settings?: (string | ISetting)[];
}
export type TreeElement = ISettingElement | IGroupElement;
export type TreeElementOrRoot = TreeElement | DefaultSettingsEditorModel | SearchResultModel;
export class SettingsTreeModel {
private _root: SettingsTreeGroupElement;
private _treeElementsById = new Map<string, SettingsTreeElement>();
constructor(
private viewState: ISettingsEditorViewState,
tocRoot: ITOCEntry,
@IConfigurationService private configurationService: IConfigurationService
) {
this.update(tocRoot);
}
get root(): SettingsTreeElement {
return this._root;
}
update(newTocRoot: ITOCEntry): void {
const newRoot = this.createSettingsTreeGroupElement(newTocRoot);
if (this._root) {
this._root.children = newRoot.children;
} else {
this._root = newRoot;
}
}
getElementById(id: string): SettingsTreeElement {
return this._treeElementsById.get(id);
}
private createSettingsTreeGroupElement(tocEntry: ITOCEntry, parent?: SettingsTreeGroupElement): SettingsTreeGroupElement {
const element = new SettingsTreeGroupElement();
element.id = tocEntry.id;
element.label = tocEntry.label;
element.parent = parent;
element.level = this.getDepth(element);
if (tocEntry.children) {
element.children = tocEntry.children.map(child => this.createSettingsTreeGroupElement(child, element));
} else if (tocEntry.settings) {
element.children = tocEntry.settings.map(s => this.createSettingsTreeSettingElement(<ISetting>s, element));
}
this._treeElementsById.set(element.id, element);
return element;
}
private getDepth(element: SettingsTreeElement): number {
if (element.parent) {
return 1 + this.getDepth(element.parent);
} else {
return 0;
}
}
private createSettingsTreeSettingElement(setting: ISetting, parent: SettingsTreeGroupElement): SettingsTreeSettingElement {
const element = createSettingsTreeSettingElement(setting, parent, this.viewState.settingsTarget, this.configurationService);
this._treeElementsById.set(element.id, element);
return element;
}
}
function sanitizeId(id: string): string {
return id.replace(/[\.\/]/, '_');
}
function createSettingsTreeSettingElement(setting: ISetting, parent: any, settingsTarget: SettingsTarget, configurationService: IConfigurationService): SettingsTreeSettingElement {
const element = new SettingsTreeSettingElement();
element.id = sanitizeId(parent.id + '_' + setting.key);
element.parent = parent;
const { isConfigured, inspected, targetSelector } = inspectSetting(setting.key, settingsTarget, configurationService);
const displayValue = isConfigured ? inspected[targetSelector] : inspected.default;
const overriddenScopeList = [];
if (targetSelector === 'user' && typeof inspected.workspace !== 'undefined') {
overriddenScopeList.push(localize('workspace', "Workspace"));
}
if (targetSelector === 'workspace' && typeof inspected.user !== 'undefined') {
overriddenScopeList.push(localize('user', "User"));
}
const displayKeyFormat = settingKeyToDisplayFormat(setting.key, parent.id);
element.setting = setting;
element.displayLabel = displayKeyFormat.label;
element.displayCategory = displayKeyFormat.category;
element.isExpanded = false;
element.value = displayValue;
element.isConfigured = isConfigured;
element.overriddenScopeList = overriddenScopeList;
element.description = setting.description.join('\n');
element.enum = setting.enum;
element.valueType = setting.type;
return element;
}
function inspectSetting(key: string, target: SettingsTarget, configurationService: IConfigurationService): { isConfigured: boolean, inspected: any, targetSelector: string } {
const inspectOverrides = URI.isUri(target) ? { resource: target } : undefined;
@ -88,126 +186,119 @@ function inspectSetting(key: string, target: SettingsTarget, configurationServic
return { isConfigured, inspected, targetSelector };
}
export function resolveSettingsTree(tocData: ITOCEntry, settingsGroups: ISettingsGroup[]): ITOCEntry {
return _resolveSettingsTree(tocData, getFlatSettings(settingsGroups));
}
function _resolveSettingsTree(tocData: ITOCEntry, allSettings: Set<ISetting>): ITOCEntry {
if (tocData.settings) {
return <ITOCEntry>{
id: tocData.id,
label: tocData.label,
settings: arrays.flatten(tocData.settings.map(pattern => getMatchingSettings(allSettings, <string>pattern)))
};
} else if (tocData.children) {
return <ITOCEntry>{
id: tocData.id,
label: tocData.label,
children: tocData.children.map(child => _resolveSettingsTree(child, allSettings))
};
}
return null;
}
function getMatchingSettings(allSettings: Set<ISetting>, pattern: string): ISetting[] {
const result: ISetting[] = [];
allSettings.forEach(s => {
if (settingMatches(s, pattern)) {
result.push(s);
allSettings.delete(s);
}
});
return result.sort((a, b) => a.key.localeCompare(b.key));
}
function settingMatches(s: ISetting, pattern: string): boolean {
pattern = escapeRegExpCharacters(pattern)
.replace(/\\\*/g, '.*');
const regexp = new RegExp(`^${pattern}`, 'i');
return regexp.test(s.key);
}
function getFlatSettings(settingsGroups: ISettingsGroup[]) {
const result: Set<ISetting> = new Set();
for (let group of settingsGroups) {
for (let section of group.sections) {
for (let s of section.settings) {
result.add(s);
}
}
}
return result;
}
export class SettingsDataSource implements IDataSource {
constructor(
private viewState: ISettingsEditorViewState,
@IConfigurationService private configurationService: IConfigurationService
) { }
getGroupElement(group: ISettingsGroup, index: number): IGroupElement {
return <IGroupElement>{
type: TreeItemType.groupTitle,
group,
id: `${group.title}_${group.id}`,
index
};
getId(tree: ITree, element: SettingsTreeElement): string {
return element.id;
}
getSettingElement(setting: ISetting, group: ISettingsGroup): ISettingElement {
const { isConfigured, inspected, targetSelector } = inspectSetting(setting.key, this.viewState.settingsTarget, this.configurationService);
const displayValue = isConfigured ? inspected[targetSelector] : inspected.default;
const overriddenScopeList = [];
if (targetSelector === 'user' && typeof inspected.workspace !== 'undefined') {
overriddenScopeList.push(localize('workspace', "Workspace"));
}
if (targetSelector === 'workspace' && typeof inspected.user !== 'undefined') {
overriddenScopeList.push(localize('user', "User"));
}
const displayKeyFormat = settingKeyToDisplayFormat(setting.key);
return <ISettingElement>{
type: TreeItemType.setting,
parent: group,
id: `${group.id}_${setting.key.replace(/\./g, '_')}`,
setting,
displayLabel: displayKeyFormat.label,
displayCategory: displayKeyFormat.category,
isExpanded: false,
value: displayValue,
isConfigured,
overriddenScopeList,
description: setting.description.join('\n'),
enum: setting.enum,
valueType: setting.type
};
}
getId(tree: ITree, element: TreeElementOrRoot): string {
return element instanceof DefaultSettingsEditorModel ? 'root' : element.id;
}
hasChildren(tree: ITree, element: TreeElementOrRoot): boolean {
if (element instanceof DefaultSettingsEditorModel) {
return true;
}
hasChildren(tree: ITree, element: SettingsTreeElement): boolean {
if (element instanceof SearchResultModel) {
return true;
}
if (element.type === TreeItemType.groupTitle) {
if (element instanceof SettingsTreeGroupElement) {
return true;
}
return false;
}
_getChildren(element: TreeElementOrRoot): TreeElement[] {
if (element instanceof DefaultSettingsEditorModel) {
return this.getRootChildren(element);
} else if (element instanceof SearchResultModel) {
return this.getGroupChildren(element.resultsAsGroup());
} else if (element.type === TreeItemType.groupTitle) {
return this.getGroupChildren(element.group);
private getSearchResultChildren(searchResult: SearchResultModel): SettingsTreeSettingElement[] {
return searchResult.getFlatSettings()
.map(s => createSettingsTreeSettingElement(s, searchResult, this.viewState.settingsTarget, this.configurationService));
}
getChildren(tree: ITree, element: SettingsTreeElement): TPromise<any, any> {
return TPromise.as(this._getChildren(element));
}
private _getChildren(element: SettingsTreeElement): SettingsTreeElement[] {
if (element instanceof SearchResultModel) {
return this.getSearchResultChildren(element);
} else if (element instanceof SettingsTreeGroupElement) {
return element.children;
} else {
// No children...
return null;
}
}
getChildren(tree: ITree, element: TreeElementOrRoot): TPromise<any, any> {
return TPromise.as(this._getChildren(element));
getParent(tree: ITree, element: SettingsTreeElement): TPromise<any, any> {
return TPromise.wrap(element.parent);
}
private getRootChildren(root: DefaultSettingsEditorModel): TreeElement[] {
return root.settingsGroups
.map((g, i) => this.getGroupElement(g, i));
}
private getGroupChildren(group: ISettingsGroup): ISettingElement[] {
const entries: ISettingElement[] = [];
for (const section of group.sections) {
for (const setting of section.settings) {
entries.push(this.getSettingElement(setting, group));
}
}
return entries;
}
getParent(tree: ITree, element: TreeElement): TPromise<any, any> {
if (!element) {
return null;
}
if (!(element instanceof DefaultSettingsEditorModel)) {
return TPromise.wrap(element.parent);
}
return TPromise.wrap(null);
shouldAutoexpand(): boolean {
return true;
}
}
export function settingKeyToDisplayFormat(key: string): { category: string, label: string } {
let label = key
.replace(/\.([a-z])/g, (match, p1) => `.${p1.toUpperCase()}`)
.replace(/([a-z])([A-Z])/g, '$1 $2') // fooBar => foo Bar
.replace(/^[a-z]/g, match => match.toUpperCase()); // foo => Foo
export function settingKeyToDisplayFormat(key: string, groupId = ''): { category: string, label: string } {
let label = wordifyKey(key);
const lastDotIdx = label.lastIndexOf('.');
let category = '';
if (lastDotIdx >= 0) {
@ -215,22 +306,69 @@ export function settingKeyToDisplayFormat(key: string): { category: string, labe
label = label.substr(lastDotIdx + 1);
}
groupId = wordifyKey(groupId.replace(/\//g, '.'));
category = trimCategoryForGroup(category, groupId);
return { category, label };
}
function wordifyKey(key: string): string {
return key
.replace(/\.([a-z])/g, (match, p1) => `.${p1.toUpperCase()}`)
.replace(/([a-z])([A-Z])/g, '$1 $2') // fooBar => foo Bar
.replace(/^[a-z]/g, match => match.toUpperCase()); // foo => Foo
}
function trimCategoryForGroup(category: string, groupId: string): string {
// const categoryWithoutSpaces = category.replace(/ /g, '');
const doTrim = forward => {
const parts = groupId.split('.');
while (parts.length) {
const reg = new RegExp(`^${parts.join('\\.')}(\\.|$)`, 'i');
if (reg.test(category)) {
return category.replace(reg, '');
}
// if (reg.test(categoryWithoutSpaces)) {
// return categoryWithoutSpaces.replace(reg, '');
// }
if (forward) {
parts.pop();
} else {
parts.shift();
}
}
return null;
};
let trimmed = doTrim(true);
if (trimmed === null) {
trimmed = doTrim(false);
}
if (trimmed === null) {
trimmed = category;
}
return trimmed;
}
export interface ISettingsEditorViewState {
settingsTarget: SettingsTarget;
showConfiguredOnly?: boolean;
}
export interface IDisposableTemplate {
interface IDisposableTemplate {
toDispose: IDisposable[];
}
export interface ISettingItemTemplate extends IDisposableTemplate {
interface ISettingItemTemplate extends IDisposableTemplate {
parent: HTMLElement;
context?: ISettingElement;
context?: SettingsTreeSettingElement;
containerElement: HTMLElement;
categoryElement: HTMLElement;
labelElement: HTMLElement;
@ -240,10 +378,9 @@ export interface ISettingItemTemplate extends IDisposableTemplate {
otherOverridesElement: HTMLElement;
}
export interface IGroupTitleTemplate extends IDisposableTemplate {
context?: IGroupElement;
interface IGroupTitleTemplate extends IDisposableTemplate {
context?: SettingsTreeGroupElement;
parent: HTMLElement;
labelElement: HTMLElement;
}
const SETTINGS_ELEMENT_TEMPLATE_ID = 'settings.entry.template';
@ -256,7 +393,7 @@ export interface ISettingChangeEvent {
export class SettingsRenderer implements IRenderer {
private static readonly SETTING_ROW_HEIGHT = 85;
private static readonly SETTING_ROW_HEIGHT = 82;
private readonly _onDidChangeSetting: Emitter<ISettingChangeEvent> = new Emitter<ISettingChangeEvent>();
public readonly onDidChangeSetting: Event<ISettingChangeEvent> = this._onDidChangeSetting.event;
@ -274,12 +411,12 @@ export class SettingsRenderer implements IRenderer {
this.measureContainer = DOM.append(_measureContainer, $('.setting-measure-container.monaco-tree-row'));
}
getHeight(tree: ITree, element: TreeElement): number {
if (element.type === TreeItemType.groupTitle) {
return 30;
getHeight(tree: ITree, element: SettingsTreeElement): number {
if (element instanceof SettingsTreeGroupElement) {
return 40 + (4 * element.level);
}
if (element.type === TreeItemType.setting) {
if (element instanceof SettingsTreeSettingElement) {
const isSelected = this.elementIsSelected(tree, element);
if (isSelected) {
return this.measureSettingElementHeight(tree, element);
@ -291,23 +428,23 @@ export class SettingsRenderer implements IRenderer {
return 0;
}
private measureSettingElementHeight(tree: ITree, element: ISettingElement): number {
private measureSettingElementHeight(tree: ITree, element: SettingsTreeSettingElement): number {
const measureHelper = DOM.append(this.measureContainer, $('.setting-measure-helper'));
const template = this.renderSettingTemplate(tree, measureHelper);
this.renderSettingElement(tree, element, template);
const height = measureHelper.offsetHeight;
this.measureContainer.removeChild(measureHelper);
this.measureContainer.removeChild(this.measureContainer.firstChild);
return Math.max(height, SettingsRenderer.SETTING_ROW_HEIGHT);
}
getTemplateId(tree: ITree, element: TreeElement): string {
if (element.type === TreeItemType.groupTitle) {
getTemplateId(tree: ITree, element: SettingsTreeElement): string {
if (element instanceof SettingsTreeGroupElement) {
return SETTINGS_GROUP_ELEMENT_TEMPLATE_ID;
}
if (element.type === TreeItemType.setting) {
if (element instanceof SettingsTreeSettingElement) {
return SETTINGS_ELEMENT_TEMPLATE_ID;
}
@ -329,12 +466,9 @@ export class SettingsRenderer implements IRenderer {
private renderGroupTitleTemplate(container: HTMLElement): IGroupTitleTemplate {
DOM.addClass(container, 'group-title');
const labelElement = DOM.append(container, $('h3.settings-group-title-label'));
const toDispose = [];
const template: IGroupTitleTemplate = {
parent: container,
labelElement,
toDispose
};
@ -380,24 +514,30 @@ export class SettingsRenderer implements IRenderer {
return template;
}
renderElement(tree: ITree, element: TreeElement, templateId: string, template: any): void {
renderElement(tree: ITree, element: SettingsTreeElement, templateId: string, template: any): void {
if (templateId === SETTINGS_ELEMENT_TEMPLATE_ID) {
return this.renderSettingElement(tree, <ISettingElement>element, template);
return this.renderSettingElement(tree, <SettingsTreeSettingElement>element, template);
}
if (templateId === SETTINGS_GROUP_ELEMENT_TEMPLATE_ID) {
(<IGroupTitleTemplate>template).labelElement.textContent = (<IGroupElement>element).group.title;
return;
return this.renderGroupElement(<SettingsTreeGroupElement>element, template);
}
}
private elementIsSelected(tree: ITree, element: TreeElement): boolean {
private renderGroupElement(element: SettingsTreeGroupElement, template: IGroupTitleTemplate): void {
template.parent.innerHTML = '';
const labelElement = DOM.append(template.parent, $('h3.settings-group-title-label'));
labelElement.classList.add(`settings-group-level-${element.level}`);
labelElement.textContent = (<SettingsTreeGroupElement>element).label;
}
private elementIsSelected(tree: ITree, element: SettingsTreeElement): boolean {
const selection = tree.getSelection();
const selectedElement: TreeElement = selection && selection[0];
const selectedElement: SettingsTreeElement = selection && selection[0];
return selectedElement && selectedElement.id === element.id;
}
private renderSettingElement(tree: ITree, element: ISettingElement, template: ISettingItemTemplate): void {
private renderSettingElement(tree: ITree, element: SettingsTreeSettingElement, template: ISettingItemTemplate): void {
const isSelected = !!this.elementIsSelected(tree, element);
const setting = element.setting;
@ -407,7 +547,7 @@ export class SettingsRenderer implements IRenderer {
template.containerElement.id = element.id;
const titleTooltip = setting.key;
template.categoryElement.textContent = element.displayCategory + ': ';
template.categoryElement.textContent = element.displayCategory && (element.displayCategory + ': ');
template.categoryElement.title = titleTooltip;
template.labelElement.textContent = element.displayLabel;
@ -415,7 +555,6 @@ export class SettingsRenderer implements IRenderer {
template.descriptionElement.textContent = element.description;
this.renderValue(element, isSelected, template);
const resetButton = new Button(template.valueElement);
const resetText = localize('resetButtonTitle', "reset");
resetButton.label = resetText;
@ -423,11 +562,11 @@ export class SettingsRenderer implements IRenderer {
resetButton.element.classList.add('setting-reset-button');
resetButton.element.tabIndex = isSelected ? 0 : -1;
attachButtonStyler(resetButton, this.themeService, {
template.toDispose.push(attachButtonStyler(resetButton, this.themeService, {
buttonBackground: Color.transparent.toString(),
buttonHoverBackground: Color.transparent.toString(),
buttonForeground: editorActiveLinkForeground
});
}));
template.toDispose.push(resetButton.onDidClick(e => {
this._onDidChangeSetting.fire({ key: element.setting.key, value: undefined });
@ -445,59 +584,65 @@ export class SettingsRenderer implements IRenderer {
}
}
private renderValue(element: ISettingElement, isSelected: boolean, template: ISettingItemTemplate): void {
private renderValue(element: SettingsTreeSettingElement, isSelected: boolean, template: ISettingItemTemplate): void {
const onChange = value => this._onDidChangeSetting.fire({ key: element.setting.key, value });
template.valueElement.innerHTML = '';
const valueControlElement = DOM.append(template.valueElement, $('.setting-item-control'));
if (element.enum && (element.valueType === 'string' || !element.valueType)) {
this.renderEnum(element, isSelected, template, onChange);
valueControlElement.classList.add('setting-type-enum');
this.renderEnum(element, isSelected, template, valueControlElement, onChange);
} else if (element.valueType === 'boolean') {
this.renderBool(element, isSelected, template, onChange);
valueControlElement.classList.add('setting-type-boolean');
this.renderBool(element, isSelected, template, valueControlElement, onChange);
} else if (element.valueType === 'string') {
this.renderText(element, isSelected, template, onChange);
valueControlElement.classList.add('setting-type-string');
this.renderText(element, isSelected, template, valueControlElement, onChange);
} else if (element.valueType === 'number' || element.valueType === 'integer') {
this.renderText(element, isSelected, template, value => onChange(parseInt(value)));
valueControlElement.classList.add('setting-type-number');
this.renderText(element, isSelected, template, valueControlElement, value => onChange(parseInt(value)));
} else {
this.renderEditInSettingsJson(element, isSelected, template);
valueControlElement.classList.add('setting-type-complex');
this.renderEditInSettingsJson(element, isSelected, template, valueControlElement);
}
}
private renderBool(element: ISettingElement, isSelected: boolean, template: ISettingItemTemplate, onChange: (value: boolean) => void): void {
const checkboxElement = <HTMLInputElement>DOM.append(template.valueElement, $('input.setting-value-checkbox.setting-value-input'));
private renderBool(dataElement: SettingsTreeSettingElement, isSelected: boolean, template: ISettingItemTemplate, element: HTMLElement, onChange: (value: boolean) => void): void {
const checkboxElement = <HTMLInputElement>DOM.append(element, $('input.setting-value-checkbox.setting-value-input'));
checkboxElement.type = 'checkbox';
checkboxElement.checked = element.value;
checkboxElement.checked = dataElement.value;
checkboxElement.tabIndex = isSelected ? 0 : -1;
template.toDispose.push(DOM.addDisposableListener(checkboxElement, 'change', e => onChange(checkboxElement.checked)));
}
private renderEnum(element: ISettingElement, isSelected: boolean, template: ISettingItemTemplate, onChange: (value: string) => void): void {
const idx = element.enum.indexOf(element.value);
const displayOptions = element.enum.map(escapeInvisibleChars);
private renderEnum(dataElement: SettingsTreeSettingElement, isSelected: boolean, template: ISettingItemTemplate, element: HTMLElement, onChange: (value: string) => void): void {
const idx = dataElement.enum.indexOf(dataElement.value);
const displayOptions = dataElement.enum.map(escapeInvisibleChars);
const selectBox = new SelectBox(displayOptions, idx, this.contextViewService);
template.toDispose.push(selectBox);
template.toDispose.push(attachSelectBoxStyler(selectBox, this.themeService));
selectBox.render(template.valueElement);
if (template.valueElement.firstElementChild) {
template.valueElement.firstElementChild.setAttribute('tabindex', isSelected ? '0' : '-1');
selectBox.render(element);
if (element.firstElementChild) {
element.firstElementChild.setAttribute('tabindex', isSelected ? '0' : '-1');
}
template.toDispose.push(
selectBox.onDidSelect(e => onChange(element.enum[e.index])));
selectBox.onDidSelect(e => onChange(dataElement.enum[e.index])));
}
private renderText(element: ISettingElement, isSelected: boolean, template: ISettingItemTemplate, onChange: (value: string) => void): void {
const inputBox = new InputBox(template.valueElement, this.contextViewService);
private renderText(dataElement: SettingsTreeSettingElement, isSelected: boolean, template: ISettingItemTemplate, element: HTMLElement, onChange: (value: string) => void): void {
const inputBox = new InputBox(element, this.contextViewService);
template.toDispose.push(attachInputBoxStyler(inputBox, this.themeService));
template.toDispose.push(inputBox);
inputBox.value = element.value;
inputBox.value = dataElement.value;
inputBox.inputElement.tabIndex = isSelected ? 0 : -1;
template.toDispose.push(
inputBox.onDidChange(e => onChange(e)));
}
private renderEditInSettingsJson(element: ISettingElement, isSelected: boolean, template: ISettingItemTemplate): void {
const openSettingsButton = new Button(template.valueElement, { title: true, buttonBackground: null, buttonHoverBackground: null });
private renderEditInSettingsJson(dataElement: SettingsTreeSettingElement, isSelected: boolean, template: ISettingItemTemplate, element: HTMLElement): void {
const openSettingsButton = new Button(element, { title: true, buttonBackground: null, buttonHoverBackground: null });
openSettingsButton.onDidClick(() => this._onDidOpenSettings.fire());
openSettingsButton.label = localize('editInSettingsJson', "Edit in settings.json");
openSettingsButton.element.classList.add('edit-in-settings-button');
@ -528,25 +673,29 @@ export class SettingsTreeFilter implements IFilter {
@IConfigurationService private configurationService: IConfigurationService
) { }
isVisible(tree: ITree, element: TreeElement): boolean {
if (this.viewState.showConfiguredOnly && element.type === TreeItemType.setting) {
isVisible(tree: ITree, element: SettingsTreeElement): boolean {
if (this.viewState.showConfiguredOnly && element instanceof SettingsTreeSettingElement) {
return element.isConfigured;
}
if (element.type === TreeItemType.groupTitle && this.viewState.showConfiguredOnly) {
return this.groupHasConfiguredSetting(element.group);
if (element instanceof SettingsTreeGroupElement && this.viewState.showConfiguredOnly) {
return this.groupHasConfiguredSetting(element);
}
return true;
}
private groupHasConfiguredSetting(group: ISettingsGroup): boolean {
for (let section of group.sections) {
for (let setting of section.settings) {
const { isConfigured } = inspectSetting(setting.key, this.viewState.settingsTarget, this.configurationService);
private groupHasConfiguredSetting(element: SettingsTreeGroupElement): boolean {
for (let child of element.children) {
if (child instanceof SettingsTreeSettingElement) {
const { isConfigured } = inspectSetting(child.setting.key, this.viewState.settingsTarget, this.configurationService);
if (isConfigured) {
return true;
}
} else {
if (child instanceof SettingsTreeGroupElement) {
return this.groupHasConfiguredSetting(child);
}
}
}
@ -563,17 +712,17 @@ export class SettingsTreeController extends WorkbenchTreeController {
}
export class SettingsAccessibilityProvider implements IAccessibilityProvider {
getAriaLabel(tree: ITree, element: TreeElement): string {
getAriaLabel(tree: ITree, element: SettingsTreeElement): string {
if (!element) {
return '';
}
if (element.type === TreeItemType.setting) {
if (element instanceof SettingsTreeSettingElement) {
return localize('settingRowAriaLabel', "{0} {1}, Setting", element.displayCategory, element.displayLabel);
}
if (element.type === TreeItemType.groupTitle) {
return localize('groupRowAriaLabel', "{0}, group", element.group.title);
if (element instanceof SettingsTreeGroupElement) {
return localize('groupRowAriaLabel', "{0}, group", element.label);
}
return '';
@ -625,7 +774,7 @@ export class SearchResultModel {
this.rawSearchResults[type] = result;
}
resultsAsGroup(): ISettingsGroup {
getFlatSettings(): ISetting[] {
const flatSettings: ISetting[] = [];
this.getUniqueResults()
.filter(r => !!r)
@ -634,14 +783,16 @@ export class SearchResultModel {
...r.filterMatches.map(m => m.setting));
});
return <ISettingsGroup>{
id: 'settingsSearchResultGroup',
range: null,
sections: [
{ settings: flatSettings }
],
title: 'searchResults',
titleRange: null
};
return flatSettings;
}
}
export class NonExpandableTree extends WorkbenchTree {
expand(): TPromise<any, any> {
return TPromise.wrap(null);
}
collapse(): TPromise<any, any> {
return TPromise.wrap(null);
}
}

View file

@ -0,0 +1,62 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as DOM from 'vs/base/browser/dom';
import { TPromise } from 'vs/base/common/winjs.base';
import { IDataSource, IRenderer, ITree } from 'vs/base/parts/tree/browser/tree';
import { SettingsTreeElement, SettingsTreeGroupElement } from 'vs/workbench/parts/preferences/browser/settingsTree';
const $ = DOM.$;
export class TOCDataSource implements IDataSource {
getId(tree: ITree, element: SettingsTreeGroupElement): string {
return element.id;
}
hasChildren(tree: ITree, element: SettingsTreeElement): boolean {
return element instanceof SettingsTreeGroupElement && element.children && element.children.every(child => child instanceof SettingsTreeGroupElement);
}
getChildren(tree: ITree, element: SettingsTreeGroupElement): TPromise<SettingsTreeElement[], any> {
return TPromise.as(<SettingsTreeElement[]>element.children);
}
getParent(tree: ITree, element: SettingsTreeElement): TPromise<any, any> {
return TPromise.wrap(element.parent);
}
shouldAutoexpand() {
return true;
}
}
const TOC_ENTRY_TEMPLATE_ID = 'settings.toc.entry';
interface ITOCEntryTemplate {
element: HTMLElement;
}
export class TOCRenderer implements IRenderer {
getHeight(tree: ITree, element: SettingsTreeElement): number {
return 22;
}
getTemplateId(tree: ITree, element: SettingsTreeElement): string {
return TOC_ENTRY_TEMPLATE_ID;
}
renderTemplate(tree: ITree, templateId: string, container: HTMLElement): ITOCEntryTemplate {
return {
element: DOM.append(container, $('.settings-toc-entry'))
};
}
renderElement(tree: ITree, element: SettingsTreeGroupElement, templateId: string, template: ITOCEntryTemplate): void {
template.element.textContent = element.label;
}
disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
}
}

View file

@ -38,4 +38,63 @@ suite('SettingsTree', () => {
label: 'Foo'
});
});
test('settingKeyToDisplayFormat - with category', () => {
assert.deepEqual(
settingKeyToDisplayFormat('foo.bar', 'foo'),
{
category: '',
label: 'Bar'
});
assert.deepEqual(
settingKeyToDisplayFormat('foo.bar.etc', 'foo'),
{
category: 'Bar',
label: 'Etc'
});
assert.deepEqual(
settingKeyToDisplayFormat('fooBar.etcSomething', 'foo'),
{
category: 'Foo Bar',
label: 'Etc Something'
});
assert.deepEqual(
settingKeyToDisplayFormat('foo.bar.etc', 'foo.bar'),
{
category: '',
label: 'Etc'
});
assert.deepEqual(
settingKeyToDisplayFormat('foo.bar.etc', 'something.foo'),
{
category: 'Bar',
label: 'Etc'
});
assert.deepEqual(
settingKeyToDisplayFormat('bar.etc', 'something.bar'),
{
category: '',
label: 'Etc'
});
assert.deepEqual(
settingKeyToDisplayFormat('fooBar.etc', 'fooBar'),
{
category: '',
label: 'Etc'
});
assert.deepEqual(
settingKeyToDisplayFormat('fooBar.somethingElse.etc', 'fooBar'),
{
category: 'Something Else',
label: 'Etc'
});
});
});

View file

@ -450,7 +450,7 @@ export class GotoSymbolHandler extends QuickOpenHandler {
// Add
results.push(new SymbolEntry(i,
label, icon, description, icon,
element.fullRange, null, this.editorService, this
element.range, null, this.editorService, this
));
}

View file

@ -134,7 +134,7 @@ export interface ProblemMatcher {
pattern: ProblemPattern | ProblemPattern[];
severity?: Severity;
watching?: WatchingMatcher;
fileSystemScheme?: string;
uriProvider?: (path: string) => URI;
}
export interface NamedProblemMatcher extends ProblemMatcher {
@ -196,8 +196,8 @@ export function getResource(filename: string, matcher: ProblemMatcher): URI {
if (fullPath[0] !== '/') {
fullPath = '/' + fullPath;
}
if (matcher.fileSystemScheme !== void 0) {
return URI.parse(`${matcher.fileSystemScheme}://${fullPath}`);
if (matcher.uriProvider !== void 0) {
return matcher.uriProvider(fullPath);
} else {
return URI.file(fullPath);
}

View file

@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import URI from 'vs/base/common/uri';
import Severity from 'vs/base/common/severity';
import { TPromise } from 'vs/base/common/winjs.base';
import { TerminateResponse } from 'vs/base/common/processes';
@ -103,9 +104,9 @@ export interface TaskTerminateResponse extends TerminateResponse {
}
export interface TaskSystemInfo {
fileSystemScheme: string;
platform: Platform;
context: any;
uriProvider: (this: void, path: string) => URI;
resolveVariables(workspaceFolder: IWorkspaceFolder, variables: Set<string>): TPromise<Map<string, string>>;
}

View file

@ -970,13 +970,13 @@ export class TerminalTaskSystem implements ITaskSystem {
}
let taskSystemInfo: TaskSystemInfo = resolver.taskSystemInfo;
let hasFilePrefix = matcher.filePrefix !== void 0;
let hasScheme = taskSystemInfo !== void 0 && taskSystemInfo.fileSystemScheme !== void 0 && taskSystemInfo.fileSystemScheme !== 'file';
if (!hasFilePrefix && !hasScheme) {
let hasUriProvider = taskSystemInfo !== void 0 && taskSystemInfo.uriProvider !== void 0;
if (!hasFilePrefix && !hasUriProvider) {
result.push(matcher);
} else {
let copy = Objects.deepClone(matcher);
if (hasScheme) {
copy.fileSystemScheme = taskSystemInfo.fileSystemScheme;
if (hasUriProvider) {
copy.uriProvider = taskSystemInfo.uriProvider;
}
if (hasFilePrefix) {
copy.filePrefix = this.resolveVariable(resolver, copy.filePrefix);

View file

@ -21,13 +21,12 @@ export class TerminalFindWidget extends SimpleFindWidget {
}
public find(previous: boolean) {
let val = this.inputValue;
let instance = this._terminalService.getActiveInstance();
const instance = this._terminalService.getActiveInstance();
if (instance !== null) {
if (previous) {
instance.findPrevious(val);
instance.findPrevious(this.inputValue);
} else {
instance.findNext(val);
instance.findNext(this.inputValue);
}
}
}

View file

@ -278,7 +278,7 @@ export class TerminalTab extends Disposable implements ITerminalTab {
// Adjust focus if the instance was active
if (wasActiveInstance && this._terminalInstances.length > 0) {
let newIndex = index < this._terminalInstances.length ? index : this._terminalInstances.length - 1;
const newIndex = index < this._terminalInstances.length ? index : this._terminalInstances.length - 1;
this.setActiveInstanceByIndex(newIndex);
// TODO: Only focus the new instance if the tab had focus?
this.activeInstance.focus(true);

View file

@ -7,7 +7,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
const WIDGET_HEIGHT = 29;
export class TerminalWidgetManager {
export class TerminalWidgetManager implements IDisposable {
private _container: HTMLElement;
private _xtermViewport: HTMLElement;
@ -24,6 +24,14 @@ export class TerminalWidgetManager {
this._initTerminalHeightWatcher(terminalWrapper);
}
public dispose(): void {
if (this._container) {
this._container.parentElement.removeChild(this._container);
this._container = null;
}
this._xtermViewport = null;
}
private _initTerminalHeightWatcher(terminalWrapper: HTMLElement) {
// Watch the xterm.js viewport for style changes and do a layout if it changes
this._xtermViewport = <HTMLElement>terminalWrapper.querySelector('.xterm-viewport');

View file

@ -37,6 +37,11 @@ export const IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY = 'terminal.integrated.isWor
export const NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY = 'terminal.integrated.neverSuggestSelectWindowsShell';
export const NEVER_MEASURE_RENDER_TIME_STORAGE_KEY = 'terminal.integrated.neverMeasureRenderTime';
// The creation of extension host terminals is delayed by this value (milliseconds). The purpose of
// this delay is to allow the terminal instance to initialize correctly and have its ID set before
// trying to create the corressponding object on the ext host.
export const EXT_HOST_CREATION_DELAY = 100;
export const ITerminalService = createDecorator<ITerminalService>(TERMINAL_SERVICE_ID);
export const TerminalCursorStyle = {
@ -159,6 +164,12 @@ export interface IShellLaunchConfig {
* of the terminal. Use \x1b over \033 or \e for the escape control character.
*/
initialText?: string;
/**
* When true the terminal will be created with no process. This is primarily used to give
* extensions full control over the terminal.
*/
isRendererOnly?: boolean;
}
export interface ITerminalService {
@ -171,9 +182,11 @@ export interface ITerminalService {
onInstanceCreated: Event<ITerminalInstance>;
onInstanceDisposed: Event<ITerminalInstance>;
onInstanceProcessIdReady: Event<ITerminalInstance>;
onInstanceDimensionsChanged: Event<ITerminalInstance>;
onInstanceRequestExtHostProcess: Event<ITerminalProcessExtHostRequest>;
onInstancesChanged: Event<void>;
onInstanceTitleChanged: Event<string>;
onActiveInstanceChanged: Event<ITerminalInstance>;
terminalInstances: ITerminalInstance[];
terminalTabs: ITerminalTab[];
@ -184,6 +197,12 @@ export interface ITerminalService {
* default shell selection dialog may display.
*/
createTerminal(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance;
/**
* Creates a terminal renderer.
* @param name The name of the terminal.
*/
createTerminalRenderer(name: string): ITerminalInstance;
/**
* Creates a raw terminal instance, this should not be used outside of the terminal part.
*/
@ -239,12 +258,27 @@ export interface ITerminalTab {
split(terminalFocusContextKey: IContextKey<boolean>, configHelper: ITerminalConfigHelper, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance;
}
export interface ITerminalDimensions {
/**
* The columns of the terminal.
*/
readonly cols: number;
/**
* The rows of the terminal.
*/
readonly rows: number;
}
export interface ITerminalInstance {
/**
* The ID of the terminal instance, this is an arbitrary number only used to identify the
* terminal instance.
*/
id: number;
readonly id: number;
readonly cols: number;
readonly rows: number;
/**
* The process ID of the shell process, this is undefined when there is no process associated
@ -268,6 +302,42 @@ export interface ITerminalInstance {
onRequestExtHostProcess: Event<ITerminalInstance>;
onDimensionsChanged: Event<void>;
onFocus: Event<ITerminalInstance>;
/**
* Attach a listener to the raw data stream coming from the pty, including ANSI escape
* sequences.
*/
onData: Event<string>;
/**
* Attach a listener to the "renderer" input event, this event fires for terminal renderers on
* keystrokes and when the Terminal.sendText extension API is used.
* @param listener The listener function.
*/
onRendererInput: Event<string>;
/**
* Attach a listener to listen for new lines added to this terminal instance.
*
* @param listener The listener function which takes new line strings added to the terminal,
* excluding ANSI escape sequences. The line event will fire when an LF character is added to
* the terminal (ie. the line is not wrapped). Note that this means that the line data will
* not fire for the last line, until either the line is ended with a LF character of the process
* is exited. The lineData string will contain the fully wrapped line, not containing any LF/CR
* characters.
*/
onLineData: Event<string>;
/**
* Attach a listener that fires when the terminal's pty process exits. The number in the event
* is the processes' exit code, an exit code of null means the process was killed as a result of
* the ITerminalInstance being disposed.
*/
onExit: Event<number>;
processReady: TPromise<void>;
/**
@ -294,7 +364,7 @@ export interface ITerminalInstance {
/**
* The shell launch config used to launch the shell.
*/
shellLaunchConfig: IShellLaunchConfig;
readonly shellLaunchConfig: IShellLaunchConfig;
/**
* Whether to disable layout for the terminal. This is useful when the size of the terminal is
@ -397,6 +467,12 @@ export interface ITerminalInstance {
*/
sendText(text: string, addNewLine: boolean): void;
/**
* Write text directly to the terminal, skipping the process if it exists.
* @param text The text to write.
*/
write(text: string): void;
/** Scroll the terminal buffer down 1 line. */
scrollDownLine(): void;
/** Scroll the terminal buffer down 1 page. */
@ -448,33 +524,6 @@ export interface ITerminalInstance {
*/
setVisible(visible: boolean): void;
/**
* Attach a listener to the raw data stream coming from the pty, including ANSI escape
* sequecnes.
* @param listener The listener function.
*/
onData(listener: (data: string) => void): IDisposable;
/**
* Attach a listener to listen for new lines added to this terminal instance.
*
* @param listener The listener function which takes new line strings added to the terminal,
* excluding ANSI escape sequences. The line event will fire when an LF character is added to
* the terminal (ie. the line is not wrapped). Note that this means that the line data will
* not fire for the last line, until either the line is ended with a LF character of the process
* is exited. The lineData string will contain the fully wrapped line, not containing any LF/CR
* characters.
*/
onLineData(listener: (lineData: string) => void): IDisposable;
/**
* Attach a listener that fires when the terminal's pty process exits.
*
* @param listener The listener function which takes the processes' exit code, an exit code of
* null means the process was killed as a result of the ITerminalInstance being disposed.
*/
onExit(listener: (exitCode: number) => void): IDisposable;
/**
* Immediately kills the terminal's current pty process and launches a new one to replace it.
*
@ -487,6 +536,8 @@ export interface ITerminalInstance {
*/
setTitle(title: string, eventFromProcess: boolean): void;
setDimensions(dimensions: ITerminalDimensions): void;
addDisposable(disposable: IDisposable): void;
}

View file

@ -160,9 +160,9 @@ const ansiColorMap = {
};
export function registerColors(): void {
for (let id in ansiColorMap) {
let entry = ansiColorMap[id];
let colorName = id.substring(13);
for (const id in ansiColorMap) {
const entry = ansiColorMap[id];
const colorName = id.substring(13);
ansiColorIdentifiers[entry.index] = registerColor(id, entry.defaults, nls.localize('terminal.ansiColor', '\'{0}\' ANSI color in the terminal.', colorName));
}
}

View file

@ -41,10 +41,14 @@ export abstract class TerminalService implements ITerminalService {
public get onInstanceProcessIdReady(): Event<ITerminalInstance> { return this._onInstanceProcessIdReady.event; }
protected readonly _onInstanceRequestExtHostProcess: Emitter<ITerminalProcessExtHostRequest> = new Emitter<ITerminalProcessExtHostRequest>();
public get onInstanceRequestExtHostProcess(): Event<ITerminalProcessExtHostRequest> { return this._onInstanceRequestExtHostProcess.event; }
protected readonly _onInstanceDimensionsChanged: Emitter<ITerminalInstance> = new Emitter<ITerminalInstance>();
public get onInstanceDimensionsChanged(): Event<ITerminalInstance> { return this._onInstanceDimensionsChanged.event; }
protected readonly _onInstancesChanged: Emitter<void> = new Emitter<void>();
public get onInstancesChanged(): Event<void> { return this._onInstancesChanged.event; }
protected readonly _onInstanceTitleChanged: Emitter<string> = new Emitter<string>();
public get onInstanceTitleChanged(): Event<string> { return this._onInstanceTitleChanged.event; }
protected readonly _onActiveInstanceChanged: Emitter<ITerminalInstance> = new Emitter<ITerminalInstance>();
public get onActiveInstanceChanged(): Event<ITerminalInstance> { return this._onActiveInstanceChanged.event; }
protected readonly _onTabDisposed: Emitter<ITerminalTab> = new Emitter<ITerminalTab>();
public get onTabDisposed(): Event<ITerminalTab> { return this._onTabDisposed.event; }
@ -71,6 +75,7 @@ export abstract class TerminalService implements ITerminalService {
protected abstract _showTerminalCloseConfirmation(): TPromise<boolean>;
public abstract createTerminal(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance;
public abstract createTerminalRenderer(name: string): ITerminalInstance;
public abstract createInstance(terminalFocusContextKey: IContextKey<boolean>, configHelper: ITerminalConfigHelper, container: HTMLElement, shellLaunchConfig: IShellLaunchConfig, doCreateProcess: boolean): ITerminalInstance;
public abstract getActiveOrCreateInstance(wasNewTerminalAction?: boolean): ITerminalInstance;
public abstract selectDefaultWindowsShell(): TPromise<string>;
@ -152,7 +157,7 @@ export abstract class TerminalService implements ITerminalService {
if (wasActiveTab && this._terminalTabs.length > 0) {
// TODO: Only focus the new tab if the removed tab had focus?
// const hasFocusOnExit = tab.activeInstance.hadFocusOnExit;
let newIndex = index < this._terminalTabs.length ? index : this._terminalTabs.length - 1;
const newIndex = index < this._terminalTabs.length ? index : this._terminalTabs.length - 1;
this.setActiveTabByIndex(newIndex);
this.getActiveInstance().focus(true);
}
@ -162,6 +167,7 @@ export abstract class TerminalService implements ITerminalService {
// launch.
if (this._terminalTabs.length === 0 && !this._isShuttingDown) {
this.hidePanel();
this._onActiveInstanceChanged.fire(undefined);
}
// Fire events
@ -287,6 +293,8 @@ export abstract class TerminalService implements ITerminalService {
instance.addDisposable(instance.onDisposed(this._onInstanceDisposed.fire, this._onInstanceDisposed));
instance.addDisposable(instance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged));
instance.addDisposable(instance.onProcessIdReady(this._onInstanceProcessIdReady.fire, this._onInstanceProcessIdReady));
instance.addDisposable(instance.onDimensionsChanged(() => this._onInstanceDimensionsChanged.fire(instance)));
instance.addDisposable(instance.onFocus(this._onActiveInstanceChanged.fire, this._onActiveInstanceChanged));
}
private _getTabForInstance(instance: ITerminalInstance): ITerminalTab {
@ -301,7 +309,7 @@ export abstract class TerminalService implements ITerminalService {
public showPanel(focus?: boolean): TPromise<void> {
return new TPromise<void>((complete) => {
let panel = this._panelService.getActivePanel();
const panel = this._panelService.getActivePanel();
if (!panel || panel.getId() !== TERMINAL_PANEL_ID) {
return this._panelService.openPanel(TERMINAL_PANEL_ID, focus).then(() => {
if (focus) {

View file

@ -66,7 +66,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenTermAction, Q
const actionBarRegistry = Registry.as<IActionBarRegistry>(ActionBarExtensions.Actionbar);
actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionTermContributor);
let configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
configurationRegistry.registerConfiguration({
'id': 'terminal',
'order': 100,
@ -368,7 +368,7 @@ registerSingleton(ITerminalService, TerminalService);
// On mac cmd+` is reserved to cycle between windows, that's why the keybindings use WinCtrl
const category = nls.localize('terminalCategory', "Terminal");
let actionRegistry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
const actionRegistry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(KillTerminalAction, KillTerminalAction.ID, KillTerminalAction.LABEL), 'Terminal: Kill the Active Terminal Instance', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CopyTerminalSelectionAction, CopyTerminalSelectionAction.ID, CopyTerminalSelectionAction.LABEL, {
primary: KeyMod.CtrlCmd | KeyCode.KEY_C,

View file

@ -71,9 +71,10 @@ export class KillTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
if (terminalInstance) {
this.terminalService.getActiveInstance().dispose();
console.log('kill');
const instance = this.terminalService.getActiveInstance();
if (instance) {
instance.dispose();
if (this.terminalService.terminalInstances.length > 0) {
this.terminalService.showPanel(true);
}
@ -121,7 +122,7 @@ export class CopyTerminalSelectionAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
const terminalInstance = this.terminalService.getActiveInstance();
if (terminalInstance) {
terminalInstance.copySelection();
}
@ -142,7 +143,7 @@ export class SelectAllTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
const terminalInstance = this.terminalService.getActiveInstance();
if (terminalInstance) {
terminalInstance.selectAll();
}
@ -161,7 +162,7 @@ export abstract class BaseSendTextTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this._terminalService.getActiveInstance();
const terminalInstance = this._terminalService.getActiveInstance();
if (terminalInstance) {
terminalInstance.sendText(this._text, false);
}
@ -598,7 +599,7 @@ export class RunSelectedTextInTerminalAction extends Action {
if (selection.isEmpty()) {
text = editor.getModel().getLineContent(selection.selectionStartLineNumber).trim();
} else {
let endOfLinePreference = os.EOL === '\n' ? EndOfLinePreference.LF : EndOfLinePreference.CRLF;
const endOfLinePreference = os.EOL === '\n' ? EndOfLinePreference.LF : EndOfLinePreference.CRLF;
text = editor.getModel().getValueInRange(selection, endOfLinePreference);
}
instance.sendText(text, true);
@ -695,7 +696,7 @@ export class ScrollDownTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
const terminalInstance = this.terminalService.getActiveInstance();
if (terminalInstance) {
terminalInstance.scrollDownLine();
}
@ -716,7 +717,7 @@ export class ScrollDownPageTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
const terminalInstance = this.terminalService.getActiveInstance();
if (terminalInstance) {
terminalInstance.scrollDownPage();
}
@ -737,7 +738,7 @@ export class ScrollToBottomTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
const terminalInstance = this.terminalService.getActiveInstance();
if (terminalInstance) {
terminalInstance.scrollToBottom();
}
@ -758,7 +759,7 @@ export class ScrollUpTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
const terminalInstance = this.terminalService.getActiveInstance();
if (terminalInstance) {
terminalInstance.scrollUpLine();
}
@ -779,7 +780,7 @@ export class ScrollUpPageTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
const terminalInstance = this.terminalService.getActiveInstance();
if (terminalInstance) {
terminalInstance.scrollUpPage();
}
@ -800,7 +801,7 @@ export class ScrollToTopTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
const terminalInstance = this.terminalService.getActiveInstance();
if (terminalInstance) {
terminalInstance.scrollToTop();
}
@ -821,7 +822,7 @@ export class ClearTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
const terminalInstance = this.terminalService.getActiveInstance();
if (terminalInstance) {
terminalInstance.clear();
}
@ -842,7 +843,7 @@ export class ClearSelectionTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
const terminalInstance = this.terminalService.getActiveInstance();
if (terminalInstance && terminalInstance.hasSelection()) {
terminalInstance.clearSelection();
}
@ -959,7 +960,7 @@ export class QuickOpenActionTermContributor extends ActionBarContributor {
}
public getActions(context: any): IAction[] {
let actions: Action[] = [];
const actions: Action[] = [];
if (context.element instanceof TerminalEntry) {
actions.push(this.instantiationService.createInstance(RenameTerminalQuickOpenAction, RenameTerminalQuickOpenAction.ID, RenameTerminalQuickOpenAction.LABEL, context.element));
actions.push(this.instantiationService.createInstance(QuickKillTerminalAction, QuickKillTerminalAction.ID, QuickKillTerminalAction.LABEL, context.element));

View file

@ -50,12 +50,12 @@ export class TerminalConfigHelper implements ITerminalConfigHelper {
public configFontIsMonospace(): boolean {
this._createCharMeasureElementIfNecessary();
let fontSize = 15;
let fontFamily = this.config.fontFamily || this._configurationService.getValue<IEditorOptions>('editor').fontFamily;
let i_rect = this._getBoundingRectFor('i', fontFamily, fontSize);
let w_rect = this._getBoundingRectFor('w', fontFamily, fontSize);
const fontSize = 15;
const fontFamily = this.config.fontFamily || this._configurationService.getValue<IEditorOptions>('editor').fontFamily;
const i_rect = this._getBoundingRectFor('i', fontFamily, fontSize);
const w_rect = this._getBoundingRectFor('w', fontFamily, fontSize);
let invalidBounds = !i_rect.width || !w_rect.width;
const invalidBounds = !i_rect.width || !w_rect.width;
if (invalidBounds) {
// There is no reason to believe the font is not Monospace.
return true;
@ -88,7 +88,7 @@ export class TerminalConfigHelper implements ITerminalConfigHelper {
private _measureFont(fontFamily: string, fontSize: number, letterSpacing: number, lineHeight: number): ITerminalFont {
this._createCharMeasureElementIfNecessary();
let rect = this._getBoundingRectFor('X', fontFamily, fontSize);
const rect = this._getBoundingRectFor('X', fontFamily, fontSize);
// Bounding client rect was invalid, use last font measurement if available.
if (this._lastFontMeasurement && !rect.width && !rect.height) {
@ -122,7 +122,7 @@ export class TerminalConfigHelper implements ITerminalConfigHelper {
}
}
let fontSize = this._toInteger(this.config.fontSize, MINIMUM_FONT_SIZE, MAXIMUM_FONT_SIZE, EDITOR_FONT_DEFAULTS.fontSize);
const fontSize = this._toInteger(this.config.fontSize, MINIMUM_FONT_SIZE, MAXIMUM_FONT_SIZE, EDITOR_FONT_DEFAULTS.fontSize);
const letterSpacing = this.config.letterSpacing ? Math.max(Math.floor(this.config.letterSpacing), MINIMUM_LETTER_SPACING) : DEFAULT_LETTER_SPACING;
const lineHeight = this.config.lineHeight ? Math.max(this.config.lineHeight, 1) : DEFAULT_LINE_HEIGHT;

View file

@ -14,7 +14,7 @@ import { Terminal as XTermTerminal } from 'vscode-xterm';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { ITerminalInstance, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, IShellLaunchConfig, ITerminalProcessManager, ProcessState, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY } from 'vs/workbench/parts/terminal/common/terminal';
import { ITerminalInstance, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, IShellLaunchConfig, ITerminalProcessManager, ProcessState, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ITerminalDimensions } from 'vs/workbench/parts/terminal/common/terminal';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { TabFocus } from 'vs/editor/common/config/commonEditorConfig';
@ -63,8 +63,8 @@ export class TerminalInstance implements ITerminalInstance {
private _terminalHasTextContextKey: IContextKey<boolean>;
private _cols: number;
private _rows: number;
private _dimensionsOverride: ITerminalDimensions;
private _windowsShellHelper: WindowsShellHelper;
private _onLineDataListeners: ((lineData: string) => void)[];
private _xtermReadyPromise: TPromise<void>;
private _disposables: lifecycle.IDisposable[];
@ -76,6 +76,8 @@ export class TerminalInstance implements ITerminalInstance {
public disableLayout: boolean;
public get id(): number { return this._id; }
public get cols(): number { return this._cols; }
public get rows(): number { return this._rows; }
// TODO: Ideally processId would be merged into processReady
public get processId(): number | undefined { return this._processManager ? this._processManager.shellProcessId : undefined; }
// TODO: How does this work with detached processes?
@ -84,10 +86,11 @@ export class TerminalInstance implements ITerminalInstance {
public get title(): string { return this._title; }
public get hadFocusOnExit(): boolean { return this._hadFocusOnExit; }
public get isTitleSetByProcess(): boolean { return !!this._messageTitleDisposable; }
public get shellLaunchConfig(): IShellLaunchConfig { return Object.freeze(this._shellLaunchConfig); }
public get shellLaunchConfig(): IShellLaunchConfig { return this._shellLaunchConfig; }
public get commandTracker(): TerminalCommandTracker { return this._commandTracker; }
private readonly _onExit: Emitter<number> = new Emitter<number>();
public get onExit(): Event<number> { return this._onExit.event; }
private readonly _onDisposed: Emitter<ITerminalInstance> = new Emitter<ITerminalInstance>();
public get onDisposed(): Event<ITerminalInstance> { return this._onDisposed.event; }
private readonly _onFocused: Emitter<ITerminalInstance> = new Emitter<ITerminalInstance>();
@ -96,15 +99,24 @@ export class TerminalInstance implements ITerminalInstance {
public get onProcessIdReady(): Event<ITerminalInstance> { return this._onProcessIdReady.event; }
private readonly _onTitleChanged: Emitter<string> = new Emitter<string>();
public get onTitleChanged(): Event<string> { return this._onTitleChanged.event; }
private readonly _onData: Emitter<string> = new Emitter<string>();
public get onData(): Event<string> { return this._onData.event; }
private readonly _onLineData: Emitter<string> = new Emitter<string>();
public get onLineData(): Event<string> { return this._onLineData.event; }
private readonly _onRendererInput: Emitter<string> = new Emitter<string>();
public get onRendererInput(): Event<string> { return this._onRendererInput.event; }
private readonly _onRequestExtHostProcess: Emitter<ITerminalInstance> = new Emitter<ITerminalInstance>();
public get onRequestExtHostProcess(): Event<ITerminalInstance> { return this._onRequestExtHostProcess.event; }
private readonly _onDimensionsChanged: Emitter<void> = new Emitter<void>();
public get onDimensionsChanged(): Event<void> { return this._onDimensionsChanged.event; }
private readonly _onFocus: Emitter<ITerminalInstance> = new Emitter<ITerminalInstance>();
public get onFocus(): Event<ITerminalInstance> { return this._onFocus.event; }
public constructor(
private _terminalFocusContextKey: IContextKey<boolean>,
private _configHelper: TerminalConfigHelper,
private readonly _terminalFocusContextKey: IContextKey<boolean>,
private readonly _configHelper: TerminalConfigHelper,
private _container: HTMLElement,
private _shellLaunchConfig: IShellLaunchConfig,
doCreateProcess: boolean,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IKeybindingService private readonly _keybindingService: IKeybindingService,
@INotificationService private readonly _notificationService: INotificationService,
@ -118,7 +130,6 @@ export class TerminalInstance implements ITerminalInstance {
) {
this._disposables = [];
this._skipTerminalCommands = [];
this._onLineDataListeners = [];
this._isExiting = false;
this._hadFocusOnExit = false;
this._isVisible = false;
@ -130,8 +141,10 @@ export class TerminalInstance implements ITerminalInstance {
this._logService.trace(`terminalInstance#ctor (id: ${this.id})`, this._shellLaunchConfig);
this._initDimensions();
if (doCreateProcess) {
if (!this.shellLaunchConfig.isRendererOnly) {
this._createProcess();
} else {
this.setTitle(this._shellLaunchConfig.name, false);
}
this._xtermReadyPromise = this._createXterm();
@ -142,14 +155,14 @@ export class TerminalInstance implements ITerminalInstance {
}
});
this._configurationService.onDidChangeConfiguration(e => {
this.addDisposable(this._configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('terminal.integrated')) {
this.updateConfig();
}
if (e.affectsConfiguration('editor.accessibilitySupport')) {
this.updateAccessibilitySupport();
}
});
}));
}
public addDisposable(disposable: lifecycle.IDisposable): void {
@ -286,6 +299,13 @@ export class TerminalInstance implements ITerminalInstance {
// TODO: How does the cwd work on detached processes?
this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, platform.platform, this._processManager.initialCwd);
}
this._xterm.on('focus', () => this._onFocus.fire(this));
// Register listener to trigger the onInput ext API if the terminal is a renderer only
if (this._shellLaunchConfig.isRendererOnly) {
this._xterm.on('data', (data) => this._sendRendererInput(data));
}
this._commandTracker = new TerminalCommandTracker(this._xterm);
this._disposables.push(this._themeService.onThemeChange(theme => this._updateTheme(theme)));
}
@ -439,7 +459,7 @@ export class TerminalInstance implements ITerminalInstance {
}
private _measureRenderTime(): void {
let frameTimes: number[] = [];
const frameTimes: number[] = [];
const textRenderLayer = (<any>this._xterm).renderer._renderLayers[0];
const originalOnGridChanged = textRenderLayer.onGridChanged;
@ -538,18 +558,21 @@ export class TerminalInstance implements ITerminalInstance {
public dispose(): void {
this._logService.trace(`terminalInstance#dispose (id: ${this.id})`);
if (this._windowsShellHelper) {
this._windowsShellHelper.dispose();
}
if (this._linkHandler) {
this._linkHandler.dispose();
}
this._windowsShellHelper = lifecycle.dispose(this._windowsShellHelper);
this._linkHandler = lifecycle.dispose(this._linkHandler);
this._commandTracker = lifecycle.dispose(this._commandTracker);
this._widgetManager = lifecycle.dispose(this._widgetManager);
if (this._xterm && this._xterm.element) {
this._hadFocusOnExit = dom.hasClass(this._xterm.element, 'focus');
}
if (this._wrapperElement) {
if ((<any>this._wrapperElement).xterm) {
(<any>this._wrapperElement).xterm = null;
}
this._container.removeChild(this._wrapperElement);
this._wrapperElement = null;
this._xtermElement = null;
}
if (this._xterm) {
const buffer = (<any>this._xterm.buffer);
@ -557,9 +580,7 @@ export class TerminalInstance implements ITerminalInstance {
this._xterm.dispose();
this._xterm = null;
}
if (this._processManager) {
this._processManager.dispose();
}
this._processManager = lifecycle.dispose(this._processManager);
if (!this._isDisposed) {
this._isDisposed = true;
this._onDisposed.fire(this);
@ -584,17 +605,39 @@ export class TerminalInstance implements ITerminalInstance {
document.execCommand('paste');
}
public sendText(text: string, addNewLine: boolean): void {
this._processManager.ptyProcessReady.then(() => {
// Normalize line endings to 'enter' press.
text = text.replace(TerminalInstance.EOL_REGEX, '\r');
if (addNewLine && text.substr(text.length - 1) !== '\r') {
text += '\r';
public write(text: string): void {
this._xtermReadyPromise.then(() => {
if (!this._xterm) {
return;
}
this._xterm.write(text);
if (this._shellLaunchConfig.isRendererOnly) {
// Fire onData API in the extension host
this._onData.fire(text);
}
this._processManager.write(text);
});
}
public sendText(text: string, addNewLine: boolean): void {
// Normalize line endings to 'enter' press.
text = text.replace(TerminalInstance.EOL_REGEX, '\r');
if (addNewLine && text.substr(text.length - 1) !== '\r') {
text += '\r';
}
if (this._shellLaunchConfig.isRendererOnly) {
// If the terminal is a renderer only, fire the onInput ext API
this._sendRendererInput(text);
} else {
// If the terminal has a process, send it to the process
if (this._processManager) {
this._processManager.ptyProcessReady.then(() => {
this._processManager.write(text);
});
}
}
}
public setVisible(visible: boolean): void {
this._isVisible = visible;
if (this._wrapperElement) {
@ -660,6 +703,8 @@ export class TerminalInstance implements ITerminalInstance {
this._processManager.onProcessExit(exitCode => this._onProcessExit(exitCode));
this._processManager.createProcess(this._shellLaunchConfig, this._cols, this._rows);
this._processManager.onProcessData(data => this._onData.fire(data));
if (this._shellLaunchConfig.name) {
this.setTitle(this._shellLaunchConfig.name, false);
} else {
@ -751,6 +796,8 @@ export class TerminalInstance implements ITerminalInstance {
}
}
}
this._onExit.fire(exitCode);
}
private _attachPressAnyKeyToCloseListener() {
@ -791,26 +838,16 @@ export class TerminalInstance implements ITerminalInstance {
this._shellLaunchConfig = shell;
}
public onData(listener: (data: string) => void): lifecycle.IDisposable {
return this._processManager.onProcessData(data => listener(data));
}
private _sendRendererInput(input: string): void {
if (this._processManager) {
throw new Error('onRendererInput attempted to be used on a regular terminal');
}
public onLineData(listener: (lineData: string) => void): lifecycle.IDisposable {
this._onLineDataListeners.push(listener);
return {
dispose: () => {
const i = this._onLineDataListeners.indexOf(listener);
if (i >= 0) {
this._onLineDataListeners.splice(i, 1);
}
}
};
// For terminal renderers onData fires on keystrokes and when sendText is called.
this._onRendererInput.fire(input);
}
private _onLineFeed(): void {
if (this._onLineDataListeners.length === 0) {
return;
}
const buffer = (<any>this._xterm.buffer);
const newLine = buffer.lines.get(buffer.ybase + buffer.y);
if (!newLine.isWrapped) {
@ -823,17 +860,7 @@ export class TerminalInstance implements ITerminalInstance {
while (lineIndex >= 0 && buffer.lines.get(lineIndex--).isWrapped) {
lineData = buffer.translateBufferLineToString(lineIndex, false) + lineData;
}
this._onLineDataListeners.forEach(listener => {
try {
listener(lineData);
} catch (err) {
console.error(`onLineData listener threw`, err);
}
});
}
public onExit(listener: (exitCode: number) => void): lifecycle.IDisposable {
return this._processManager.onProcessExit(listener);
this._onLineData.fire(lineData);
}
public updateConfig(): void {
@ -905,6 +932,21 @@ export class TerminalInstance implements ITerminalInstance {
return;
}
if (this._xterm) {
this._xterm.element.style.width = terminalWidth + 'px';
}
this._resize();
}
private _resize(): void {
let cols = this._cols;
let rows = this._rows;
if (this._dimensionsOverride && this._dimensionsOverride.cols && this._dimensionsOverride.rows) {
cols = Math.min(Math.max(this._dimensionsOverride.cols, 2), this._cols);
rows = Math.min(Math.max(this._dimensionsOverride.rows, 2), this._rows);
}
if (this._xterm) {
const font = this._configHelper.getFont(this._xterm);
@ -921,8 +963,11 @@ export class TerminalInstance implements ITerminalInstance {
this._safeSetOption('drawBoldTextInBrightColors', config.drawBoldTextInBrightColors);
}
this._xterm.resize(this._cols, this._rows);
this._xterm.element.style.width = terminalWidth + 'px';
if (cols !== this._xterm.getOption('cols') || rows !== this._xterm.getOption('rows')) {
this._onDimensionsChanged.fire();
}
this._xterm.resize(cols, rows);
if (this._isVisible) {
// Force the renderer to unpause by simulating an IntersectionObserver event. This
// is to fix an issue where dragging the window to the top of the screen to maximize
@ -935,7 +980,7 @@ export class TerminalInstance implements ITerminalInstance {
}
if (this._processManager) {
this._processManager.ptyProcessReady.then(() => this._processManager.setDimensions(this._cols, this._rows));
this._processManager.ptyProcessReady.then(() => this._processManager.setDimensions(cols, rows));
}
}
@ -964,6 +1009,11 @@ export class TerminalInstance implements ITerminalInstance {
}
}
public setDimensions(dimensions: ITerminalDimensions): void {
this._dimensionsOverride = dimensions;
this._resize();
}
private _getXtermTheme(theme?: ITheme): any {
if (!theme) {
theme = this._themeService.getTheme();

View file

@ -121,6 +121,7 @@ export class TerminalLinkHandler {
}
public dispose(): void {
this._xterm = null;
this._hoverDisposables = dispose(this._hoverDisposables);
this._mouseMoveDisposable = dispose(this._mouseMoveDisposable);
}
@ -172,7 +173,7 @@ export class TerminalLinkHandler {
}
private _handleHypertextLink(url: string): void {
let uri = Uri.parse(url);
const uri = Uri.parse(url);
this._openerService.open(uri);
}

View file

@ -77,7 +77,7 @@ export class TerminalPanel extends Panel {
}
if (e.affectsConfiguration('terminal.integrated.fontFamily') || e.affectsConfiguration('editor.fontFamily')) {
let configHelper = this._terminalService.configHelper;
const configHelper = this._terminalService.configHelper;
if (configHelper instanceof TerminalConfigHelper) {
if (!configHelper.configFontIsMonospace()) {
const choices: IPromptChoice[] = [{
@ -203,7 +203,7 @@ export class TerminalPanel extends Panel {
this._terminalService.getActiveInstance().focus();
} else if (event.which === 3) {
if (this._terminalService.configHelper.config.rightClickBehavior === 'copyPaste') {
let terminal = this._terminalService.getActiveInstance();
const terminal = this._terminalService.getActiveInstance();
if (terminal.hasSelection()) {
terminal.copySelection();
terminal.clearSelection();
@ -230,7 +230,7 @@ export class TerminalPanel extends Panel {
}
if (event.which === 1) {
let terminal = this._terminalService.getActiveInstance();
const terminal = this._terminalService.getActiveInstance();
if (terminal.hasSelection()) {
terminal.copySelection();
}
@ -240,7 +240,7 @@ export class TerminalPanel extends Panel {
this._register(dom.addDisposableListener(this._parentDomElement, 'contextmenu', (event: MouseEvent) => {
if (!this._cancelContextMenu) {
const standardEvent = new StandardMouseEvent(event);
let anchor: { x: number, y: number } = { x: standardEvent.posx, y: standardEvent.posy };
const anchor: { x: number, y: number } = { x: standardEvent.posx, y: standardEvent.posy };
this._contextMenuService.showContextMenu({
getAnchor: () => anchor,
getActions: () => TPromise.as(this._getContextMenuActions()),
@ -269,7 +269,7 @@ export class TerminalPanel extends Panel {
// Check if files were dragged from the tree explorer
let path: string;
let resources = e.dataTransfer.getData(DataTransfers.RESOURCES);
const resources = e.dataTransfer.getData(DataTransfers.RESOURCES);
if (resources) {
path = URI.parse(JSON.parse(resources)[0]).path;
} else if (e.dataTransfer.files.length > 0) {
@ -315,15 +315,15 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
}
// Borrow the editor's hover background for now
let hoverBackground = theme.getColor(editorHoverBackground);
const hoverBackground = theme.getColor(editorHoverBackground);
if (hoverBackground) {
collector.addRule(`.monaco-workbench .panel.integrated-terminal .terminal-message-widget { background-color: ${hoverBackground}; }`);
}
let hoverBorder = theme.getColor(editorHoverBorder);
const hoverBorder = theme.getColor(editorHoverBorder);
if (hoverBorder) {
collector.addRule(`.monaco-workbench .panel.integrated-terminal .terminal-message-widget { border: 1px solid ${hoverBorder}; }`);
}
let hoverForeground = theme.getColor(editorForeground);
const hoverForeground = theme.getColor(editorForeground);
if (hoverForeground) {
collector.addRule(`.monaco-workbench .panel.integrated-terminal .terminal-message-widget { color: ${hoverForeground}; }`);
}

View file

@ -91,8 +91,12 @@ export class TerminalService extends AbstractTerminalService implements ITermina
return instance;
}
public createTerminalRenderer(name: string): ITerminalInstance {
return this.createTerminal({ name, isRendererOnly: true });
}
public createInstance(terminalFocusContextKey: IContextKey<boolean>, configHelper: ITerminalConfigHelper, container: HTMLElement, shellLaunchConfig: IShellLaunchConfig, doCreateProcess: boolean): ITerminalInstance {
const instance = this._instantiationService.createInstance(TerminalInstance, terminalFocusContextKey, configHelper, container, shellLaunchConfig, true);
const instance = this._instantiationService.createInstance(TerminalInstance, terminalFocusContextKey, configHelper, container, shellLaunchConfig);
this._onInstanceCreated.fire(instance);
return instance;
}
@ -109,7 +113,7 @@ export class TerminalService extends AbstractTerminalService implements ITermina
public focusFindWidget(): TPromise<void> {
return this.showPanel(false).then(() => {
let panel = this._panelService.getActivePanel() as TerminalPanel;
const panel = this._panelService.getActivePanel() as TerminalPanel;
panel.focusFindWidget();
this._findWidgetVisible.set(true);
});

View file

@ -5,6 +5,7 @@
import { Terminal, IMarker } from 'vscode-xterm';
import { ITerminalCommandTracker } from 'vs/workbench/parts/terminal/common/terminal';
import { IDisposable } from 'vs/base/common/lifecycle';
/**
* The minimize size of the prompt in which to assume the line is a command.
@ -21,7 +22,7 @@ export enum ScrollPosition {
Middle
}
export class TerminalCommandTracker implements ITerminalCommandTracker {
export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposable {
private _currentMarker: IMarker | Boundary = Boundary.Bottom;
private _selectionStart: IMarker | Boundary | null = null;
private _isDisposable: boolean = false;
@ -32,6 +33,10 @@ export class TerminalCommandTracker implements ITerminalCommandTracker {
this._xterm.on('key', key => this._onKey(key));
}
public dispose(): void {
this._xterm = null;
}
private _onKey(key: string): void {
if (key === '\x0d') {
this._onEnter();
@ -194,7 +199,7 @@ export class TerminalCommandTracker implements ITerminalCommandTracker {
if (this._currentMarker === Boundary.Bottom) {
this._currentMarker = this._xterm.addMarker(this._getOffset() - 1);
} else {
let offset = this._getOffset();
const offset = this._getOffset();
if (this._isDisposable) {
this._currentMarker.dispose();
}
@ -217,7 +222,7 @@ export class TerminalCommandTracker implements ITerminalCommandTracker {
if (this._currentMarker === Boundary.Top) {
this._currentMarker = this._xterm.addMarker(this._getOffset() + 1);
} else {
let offset = this._getOffset();
const offset = this._getOffset();
if (this._isDisposable) {
this._currentMarker.dispose();
}

Some files were not shown because too many files have changed in this diff Show more