提交 2c2ee3aa 编写于 作者: D Daniel Imms

Merge remote-tracking branch 'origin/master' into tyriar/hot_exit/empty_workspaces

......@@ -3,7 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path="../src/typings/mocha.d.ts" />
/// <reference path="../src/typings/thenable.d.ts" />
// Declaring the following because the code gets compiled with es5, which lack definitions for console and timers.
......
......@@ -11,7 +11,7 @@
],
"stopOnEntry": false,
"sourceMaps": true,
"outDir": "${workspaceRoot}/client/out",
"outFiles": ["${workspaceRoot}/client/out"],
"preLaunchTask": "npm"
},
{
......@@ -22,8 +22,17 @@
"args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/client/out/test" ],
"stopOnEntry": false,
"sourceMaps": true,
"outDir": "${workspaceRoot}/client/out/test",
"outFiles": ["${workspaceRoot}/client/out/test"],
"preLaunchTask": "npm"
}
},
{
"name": "Attach Language Server",
"type": "node",
"request": "attach",
"port": 6004,
"sourceMaps": true,
"outFiles": ["${workspaceRoot}/server/out"]
}
]
}
\ No newline at end of file
......@@ -43,9 +43,9 @@
"resolved": "https://registry.npmjs.org/request-light/-/request-light-0.1.0.tgz"
},
"vscode-json-languageservice": {
"version": "2.0.0-next.5",
"version": "2.0.0-next.7",
"from": "vscode-json-languageservice@next",
"resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-2.0.0-next.5.tgz"
"resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-2.0.0-next.7.tgz"
},
"vscode-jsonrpc": {
"version": "3.0.1-alpha.2",
......
......@@ -9,7 +9,7 @@
},
"dependencies": {
"request-light": "^0.1.0",
"vscode-json-languageservice": "^2.0.0-next.5",
"vscode-json-languageservice": "^2.0.0-next.7",
"vscode-languageserver": "3.0.1-alpha.3",
"vscode-nls": "^1.0.7"
},
......
......@@ -15,7 +15,7 @@ interface IPackageInfo {
aiKey: string;
}
var telemetryReporter: TelemetryReporter;
var telemetryReporter: TelemetryReporter | null;
export function activate(context: vscode.ExtensionContext) {
......@@ -89,15 +89,17 @@ function showPreview(uri?: vscode.Uri, sideBySide: boolean = false) {
getViewColumn(sideBySide),
`Preview '${path.basename(resource.fsPath)}'`);
telemetryReporter.sendTelemetryEvent('openPreview', {
where: sideBySide ? 'sideBySide' : 'inPlace',
how: (uri instanceof vscode.Uri) ? 'action' : 'pallete'
});
if (telemetryReporter) {
telemetryReporter.sendTelemetryEvent('openPreview', {
where: sideBySide ? 'sideBySide' : 'inPlace',
how: (uri instanceof vscode.Uri) ? 'action' : 'pallete'
});
}
return thenable;
}
function getViewColumn(sideBySide): vscode.ViewColumn {
function getViewColumn(sideBySide: boolean): vscode.ViewColumn | undefined {
const active = vscode.window.activeTextEditor;
if (!active) {
return vscode.ViewColumn.One;
......@@ -135,7 +137,7 @@ function showSource(mdUri: vscode.Uri) {
});
}
function getPackageInfo(context: vscode.ExtensionContext): IPackageInfo {
function getPackageInfo(context: vscode.ExtensionContext): IPackageInfo | null {
let extensionPackage = require(context.asAbsolutePath('./package.json'));
if (extensionPackage) {
return {
......@@ -169,7 +171,7 @@ class MDDocumentContentProvider implements vscode.TextDocumentContentProvider {
const mdnh = require('markdown-it-named-headers');
const md = require('markdown-it')({
html: true,
highlight: function (str, lang) {
highlight: (str: string, lang: string) => {
if (lang && hljs.getLanguage(lang)) {
try {
return `<pre class="hljs"><code><div>${hljs.highlight(lang, str, true).value}</div></code></pre>`;
......@@ -181,11 +183,11 @@ class MDDocumentContentProvider implements vscode.TextDocumentContentProvider {
return md;
}
private getMediaPath(mediaFile): string {
private getMediaPath(mediaFile: string): string {
return this._context.asAbsolutePath(path.join('media', mediaFile));
}
private isAbsolute(p): boolean {
private isAbsolute(p: string): boolean {
return path.normalize(p + '/') === path.normalize(path.resolve(p) + '/');
}
......@@ -213,14 +215,14 @@ class MDDocumentContentProvider implements vscode.TextDocumentContentProvider {
return href;
}
private computeCustomStyleSheetIncludes(uri: vscode.Uri): string[] {
private computeCustomStyleSheetIncludes(uri: vscode.Uri): string {
const styles = vscode.workspace.getConfiguration('markdown')['styles'];
if (styles && Array.isArray(styles) && styles.length > 0) {
return styles.map((style) => {
return `<link rel="stylesheet" href="${this.fixHref(uri, style)}" type="text/css" media="screen">`;
});
}).join('\n');
}
return [];
return '';
}
private getSettingsOverrideStyles(): string {
......@@ -242,7 +244,7 @@ class MDDocumentContentProvider implements vscode.TextDocumentContentProvider {
public provideTextDocumentContent(uri: vscode.Uri): Thenable<string> {
return vscode.workspace.openTextDocument(vscode.Uri.parse(uri.query)).then(document => {
const scrollBeyondLastLine = vscode.workspace.getConfiguration('editor')['scrollBeyondLastLine'];
const head = [].concat(
const head = ([] as Array<string>).concat(
'<!DOCTYPE html>',
'<html>',
'<head>',
......
......@@ -4,6 +4,4 @@
*--------------------------------------------------------------------------------------------*/
/// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../declares.d.ts'/>
/// <reference path='../../../lib.core.d.ts'/>
/// <reference path='../../../node.d.ts'/>
......@@ -2,9 +2,15 @@
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"lib": [
"es5",
"es2015.promise"
],
"outDir": "./out",
"noLib": true,
"sourceMap": true
"sourceMap": true,
"strictNullChecks": true,
"noImplicitAny": true,
"noImplicitReturns": true
},
"exclude": [
"node_modules"
......
......@@ -9,11 +9,11 @@
["(", ")"]
],
"autoClosingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
{ "open": "{", "close": "}" },
{ "open": "[", "close": "]" },
{ "open": "(", "close": ")" },
{ "open": "\"", "close": "\"", "notIn": ["string"] },
{ "open": "'", "close": "'", "notIn": ["string", "comment"] }
],
"surroundingPairs": [
["{", "}"],
......
{
"name": "ruby",
"version": "0.1.0",
"version": "0.2.0",
"publisher": "vscode",
"engines": { "vscode": "*" },
"activationEvents": ["onLanguage:ruby"],
"main": "./out/rubyMain",
"contributes": {
"languages": [{
"id": "ruby",
......@@ -17,5 +19,9 @@
"scopeName": "source.ruby",
"path": "./syntaxes/Ruby.plist"
}]
},
"scripts": {
"compile": "gulp compile-extension:ruby",
"watch": "gulp watch-extension:ruby"
}
}
......@@ -2,25 +2,15 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ExtensionContext, languages } from 'vscode';
import { workspace } from 'vscode';
export interface IConfiguration {
useCodeSnippetsOnMethodSuggest?: boolean;
export function activate(context: ExtensionContext): any {
languages.setLanguageConfiguration('ruby', {
indentationRules: {
increaseIndentPattern: /^\s*((begin|class|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|while)|(.*\sdo\b))\b[^\{;]*$/,
decreaseIndentPattern: /^\s*([}\]]([,)]?\s*(#|$)|\.[a-zA-Z_]\w*\b)|(end|rescue|ensure|else|elsif|when)\b)/
},
wordPattern: /(-?\d+(?:\.\d+))|(:?[A-Za-z][^-`~@#%^&()=+[{}|;:'",<>/.*\]\s\\!?]*[!?]?)/
});
}
export var defaultConfiguration: IConfiguration = {
useCodeSnippetsOnMethodSuggest: false
};
export function load(myPluginId: string): IConfiguration {
let configuration = workspace.getConfiguration(myPluginId);
let useCodeSnippetsOnMethodSuggest = configuration.get('useCodeSnippetsOnMethodSuggest', defaultConfiguration.useCodeSnippetsOnMethodSuggest);
return {
useCodeSnippetsOnMethodSuggest
};
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../../src/typings/mocha.d.ts'/>
/// <reference path='../../../../extensions/node.d.ts'/>
/// <reference path='../../../../extensions/lib.core.d.ts'/>
/// <reference path='../../../../extensions/declares.d.ts'/>
\ No newline at end of file
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"outDir": "./out",
"noLib": true,
"sourceMap": true
},
"exclude": [
"node_modules"
]
}
\ No newline at end of file
......@@ -5,7 +5,7 @@
'use strict';
import { CompletionItem, TextDocument, Position, CompletionItemKind, CompletionItemProvider, CancellationToken, WorkspaceConfiguration, TextEdit, Range, SnippetString } from 'vscode';
import { CompletionItem, TextDocument, Position, CompletionItemKind, CompletionItemProvider, CancellationToken, WorkspaceConfiguration, TextEdit, Range, SnippetString, workspace } from 'vscode';
import { ITypescriptServiceClient } from '../typescriptService';
......@@ -87,7 +87,9 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
}
public updateConfiguration(config: WorkspaceConfiguration): void {
this.config.useCodeSnippetsOnMethodSuggest = config.get(Configuration.useCodeSnippetsOnMethodSuggest, false);
// Use shared setting for js and ts
let typeScriptConfig = workspace.getConfiguration('typescript');
this.config.useCodeSnippetsOnMethodSuggest = typeScriptConfig.get(Configuration.useCodeSnippetsOnMethodSuggest, false);
}
public provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken): Promise<CompletionItem[]> {
......@@ -162,7 +164,7 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
item.detail = Previewer.plain(detail.displayParts);
}
if (detail && this.config.useCodeSnippetsOnMethodSuggest && item.kind === CompletionItemKind.Function) {
if (detail && this.config.useCodeSnippetsOnMethodSuggest && (item.kind === CompletionItemKind.Function || item.kind === CompletionItemKind.Method)) {
let codeSnippet = detail.name;
let suggestionArgumentNames: string[];
......
......@@ -5,7 +5,7 @@
'use strict';
import { CancellationToken, Uri } from 'vscode';
import { CancellationToken, Uri, Event } from 'vscode';
import * as Proto from './protocol';
import * as semver from 'semver';
......@@ -53,6 +53,10 @@ export class API {
public has208Features(): boolean {
return semver.gte(this._version, '2.0.8');
}
public has213Features(): boolean {
return semver.gte(this._version, '2.1.3');
}
}
export interface ITypescriptServiceClient {
......@@ -63,6 +67,8 @@ export interface ITypescriptServiceClient {
warn(message: string, data?: any): void;
error(message: string, data?: any): void;
onProjectLanguageServiceStateChanged: Event<Proto.ProjectLanguageServiceStateEventBody>;
logTelemetry(eventName: string, properties?: { [prop: string]: string });
experimentalAutoBuild: boolean;
......
......@@ -12,7 +12,7 @@ import * as fs from 'fs';
import * as electron from './utils/electron';
import { Reader } from './utils/wireProtocol';
import { workspace, window, Uri, CancellationToken, OutputChannel, Memento, MessageItem } from 'vscode';
import { workspace, window, Uri, CancellationToken, OutputChannel, Memento, MessageItem, EventEmitter, Event } from 'vscode';
import * as Proto from './protocol';
import { ITypescriptServiceClient, ITypescriptServiceClientHost, API } from './typescriptService';
......@@ -102,6 +102,7 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
private requestQueue: RequestItem[];
private pendingResponses: number;
private callbacks: CallbackMap;
private _onProjectLanguageServiceStateChanged = new EventEmitter<Proto.ProjectLanguageServiceStateEventBody>();
private _packageInfo: IPackageInfo | null;
private _apiVersion: API;
......@@ -148,6 +149,10 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
this.startService();
}
get onProjectLanguageServiceStateChanged(): Event<Proto.ProjectLanguageServiceStateEventBody> {
return this._onProjectLanguageServiceStateChanged.event;
}
private get output(): OutputChannel {
if (!this._output) {
this._output = window.createOutputChannel(localize('channelName', 'TypeScript'));
......@@ -684,6 +689,11 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
break;
}
this.logTelemetry(telemetryData.telemetryEventName, properties);
} else if (event.event === 'projectLanguageServiceState') {
const data = (event as Proto.ProjectLanguageServiceStateEvent).body;
if (data) {
this._onProjectLanguageServiceStateChanged.fire(data);
}
}
} else {
throw new Error('Unknown message type ' + message.type + ' recevied');
......
......@@ -8,7 +8,7 @@
import * as vscode from 'vscode';
import { ITypescriptServiceClient } from '../typescriptService';
import { loadMessageBundle } from 'vscode-nls';
import { dirname, join } from 'path';
import { dirname } from 'path';
const localize = loadMessageBundle();
const selector = ['javascript', 'javascriptreact'];
......@@ -22,37 +22,69 @@ interface Hint {
options: Option[];
}
interface ProjectHintedMap {
[k: string]: boolean;
}
const fileLimit = 500;
export function create(client: ITypescriptServiceClient, isOpen: (path: string) => Promise<boolean>, memento: vscode.Memento) {
class ExcludeHintItem {
private _item: vscode.StatusBarItem;
private _client: ITypescriptServiceClient;
private _currentHint: Hint;
constructor(client: ITypescriptServiceClient) {
this._client = client;
this._item = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MIN_VALUE);
this._item.command = 'js.projectStatus.command';
}
public getCurrentHint(): Hint {
return this._currentHint;
}
public hide() {
this._item.hide();
}
public show(configFileName: string, largeRoots: string, onExecute: () => void) {
this._currentHint = {
message: largeRoots.length > 0
? localize('hintExclude', "For better performance exclude folders with many files, like: {0}", largeRoots)
: localize('hintExclude.generic', "For better performance exclude folders with many files."),
options: [{
title: localize('open', "Configure Excludes"),
execute: () => {
this._client.logTelemetry('js.hintProjectExcludes.accepted');
onExecute();
this._item.hide();
return vscode.workspace.openTextDocument(configFileName)
.then(vscode.window.showTextDocument);
}
}]
};
this._item.tooltip = this._currentHint.message;
this._item.text = localize('large.label', "Configure Excludes");
this._item.tooltip = localize('hintExclude.tooltip', "For better performance exclude folders with many files.");
this._item.color = '#A5DF3B';
this._item.show();
this._client.logTelemetry('js.hintProjectExcludes');
}
}
function createLargeProjectMonitorForProject(item: ExcludeHintItem, client: ITypescriptServiceClient, isOpen: (path: string) => Promise<boolean>, memento: vscode.Memento): vscode.Disposable[] {
const toDispose: vscode.Disposable[] = [];
const projectHinted: { [k: string]: boolean } = Object.create(null);
const projectHinted: ProjectHintedMap = Object.create(null);
const projectHintIgnoreList = memento.get<string[]>('projectHintIgnoreList', []);
for (let path of projectHintIgnoreList) {
if (!path) {
if (path === null) {
path = 'undefined';
}
projectHinted[path] = true;
}
let currentHint: Hint;
let item = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MIN_VALUE);
item.command = 'js.projectStatus.command';
toDispose.push(vscode.commands.registerCommand('js.projectStatus.command', () => {
let {message, options} = currentHint;
return vscode.window.showInformationMessage(message, ...options).then(selection => {
if (selection) {
return selection.execute();
}
});
}));
toDispose.push(vscode.workspace.onDidChangeTextDocument(e => {
delete projectHinted[e.document.fileName];
}));
function onEditor(editor: vscode.TextEditor | undefined): void {
if (!editor
|| !vscode.languages.match(selector, editor.document)
......@@ -66,56 +98,26 @@ export function create(client: ITypescriptServiceClient, isOpen: (path: string)
if (!file) {
return;
}
isOpen(file).then(value => {
if (!value) {
return;
}
return client.execute('projectInfo', { file, needFileNameList: true }).then(res => {
return client.execute('projectInfo', { file, needFileNameList: true } as protocol.ProjectInfoRequestArgs).then(res => {
if (!res.body) {
return;
}
let {configFileName, fileNames} = res.body;
if (projectHinted[configFileName] === true) {
if (projectHinted[configFileName] === true || !fileNames) {
return;
}
if (fileNames && fileNames.length > fileLimit) {
if (fileNames.length > fileLimit || res.body.languageServiceDisabled) {
let largeRoots = computeLargeRoots(configFileName, fileNames).map(f => `'/${f}/'`).join(', ');
currentHint = {
message: largeRoots.length > 0
? localize('hintExclude', "For better performance exclude folders with many files, like: {0}", largeRoots)
: localize('hintExclude.generic', "For better performance exclude folders with many files."),
options: [{
title: localize('open', "Configure Excludes"),
execute: () => {
client.logTelemetry('js.hintProjectExcludes.accepted');
projectHinted[configFileName] = true;
item.hide();
let configFileUri: vscode.Uri;
let rootPath = vscode.workspace.rootPath;
if (rootPath && dirname(configFileName).indexOf('' + rootPath) === 0) {
configFileUri = vscode.Uri.file(configFileName);
} else {
configFileUri = vscode.Uri.parse('untitled://' + join(rootPath, 'jsconfig.json'));
}
return vscode.workspace.openTextDocument(configFileUri)
.then(vscode.window.showTextDocument);
}
}]
};
item.tooltip = currentHint.message;
item.text = localize('large.label', "Configure Excludes");
item.tooltip = localize('hintExclude.tooltip', "For better performance exclude folders with many files.");
item.color = '#A5DF3B';
item.show();
client.logTelemetry('js.hintProjectExcludes');
item.show(configFileName, largeRoots, () => {
projectHinted[configFileName] = true;
});
} else {
item.hide();
}
......@@ -125,9 +127,45 @@ export function create(client: ITypescriptServiceClient, isOpen: (path: string)
});
}
toDispose.push(vscode.workspace.onDidChangeTextDocument(e => {
delete projectHinted[e.document.fileName];
}));
toDispose.push(vscode.window.onDidChangeActiveTextEditor(onEditor));
onEditor(vscode.window.activeTextEditor);
return toDispose;
}
function createLargeProjectMonitorFromTypeScript(item: ExcludeHintItem, client: ITypescriptServiceClient): vscode.Disposable {
return client.onProjectLanguageServiceStateChanged(body => {
if (body.languageServiceEnabled) {
item.hide();
} else {
item.show(body.projectName, '', () => { });
}
});
}
export function create(client: ITypescriptServiceClient, isOpen: (path: string) => Promise<boolean>, memento: vscode.Memento) {
const toDispose: vscode.Disposable[] = [];
let item = new ExcludeHintItem(client);
toDispose.push(vscode.commands.registerCommand('js.projectStatus.command', () => {
let {message, options} = item.getCurrentHint();
return vscode.window.showInformationMessage(message, ...options).then(selection => {
if (selection) {
return selection.execute();
}
});
}));
if (client.apiVersion.has213Features()) {
toDispose.push(createLargeProjectMonitorFromTypeScript(item, client));
} else {
toDispose.push(...createLargeProjectMonitorForProject(item, client, isOpen, memento));
}
return vscode.Disposable.from(...toDispose);
}
......
......@@ -42,7 +42,8 @@ export interface IJSONSchema {
defaultSnippets?: IJSONSchemaSnippet[]; // VSCode extension
errorMessage?: string; // VSCode extension
deprecatedMessage?: string; // VSCode extension
deprecationMessage?: string; // VSCode extension
enumDescriptions?: string[]; // VSCode extension
}
export interface IJSONSchemaMap {
......
......@@ -327,7 +327,7 @@ export class VSCodeMenu {
const hide = new MenuItem({ label: nls.localize('mHide', "Hide {0}", product.nameLong), role: 'hide', accelerator: 'Command+H' });
const hideOthers = new MenuItem({ label: nls.localize('mHideOthers', "Hide Others"), role: 'hideothers', accelerator: 'Command+Alt+H' });
const showAll = new MenuItem({ label: nls.localize('mShowAll', "Show All"), role: 'unhide' });
const quit = new MenuItem({ label: nls.localize('miQuit', "Quit {0}", product.nameLong), click: () => this.windowsService.quit(), accelerator: this.getAccelerator('workbench.action.quit', 'Command+Q') });
const quit = new MenuItem(this.likeAction('workbench.action.quit', { label: nls.localize('miQuit', "Quit {0}", product.nameLong), click: () => this.windowsService.quit(), accelerator: this.getAccelerator('workbench.action.quit', 'Command+Q') }));
const actions = [about];
actions.push(...checkForUpdates);
......@@ -350,17 +350,17 @@ export class VSCodeMenu {
let newFile: Electron.MenuItem;
if (hasNoWindows) {
newFile = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New File")), accelerator: this.getAccelerator('workbench.action.files.newUntitledFile'), click: () => this.windowsService.openNewWindow() });
newFile = new MenuItem(this.likeAction('workbench.action.files.newUntitledFile', { label: mnemonicLabel(nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New File")), click: () => this.windowsService.openNewWindow() }));
} else {
newFile = this.createMenuItem(nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New File"), 'workbench.action.files.newUntitledFile');
}
const open = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open...")), accelerator: this.getAccelerator('workbench.action.files.openFileFolder'), click: () => this.windowsService.openFileFolderPicker() });
const openFolder = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder...")), accelerator: this.getAccelerator('workbench.action.files.openFolder'), click: () => this.windowsService.openFolderPicker() });
const open = new MenuItem(this.likeAction('workbench.action.files.openFileFolder', { label: mnemonicLabel(nls.localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open...")), click: () => this.windowsService.openFileFolderPicker() }));
const openFolder = new MenuItem(this.likeAction('workbench.action.files.openFolder', { label: mnemonicLabel(nls.localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder...")), click: () => this.windowsService.openFolderPicker() }));
let openFile: Electron.MenuItem;
if (hasNoWindows) {
openFile = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File...")), accelerator: this.getAccelerator('workbench.action.files.openFile'), click: () => this.windowsService.openFilePicker() });
openFile = new MenuItem(this.likeAction('workbench.action.files.openFile', { label: mnemonicLabel(nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File...")), click: () => this.windowsService.openFilePicker() }));
} else {
openFile = this.createMenuItem(nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File..."), 'workbench.action.files.openFile');
}
......@@ -374,18 +374,18 @@ export class VSCodeMenu {
const saveAllFiles = this.createMenuItem(nls.localize({ key: 'miSaveAll', comment: ['&& denotes a mnemonic'] }, "Save A&&ll"), 'workbench.action.files.saveAll', this.windowsService.getWindowCount() > 0);
const autoSaveEnabled = [AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE].some(s => this.currentAutoSaveSetting === s);
const autoSave = new MenuItem({ label: mnemonicLabel(nls.localize('miAutoSave', "Auto Save")), type: 'checkbox', checked: autoSaveEnabled, enabled: this.windowsService.getWindowCount() > 0, click: () => this.windowsService.sendToFocused('vscode.toggleAutoSave') });
const autoSave = new MenuItem(this.likeAction('vscode.toggleAutoSave', { label: mnemonicLabel(nls.localize('miAutoSave', "Auto Save")), type: 'checkbox', checked: autoSaveEnabled, enabled: this.windowsService.getWindowCount() > 0, click: () => this.windowsService.sendToFocused('vscode.toggleAutoSave') }, false));
const preferences = this.getPreferencesMenu();
const newWindow = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "&&New Window")), accelerator: this.getAccelerator('workbench.action.newWindow'), click: () => this.windowsService.openNewWindow() });
const newWindow = new MenuItem(this.likeAction('workbench.action.newWindow', { label: mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "&&New Window")), click: () => this.windowsService.openNewWindow() }));
const revertFile = this.createMenuItem(nls.localize({ key: 'miRevert', comment: ['&& denotes a mnemonic'] }, "Revert F&&ile"), 'workbench.action.files.revert', this.windowsService.getWindowCount() > 0);
const closeWindow = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miCloseWindow', comment: ['&& denotes a mnemonic'] }, "Close &&Window")), accelerator: this.getAccelerator('workbench.action.closeWindow'), click: () => this.windowsService.getLastActiveWindow().win.close(), enabled: this.windowsService.getWindowCount() > 0 });
const closeWindow = new MenuItem(this.likeAction('workbench.action.closeWindow', { label: mnemonicLabel(nls.localize({ key: 'miCloseWindow', comment: ['&& denotes a mnemonic'] }, "Close &&Window")), click: () => this.windowsService.getLastActiveWindow().win.close(), enabled: this.windowsService.getWindowCount() > 0 }));
const closeFolder = this.createMenuItem(nls.localize({ key: 'miCloseFolder', comment: ['&& denotes a mnemonic'] }, "Close &&Folder"), 'workbench.action.closeFolder');
const closeEditor = this.createMenuItem(nls.localize({ key: 'miCloseEditor', comment: ['&& denotes a mnemonic'] }, "Close &&Editor"), 'workbench.action.closeActiveEditor');
const exit = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miExit', comment: ['&& denotes a mnemonic'] }, "E&&xit")), accelerator: this.getAccelerator('workbench.action.quit'), click: () => this.windowsService.quit() });
const exit = new MenuItem(this.likeAction('workbench.action.quit', { label: mnemonicLabel(nls.localize({ key: 'miExit', comment: ['&& denotes a mnemonic'] }, "E&&xit")), click: () => this.windowsService.quit() }));
arrays.coalesce([
newFile,
......@@ -447,7 +447,7 @@ export class VSCodeMenu {
openRecentMenu.append(__separator__());
for (let i = 0; i < VSCodeMenu.MAX_MENU_RECENT_ENTRIES && i < folders.length; i++) {
openRecentMenu.append(this.createOpenRecentMenuItem(folders[i]));
openRecentMenu.append(this.createOpenRecentMenuItem(folders[i], 'openRecentFolder'));
}
}
......@@ -456,18 +456,18 @@ export class VSCodeMenu {
openRecentMenu.append(__separator__());
for (let i = 0; i < VSCodeMenu.MAX_MENU_RECENT_ENTRIES && i < files.length; i++) {
openRecentMenu.append(this.createOpenRecentMenuItem(files[i]));
openRecentMenu.append(this.createOpenRecentMenuItem(files[i], 'openRecentFile'));
}
}
if (folders.length || files.length) {
openRecentMenu.append(__separator__());
openRecentMenu.append(new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miClearItems', comment: ['&& denotes a mnemonic'] }, "&&Clear Items")), click: () => this.windowsService.clearRecentPathsList() }));
openRecentMenu.append(new MenuItem(this.likeAction('clearRecentlyOpened', { label: mnemonicLabel(nls.localize({ key: 'miClearItems', comment: ['&& denotes a mnemonic'] }, "&&Clear Items")), click: () => this.windowsService.clearRecentPathsList() }, false)));
}
}
private createOpenRecentMenuItem(path: string): Electron.MenuItem {
return new MenuItem({
private createOpenRecentMenuItem(path: string, actionId: string): Electron.MenuItem {
return new MenuItem(this.likeAction(actionId, {
label: unMnemonicLabel(path), click: (menuItem, win, event) => {
const openInNewWindow = event && ((!platform.isMacintosh && event.ctrlKey) || (platform.isMacintosh && event.metaKey));
const success = !!this.windowsService.open({ cli: this.environmentService.args, pathsToOpen: [path], forceNewWindow: openInNewWindow });
......@@ -475,7 +475,7 @@ export class VSCodeMenu {
this.windowsService.removeFromRecentPathsList(path);
}
}
});
}, false));
}
private createRoleMenuItem(label: string, actionId: string, role: Electron.MenuItemRole): Electron.MenuItem {
......@@ -761,20 +761,19 @@ export class VSCodeMenu {
}
private setHelpMenu(helpMenu: Electron.Menu): void {
const toggleDevToolsItem = new MenuItem({
const toggleDevToolsItem = new MenuItem(this.likeAction('workbench.action.toggleDevTools', {
label: mnemonicLabel(nls.localize({ key: 'miToggleDevTools', comment: ['&& denotes a mnemonic'] }, "&&Toggle Developer Tools")),
accelerator: this.getAccelerator('workbench.action.toggleDevTools'),
click: () => this.toggleDevTools(),
enabled: (this.windowsService.getWindowCount() > 0)
});
}));
const showAccessibilityOptions = new MenuItem({
const showAccessibilityOptions = new MenuItem(this.likeAction('accessibilityOptions', {
label: mnemonicLabel(nls.localize({ key: 'miAccessibilityOptions', comment: ['&& denotes a mnemonic'] }, "Accessibility &&Options")),
accelerator: null,
click: () => {
this.windowsService.openAccessibilityOptions();
}
});
}, false));
let reportIssuesItem: Electron.MenuItem = null;
if (product.reportIssueUrl) {
......@@ -927,6 +926,20 @@ export class VSCodeMenu {
});
}
private likeAction(actionId: string, options: Electron.MenuItemOptions, setAccelerator = !options.accelerator): Electron.MenuItemOptions {
if (setAccelerator) {
options.accelerator = this.getAccelerator(actionId);
}
const originalClick = options.click;
options.click = (item, window, event) => {
this.reportMenuActionTelemetry(actionId);
if (originalClick) {
originalClick(item, window, event);
}
};
return options;
}
private getAccelerator(actionId: string, fallback?: string): string {
if (actionId) {
const resolvedKeybinding = this.mapResolvedKeybindingToActionId[actionId];
......
......@@ -118,6 +118,8 @@ const schemaId = 'vscode://schemas/vscode-extensions';
const schema: IJSONSchema = {
properties: {
engines: {
type: 'object',
properties: {
'vscode': {
type: 'string',
......
......@@ -11,6 +11,7 @@ import paths = require('vs/base/common/paths');
import URI from 'vs/base/common/uri';
import { ConfigurationSource, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILifecycleService, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
......@@ -171,6 +172,12 @@ export function configurationTelemetry(telemetryService: ITelemetryService, conf
});
}
export function lifecycleTelemetry(telemetryService: ITelemetryService, lifecycleService: ILifecycleService): IDisposable {
return lifecycleService.onShutdown(event => {
telemetryService.publicLog('shutdown', { reason: ShutdownReason[event] });
});
}
function flattenKeys(value: Object): string[] {
if (!value) {
return [];
......
......@@ -23,7 +23,7 @@ import timer = require('vs/base/common/timer');
import { IStartupFingerprint, IMemoryInfo } from 'vs/workbench/electron-browser/common';
import { Workbench } from 'vs/workbench/electron-browser/workbench';
import { StorageService, inMemoryLocalStorageInstance } from 'vs/workbench/services/storage/common/storageService';
import { ITelemetryService, NullTelemetryService, configurationTelemetry, loadExperiments } from 'vs/platform/telemetry/common/telemetry';
import { ITelemetryService, NullTelemetryService, configurationTelemetry, loadExperiments, lifecycleTelemetry } from 'vs/platform/telemetry/common/telemetry';
import { ITelemetryAppenderChannel, TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc';
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
import { IdleMonitor, UserStatus } from 'vs/platform/telemetry/browser/idleMonitor';
......@@ -374,6 +374,7 @@ export class WorkbenchShell {
const lifecycleService = instantiationService.createInstance(LifecycleService);
this.toUnbind.push(lifecycleService.onShutdown(reason => disposables.dispose()));
serviceCollection.set(ILifecycleService, lifecycleService);
disposables.add(lifecycleTelemetry(this.telemetryService, lifecycleService));
const extensionManagementChannel = getDelayedChannel<IExtensionManagementChannel>(sharedProcess.then(c => c.getChannel('extensions')));
const extensionManagementChannelClient = new ExtensionManagementChannelClient(extensionManagementChannel);
......
......@@ -13,14 +13,17 @@ import { Button } from 'vs/base/browser/ui/button/button';
import { $ } from 'vs/base/browser/builder';
import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { CollapsibleView } from 'vs/base/browser/ui/splitview/splitview';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Registry } from 'vs/platform/platform';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actionRegistry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export class EmptyView extends CollapsibleView {
private openFolderButton: Button;
constructor( @IInstantiationService private instantiationService: IInstantiationService) {
constructor( @ITelemetryService private telemetryService: ITelemetryService,
@IInstantiationService private instantiationService: IInstantiationService
) {
super({
minimumSize: 2 * 22,
ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section")
......@@ -52,6 +55,7 @@ export class EmptyView extends CollapsibleView {
}
private runWorkbenchAction(actionId: string): void {
this.telemetryService.publicLog('workbenchActionExecuted', { id: actionId, from: 'explorer' });
let actionRegistry = <IWorkbenchActionRegistry>Registry.as(Extensions.WorkbenchActions);
let actionDescriptor = actionRegistry.getWorkbenchAction(actionId);
......
......@@ -20,6 +20,7 @@ import { PPromise, TPromise } from 'vs/base/common/winjs.base';
import { MAX_FILE_SIZE } from 'vs/platform/files/common/files';
import { FileWalker, Engine as FileSearchEngine } from 'vs/workbench/services/search/node/fileSearch';
import { Engine as TextSearchEngine } from 'vs/workbench/services/search/node/textSearch';
import { TextSearchWorkerProvider } from 'vs/workbench/services/search/node/textSearchWorkerProvider';
import { IRawSearchService, IRawSearch, IRawFileMatch, ISerializedFileMatch, ISerializedSearchProgressItem, ISerializedSearchComplete, ISearchEngine } from './search';
import { ICachedSearchStats, IProgress } from 'vs/platform/search/common/search';
......@@ -31,19 +32,28 @@ export class SearchService implements IRawSearchService {
private caches: { [cacheKey: string]: Cache; } = Object.create(null);
private textSearchWorkerProvider: TextSearchWorkerProvider;
public fileSearch(config: IRawSearch): PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem> {
return this.doFileSearch(FileSearchEngine, config, SearchService.BATCH_SIZE);
}
public textSearch(config: IRawSearch): PPromise<ISerializedSearchComplete, ISerializedSearchProgressItem> {
let engine = new TextSearchEngine(config, new FileWalker({
rootFolders: config.rootFolders,
extraFiles: config.extraFiles,
includePattern: config.includePattern,
excludePattern: config.excludePattern,
filePattern: config.filePattern,
maxFilesize: MAX_FILE_SIZE
}));
if (!this.textSearchWorkerProvider) {
this.textSearchWorkerProvider = new TextSearchWorkerProvider();
}
let engine = new TextSearchEngine(
config,
new FileWalker({
rootFolders: config.rootFolders,
extraFiles: config.extraFiles,
includePattern: config.includePattern,
excludePattern: config.excludePattern,
filePattern: config.filePattern,
maxFilesize: MAX_FILE_SIZE
}),
this.textSearchWorkerProvider);
return this.doSearchWithBatchTimeout(engine, SearchService.BATCH_SIZE);
}
......
......@@ -5,20 +5,15 @@
'use strict';
import uri from 'vs/base/common/uri';
import * as os from 'os';
import * as path from 'path';
import * as ipc from 'vs/base/parts/ipc/common/ipc';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IProgress } from 'vs/platform/search/common/search';
import { FileWalker } from 'vs/workbench/services/search/node/fileSearch';
import { ISerializedFileMatch, ISerializedSearchComplete, IRawSearch, ISearchEngine } from './search';
import { ISearchWorkerConfig, ISearchWorker, ISearchWorkerChannel, SearchWorkerChannelClient } from './worker/searchWorkerIpc';
import { Client } from 'vs/base/parts/ipc/node/ipc.cp';
import { ISerializedFileMatch, ISerializedSearchComplete, IRawSearch, ISearchEngine } from './search';
import { ISearchWorker, ISearchWorkerConfig } from './worker/searchWorkerIpc';
import { ITextSearchWorkerProvider } from './textSearchWorkerProvider';
export class Engine implements ISearchEngine<ISerializedFileMatch> {
......@@ -37,13 +32,15 @@ export class Engine implements ISearchEngine<ISerializedFileMatch> {
private limitReached = false;
private numResults = 0;
private workerProvider: ITextSearchWorkerProvider;
private workers: ISearchWorker[];
private nextWorker = 0;
private workers: ISearchWorker[] = [];
private workerClients: Client[] = [];
constructor(config: IRawSearch, walker: FileWalker) {
constructor(config: IRawSearch, walker: FileWalker, workerProvider: ITextSearchWorkerProvider) {
this.config = config;
this.walker = walker;
this.workerProvider = workerProvider;
}
cancel(): void {
......@@ -56,8 +53,18 @@ export class Engine implements ISearchEngine<ISerializedFileMatch> {
});
}
initializeWorkers(): void {
this.workers.forEach(w => {
const config: ISearchWorkerConfig = { pattern: this.config.contentPattern, fileEncoding: this.config.fileEncoding };
w.initialize(config)
.then(null, onUnexpectedError);
});
}
search(onResult: (match: ISerializedFileMatch) => void, onProgress: (progress: IProgress) => void, done: (error: Error, complete: ISerializedSearchComplete) => void): void {
this.startWorkers();
this.workers = this.workerProvider.getWorkers();
this.initializeWorkers();
const progress = () => {
if (++this.progressed % Engine.PROGRESS_FLUSH_CHUNK_SIZE === 0) {
onProgress({ total: this.totalBytes, worked: this.processedBytes }); // buffer progress in chunks to reduce pressure
......@@ -75,7 +82,6 @@ export class Engine implements ISearchEngine<ISerializedFileMatch> {
// Emit done()
if (!this.isDone && this.processedBytes === this.totalBytes && this.walkerIsDone) {
this.isDone = true;
this.disposeWorkers();
done(this.walkerError, {
limitHit: this.limitReached,
stats: this.walker.getStats()
......@@ -153,39 +159,4 @@ export class Engine implements ISearchEngine<ISerializedFileMatch> {
this.walkerError = error;
});
}
private startWorkers(): void {
// If the CPU has hyperthreading enabled, this will report (# of physical cores)*2.
const numWorkers = os.cpus().length;
for (let i = 0; i < numWorkers; i++) {
this.createWorker(i);
}
}
private createWorker(id: number): void {
let client = new Client(
uri.parse(require.toUrl('bootstrap')).fsPath,
{
serverName: 'Search Worker ' + id,
args: ['--type=searchWorker'],
env: {
AMD_ENTRYPOINT: 'vs/workbench/services/search/node/worker/searchWorkerApp',
PIPE_LOGGING: 'true',
VERBOSE_LOGGING: process.env.VERBOSE_LOGGING
}
});
// Make async?
const channel = ipc.getNextTickChannel(client.getChannel<ISearchWorkerChannel>('searchWorker'));
const channelClient = new SearchWorkerChannelClient(channel);
const config: ISearchWorkerConfig = { pattern: this.config.contentPattern, id, fileEncoding: this.config.fileEncoding };
channelClient.initialize(config).then(null, onUnexpectedError);
this.workers.push(channelClient);
this.workerClients.push(client);
}
private disposeWorkers(): void {
this.workerClients.forEach(c => c.dispose());
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as os from 'os';
import uri from 'vs/base/common/uri';
import * as ipc from 'vs/base/parts/ipc/common/ipc';
import { Client } from 'vs/base/parts/ipc/node/ipc.cp';
import { ISearchWorker, ISearchWorkerChannel, SearchWorkerChannelClient } from './worker/searchWorkerIpc';
export interface ITextSearchWorkerProvider {
getWorkers(): ISearchWorker[];
}
export class TextSearchWorkerProvider implements ITextSearchWorkerProvider {
private workers: ISearchWorker[] = [];
getWorkers(): ISearchWorker[] {
const numWorkers = os.cpus().length;
while (this.workers.length < numWorkers) {
this.createWorker();
}
return this.workers;
}
private createWorker(): void {
let client = new Client(
uri.parse(require.toUrl('bootstrap')).fsPath,
{
serverName: 'Search Worker ' + this.workers.length,
args: ['--type=searchWorker'],
timeout: 30 * 1000,
env: {
AMD_ENTRYPOINT: 'vs/workbench/services/search/node/worker/searchWorkerApp',
PIPE_LOGGING: 'true',
VERBOSE_LOGGING: process.env.VERBOSE_LOGGING
}
});
const channel = ipc.getNextTickChannel(client.getChannel<ISearchWorkerChannel>('searchWorker'));
const channelClient = new SearchWorkerChannelClient(channel);
this.workers.push(channelClient);
}
}
\ No newline at end of file
......@@ -25,9 +25,6 @@ interface ReadLinesOptions {
encoding: string;
}
// Global isCanceled flag for the process. It's only set once and this avoids awkwardness in passing it around.
let isCanceled = false;
const MAX_FILE_ERRORS = 5; // Don't report more than this number of errors, 1 per file, to avoid flooding the log when there's a general issue
let numErrorsLogged = 0;
function onError(error: any): void {
......@@ -36,71 +33,27 @@ function onError(error: any): void {
}
}
export class SearchWorker implements ISearchWorker {
private contentPattern: RegExp;
private nextSearch = TPromise.wrap(null);
private config: ISearchWorkerConfig;
private fileEncoding: string;
export class SearchWorkerManager implements ISearchWorker {
private currentSearchEngine: SearchWorkerEngine;
initialize(config: ISearchWorkerConfig): TPromise<void> {
this.contentPattern = strings.createRegExp(config.pattern.pattern, config.pattern.isRegExp, { matchCase: config.pattern.isCaseSensitive, wholeWord: config.pattern.isWordMatch, multiline: false, global: true });
this.config = config;
this.fileEncoding = encodingExists(config.fileEncoding) ? config.fileEncoding : UTF8;
this.currentSearchEngine = new SearchWorkerEngine(config);
return TPromise.wrap<void>(undefined);
}
cancel(): TPromise<void> {
isCanceled = true;
// Cancel the current search. It will stop searching and close its open files.
this.currentSearchEngine.cancel();
return TPromise.wrap<void>(null);
}
search(args: ISearchWorkerSearchArgs): TPromise<ISearchWorkerSearchResult> {
// Queue this search to run after the current one
return this.nextSearch = this.nextSearch
.then(() => searchBatch(args.absolutePaths, this.contentPattern, this.fileEncoding, args.maxResults));
}
}
if (!this.currentSearchEngine) {
return TPromise.wrapError(new Error('SearchWorker is not initialized'));
}
/**
* Searches some number of the given paths concurrently, and starts searches in other paths when those complete.
*/
function searchBatch(absolutePaths: string[], contentPattern: RegExp, fileEncoding: string, maxResults?: number): TPromise<ISearchWorkerSearchResult> {
if (isCanceled) {
return TPromise.wrap(null);
return this.currentSearchEngine.searchBatch(args);
}
return new TPromise(batchDone => {
const result: ISearchWorkerSearchResult = {
matches: [],
numMatches: 0,
limitReached: false
};
// Search in the given path, and when it's finished, search in the next path in absolutePaths
const startSearchInFile = (absolutePath: string): TPromise<void> => {
return searchInFile(absolutePath, contentPattern, fileEncoding, maxResults && (maxResults - result.numMatches)).then(fileResult => {
// Finish early if search is canceled
if (isCanceled) {
return;
}
if (fileResult) {
result.numMatches += fileResult.numMatches;
result.matches.push(fileResult.match.serialize());
if (fileResult.limitReached) {
// If the limit was reached, terminate early with the results so far and cancel in-progress searches.
isCanceled = true;
result.limitReached = true;
return batchDone(result);
}
}
}, onError);
};
TPromise.join(absolutePaths.map(startSearchInFile)).then(() => {
batchDone(result);
});
});
}
interface IFileSearchResult {
......@@ -109,160 +62,225 @@ interface IFileSearchResult {
limitReached?: boolean;
}
function searchInFile(absolutePath: string, contentPattern: RegExp, fileEncoding: string, maxResults?: number): TPromise<IFileSearchResult> {
let fileMatch: FileMatch = null;
let limitReached = false;
let numMatches = 0;
const perLineCallback = (line: string, lineNumber: number) => {
let lineMatch: LineMatch = null;
let match = contentPattern.exec(line);
export class SearchWorkerEngine {
private contentPattern: RegExp;
private fileEncoding: string;
private nextSearch = TPromise.wrap(null);
// Record all matches into file result
while (match !== null && match[0].length > 0 && !isCanceled && !limitReached) {
if (fileMatch === null) {
fileMatch = new FileMatch(absolutePath);
}
private isCanceled = false;
if (lineMatch === null) {
lineMatch = new LineMatch(line, lineNumber);
fileMatch.addMatch(lineMatch);
}
constructor(config: ISearchWorkerConfig) {
this.contentPattern = strings.createRegExp(config.pattern.pattern, config.pattern.isRegExp, { matchCase: config.pattern.isCaseSensitive, wholeWord: config.pattern.isWordMatch, multiline: false, global: true });
this.fileEncoding = encodingExists(config.fileEncoding) ? config.fileEncoding : UTF8;
}
lineMatch.addMatch(match.index, match[0].length);
/**
* Searches some number of the given paths concurrently, and starts searches in other paths when those complete.
*/
searchBatch(args: ISearchWorkerSearchArgs): TPromise<ISearchWorkerSearchResult> {
return this.nextSearch =
this.nextSearch.then(() => this._searchBatch(args));
}
numMatches++;
if (maxResults && numMatches >= maxResults) {
limitReached = true;
}
match = contentPattern.exec(line);
private _searchBatch(args: ISearchWorkerSearchArgs): TPromise<ISearchWorkerSearchResult> {
if (this.isCanceled) {
return TPromise.wrap(null);
}
};
// Read lines buffered to support large files
return readlinesAsync(absolutePath, perLineCallback, { bufferLength: 8096, encoding: fileEncoding }).then(
() => fileMatch ? { match: fileMatch, limitReached, numMatches } : null);
}
return new TPromise(batchDone => {
const result: ISearchWorkerSearchResult = {
matches: [],
numMatches: 0,
limitReached: false
};
function readlinesAsync(filename: string, perLineCallback: (line: string, lineNumber: number) => void, options: ReadLinesOptions): TPromise<void> {
return new TPromise<void>((resolve, reject) => {
fs.open(filename, 'r', null, (error: Error, fd: number) => {
if (error) {
return reject(error);
}
// Search in the given path, and when it's finished, search in the next path in absolutePaths
const startSearchInFile = (absolutePath: string): TPromise<void> => {
return this.searchInFile(absolutePath, this.contentPattern, this.fileEncoding, args.maxResults && (args.maxResults - result.numMatches)).then(fileResult => {
// Finish early if search is canceled
if (this.isCanceled) {
return;
}
if (fileResult) {
result.numMatches += fileResult.numMatches;
result.matches.push(fileResult.match.serialize());
if (fileResult.limitReached) {
// If the limit was reached, terminate early with the results so far and cancel in-progress searches.
this.cancel();
result.limitReached = true;
return batchDone(result);
}
}
}, onError);
};
TPromise.join(args.absolutePaths.map(startSearchInFile)).then(() => {
batchDone(result);
});
});
}
cancel(): void {
this.isCanceled = true;
}
let buffer = new Buffer(options.bufferLength);
let pos: number;
let i: number;
let line = '';
let lineNumber = 0;
let lastBufferHadTraillingCR = false;
private searchInFile(absolutePath: string, contentPattern: RegExp, fileEncoding: string, maxResults?: number): TPromise<IFileSearchResult> {
let fileMatch: FileMatch = null;
let limitReached = false;
let numMatches = 0;
const perLineCallback = (line: string, lineNumber: number) => {
let lineMatch: LineMatch = null;
let match = contentPattern.exec(line);
const decodeBuffer = (buffer: NodeBuffer, start, end): string => {
if (options.encoding === UTF8 || options.encoding === UTF8_with_bom) {
return buffer.toString(undefined, start, end); // much faster to use built in toString() when encoding is default
// Record all matches into file result
while (match !== null && match[0].length > 0 && !this.isCanceled && !limitReached) {
if (fileMatch === null) {
fileMatch = new FileMatch(absolutePath);
}
return decode(buffer.slice(start, end), options.encoding);
};
if (lineMatch === null) {
lineMatch = new LineMatch(line, lineNumber);
fileMatch.addMatch(lineMatch);
}
const lineFinished = (offset: number): void => {
line += decodeBuffer(buffer, pos, i + offset);
perLineCallback(line, lineNumber);
line = '';
lineNumber++;
pos = i + offset;
};
lineMatch.addMatch(match.index, match[0].length);
numMatches++;
if (maxResults && numMatches >= maxResults) {
limitReached = true;
}
match = contentPattern.exec(line);
}
};
// Read lines buffered to support large files
return this.readlinesAsync(absolutePath, perLineCallback, { bufferLength: 8096, encoding: fileEncoding }).then(
() => fileMatch ? { match: fileMatch, limitReached, numMatches } : null);
}
const readFile = (isFirstRead: boolean, clb: (error: Error) => void): void => {
if (isCanceled) {
return clb(null); // return early if canceled or limit reached
private readlinesAsync(filename: string, perLineCallback: (line: string, lineNumber: number) => void, options: ReadLinesOptions): TPromise<void> {
return new TPromise<void>((resolve, reject) => {
fs.open(filename, 'r', null, (error: Error, fd: number) => {
if (error) {
return reject(error);
}
fs.read(fd, buffer, 0, buffer.length, null, (error: Error, bytesRead: number, buffer: NodeBuffer) => {
if (error || bytesRead === 0 || isCanceled) {
return clb(error); // return early if canceled or limit reached or no more bytes to read
let buffer = new Buffer(options.bufferLength);
let pos: number;
let i: number;
let line = '';
let lineNumber = 0;
let lastBufferHadTraillingCR = false;
const decodeBuffer = (buffer: NodeBuffer, start, end): string => {
if (options.encoding === UTF8 || options.encoding === UTF8_with_bom) {
return buffer.toString(undefined, start, end); // much faster to use built in toString() when encoding is default
}
pos = 0;
i = 0;
return decode(buffer.slice(start, end), options.encoding);
};
// Detect encoding and mime when this is the beginning of the file
if (isFirstRead) {
let mimeAndEncoding = detectMimeAndEncodingFromBuffer(buffer, bytesRead);
if (mimeAndEncoding.mimes[mimeAndEncoding.mimes.length - 1] !== baseMime.MIME_TEXT) {
return clb(null); // skip files that seem binary
}
const lineFinished = (offset: number): void => {
line += decodeBuffer(buffer, pos, i + offset);
perLineCallback(line, lineNumber);
line = '';
lineNumber++;
pos = i + offset;
};
// Check for BOM offset
switch (mimeAndEncoding.encoding) {
case UTF8:
pos = i = 3;
options.encoding = UTF8;
break;
case UTF16be:
pos = i = 2;
options.encoding = UTF16be;
break;
case UTF16le:
pos = i = 2;
options.encoding = UTF16le;
break;
}
const readFile = (isFirstRead: boolean, clb: (error: Error) => void): void => {
if (this.isCanceled) {
return clb(null); // return early if canceled or limit reached
}
if (lastBufferHadTraillingCR) {
if (buffer[i] === 0x0a) { // LF (Line Feed)
lineFinished(1);
i++;
} else {
lineFinished(0);
fs.read(fd, buffer, 0, buffer.length, null, (error: Error, bytesRead: number, buffer: NodeBuffer) => {
if (error || bytesRead === 0 || this.isCanceled) {
return clb(error); // return early if canceled or limit reached or no more bytes to read
}
lastBufferHadTraillingCR = false;
}
pos = 0;
i = 0;
// Detect encoding and mime when this is the beginning of the file
if (isFirstRead) {
let mimeAndEncoding = detectMimeAndEncodingFromBuffer(buffer, bytesRead);
if (mimeAndEncoding.mimes[mimeAndEncoding.mimes.length - 1] !== baseMime.MIME_TEXT) {
return clb(null); // skip files that seem binary
}
// Check for BOM offset
switch (mimeAndEncoding.encoding) {
case UTF8:
pos = i = 3;
options.encoding = UTF8;
break;
case UTF16be:
pos = i = 2;
options.encoding = UTF16be;
break;
case UTF16le:
pos = i = 2;
options.encoding = UTF16le;
break;
}
}
for (; i < bytesRead; ++i) {
if (buffer[i] === 0x0a) { // LF (Line Feed)
lineFinished(1);
} else if (buffer[i] === 0x0d) { // CR (Carriage Return)
if (i + 1 === bytesRead) {
lastBufferHadTraillingCR = true;
} else if (buffer[i + 1] === 0x0a) { // LF (Line Feed)
lineFinished(2);
if (lastBufferHadTraillingCR) {
if (buffer[i] === 0x0a) { // LF (Line Feed)
lineFinished(1);
i++;
} else {
lineFinished(1);
lineFinished(0);
}
}
}
line += decodeBuffer(buffer, pos, bytesRead);
lastBufferHadTraillingCR = false;
}
readFile(/*isFirstRead=*/false, clb); // Continue reading
});
};
for (; i < bytesRead; ++i) {
if (buffer[i] === 0x0a) { // LF (Line Feed)
lineFinished(1);
} else if (buffer[i] === 0x0d) { // CR (Carriage Return)
if (i + 1 === bytesRead) {
lastBufferHadTraillingCR = true;
} else if (buffer[i + 1] === 0x0a) { // LF (Line Feed)
lineFinished(2);
i++;
} else {
lineFinished(1);
}
}
}
readFile(/*isFirstRead=*/true, (error: Error) => {
if (error) {
return reject(error);
}
line += decodeBuffer(buffer, pos, bytesRead);
if (line.length) {
perLineCallback(line, lineNumber); // handle last line
}
readFile(/*isFirstRead=*/false, clb); // Continue reading
});
};
fs.close(fd, (error: Error) => {
readFile(/*isFirstRead=*/true, (error: Error) => {
if (error) {
reject(error);
} else {
resolve(null);
return reject(error);
}
if (line.length) {
perLineCallback(line, lineNumber); // handle last line
}
fs.close(fd, (error: Error) => {
if (error) {
reject(error);
} else {
resolve(null);
}
});
});
});
});
});
}
}
export class FileMatch implements ISerializedFileMatch {
......
......@@ -7,9 +7,9 @@
import { Server } from 'vs/base/parts/ipc/node/ipc.cp';
import { SearchWorkerChannel } from './searchWorkerIpc';
import { SearchWorker } from './searchWorker';
import { SearchWorkerManager } from './searchWorker';
const server = new Server();
const worker = new SearchWorker();
const worker = new SearchWorkerManager();
const channel = new SearchWorkerChannel(worker);
server.registerChannel('searchWorker', channel);
......@@ -9,12 +9,11 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { ISerializedFileMatch } from '../search';
import { IPatternInfo } from 'vs/platform/search/common/search';
import { SearchWorker } from './searchWorker';
import { SearchWorkerManager } from './searchWorker';
export interface ISearchWorkerConfig {
pattern: IPatternInfo;
fileEncoding: string;
id: number;
}
export interface ISearchWorkerSearchArgs {
......@@ -42,7 +41,7 @@ export interface ISearchWorkerChannel extends IChannel {
}
export class SearchWorkerChannel implements ISearchWorkerChannel {
constructor(private worker: SearchWorker) {
constructor(private worker: SearchWorkerManager) {
}
call(command: string, arg?: any): TPromise<any> {
......
......@@ -16,6 +16,7 @@ import { LineMatch } from 'vs/platform/search/common/search';
import { FileWalker, Engine as FileSearchEngine } from 'vs/workbench/services/search/node/fileSearch';
import { IRawFileMatch } from 'vs/workbench/services/search/node/search';
import { Engine as TextSearchEngine } from 'vs/workbench/services/search/node/textSearch';
import { TextSearchWorkerProvider } from 'vs/workbench/services/search/node/textSearchWorkerProvider';
function count(lineMatches: LineMatch[]): number {
let count = 0;
......@@ -34,6 +35,8 @@ function rootfolders() {
return [path.normalize(require.toUrl('./fixtures'))];
}
const textSearchWorkerProvider = new TextSearchWorkerProvider();
suite('Search', () => {
test('Files: *.js', function (done: () => void) {
......@@ -622,7 +625,7 @@ suite('Search', () => {
contentPattern: { pattern: 'GameOfLife', modifiers: 'i' }
};
let engine = new TextSearchEngine(config, new FileWalker(config));
let engine = new TextSearchEngine(config, new FileWalker(config), textSearchWorkerProvider);
engine.search((result) => {
if (result && result.lineMatches) {
......@@ -643,7 +646,7 @@ suite('Search', () => {
contentPattern: { pattern: 'Game.?fL\\w?fe', isRegExp: true }
};
let engine = new TextSearchEngine(config, new FileWalker(config));
let engine = new TextSearchEngine(config, new FileWalker(config), textSearchWorkerProvider);
engine.search((result) => {
if (result && result.lineMatches) {
......@@ -664,7 +667,7 @@ suite('Search', () => {
contentPattern: { pattern: 'GameOfLife', isWordMatch: true, isCaseSensitive: true }
};
let engine = new TextSearchEngine(config, new FileWalker(config));
let engine = new TextSearchEngine(config, new FileWalker(config), textSearchWorkerProvider);
engine.search((result) => {
if (result && result.lineMatches) {
......@@ -685,7 +688,7 @@ suite('Search', () => {
contentPattern: { pattern: 'Helvetica', modifiers: 'i' }
};
let engine = new TextSearchEngine(config, new FileWalker(config));
let engine = new TextSearchEngine(config, new FileWalker(config), textSearchWorkerProvider);
engine.search((result) => {
if (result && result.lineMatches) {
......@@ -706,7 +709,7 @@ suite('Search', () => {
contentPattern: { pattern: 'e', modifiers: 'i' }
};
let engine = new TextSearchEngine(config, new FileWalker(config));
let engine = new TextSearchEngine(config, new FileWalker(config), textSearchWorkerProvider);
engine.search((result) => {
if (result && result.lineMatches) {
......@@ -728,7 +731,7 @@ suite('Search', () => {
excludePattern: { '**/examples': true }
};
let engine = new TextSearchEngine(config, new FileWalker(config));
let engine = new TextSearchEngine(config, new FileWalker(config), textSearchWorkerProvider);
engine.search((result) => {
if (result && result.lineMatches) {
......@@ -750,7 +753,7 @@ suite('Search', () => {
includePattern: { '**/examples/**': true }
};
let engine = new TextSearchEngine(config, new FileWalker(config));
let engine = new TextSearchEngine(config, new FileWalker(config), textSearchWorkerProvider);
engine.search((result) => {
if (result && result.lineMatches) {
......@@ -773,7 +776,7 @@ suite('Search', () => {
excludePattern: { '**/examples/small.js': true }
};
let engine = new TextSearchEngine(config, new FileWalker(config));
let engine = new TextSearchEngine(config, new FileWalker(config), textSearchWorkerProvider);
engine.search((result) => {
if (result && result.lineMatches) {
......@@ -795,7 +798,7 @@ suite('Search', () => {
maxResults: 520
};
let engine = new TextSearchEngine(config, new FileWalker(config));
let engine = new TextSearchEngine(config, new FileWalker(config), textSearchWorkerProvider);
engine.search((result) => {
if (result && result.lineMatches) {
......@@ -819,7 +822,7 @@ suite('Search', () => {
contentPattern: { pattern: 'ahsogehtdas', modifiers: 'i' }
};
let engine = new TextSearchEngine(config, new FileWalker(config));
let engine = new TextSearchEngine(config, new FileWalker(config), textSearchWorkerProvider);
engine.search((result) => {
if (result && result.lineMatches) {
......
......@@ -9,6 +9,7 @@ import winjs = require('vs/base/common/winjs.base');
import errors = require('vs/base/common/errors');
import URI from 'vs/base/common/uri';
import { IFileService } from 'vs/platform/files/common/files';
import product from 'vs/platform/product';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IOptions } from 'vs/workbench/common/options';
......@@ -36,7 +37,7 @@ export class WorkspaceStats {
const workspace = this.contextService.getWorkspace();
tags['workspace.empty'] = !workspace;
const folder = workspace ? workspace.resource : this.findFolder(workbenchOptions);
const folder = workspace ? workspace.resource : product.quality !== 'stable' && this.findFolder(workbenchOptions);
if (folder && this.fileService) {
return this.fileService.resolveFile(folder).then(stats => {
let names = stats.children.map(c => c.name);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册