Merge remote-tracking branch 'origin/master' into alex/asar

This commit is contained in:
Alex Dima
2018-01-29 15:34:35 +01:00
52 changed files with 832 additions and 461 deletions
+3 -17
View File
@@ -93,7 +93,7 @@ const BUNDLED_FILE_HEADER = [
' *--------------------------------------------------------*/'
].join('\n');
const languages = i18n.defaultLanguages.concat(process.env.VSCODE_QUALITY !== 'stable' ? i18n.extraLanguages: []);
const languages = i18n.defaultLanguages.concat(process.env.VSCODE_QUALITY !== 'stable' ? i18n.extraLanguages : []);
gulp.task('clean-optimized-vscode', util.rimraf('out-vscode'));
gulp.task('optimize-vscode', ['clean-optimized-vscode', 'compile-build', 'compile-extensions-build'], common.optimizeTask({
@@ -411,7 +411,7 @@ gulp.task('vscode-translations-push', function () {
gulp.src(pathToSetup).pipe(i18n.createXlfFilesForIsl()),
gulp.src(pathToExtensions).pipe(i18n.createXlfFilesForExtensions())
).pipe(i18n.findObsoleteResources(apiHostname, apiName, apiToken)
).pipe(i18n.pushXlfFiles(apiHostname, apiName, apiToken));
).pipe(i18n.pushXlfFiles(apiHostname, apiName, apiToken));
});
gulp.task('vscode-translations-push-test', function () {
@@ -424,7 +424,7 @@ gulp.task('vscode-translations-push-test', function () {
gulp.src(pathToSetup).pipe(i18n.createXlfFilesForIsl()),
gulp.src(pathToExtensions).pipe(i18n.createXlfFilesForExtensions())
).pipe(i18n.findObsoleteResources(apiHostname, apiName, apiToken)
).pipe(vfs.dest('../vscode-transifex-input'));
).pipe(vfs.dest('../vscode-transifex-input'));
});
gulp.task('vscode-translations-pull', function () {
@@ -599,17 +599,3 @@ gulp.task('generate-vscode-configuration', () => {
console.error(e.toString());
});
});
//#region Built-In Extensions
gulp.task('clean-builtin-extensions', util.rimraf('.build/builtInExtensions'));
gulp.task('download-builtin-extensions', ['clean-builtin-extensions'], function () {
const marketplaceExtensions = es.merge(...builtInExtensions.map(extension => {
return ext.fromMarketplace(extension.name, extension.version)
.pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`));
}));
return marketplaceExtensions
.pipe(util.setExecutableBit(['**/*.sh']))
.pipe(vfs.dest('.build/builtInExtensions'));
});
//#endregion
+98 -8
View File
@@ -7,26 +7,116 @@
const fs = require('fs');
const path = require('path');
const mkdirp = require('mkdirp');
const rimraf = require('rimraf');
const es = require('event-stream');
const rename = require('gulp-rename');
const vfs = require('vinyl-fs');
const ext = require('./extensions');
const util = require('gulp-util');
const root = path.dirname(path.dirname(__dirname));
const builtInExtensions = require('../builtInExtensions');
const controlFilePath = path.join(process.env['HOME'], '.vscode-oss-dev', 'extensions', 'control.json');
function getExtensionPath(extension) {
return path.join(root, '.build', 'builtInExtensions', extension.name);
}
function isUpToDate(extension) {
const packagePath = path.join(root, '.build', 'builtInExtensions', extension.name, 'package.json');
const packagePath = path.join(getExtensionPath(extension), 'package.json');
if (!fs.existsSync(packagePath)) {
return false;
}
const packageContents = fs.readFileSync(packagePath);
try {
const diskVersion = JSON.parse(packageContents).version;
return (diskVersion === extension.version);
} catch(err) {
} catch (err) {
return false;
}
}
const builtInExtensions = require('../builtInExtensions');
builtInExtensions.forEach((extension) => {
if (!isUpToDate(extension)) {
process.exit(1);
function syncMarketplaceExtension(extension) {
if (isUpToDate(extension)) {
util.log(util.colors.blue('[marketplace]'), `${extension.name}@${extension.version}`, util.colors.green('✔︎'));
return es.readArray([]);
}
});
process.exit(0);
rimraf.sync(getExtensionPath(extension));
return ext.fromMarketplace(extension.name, extension.version)
.pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`))
.pipe(vfs.dest('.build/builtInExtensions'))
.on('end', () => util.log(util.colors.blue('[marketplace]'), extension.name, util.colors.green('✔︎')));
}
function syncExtension(extension, controlState) {
switch (controlState) {
case 'disabled':
util.log(util.colors.blue('[disabled]'), util.colors.gray(extension.name));
rimraf.sync(getExtensionPath(extension));
return es.readArray([]);
case 'marketplace':
return syncMarketplaceExtension(extension);
default:
if (!fs.existsSync(controlState)) {
util.log(util.colors.red(`Error: Built-in extension '${extension.name}' is configured to run from '${controlState}' but that path does not exist.`));
return es.readArray([]);
} else if (!fs.existsSync(path.join(controlState, 'package.json'))) {
util.log(util.colors.red(`Error: Built-in extension '${extension.name}' is configured to run from '${controlState}' but there is no 'package.json' file in that directory.`));
return es.readArray([]);
}
util.log(util.colors.blue('[local]'), `${extension.name}: ${controlState}`, util.colors.green('✔︎'));
return es.readArray([]);
}
}
function readControlFile() {
try {
return JSON.parse(fs.readFileSync(controlFilePath, 'utf8'));
} catch (err) {
return {};
}
}
function writeControlFile(control) {
mkdirp.sync(path.dirname(controlFilePath));
fs.writeFileSync(controlFilePath, JSON.stringify(control, null, 2));
}
function main() {
util.log('Syncronizing built-in extensions...');
util.log('Control file:', controlFilePath);
const control = readControlFile();
const streams = [];
for (const extension of builtInExtensions) {
let controlState = control[extension.name] || 'marketplace';
control[extension.name] = controlState;
streams.push(syncExtension(extension, controlState));
}
writeControlFile(control);
es.merge(streams)
.on('error', err => {
console.error(err);
process.exit(1);
})
.on('end', () => {
util.log(`${streams.length} built-in extensions processed.`);
process.exit(0);
});
}
main();
+1 -2
View File
@@ -17,9 +17,8 @@ set CODE=".build\electron\%NAMESHORT%"
node build\lib\electron.js
if %errorlevel% neq 0 node .\node_modules\gulp\bin\gulp.js electron
:: Get built-in extensions
:: Sync built-in extensions
node build\lib\builtInExtensions.js
if %errorlevel% neq 0 node .\node_modules\gulp\bin\gulp.js download-builtin-extensions
:: Build
if not exist out node .\node_modules\gulp\bin\gulp.js compile
+2 -2
View File
@@ -24,8 +24,8 @@ function code() {
# Get electron
node build/lib/electron.js || ./node_modules/.bin/gulp electron
# Get built-in extensions
node build/lib/builtInExtensions.js || ./node_modules/.bin/gulp download-builtin-extensions
# Sync built-in extensions
node build/lib/builtInExtensions.js
# Build
test -d out || ./node_modules/.bin/gulp compile
+6
View File
@@ -17,6 +17,12 @@ export interface IRenderer<TElement, TTemplateData> {
disposeTemplate(templateData: TTemplateData): void;
}
export interface IListOpenEvent<T> {
elements: T[];
indexes: number[];
browserEvent?: UIEvent;
}
export interface IListEvent<T> {
elements: T[];
indexes: number[];
+7 -3
View File
@@ -6,7 +6,7 @@
import 'vs/css!./list';
import { IDisposable } from 'vs/base/common/lifecycle';
import { range } from 'vs/base/common/arrays';
import { IDelegate, IRenderer, IListEvent } from './list';
import { IDelegate, IRenderer, IListEvent, IListOpenEvent } from './list';
import { List, IListStyles, IListOptions } from './listWidget';
import { IPagedModel } from 'vs/base/common/paging';
import Event, { mapEvent } from 'vs/base/common/event';
@@ -97,6 +97,10 @@ export class PagedList<T> {
return mapEvent(this.list.onFocusChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
}
get onOpen(): Event<IListOpenEvent<T>> {
return mapEvent(this.list.onOpen, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
}
get onSelectionChange(): Event<IListEvent<T>> {
return mapEvent(this.list.onSelectionChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
}
@@ -126,8 +130,8 @@ export class PagedList<T> {
this.list.scrollTop = scrollTop;
}
open(indexes: number[]): void {
this.list.open(indexes);
open(indexes: number[], browserEvent?: UIEvent): void {
this.list.open(indexes, browserEvent);
}
setFocus(indexes: number[]): void {
+34 -10
View File
@@ -15,7 +15,7 @@ import { KeyCode } from 'vs/base/common/keyCodes';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import Event, { Emitter, EventBufferer, chain, mapEvent, anyEvent } from 'vs/base/common/event';
import { domEvent } from 'vs/base/browser/event';
import { IDelegate, IRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent } from './list';
import { IDelegate, IRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IListOpenEvent } from './list';
import { ListView, IListViewOptions } from './listView';
import { Color } from 'vs/base/common/color';
import { mixin } from 'vs/base/common/objects';
@@ -261,6 +261,7 @@ function isInputElement(e: HTMLElement): boolean {
class KeyboardController<T> implements IDisposable {
private disposables: IDisposable[];
private openController: IOpenController;
constructor(
private list: List<T>,
@@ -270,6 +271,8 @@ class KeyboardController<T> implements IDisposable {
const multipleSelectionSupport = !(options.multipleSelectionSupport === false);
this.disposables = [];
this.openController = options.openController || DefaultOpenController;
const onKeyDown = chain(domEvent(view.domNode, 'keydown'))
.filter(e => !isInputElement(e.target as HTMLElement))
.map(e => new StandardKeyboardEvent(e));
@@ -290,7 +293,10 @@ class KeyboardController<T> implements IDisposable {
e.preventDefault();
e.stopPropagation();
this.list.setSelection(this.list.getFocus());
this.list.open(this.list.getFocus());
if (this.openController.shouldOpen(e.browserEvent)) {
this.list.open(this.list.getFocus(), e.browserEvent);
}
}
private onUpArrow(e: StandardKeyboardEvent): void {
@@ -357,10 +363,15 @@ const DefaultMultipleSelectionContoller = {
isSelectionRangeChangeEvent
};
const DefaultOpenController = {
shouldOpen: (event: UIEvent) => true
};
class MouseController<T> implements IDisposable {
private multipleSelectionSupport: boolean;
private multipleSelectionController: IMultipleSelectionController<T> | undefined;
private multipleSelectionController: IMultipleSelectionController<T>;
private openController: IOpenController;
private didJustPressContextMenuKey: boolean = false;
private disposables: IDisposable[] = [];
@@ -406,6 +417,8 @@ class MouseController<T> implements IDisposable {
this.multipleSelectionController = options.multipleSelectionController || DefaultMultipleSelectionContoller;
}
this.openController = options.openController || DefaultOpenController;
view.onMouseDown(this.onMouseDown, this, this.disposables);
view.onMouseClick(this.onPointer, this, this.disposables);
view.onMouseDblClick(this.onDoubleClick, this, this.disposables);
@@ -458,7 +471,10 @@ class MouseController<T> implements IDisposable {
if (this.options.selectOnMouseDown) {
this.list.setSelection([focus]);
this.list.open([focus]);
if (this.openController.shouldOpen(e.browserEvent)) {
this.list.open([focus], e.browserEvent);
}
}
}
@@ -470,7 +486,10 @@ class MouseController<T> implements IDisposable {
if (!this.options.selectOnMouseDown) {
const focus = this.list.getFocus();
this.list.setSelection(focus);
this.list.open(focus);
if (this.openController.shouldOpen(e.browserEvent)) {
this.list.open(focus, e.browserEvent);
}
}
}
@@ -523,6 +542,10 @@ export interface IMultipleSelectionController<T> {
isSelectionRangeChangeEvent(event: IListMouseEvent<T> | IListTouchEvent<T>): boolean;
}
export interface IOpenController {
shouldOpen(event: UIEvent): boolean;
}
export interface IListOptions<T> extends IListViewOptions, IListStyles {
identityProvider?: IIdentityProvider<T>;
ariaLabel?: string;
@@ -533,6 +556,7 @@ export interface IListOptions<T> extends IListViewOptions, IListStyles {
verticalScrollMode?: ScrollbarVisibility;
multipleSelectionSupport?: boolean;
multipleSelectionController?: IMultipleSelectionController<T>;
openController?: IOpenController;
}
export interface IListStyles {
@@ -708,9 +732,9 @@ export class List<T> implements ISpliceable<T>, IDisposable {
readonly onContextMenu: Event<IListContextMenuEvent<T>> = Event.None;
private _onOpen = new Emitter<number[]>();
@memoize get onOpen(): Event<IListEvent<T>> {
return mapEvent(this._onOpen.event, indexes => this.toListEvent({ indexes }));
private _onOpen = new Emitter<IListOpenEvent<T>>();
@memoize get onOpen(): Event<IListOpenEvent<T>> {
return this._onOpen.event;
}
private _onPin = new Emitter<number[]>();
@@ -973,8 +997,8 @@ export class List<T> implements ISpliceable<T>, IDisposable {
return this.view.domNode;
}
open(indexes: number[]): void {
this._onOpen.fire(indexes);
open(indexes: number[], browserEvent?: UIEvent): void {
this._onOpen.fire({ indexes, elements: indexes.map(i => this.view.element(i)), browserEvent });
}
pin(indexes: number[]): void {
+22 -5
View File
@@ -38,8 +38,14 @@ export enum ClickBehavior {
ON_MOUSE_UP
}
export enum OpenMode {
SINGLE_CLICK,
DOUBLE_CLICK
}
export interface IControllerOptions {
clickBehavior?: ClickBehavior;
openMode?: OpenMode;
keyboardSupport?: boolean;
}
@@ -82,7 +88,7 @@ export class DefaultController implements _.IController {
private options: IControllerOptions;
constructor(options: IControllerOptions = { clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: true }) {
constructor(options: IControllerOptions = { clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: true, openMode: OpenMode.SINGLE_CLICK }) {
this.options = options;
this.downKeyBindingDispatcher = new KeybindingDispatcher();
@@ -153,6 +159,7 @@ export class DefaultController implements _.IController {
protected onLeftClick(tree: _.ITree, element: any, eventish: ICancelableEvent, origin: string = 'mouse'): boolean {
const payload = { origin: origin, originalEvent: eventish };
const isDoubleClick = (origin === 'mouse' && (<mouse.IMouseEvent>eventish).detail === 2);
if (tree.getInput() === element) {
tree.clearFocus(payload);
@@ -168,16 +175,26 @@ export class DefaultController implements _.IController {
tree.setSelection([element], payload);
tree.setFocus(element, payload);
if (tree.isExpanded(element)) {
tree.collapse(element).done(null, errors.onUnexpectedError);
} else {
tree.expand(element).done(null, errors.onUnexpectedError);
if (this.openOnSingleClick || isDoubleClick) {
if (tree.isExpanded(element)) {
tree.collapse(element).done(null, errors.onUnexpectedError);
} else {
tree.expand(element).done(null, errors.onUnexpectedError);
}
}
}
return true;
}
protected setOpenMode(openMode: OpenMode) {
this.options.openMode = openMode;
}
protected get openOnSingleClick(): boolean {
return this.options.openMode === OpenMode.SINGLE_CLICK;
}
public onContextMenu(tree: _.ITree, element: any, event: _.ContextMenuEvent): boolean {
if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') {
return false; // allow context menu on input fields
@@ -23,7 +23,6 @@ import { GestureEvent } from 'vs/base/browser/touch';
import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
import { FileLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
import * as tree from 'vs/base/parts/tree/browser/tree';
import { DefaultController } from 'vs/base/parts/tree/browser/treeDefaults';
import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { Range, IRange } from 'vs/editor/common/core/range';
@@ -41,7 +40,7 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import URI from 'vs/base/common/uri';
import { TrackedRangeStickiness, IModelDeltaDecoration } from 'vs/editor/common/model';
import { WorkbenchTree } from 'vs/platform/list/browser/listService';
import { WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser/listService';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { Location } from 'vs/editor/common/modes';
@@ -218,7 +217,7 @@ class DataSource implements tree.IDataSource {
}
}
class Controller extends DefaultController {
class Controller extends WorkbenchTreeController {
private _onDidFocus = new Emitter<any>();
readonly onDidFocus: Event<any> = this._onDidFocus.event;
@@ -243,19 +242,22 @@ class Controller extends DefaultController {
}
public onMouseDown(tree: tree.ITree, element: any, event: IMouseEvent): boolean {
var isDoubleClick = event.detail === 2;
if (event.leftButton) {
if (element instanceof FileReferences) {
event.preventDefault();
event.stopPropagation();
return this._expandCollapse(tree, element);
if (this.openOnSingleClick || isDoubleClick) {
event.preventDefault();
event.stopPropagation();
return this._expandCollapse(tree, element);
}
}
var result = super.onClick(tree, element, event);
if (event.ctrlKey || event.metaKey || event.altKey) {
this._onDidOpenToSide.fire(element);
} else if (event.detail === 2) {
} else if (isDoubleClick) {
this._onDidSelect.fire(element);
} else {
} else if (this.openOnSingleClick) {
this._onDidFocus.fire(element);
}
return result;
@@ -631,7 +633,9 @@ export class ReferenceWidget extends PeekViewWidget {
// tree
container.div({ 'class': 'ref-tree inline' }, (div: Builder) => {
const controller = new Controller();
var controller = this._instantiationService.createInstance(Controller, {});
this._callOnDispose.push(controller);
var config = <tree.ITreeConfiguration>{
dataSource: this._instantiationService.createInstance(DataSource),
renderer: this._instantiationService.createInstance(Renderer),
@@ -641,8 +645,7 @@ export class ReferenceWidget extends PeekViewWidget {
var options: tree.ITreeOptions = {
twistiePixels: 20,
ariaLabel: nls.localize('treeAriaLabel', "References"),
keyboardSupport: false
ariaLabel: nls.localize('treeAriaLabel', "References")
};
this._tree = this._instantiationService.createInstance(WorkbenchTree, div.getHTMLElement(), config, options);
+32 -24
View File
@@ -444,8 +444,7 @@ export class SnippetSession {
return false;
}
let ranges: Range[] = [];
let placeholderIndex: number = -1;
let allPossibleSelections: Map<number, Range[]>;
for (const snippet of this._snippets) {
const possibleSelections = snippet.computePossibleSelections();
@@ -453,49 +452,58 @@ export class SnippetSession {
// for the first snippet find the placeholder (and its ranges)
// that contain at least one selection. for all remaining snippets
// the same placeholder (and their ranges) must be used.
if (placeholderIndex < 0) {
if (!allPossibleSelections) {
allPossibleSelections = new Map<number, Range[]>();
possibleSelections.forEach((ranges, index) => {
if (placeholderIndex >= 0) {
return;
}
ranges.sort(Range.compareRangesUsingStarts);
for (const selection of selections) {
if (ranges[0].containsRange(selection)) {
placeholderIndex = index;
allPossibleSelections.set(index, []);
break;
}
}
});
}
if (placeholderIndex < 0) {
if (allPossibleSelections.size === 0) {
// return false if we couldn't associate a selection to
// this (the first) snippet
return false;
}
ranges.push(...possibleSelections.get(placeholderIndex));
// add selections from 'this' snippet so that we know all
// selections for this placeholder
allPossibleSelections.forEach((array, index) => {
array.push(...possibleSelections.get(index));
});
}
if (selections.length !== ranges.length) {
// this means we started at a placeholder with N
// ranges and new have M (N > M) selections.
// So (at least) one placeholder is without selection -> cancel
return false;
}
// also sort (placeholder)-ranges. then walk both arrays and
// make sure the placeholder-ranges contain the corresponding
// sort selections (and later placeholder-ranges). then walk both
// arrays and make sure the placeholder-ranges contain the corresponding
// selection
selections.sort(Range.compareRangesUsingStarts);
ranges.sort(Range.compareRangesUsingStarts);
for (let i = 0; i < ranges.length; i++) {
if (!ranges[i].containsRange(selections[i])) {
return false;
allPossibleSelections.forEach((ranges, index) => {
if (ranges.length !== selections.length) {
allPossibleSelections.delete(index);
return;
}
}
return true;
ranges.sort(Range.compareRangesUsingStarts);
for (let i = 0; i < ranges.length; i++) {
if (!ranges[i].containsRange(selections[i])) {
allPossibleSelections.delete(index);
return;
}
}
});
// from all possible selections we have deleted those
// that don't match with the current selection. if we don't
// have any left, we don't have a selection anymore
return allPossibleSelections.size > 0;
}
}
@@ -303,4 +303,18 @@ suite('SnippetController2', function () {
assertSelections(editor, new Selection(2, 9, 2, 9), new Selection(1, 7, 1, 7));
assertContextKeys(contextKeys, true, false, true);
});
test('“Nested” snippets terminating abruptly in VSCode 1.19.2. #42012', function () {
const ctrl = new SnippetController2(editor, logService, contextKeys);
model.setValue('');
editor.setSelection(new Selection(1, 1, 1, 1));
ctrl.insert('var ${2:${1:name}} = ${1:name} + 1;${0}');
assertSelections(editor, new Selection(1, 5, 1, 9), new Selection(1, 12, 1, 16));
assertContextKeys(contextKeys, true, false, true);
ctrl.next();
assertContextKeys(contextKeys, true, true, true);
});
});
@@ -273,7 +273,7 @@ export class ExtensionManagementService implements IExtensionManagementService {
installingExtension = this.getExtensionsReport()
.then(report => {
if (getMaliciousExtensionsSet(report).has(extension.identifier.id)) {
throw new Error(nls.localize('malicious extension', "Can't install extension since it was reported to be malicious."));
throw new Error(nls.localize('malicious extension', "Can't install extension since it was reported to be problematic."));
} else {
return extension;
}
+250 -27
View File
@@ -5,9 +5,9 @@
'use strict';
import { ITree, ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree';
import { List, IListOptions, isSelectionRangeChangeEvent, isSelectionSingleChangeEvent, IMultipleSelectionController } from 'vs/base/browser/ui/list/listWidget';
import { List, IListOptions, isSelectionRangeChangeEvent, isSelectionSingleChangeEvent, IMultipleSelectionController, IOpenController } from 'vs/base/browser/ui/list/listWidget';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable, toDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle';
import { IDisposable, toDisposable, combinedDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
import { IContextKeyService, IContextKey, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { PagedList, IPagedRenderer } from 'vs/base/browser/ui/list/listPaging';
import { IDelegate, IRenderer, IListMouseEvent, IListTouchEvent } from 'vs/base/browser/ui/list/list';
@@ -20,7 +20,10 @@ import { mixin } from 'vs/base/common/objects';
import { localize } from 'vs/nls';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { DefaultController, IControllerOptions, OpenMode } from 'vs/base/parts/tree/browser/treeDefaults';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import Event, { Emitter } from 'vs/base/common/event';
export type ListWidget = List<any> | PagedList<any> | ITree;
export const IListService = createDecorator<IListService>('listService');
@@ -94,12 +97,17 @@ function createScopedContextKeyService(contextKeyService: IContextKeyService, wi
return result;
}
export const multiSelectModifierSettingKey = 'workbench.multiSelectModifier';
export const multiSelectModifierSettingKey = 'workbench.list.multiSelectModifier';
export const openModeSettingKey = 'workbench.list.openMode';
export function useAltAsMultipleSelectionModifier(configurationService: IConfigurationService): boolean {
function useAltAsMultipleSelectionModifier(configurationService: IConfigurationService): boolean {
return configurationService.getValue(multiSelectModifierSettingKey) === 'alt';
}
function useSingleClickToOpen(configurationService: IConfigurationService): boolean {
return configurationService.getValue(openModeSettingKey) !== 'doubleClick';
}
class MultipleSelectionController<T> implements IMultipleSelectionController<T> {
constructor(private configurationService: IConfigurationService) { }
@@ -117,11 +125,39 @@ class MultipleSelectionController<T> implements IMultipleSelectionController<T>
}
}
class OpenController implements IOpenController {
constructor(private configurationService: IConfigurationService) { }
shouldOpen(event: UIEvent): boolean {
if (event instanceof MouseEvent) {
const isDoubleClick = event.detail === 2;
return useSingleClickToOpen(this.configurationService) || isDoubleClick;
}
return true;
}
}
function handleListControllers<T>(options: IListOptions<T>, configurationService: IConfigurationService): IListOptions<T> {
if (options.multipleSelectionSupport === true && !options.multipleSelectionController) {
options.multipleSelectionController = new MultipleSelectionController(configurationService);
}
options.openController = new OpenController(configurationService);
return options;
}
export class WorkbenchList<T> extends List<T> {
readonly contextKeyService: IContextKeyService;
private listDoubleSelection: IContextKey<boolean>;
private _useAltAsMultipleSelectionModifier: boolean;
constructor(
container: HTMLElement,
delegate: IDelegate<T>,
@@ -130,31 +166,45 @@ export class WorkbenchList<T> extends List<T> {
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService
@IConfigurationService private configurationService: IConfigurationService
) {
const multipleSelectionSupport = !(options.multipleSelectionSupport === false);
super(container, delegate, renderers, mixin(handleListControllers(options, configurationService), { keyboardSupport: false } as IListOptions<any>, false));
if (multipleSelectionSupport && !options.multipleSelectionController) {
options.multipleSelectionController = new MultipleSelectionController(configurationService);
}
super(container, delegate, renderers, mixin(options, useAltAsMultipleSelectionModifier(configurationService)));
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
this.listDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService);
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
this.disposables.push(combinedDisposable([
this.contextKeyService,
(listService as ListService).register(this),
attachListStyler(this, themeService),
this.onSelectionChange(() => this.listDoubleSelection.set(this.getSelection().length === 2))
]));
this.registerListeners();
}
public get useAltAsMultipleSelectionModifier(): boolean {
return this._useAltAsMultipleSelectionModifier;
}
private registerListeners(): void {
this.disposables.push(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(this.configurationService);
}
}));
}
}
export class WorkbenchPagedList<T> extends PagedList<T> {
readonly contextKeyService: IContextKeyService;
private disposable: IDisposable;
private disposables: IDisposable[] = [];
private _useAltAsMultipleSelectionModifier: boolean;
constructor(
container: HTMLElement,
@@ -164,57 +214,100 @@ export class WorkbenchPagedList<T> extends PagedList<T> {
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService
@IConfigurationService private configurationService: IConfigurationService
) {
const multipleSelectionSupport = !(options.multipleSelectionSupport === false);
super(container, delegate, renderers, mixin(handleListControllers(options, configurationService), { keyboardSupport: false } as IListOptions<any>, false));
if (multipleSelectionSupport && !options.multipleSelectionController) {
options.multipleSelectionController = new MultipleSelectionController(configurationService);
}
super(container, delegate, renderers, mixin(options, useAltAsMultipleSelectionModifier(configurationService)));
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
this.disposable = combinedDisposable([
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
this.disposables.push(combinedDisposable([
this.contextKeyService,
(listService as ListService).register(this),
attachListStyler(this, themeService)
]);
]));
this.registerListeners();
}
public get useAltAsMultipleSelectionModifier(): boolean {
return this._useAltAsMultipleSelectionModifier;
}
private registerListeners(): void {
this.disposables.push(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(this.configurationService);
}
}));
}
dispose(): void {
this.disposable.dispose();
this.disposables = dispose(this.disposables);
}
}
export class WorkbenchTree extends Tree {
readonly contextKeyService: IContextKeyService;
protected disposables: IDisposable[] = [];
private listDoubleSelection: IContextKey<boolean>;
private _openOnSingleClick: boolean;
private _useAltAsMultipleSelectionModifier: boolean;
constructor(
container: HTMLElement,
configuration: ITreeConfiguration,
options: ITreeOptions,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService
@IThemeService themeService: IThemeService,
@IConfigurationService private configurationService: IConfigurationService
) {
super(container, configuration, options);
super(container, configuration, mixin(options, { keyboardSupport: false } as ITreeOptions, false));
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
this.listDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService);
this._openOnSingleClick = useSingleClickToOpen(configurationService);
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
this.disposables.push(
this.contextKeyService,
(listService as ListService).register(this),
attachListStyler(this, themeService)
);
this.registerListeners();
}
public get openOnSingleClick(): boolean {
return this._openOnSingleClick;
}
public get useAltAsMultipleSelectionModifier(): boolean {
return this._useAltAsMultipleSelectionModifier;
}
private registerListeners(): void {
this.disposables.push(this.onDidChangeSelection(() => {
const selection = this.getSelection();
this.listDoubleSelection.set(selection && selection.length === 2);
}));
this.disposables.push(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(openModeSettingKey)) {
this._openOnSingleClick = useSingleClickToOpen(this.configurationService);
}
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(this.configurationService);
}
}));
}
dispose(): void {
@@ -222,6 +315,123 @@ export class WorkbenchTree extends Tree {
}
}
export class WorkbenchTreeController extends DefaultController {
protected disposables: IDisposable[] = [];
constructor(
options: IControllerOptions,
@IConfigurationService private configurationService: IConfigurationService
) {
super(options);
// if the open mode is not set, we configure it based on settings
if (isUndefinedOrNull(options.openMode)) {
this.setOpenMode(this.getOpenModeSetting());
this.registerListeners();
}
}
private registerListeners(): void {
this.disposables.push(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(openModeSettingKey)) {
this.setOpenMode(this.getOpenModeSetting());
}
}));
}
private getOpenModeSetting(): OpenMode {
return useSingleClickToOpen(this.configurationService) ? OpenMode.SINGLE_CLICK : OpenMode.DOUBLE_CLICK;
}
dispose(): void {
this.disposables = dispose(this.disposables);
}
}
export interface IOpenResourceOptions {
editorOptions: IEditorOptions;
sideBySide: boolean;
element: any;
payload: any;
}
export interface IResourceResultsNavigationOptions {
openOnFocus: boolean;
}
export default class ResourceResultsNavigation extends Disposable {
private _openResource: Emitter<IOpenResourceOptions> = new Emitter<IOpenResourceOptions>();
public readonly openResource: Event<IOpenResourceOptions> = this._openResource.event;
constructor(private tree: WorkbenchTree, private options?: IResourceResultsNavigationOptions) {
super();
this.registerListeners();
}
private registerListeners(): void {
if (this.options && this.options.openOnFocus) {
this._register(this.tree.onDidChangeFocus(e => this.onFocus(e)));
}
this._register(this.tree.onDidChangeSelection(e => this.onSelection(e)));
}
private onFocus({ payload }: any): void {
const element = this.tree.getFocus();
this.tree.setSelection([element], { fromFocus: true });
const originalEvent: KeyboardEvent | MouseEvent = payload && payload.originalEvent;
const isMouseEvent = payload && payload.origin === 'mouse';
const isDoubleClick = isMouseEvent && originalEvent && originalEvent.detail === 2;
if (!isMouseEvent || this.tree.openOnSingleClick || isDoubleClick) {
this._openResource.fire({
editorOptions: {
preserveFocus: true,
pinned: false,
revealIfVisible: true
},
sideBySide: false,
element,
payload
});
}
}
private onSelection({ payload }: any): void {
if (payload && payload.fromFocus) {
return;
}
const originalEvent: KeyboardEvent | MouseEvent = payload && payload.originalEvent;
const isMouseEvent = payload && payload.origin === 'mouse';
const isDoubleClick = isMouseEvent && originalEvent && originalEvent.detail === 2;
if (!isMouseEvent || this.tree.openOnSingleClick || isDoubleClick) {
if (isDoubleClick && originalEvent) {
originalEvent.preventDefault(); // focus moves to editor, we need to prevent default
}
const isFromKeyboard = payload && payload.origin === 'keyboard';
const sideBySide = (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey || originalEvent.altKey));
const preserveFocus = !((isFromKeyboard && (!payload || !payload.preserveFocus)) || isDoubleClick || (payload && payload.focusEditor));
this._openResource.fire({
editorOptions: {
preserveFocus,
pinned: isDoubleClick,
revealIfVisible: true
},
sideBySide,
element: this.tree.getSelection()[0],
payload
});
}
}
}
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({
@@ -230,7 +440,7 @@ configurationRegistry.registerConfiguration({
'title': localize('workbenchConfigurationTitle', "Workbench"),
'type': 'object',
'properties': {
'workbench.multiSelectModifier': {
'workbench.list.multiSelectModifier': {
'type': 'string',
'enum': ['ctrlCmd', 'alt'],
'enumDescriptions': [
@@ -244,7 +454,20 @@ configurationRegistry.registerConfiguration({
'- `ctrlCmd` refers to a value the setting can take and should not be localized.',
'- `Control` and `Command` refer to the modifier keys Ctrl or Cmd on the keyboard and can be localized.'
]
}, "The modifier to be used to add an item to a multi-selection with the mouse (for example in trees and lists, if supported). `ctrlCmd` maps to `Control` on Windows and Linux and to `Command` on macOS. The 'Open to Side' mouse gestures - if supported - will adapt such that they do not conflict with the multiselect modifier.")
}, "The modifier to be used to add an item in trees and lists to a multi-selection with the mouse (if supported). `ctrlCmd` maps to `Control` on Windows and Linux and to `Command` on macOS. The 'Open to Side' mouse gestures - if supported - will adapt such that they do not conflict with the multiselect modifier.")
},
'workbench.list.openMode': {
'type': 'string',
'enum': ['singleClick', 'doubleClick'],
'enumDescriptions': [
localize('openMode.singleClick', "Opens items on mouse single click."),
localize('openMode.doubleClick', "Open items on mouse double click.")
],
'default': 'singleClick',
'description': localize({
key: 'openModeModifier',
comment: ['`singleClick` and `doubleClick` refers to a value the setting can take and should not be localized.']
}, "Controls how to open items in trees and lists using the mouse (if supported). Set to `singleClick` to open items with a single mouse click and `doubleClick` to only open via mouse double click. For parents with children in trees, this setting will control if a single click expands the parent or a double click. Note that some trees and lists might chose to ignore this setting if it is not applicable. ")
}
}
});
+3 -2
View File
@@ -5495,13 +5495,14 @@ declare module 'vscode' {
* will be matched against the file paths of resulting matches relative to their workspace. Use a [relative pattern](#RelativePattern)
* to restrict the search results to a [workspace folder](#WorkspaceFolder).
* @param exclude A [glob pattern](#GlobPattern) that defines files and folders to exclude. The glob pattern
* will be matched against the file paths of resulting matches relative to their workspace.
* will be matched against the file paths of resulting matches relative to their workspace. When `undefined` only default excludes will
* apply, when `null` no excludes will apply.
* @param maxResults An upper-bound for the result.
* @param token A token that can be used to signal cancellation to the underlying search engine.
* @return A thenable that resolves to an array of resource identifiers. Will return no results if no
* [workspace folders](#workspace.workspaceFolders) are opened.
*/
export function findFiles(include: GlobPattern, exclude?: GlobPattern, maxResults?: number, token?: CancellationToken): Thenable<Uri[]>;
export function findFiles(include: GlobPattern, exclude?: GlobPattern | null, maxResults?: number, token?: CancellationToken): Thenable<Uri[]>;
/**
* Save all dirty files.
+3 -3
View File
@@ -159,7 +159,7 @@ declare module 'vscode' {
export function registerFileSystemProvider(scheme: string, provider: FileSystemProvider): Disposable;
/**
* Updates the workspace folders of the currently opened workspace. This method allows to add, remove
* Updates the [workspace folders](#workspace.workspaceFolders) of the currently opened workspace. This method allows to add, remove
* and change workspace folders a the same time. Use the [onDidChangeWorkspaceFolders()](#onDidChangeWorkspaceFolders)
* event to get notified when the workspace folders have been updated.
*
@@ -181,11 +181,11 @@ declare module 'vscode' {
* It is valid to remove an existing workspace folder and add it again with a different name
* to rename that folder.
*
* Note: if the first workspace folder is added, removed or changed, all extensions will be restarted
* **Note:** if the first workspace folder is added, removed or changed, all extensions will be restarted
* so that the (deprecated) `rootPath` property is updated to point to the first workspace
* folder.
*
* Note: it is not valid to call [updateWorkspaceFolders()](#updateWorkspaceFolders) multiple times
* **Note:** it is not valid to call [updateWorkspaceFolders()](#updateWorkspaceFolders) multiple times
* without waiting for the [onDidChangeWorkspaceFolders()](#onDidChangeWorkspaceFolders) to fire.
*
* @param start the zero-based location in the list of currently opened [workspace folders](#WorkspaceFolder)
@@ -97,7 +97,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
// --- search ---
$startSearch(includePattern: string, includeFolder: string, excludePattern: string, maxResults: number, requestId: number): Thenable<URI[]> {
$startSearch(includePattern: string, includeFolder: string, excludePatternOrDisregardExcludes: string | false, maxResults: number, requestId: number): Thenable<URI[]> {
const workspace = this._contextService.getWorkspace();
if (!workspace.folders.length) {
return undefined;
@@ -129,7 +129,8 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
type: QueryType.File,
maxResults,
includePattern: { [typeof includePattern === 'string' ? includePattern : undefined]: true },
excludePattern: { [typeof excludePattern === 'string' ? excludePattern : undefined]: true },
excludePattern: { [typeof excludePatternOrDisregardExcludes === 'string' ? excludePatternOrDisregardExcludes : undefined]: true },
disregardExcludeSettings: excludePatternOrDisregardExcludes === false,
useRipgrep,
ignoreSymlinks
};
@@ -168,4 +169,4 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
return result.results.every(each => each.success === true);
});
}
}
}
@@ -346,7 +346,7 @@ export interface MainThreadTelemetryShape extends IDisposable {
}
export interface MainThreadWorkspaceShape extends IDisposable {
$startSearch(includePattern: string, includeFolder: string, excludePattern: string, maxResults: number, requestId: number): Thenable<UriComponents[]>;
$startSearch(includePattern: string, includeFolder: string, excludePatternOrDisregardExcludes: string | false, maxResults: number, requestId: number): Thenable<UriComponents[]>;
$cancelSearch(requestId: number): Thenable<boolean>;
$saveAll(includeUntitled?: boolean): Thenable<boolean>;
$updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, workspaceFoldersToAdd: { uri: UriComponents, name?: string }[]): Thenable<void>;
@@ -603,11 +603,11 @@ export function toGlobPattern(pattern: vscode.GlobPattern): string | IRelativePa
return pattern;
}
if (!isRelativePattern(pattern)) {
return undefined;
if (isRelativePattern(pattern)) {
return new types.RelativePattern(pattern.base, pattern.pattern);
}
return new types.RelativePattern(pattern.base, pattern.pattern);
return pattern; // preserve `undefined` and `null`
}
function isRelativePattern(obj: any): obj is vscode.RelativePattern {
+11 -11
View File
@@ -222,7 +222,9 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
}
// Try to accept directly
return this.trySetWorkspaceFolders(newWorkspaceFolders);
this.trySetWorkspaceFolders(newWorkspaceFolders);
return true;
}
getWorkspaceFolder(uri: vscode.Uri, resolveParent?: boolean): vscode.WorkspaceFolder {
@@ -281,7 +283,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
return normalize(result, true);
}
private trySetWorkspaceFolders(folders: vscode.WorkspaceFolder[]): boolean {
private trySetWorkspaceFolders(folders: vscode.WorkspaceFolder[]): void {
// Update directly here. The workspace is unconfirmed as long as we did not get an
// acknowledgement from the main side (via $acceptWorkspaceData)
@@ -292,11 +294,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
configuration: this._actualWorkspace.configuration,
folders
} as IWorkspaceData, this._actualWorkspace).workspace;
return true;
}
return false;
}
$acceptWorkspaceData(data: IWorkspaceData): void {
@@ -331,16 +329,18 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
}
}
let excludePattern: string;
if (exclude) {
let excludePatternOrDisregardExcludes: string | false;
if (exclude === null) {
excludePatternOrDisregardExcludes = false;
} else if (exclude) {
if (typeof exclude === 'string') {
excludePattern = exclude;
excludePatternOrDisregardExcludes = exclude;
} else {
excludePattern = exclude.pattern;
excludePatternOrDisregardExcludes = exclude.pattern;
}
}
const result = this._proxy.$startSearch(includePattern, includeFolder, excludePattern, maxResults, requestId);
const result = this._proxy.$startSearch(includePattern, includeFolder, excludePatternOrDisregardExcludes, maxResults, requestId);
if (token) {
token.onCancellationRequested(() => this._proxy.$cancelSearch(requestId));
}
@@ -270,17 +270,17 @@ function registerEditorCommands() {
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: void 0,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_U),
handler: (accessor, resource: URI, context: IEditorIdentifier | IEditorCommandsContext) => {
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const model = editorGroupService.getStacksModel();
const editorService = accessor.get(IWorkbenchEditorService);
const contexts = getMultiSelectedEditorContexts(toEditorIdentifier(context, editorGroupService), accessor.get(IListService));
const contexts = getMultiSelectedEditorContexts(context, accessor.get(IListService));
let positionOne: { unmodifiedOnly: boolean } = void 0;
let positionTwo: { unmodifiedOnly: boolean } = void 0;
let positionThree: { unmodifiedOnly: boolean } = void 0;
contexts.forEach(c => {
switch (model.positionOfGroup(c.group)) {
switch (model.positionOfGroup(model.getGroup(c.groupId))) {
case Position.ONE: positionOne = { unmodifiedOnly: true }; break;
case Position.TWO: positionTwo = { unmodifiedOnly: true }; break;
case Position.THREE: positionThree = { unmodifiedOnly: true }; break;
@@ -296,14 +296,15 @@ function registerEditorCommands() {
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: void 0,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_W),
handler: (accessor, resource: URI, context: IEditorIdentifier | IEditorCommandsContext) => {
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
const contexts = getMultiSelectedEditorContexts(toEditorIdentifier(context, editorGroupService), accessor.get(IListService));
const distinctGroups = distinct(contexts.map(c => c.group));
const contexts = getMultiSelectedEditorContexts(context, accessor.get(IListService));
const distinctGroupIds = distinct(contexts.map(c => c.groupId));
const model = editorGroupService.getStacksModel();
if (distinctGroups.length) {
return editorService.closeEditors(distinctGroups.map(g => editorGroupService.getStacksModel().positionOfGroup(g)));
if (distinctGroupIds.length) {
return editorService.closeEditors(distinctGroupIds.map(gid => model.positionOfGroup(model.getGroup(gid))));
}
const activeEditor = editorService.getActiveEditor();
if (activeEditor) {
@@ -320,21 +321,23 @@ function registerEditorCommands() {
when: void 0,
primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
handler: (accessor, resource: URI, context: IEditorIdentifier | IEditorCommandsContext) => {
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
const contexts = getMultiSelectedEditorContexts(toEditorIdentifier(context, editorGroupService), accessor.get(IListService));
const groups = distinct(contexts.map(context => context.group));
const contexts = getMultiSelectedEditorContexts(context, accessor.get(IListService));
const groupIds = distinct(contexts.map(context => context.groupId));
const model = editorGroupService.getStacksModel();
const editorsToClose = new Map<Position, IEditorInput[]>();
groups.forEach(group => {
const position = editorGroupService.getStacksModel().positionOfGroup(group);
groupIds.forEach(groupId => {
const group = model.getGroup(groupId);
const position = model.positionOfGroup(group);
if (position >= 0) {
editorsToClose.set(position, contexts.map(c => {
if (group === c.group) {
let input = c ? c.editor : void 0;
if (c && groupId === c.groupId) {
let input = group.getEditor(c.editorIndex);
if (!input) {
// Get Top Editor at Position
@@ -373,24 +376,26 @@ function registerEditorCommands() {
when: void 0,
primary: void 0,
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T },
handler: (accessor, resource: URI, context: IEditorIdentifier | IEditorCommandsContext) => {
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
const contexts = getMultiSelectedEditorContexts(toEditorIdentifier(context, editorGroupService), accessor.get(IListService));
const groups = distinct(contexts.map(context => context.group));
const contexts = getMultiSelectedEditorContexts(context, accessor.get(IListService));
const groupIds = distinct(contexts.map(context => context.groupId));
const editorsToClose = new Map<Position, IEditorInput[]>();
const model = editorGroupService.getStacksModel();
groups.forEach(group => {
groupIds.forEach(groupId => {
const group = model.getGroup(groupId);
const inputsToSkip = contexts.map(c => {
if (!!c.editor && c.group === group) {
return c.editor;
if (c.groupId === groupId) {
return group.getEditor(c.editorIndex);
}
return void 0;
}).filter(input => !!input);
const toClose = group.getEditors().filter(input => inputsToSkip.indexOf(input) === -1);
editorsToClose.set(editorGroupService.getStacksModel().positionOfGroup(group), toClose);
editorsToClose.set(model.positionOfGroup(group), toClose);
});
return editorService.closeEditors({
@@ -406,7 +411,7 @@ function registerEditorCommands() {
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: void 0,
primary: void 0,
handler: (accessor, resource: URI, context: IEditorIdentifier | IEditorCommandsContext) => {
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
@@ -425,7 +430,7 @@ function registerEditorCommands() {
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: void 0,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.Enter),
handler: (accessor, resource: URI, context: IEditorIdentifier | IEditorCommandsContext) => {
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
@@ -489,12 +494,13 @@ function registerEditorCommands() {
});
}
function positionAndInput(editorGroupService: IEditorGroupService, editorService: IWorkbenchEditorService, context?: IEditorIdentifier | IEditorCommandsContext): { position: Position, input: IEditorInput } {
function positionAndInput(editorGroupService: IEditorGroupService, editorService: IWorkbenchEditorService, context?: IEditorCommandsContext): { position: Position, input: IEditorInput } {
// Resolve from context
const editorContext = toEditorIdentifier(context, editorGroupService);
let position = editorContext ? editorGroupService.getStacksModel().positionOfGroup(editorContext.group) : null;
let input = editorContext ? editorContext.editor : null;
const model = editorGroupService.getStacksModel();
const group = context ? model.getGroup(context.groupId) : undefined;
let position = group ? model.positionOfGroup(group) : undefined;
let input = group ? group.getEditor(context.editorIndex) : undefined;
// If position or input are not passed in take the position and input of the active editor.
const active = editorService.getActiveEditor();
@@ -506,54 +512,28 @@ function positionAndInput(editorGroupService: IEditorGroupService, editorService
return { position, input };
}
export function getMultiSelectedEditorContexts(editorContext: IEditorIdentifier, listService: IListService): IEditorIdentifier[] {
const elementToContext = (element: IEditorIdentifier | EditorGroup) => element instanceof EditorGroup ? { group: element, editor: void 0 } : element;
export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsContext, listService: IListService): IEditorCommandsContext[] {
const elementToContext = (element: IEditorIdentifier | EditorGroup) =>
element instanceof EditorGroup ? { groupId: element.id, editorIndex: undefined } : { groupId: element.group.id, editorIndex: element.group.indexOf(element.editor) };
// First check for a focused list to return the selected items from
const list = listService.lastFocusedList;
if (list instanceof List && list.isDOMFocused()) {
const selection = list.getSelectedElements();
const focus = list.getFocusedElements();
const focusedElements: (IEditorIdentifier | EditorGroup)[] = list.getFocusedElements();
// need to take into account when editor context is { group: group }
const focus = editorContext ? editorContext : focusedElements.length ? focusedElements.map(elementToContext)[0] : undefined;
// Only respect selection if it contains focused element
if (focus.length && selection && selection.indexOf(focus[0]) >= 0) {
return list.getSelectedElements().filter(e => e instanceof EditorGroup || isEditorIdentifier(e)).map(elementToContext);
}
if (focus) {
const selection: (IEditorIdentifier | EditorGroup)[] = list.getSelectedElements();
// Only respect selection if it contains focused element
if (selection && selection.some(s => s instanceof EditorGroup ? s.id === focus.groupId : s.group.id === focus.groupId && s.group.indexOf(s.editor) === focus.editorIndex)) {
return selection.map(elementToContext);
}
if (focus.length) {
return focus.filter(e => e instanceof EditorGroup || isEditorIdentifier(e)).map(elementToContext);
return [focus];
}
}
// Otherwise go with passed in context
return !!editorContext ? [editorContext] : [];
}
function isEditorIdentifier(object: any): object is IEditorIdentifier {
const identifier = object as IEditorIdentifier;
return identifier && !!identifier.group && !!identifier.editor;
}
function isEditorGroupContext(object: any): object is IEditorCommandsContext {
const context = object as IEditorCommandsContext;
return context && typeof context.groupId === 'number';
}
function toEditorIdentifier(object: IEditorIdentifier | IEditorCommandsContext, editorGroupService: IEditorGroupService): IEditorIdentifier {
if (isEditorIdentifier(object)) {
return object as IEditorIdentifier;
}
if (isEditorGroupContext(object)) {
const stacks = editorGroupService.getStacksModel();
const group = stacks.getGroup(object.groupId);
return {
group,
editor: typeof object.editorIndex === 'number' ? group.getEditor(object.editorIndex) : void 0
};
}
return void 0;
}
@@ -635,7 +635,7 @@ export class TabsTitleControl extends TitleControl {
return void 0; // only for left mouse click
}
const { editor, position } = this.toTabContext(index);
const { editor, position } = this.getGroupPositionAndEditor(index);
if (!this.isTabActionBar(((e as GestureEvent).initialTarget || e.target || e.srcElement) as HTMLElement)) {
setTimeout(() => this.editorService.openEditor(editor, null, position).done(null, errors.onUnexpectedError)); // timeout to keep focus in editor after mouse up
}
@@ -646,7 +646,7 @@ export class TabsTitleControl extends TitleControl {
const showContextMenu = (e: Event) => {
DOM.EventHelper.stop(e);
const { group, editor } = this.toTabContext(index);
const { group, editor } = this.getGroupPositionAndEditor(index);
this.onContextMenu({ group, editor }, e, tab);
};
@@ -668,7 +668,7 @@ export class TabsTitleControl extends TitleControl {
tab.blur();
if (e.button === 1 /* Middle Button*/ && !this.isTabActionBar((e.target || e.srcElement) as HTMLElement)) {
this.closeEditorAction.run(this.toTabContext(index)).done(null, errors.onUnexpectedError);
this.closeEditorAction.run(this.getGroupPositionAndEditor(index)).done(null, errors.onUnexpectedError);
}
}));
@@ -690,7 +690,7 @@ export class TabsTitleControl extends TitleControl {
const event = new StandardKeyboardEvent(e);
let handled = false;
const { group, position, editor } = this.toTabContext(index);
const { group, position, editor } = this.getGroupPositionAndEditor(index);
// Run action on Enter/Space
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
@@ -733,7 +733,7 @@ export class TabsTitleControl extends TitleControl {
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DBLCLICK, (e: MouseEvent) => {
DOM.EventHelper.stop(e);
const { group, editor } = this.toTabContext(index);
const { group, editor } = this.getGroupPositionAndEditor(index);
this.editorGroupService.pinEditor(group, editor);
}));
@@ -741,14 +741,14 @@ export class TabsTitleControl extends TitleControl {
// Context menu
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.CONTEXT_MENU, (e: Event) => {
DOM.EventHelper.stop(e, true);
const { group, editor } = this.toTabContext(index);
const { group, editor } = this.getGroupPositionAndEditor(index);
this.onContextMenu({ group, editor }, e, tab);
}, true /* use capture to fix https://github.com/Microsoft/vscode/issues/19145 */));
// Drag start
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DRAG_START, (e: DragEvent) => {
const { group, editor } = this.toTabContext(index);
const { group, editor } = this.getGroupPositionAndEditor(index);
this.onEditorDragStart({ editor, group });
e.dataTransfer.effectAllowed = 'copyMove';
@@ -795,7 +795,7 @@ export class TabsTitleControl extends TitleControl {
let draggedEditorIsTab = false;
const draggedEditor = TabsTitleControl.getDraggedEditor();
if (draggedEditor) {
const { group, editor } = this.toTabContext(index);
const { group, editor } = this.getGroupPositionAndEditor(index);
if (draggedEditor.editor === editor && draggedEditor.group === group) {
draggedEditorIsTab = true;
}
@@ -832,7 +832,7 @@ export class TabsTitleControl extends TitleControl {
DOM.removeClass(tab, 'dragged-over');
this.updateDropFeedback(tab, false, index);
const { group, position } = this.toTabContext(index);
const { group, position } = this.getGroupPositionAndEditor(index);
this.onDrop(e, group, position, index);
}));
@@ -844,7 +844,7 @@ export class TabsTitleControl extends TitleControl {
return !!DOM.findParentWithClass(element, 'monaco-action-bar', 'tab');
}
private toTabContext(index: number): { group: IEditorGroup, position: Position, editor: IEditorInput } {
private getGroupPositionAndEditor(index: number): { group: IEditorGroup, position: Position, editor: IEditorInput } {
const group = this.context;
const position = this.stacks.positionOfGroup(group);
const editor = group.getEditor(index);
@@ -907,7 +907,7 @@ class TabActionRunner extends ActionRunner {
return TPromise.as(void 0);
}
return super.run(action, { group, editor: group.getEditor(this.index) });
return super.run(action, { groupId: group.id, editorIndex: this.index });
}
}
@@ -14,7 +14,7 @@ import { IAction, IActionItem, ActionRunner } from 'vs/base/common/actions';
import { IMessageService } from 'vs/platform/message/common/message';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { ClickBehavior, DefaultController } from 'vs/base/parts/tree/browser/treeDefaults';
import { ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults';
import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
import { IThemeService, LIGHT } from 'vs/platform/theme/common/themeService';
import { createActionItem, fillInActions } from 'vs/platform/actions/browser/menuItemActionItem';
@@ -26,12 +26,13 @@ import { ViewsRegistry, TreeItemCollapsibleState, ITreeItem, ITreeViewDataProvid
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { IViewletViewOptions, IViewOptions, TreeViewsViewletPanel, FileIconThemableWorkbenchTree } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { WorkbenchTree } from 'vs/platform/list/browser/listService';
import { WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser/listService';
import { ResourceLabel } from 'vs/workbench/browser/labels';
import URI from 'vs/base/common/uri';
import { basename } from 'vs/base/common/paths';
import { FileKind } from 'vs/platform/files/common/files';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export class TreeView extends TreeViewsViewletPanel {
@@ -92,11 +93,11 @@ export class TreeView extends TreeViewsViewletPanel {
const tree = this.instantiationService.createInstance(FileIconThemableWorkbenchTree,
container.getHTMLElement(),
{ dataSource, renderer, controller },
{ keyboardSupport: false }
{}
);
tree.contextKeyService.createKey<boolean>(this.id, true);
this.disposables.push(tree.onDidChangeSelection(() => this.onSelection()));
this.disposables.push(tree.onDidChangeSelection(e => this.onSelection(e)));
return tree;
}
@@ -159,11 +160,17 @@ export class TreeView extends TreeViewsViewletPanel {
return DOM.getLargestChildWidth(parentNode, childNodes);
}
private onSelection(): void {
private onSelection({ payload }: any): void {
const selection: ITreeItem = this.tree.getSelection()[0];
if (selection) {
if (selection.command) {
this.commandService.executeCommand(selection.command.id, ...(selection.command.arguments || []));
const originalEvent: KeyboardEvent | MouseEvent = payload && payload.originalEvent;
const isMouseEvent = payload && payload.origin === 'mouse';
const isDoubleClick = isMouseEvent && originalEvent && originalEvent.detail === 2;
if (!isMouseEvent || this.tree.openOnSingleClick || isDoubleClick) {
this.commandService.executeCommand(selection.command.id, ...(selection.command.arguments || []));
}
}
}
}
@@ -347,15 +354,16 @@ class TreeRenderer implements IRenderer {
}
}
class TreeController extends DefaultController {
class TreeController extends WorkbenchTreeController {
constructor(
private treeViewId: string,
private menus: Menus,
@IContextMenuService private contextMenuService: IContextMenuService,
@IKeybindingService private _keybindingService: IKeybindingService
@IKeybindingService private _keybindingService: IKeybindingService,
@IConfigurationService configurationService: IConfigurationService
) {
super({ clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change to not break DND */, keyboardSupport: false });
super({ clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change to not break DND */, keyboardSupport: false }, configurationService);
}
public onContextMenu(tree: ITree, node: ITreeItem, event: ContextMenuEvent): boolean {
@@ -31,6 +31,7 @@ import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listServic
import { IWorkbenchThemeService, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree';
import Event, { Emitter } from 'vs/base/common/event';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export interface IViewOptions extends IPanelOptions {
id: string;
@@ -771,9 +772,10 @@ export class FileIconThemableWorkbenchTree extends WorkbenchTree {
options: ITreeOptions,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IWorkbenchThemeService
@IThemeService themeService: IWorkbenchThemeService,
@IConfigurationService configurationService: IConfigurationService
) {
super(container, configuration, { ...options, ...{ showTwistie: false, twistiePixels: 12 } }, contextKeyService, listService, themeService);
super(container, configuration, { ...options, ...{ showTwistie: false, twistiePixels: 12 } }, contextKeyService, listService, themeService, configurationService);
DOM.addClass(container, 'file-icon-themable-tree');
DOM.addClass(container, 'show-file-icons');
@@ -380,9 +380,9 @@ export class StackFrame implements IStackFrame {
return `${this.name} (${this.source.inMemory ? this.source.name : this.source.uri.fsPath}:${this.range.startLineNumber})`;
}
public openInEditor(editorService: IWorkbenchEditorService, preserveFocus?: boolean, sideBySide?: boolean): TPromise<any> {
public openInEditor(editorService: IWorkbenchEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): TPromise<any> {
return !this.source.available ? TPromise.as(null) :
this.source.openInEditor(editorService, this.range, preserveFocus, sideBySide);
this.source.openInEditor(editorService, this.range, preserveFocus, sideBySide, pinned);
}
}
@@ -56,7 +56,7 @@ export class Source {
return this.uri.scheme === DEBUG_SCHEME;
}
public openInEditor(editorService: IWorkbenchEditorService, selection: IRange, preserveFocus?: boolean, sideBySide?: boolean): TPromise<any> {
public openInEditor(editorService: IWorkbenchEditorService, selection: IRange, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): TPromise<any> {
return !this.available ? TPromise.as(null) : editorService.openEditor({
resource: this.uri,
description: this.origin,
@@ -65,7 +65,7 @@ export class Source {
selection,
revealIfVisible: true,
revealInCenterIfOutsideViewport: true,
pinned: !preserveFocus && !this.inMemory
pinned: pinned || (!preserveFocus && !this.inMemory)
}
}, sideBySide);
}
@@ -15,11 +15,13 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { once } from 'vs/base/common/functional';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions';
import { ClickBehavior, DefaultController } from 'vs/base/parts/tree/browser/treeDefaults';
import { ClickBehavior, IControllerOptions } from 'vs/base/parts/tree/browser/treeDefaults';
import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem';
import { KeyCode } from 'vs/base/common/keyCodes';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { onUnexpectedError } from 'vs/base/common/errors';
import { WorkbenchTreeController } from 'vs/platform/list/browser/listService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;
export const twistiePixels = 20;
@@ -190,19 +192,23 @@ export function renderRenameBox(debugService: IDebugService, contextViewService:
}));
}
export class BaseDebugController extends DefaultController {
export const DefaultDebugControllerOptions: IControllerOptions = { clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false };
export class BaseDebugController extends WorkbenchTreeController {
private contributedContextMenu: IMenu;
constructor(
private actionProvider: IActionProvider,
menuId: MenuId,
options: IControllerOptions,
@IDebugService protected debugService: IDebugService,
@IContextMenuService private contextMenuService: IContextMenuService,
@IContextKeyService contextKeyService: IContextKeyService,
@IMenuService menuService: IMenuService
@IMenuService menuService: IMenuService,
@IConfigurationService configurationService: IConfigurationService
) {
super({ clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false });
super(options, configurationService);
this.contributedContextMenu = menuService.createMenu(menuId, contextKeyService);
}
@@ -78,29 +78,39 @@ export class BreakpointsView extends ViewsViewletPanel {
this.list.onContextMenu(this.onListContextMenu, this, this.disposables);
const handleBreakpointFocus = (preserveFocuse: boolean, sideBySide: boolean, selectFunctionBreakpoint: boolean) => {
const handleBreakpointFocus = (preserveFocus: boolean, sideBySide: boolean, selectFunctionBreakpoint: boolean) => {
const focused = this.list.getFocusedElements();
const element = focused.length ? focused[0] : undefined;
if (element instanceof Breakpoint) {
openBreakpointSource(element, sideBySide, preserveFocuse, this.debugService, this.editorService).done(undefined, onUnexpectedError);
openBreakpointSource(element, sideBySide, preserveFocus, this.debugService, this.editorService).done(undefined, onUnexpectedError);
}
if (selectFunctionBreakpoint && element instanceof FunctionBreakpoint && element !== this.debugService.getViewModel().getSelectedFunctionBreakpoint()) {
this.debugService.getViewModel().setSelectedFunctionBreakpoint(element);
this.onBreakpointsChange();
}
};
this.disposables.push(this.list.onOpen(e => {
let isSingleClick = false;
let isDoubleClick = false;
let openToSide = false;
const browserEvent = e.browserEvent;
if (browserEvent instanceof MouseEvent) {
isSingleClick = browserEvent.detail === 1;
isDoubleClick = browserEvent.detail === 2;
openToSide = (browserEvent.ctrlKey || browserEvent.metaKey || browserEvent.altKey);
}
handleBreakpointFocus(isSingleClick, openToSide, isDoubleClick);
}));
// TODO@Isidor this should be a command (breakpoints.openToSide)
this.disposables.push(this.list.onKeyUp(e => {
const event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.Enter)) {
handleBreakpointFocus(false, event && (event.ctrlKey || event.metaKey || event.altKey), false);
if (event.equals(KeyCode.Enter) && (event.ctrlKey || event.metaKey || event.altKey)) {
handleBreakpointFocus(false, true, false);
}
}));
this.disposables.push(this.list.onMouseDblClick(e => {
handleBreakpointFocus(false, e.browserEvent.altKey, true);
}));
this.disposables.push(this.list.onMouseClick(e => {
handleBreakpointFocus(true, e.browserEvent.altKey, false);
}));
this.list.splice(0, this.list.length, this.elements);
}
@@ -15,7 +15,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { MenuId } from 'vs/platform/actions/common/actions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { BaseDebugController, twistiePixels, renderViewTree } from 'vs/workbench/parts/debug/electron-browser/baseDebugView';
import { BaseDebugController, twistiePixels, renderViewTree, DefaultDebugControllerOptions } from 'vs/workbench/parts/debug/electron-browser/baseDebugView';
import { ITree, IActionProvider, IDataSource, IRenderer, IAccessibilityProvider } from 'vs/base/parts/tree/browser/tree';
import { IAction, IActionItem } from 'vs/base/common/actions';
import { RestartAction, StopAction, ContinueAction, StepOverAction, StepIntoAction, StepOutAction, PauseAction, RestartFrameAction } from 'vs/workbench/parts/debug/browser/debugActions';
@@ -24,9 +24,8 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { Source } from 'vs/workbench/parts/debug/common/debugSource';
import { basenameOrAuthority } from 'vs/base/common/resources';
import { WorkbenchTree } from 'vs/platform/list/browser/listService';
import ResourceResultsNavigation, { WorkbenchTree } from 'vs/platform/list/browser/listService';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import FileResultsNavigation from 'vs/workbench/parts/files/browser/fileResultsNavigation';
const $ = dom.$;
@@ -93,7 +92,7 @@ export class CallStackView extends TreeViewsViewletPanel {
dom.addClass(container, 'debug-call-stack');
this.treeContainer = renderViewTree(container);
const actionProvider = new CallStackActionProvider(this.debugService, this.keybindingService);
const controller = this.instantiationService.createInstance(CallStackController, actionProvider, MenuId.DebugCallStackContext);
const controller = this.instantiationService.createInstance(CallStackController, actionProvider, MenuId.DebugCallStackContext, DefaultDebugControllerOptions);
this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, {
dataSource: new CallStackDataSource(),
@@ -102,13 +101,12 @@ export class CallStackView extends TreeViewsViewletPanel {
controller
}, {
ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'callStackAriaLabel' }, "Debug Call Stack"),
twistiePixels,
keyboardSupport: false
twistiePixels
});
const fileResultsNavigation = new FileResultsNavigation(this.tree);
const fileResultsNavigation = new ResourceResultsNavigation(this.tree);
this.disposables.push(fileResultsNavigation);
this.disposables.push(fileResultsNavigation.openFile(e => {
this.disposables.push(fileResultsNavigation.openResource(e => {
if (this.ignoreSelectionChangedEvent) {
return;
}
@@ -116,7 +114,7 @@ export class CallStackView extends TreeViewsViewletPanel {
const element = e.element;
if (element instanceof StackFrame) {
this.debugService.focusStackFrame(element, element.thread, element.thread.process, true);
element.openInEditor(this.editorService, e.editorOptions.preserveFocus, e.sideBySide).done(undefined, errors.onUnexpectedError);
element.openInEditor(this.editorService, e.editorOptions.preserveFocus, e.sideBySide, e.editorOptions.pinned).done(undefined, errors.onUnexpectedError);
}
if (element instanceof Thread) {
this.debugService.focusStackFrame(undefined, element, element.process, true);
@@ -11,7 +11,7 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import * as dom from 'vs/base/browser/dom';
import { ITree } from 'vs/base/parts/tree/browser/tree';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { DefaultController, ICancelableEvent, ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults';
import { DefaultController, ICancelableEvent, ClickBehavior, OpenMode } from 'vs/base/parts/tree/browser/treeDefaults';
import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
@@ -75,8 +75,7 @@ export class DebugHoverWidget implements IContentWidget {
}, {
indentPixels: 6,
twistiePixels: 15,
ariaLabel: nls.localize('treeAriaLabel', "Debug Hover"),
keyboardSupport: false
ariaLabel: nls.localize('treeAriaLabel', "Debug Hover")
});
this.valueContainer = $('.value');
@@ -337,7 +336,7 @@ export class DebugHoverWidget implements IContentWidget {
class DebugHoverController extends DefaultController {
constructor(private editor: ICodeEditor) {
super({ clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false });
super({ clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false, openMode: OpenMode.SINGLE_CLICK });
}
protected onLeftClick(tree: ITree, element: any, eventish: ICancelableEvent, origin = 'mouse'): boolean {
@@ -44,13 +44,13 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { WorkbenchTree } from 'vs/platform/list/browser/listService';
import { memoize } from 'vs/base/common/decorators';
import { dispose } from 'vs/base/common/lifecycle';
import { ClickBehavior, OpenMode } from 'vs/base/parts/tree/browser/treeDefaults';
const $ = dom.$;
const replTreeOptions: ITreeOptions = {
twistiePixels: 20,
ariaLabel: nls.localize('replAriaLabel', "Read Eval Print Loop Panel"),
keyboardSupport: false
ariaLabel: nls.localize('replAriaLabel', "Read Eval Print Loop Panel")
};
const HISTORY_STORAGE_KEY = 'debug.repl.history';
@@ -135,7 +135,7 @@ export class Repl extends Panel implements IPrivateReplService {
this.createReplInput(this.container);
this.renderer = this.instantiationService.createInstance(ReplExpressionsRenderer);
const controller = this.instantiationService.createInstance(ReplExpressionsController, new ReplExpressionsActionProvider(this.instantiationService), MenuId.DebugConsoleContext);
const controller = this.instantiationService.createInstance(ReplExpressionsController, new ReplExpressionsActionProvider(this.instantiationService), MenuId.DebugConsoleContext, { clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false, openMode: OpenMode.SINGLE_CLICK });
controller.toFocusOnClick = this.replInput;
this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, {
@@ -28,6 +28,7 @@ import { ViewModel } from 'vs/workbench/parts/debug/common/debugViewModel';
import { equalsIgnoreCase } from 'vs/base/common/strings';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { WorkbenchTree } from 'vs/platform/list/browser/listService';
import { ClickBehavior, OpenMode } from 'vs/base/parts/tree/browser/treeDefaults';
const $ = dom.$;
@@ -87,11 +88,10 @@ export class VariablesView extends TreeViewsViewletPanel {
dataSource: new VariablesDataSource(),
renderer: this.instantiationService.createInstance(VariablesRenderer),
accessibilityProvider: new VariablesAccessibilityProvider(),
controller: this.instantiationService.createInstance(VariablesController, new VariablesActionProvider(this.debugService, this.keybindingService), MenuId.DebugVariablesContext)
controller: this.instantiationService.createInstance(VariablesController, new VariablesActionProvider(this.debugService, this.keybindingService), MenuId.DebugVariablesContext, { clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false, openMode: OpenMode.SINGLE_CLICK })
}, {
ariaLabel: nls.localize('variablesAriaTreeLabel', "Debug Variables"),
twistiePixels,
keyboardSupport: false
twistiePixels
});
CONTEXT_VARIABLES_FOCUSED.bindTo(this.tree.contextKeyService);
@@ -26,7 +26,7 @@ import { CopyValueAction } from 'vs/workbench/parts/debug/electron-browser/elect
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { equalsIgnoreCase } from 'vs/base/common/strings';
import { IMouseEvent, DragMouseEvent } from 'vs/base/browser/mouseEvent';
import { DefaultDragAndDrop } from 'vs/base/parts/tree/browser/treeDefaults';
import { DefaultDragAndDrop, ClickBehavior, OpenMode } from 'vs/base/parts/tree/browser/treeDefaults';
import { IVariableTemplateData, renderVariable, renderRenameBox, renderExpressionValue, BaseDebugController, twistiePixels, renderViewTree } from 'vs/workbench/parts/debug/electron-browser/baseDebugView';
import { WorkbenchTree } from 'vs/platform/list/browser/listService';
@@ -65,12 +65,11 @@ export class WatchExpressionsView extends TreeViewsViewletPanel {
dataSource: new WatchExpressionsDataSource(this.debugService),
renderer: this.instantiationService.createInstance(WatchExpressionsRenderer),
accessibilityProvider: new WatchExpressionsAccessibilityProvider(),
controller: this.instantiationService.createInstance(WatchExpressionsController, actionProvider, MenuId.DebugWatchContext),
controller: this.instantiationService.createInstance(WatchExpressionsController, actionProvider, MenuId.DebugWatchContext, { clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false, openMode: OpenMode.SINGLE_CLICK }),
dnd: new WatchExpressionsDragAndDrop(this.debugService)
}, {
ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'watchAriaTreeLabel' }, "Debug Watch Expressions"),
twistiePixels,
keyboardSupport: false
twistiePixels
});
CONTEXT_WATCH_EXPRESSIONS_FOCUSED.bindTo(this.tree.contextKeyService);
@@ -9,13 +9,15 @@ import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { TPromise, Promise } from 'vs/base/common/winjs.base';
import { IDataSource, ITree, IRenderer } from 'vs/base/parts/tree/browser/tree';
import { DefaultController, ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults';
import { ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults';
import { Action } from 'vs/base/common/actions';
import { IExtensionDependencies, IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions';
import { once } from 'vs/base/common/event';
import { domEvent } from 'vs/base/browser/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { WorkbenchTreeController } from 'vs/platform/list/browser/listService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export interface IExtensionTemplateData {
icon: HTMLImageElement;
@@ -156,21 +158,24 @@ export class Renderer implements IRenderer {
}
}
export class Controller extends DefaultController {
export class Controller extends WorkbenchTreeController {
constructor( @IExtensionsWorkbenchService private extensionsWorkdbenchService: IExtensionsWorkbenchService) {
super({ clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false });
constructor(
@IExtensionsWorkbenchService private extensionsWorkdbenchService: IExtensionsWorkbenchService,
@IConfigurationService configurationService: IConfigurationService
) {
super({ clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false }, configurationService);
// TODO@Sandeep this should be a command
this.downKeyBindingDispatcher.set(KeyMod.CtrlCmd | KeyCode.Enter, (tree: ITree, event: any) => this.openExtension(tree, true));
}
protected onLeftClick(tree: ITree, element: IExtensionDependencies, event: IMouseEvent): boolean {
let currentFoucssed = tree.getFocus();
let currentFocused = tree.getFocus();
if (super.onLeftClick(tree, element, event)) {
if (element.dependent === null) {
if (currentFoucssed) {
tree.setFocus(currentFoucssed);
if (currentFocused) {
tree.setFocus(currentFocused);
} else {
tree.focusFirst();
}
@@ -526,8 +526,7 @@ export class ExtensionEditor extends BaseEditor {
controller
}, {
indentPixels: 40,
twistiePixels: 20,
keyboardSupport: false
twistiePixels: 20
});
tree.setInput(extensionDependencies);
@@ -1564,10 +1564,10 @@ export class MaliciousStatusLabelAction extends Action {
set extension(extension: IExtension) { this._extension = extension; this.update(); }
constructor(long: boolean) {
const tooltip = localize('malicious tooltip', "This extension was reported to be malicious.");
const tooltip = localize('malicious tooltip', "This extension was reported to be problematic.");
const label = long ? tooltip : localize('malicious', "Malicious");
super('extensions.install', label, '', false);
this.tooltip = localize('malicious tooltip', "This extension was reported to be malicious.");
this.tooltip = localize('malicious tooltip', "This extension was reported to be problematic.");
}
private update(): void {
@@ -486,7 +486,7 @@ export class MaliciousExtensionChecker implements IWorkbenchContribution {
if (maliciousExtensions.length) {
return TPromise.join(maliciousExtensions.map(e => this.extensionsManagementService.uninstall(e, true).then(() => {
this.messageService.show(Severity.Warning, {
message: localize('malicious warning', "We have uninstalled '{0}' which was reported to be malicious.", getGalleryExtensionIdFromLocal(e)),
message: localize('malicious warning', "We have uninstalled '{0}' which was reported to be problematic.", getGalleryExtensionIdFromLocal(e)),
actions: [this.instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, localize('reloadNow', "Reload Now"))]
});
})));
@@ -78,11 +78,10 @@ export class ExtensionsListView extends ViewsViewletPanel {
const delegate = new Delegate();
const renderer = this.instantiationService.createInstance(Renderer);
this.list = this.instantiationService.createInstance(WorkbenchPagedList, this.extensionsList, delegate, [renderer], {
ariaLabel: localize('extensions', "Extensions"),
keyboardSupport: false
ariaLabel: localize('extensions', "Extensions")
});
chain(this.list.onSelectionChange)
chain(this.list.onOpen)
.map(e => e.elements[0])
.filter(e => !!e)
.on(this.openExtension, this, this.disposables);
@@ -443,6 +442,7 @@ export class ExtensionsListView extends ViewsViewletPanel {
const activeEditorInput = this.editorService.getActiveEditorInput();
this.editorInputService.pinEditor(activeEditor.position, activeEditorInput);
activeEditor.focus();
}
@@ -569,7 +569,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
}
if (extension.isMalicious) {
return TPromise.wrapError<void>(new Error(nls.localize('malicious', "This extension is reported to be malicious.")));
return TPromise.wrapError<void>(new Error(nls.localize('malicious', "This extension is reported to be problematic.")));
}
const ext = extension as Extension;
@@ -581,7 +581,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
return this.progressService.withProgress({
location: ProgressLocation.Extensions,
title: nls.localize('installingMarketPlaceExtension', 'Installing extension from Market place....'),
title: nls.localize('installingMarketPlaceExtension', 'Installing extension from Marketplace....'),
tooltip: `${extension.id}`
}, () => this.extensionService.installFromGallery(gallery));
}
@@ -1,76 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import Event, { Emitter } from 'vs/base/common/event';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { ITree } from 'vs/base/parts/tree/browser/tree';
export interface IOpenFileOptions {
editorOptions: IEditorOptions;
sideBySide: boolean;
element: any;
payload: any;
}
export interface IFileResultsNavigationOptions {
openOnFocus: boolean;
}
export default class FileResultsNavigation extends Disposable {
private _openFile: Emitter<IOpenFileOptions> = new Emitter<IOpenFileOptions>();
public readonly openFile: Event<IOpenFileOptions> = this._openFile.event;
constructor(private tree: ITree, options?: IFileResultsNavigationOptions) {
super();
if (options && options.openOnFocus) {
this._register(this.tree.onDidChangeFocus(e => this.onFocus(e)));
}
this._register(this.tree.onDidChangeSelection(e => this.onSelection(e)));
}
private onFocus(event: any): void {
const element = this.tree.getFocus();
this.tree.setSelection([element], { fromFocus: true });
this._openFile.fire({
editorOptions: {
preserveFocus: true,
pinned: false,
revealIfVisible: true
},
sideBySide: false,
element,
payload: event.payload
});
}
private onSelection({ payload }: any): void {
if (payload && payload.fromFocus) {
return;
}
const keyboard = payload && payload.origin === 'keyboard';
const originalEvent: KeyboardEvent | MouseEvent = payload && payload.originalEvent;
const pinned = (payload && payload.origin === 'mouse' && originalEvent && originalEvent.detail === 2);
if (pinned && originalEvent) {
originalEvent.preventDefault(); // focus moves to editor, we need to prevent default
}
const sideBySide = (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey || originalEvent.altKey));
const preserveFocus = !((keyboard && (!payload || !payload.preserveFocus)) || pinned || (payload && payload.focusEditor));
this._openFile.fire({
editorOptions: {
preserveFocus,
pinned,
revealIfVisible: true
},
sideBySide,
element: this.tree.getSelection()[0],
payload
});
}
}
+16 -7
View File
@@ -12,6 +12,7 @@ import { FileStat, OpenEditor } from 'vs/workbench/parts/files/common/explorerMo
import { toResource } from 'vs/workbench/common/editor';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { List } from 'vs/base/browser/ui/list/listWidget';
import { IFileStat } from 'vs/platform/files/common/files';
// Commands can get exeucted from a command pallete, from a context menu or from some list using a keybinding
// To cover all these cases we need to properly compute the resource on which the command is being executed
@@ -38,18 +39,26 @@ export function getMultiSelectedResources(resource: URI, listService: IListServi
if (list && list.isDOMFocused()) {
// Explorer
if (list instanceof Tree) {
const focus = list.getFocus();
const selection = list.getSelection();
if (selection && selection.indexOf(focus) >= 0) {
return selection.map(fs => fs.resource);
const focus: IFileStat = list.getFocus();
// If the resource is passed it has to be a part of the returned context.
if (focus && (!URI.isUri(resource) || focus.resource.toString() === resource.toString())) {
const selection = list.getSelection();
// We only respect the selection if it contains the focused element.
if (selection && selection.indexOf(focus) >= 0) {
return selection.map(fs => fs.resource);
}
}
}
// Open editors view
if (list instanceof List) {
const focus = list.getFocusedElements();
const selection = list.getSelectedElements();
if (selection && focus.length && selection.indexOf(focus[0]) >= 0) {
return selection.filter(s => s instanceof OpenEditor).map((oe: OpenEditor) => oe.getResource());
// If the resource is passed it has to be a part of the returned context.
if (focus.length && (!URI.isUri(resource) || (focus[0] instanceof OpenEditor && focus[0].getResource().toString() === resource.toString()))) {
const selection = list.getSelectedElements();
// We only respect the selection if it contains the focused element.
if (selection && selection.indexOf(focus[0]) >= 0) {
return selection.filter(s => s instanceof OpenEditor).map((oe: OpenEditor) => oe.getResource());
}
}
}
}
@@ -380,6 +380,10 @@ export class OpenEditor implements IEditorIdentifier {
return this._editor;
}
public get editorIndex() {
return this._group.indexOf(this.editor);
}
public get group() {
return this._group;
}
@@ -12,7 +12,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
import * as labels from 'vs/base/common/labels';
import URI from 'vs/base/common/uri';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { toResource, IEditorIdentifier } from 'vs/workbench/common/editor';
import { toResource, IEditorCommandsContext } from 'vs/workbench/common/editor';
import { IWindowsService } from 'vs/platform/windows/common/windows';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
@@ -492,8 +492,9 @@ CommandsRegistry.registerCommand({
CommandsRegistry.registerCommand({
id: SAVE_ALL_IN_GROUP_COMMAND_ID,
handler: (accessor, resource: URI, editorContext: IEditorIdentifier) => {
handler: (accessor, resource: URI, editorContext: IEditorCommandsContext) => {
const contexts = getMultiSelectedEditorContexts(editorContext, accessor.get(IListService));
const editorGroupService = accessor.get(IEditorGroupService);
let saveAllArg: any;
if (!contexts.length) {
saveAllArg = true;
@@ -501,7 +502,7 @@ CommandsRegistry.registerCommand({
const fileService = accessor.get(IFileService);
saveAllArg = [];
contexts.forEach(context => {
const editorGroup = context.group;
const editorGroup = editorGroupService.getStacksModel().getGroup(context.groupId);
editorGroup.getEditors().forEach(editor => {
const resource = toResource(editor, { supportSideBySide: true });
if (resource && (resource.scheme === 'untitled' || fileService.canHandleResource(resource))) {
@@ -403,8 +403,7 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView
accessibilityProvider
}, {
autoExpandSingleChildren: true,
ariaLabel: nls.localize('treeAriaLabel', "Files Explorer"),
keyboardSupport: false
ariaLabel: nls.localize('treeAriaLabel', "Files Explorer")
});
// Bind context keys
@@ -29,7 +29,7 @@ import { ResourceMap } from 'vs/base/common/map';
import { DuplicateFileAction, ImportFileAction, IEditableData, IFileViewletState } from 'vs/workbench/parts/files/electron-browser/fileActions';
import { IDataSource, ITree, IAccessibilityProvider, IRenderer, ContextMenuEvent, ISorter, IFilter, IDragAndDropData, IDragOverReaction, DRAG_OVER_ACCEPT_BUBBLE_DOWN, DRAG_OVER_ACCEPT_BUBBLE_DOWN_COPY, DRAG_OVER_ACCEPT_BUBBLE_UP, DRAG_OVER_ACCEPT_BUBBLE_UP_COPY, DRAG_OVER_REJECT } from 'vs/base/parts/tree/browser/tree';
import { DesktopDragAndDropData, ExternalElementsDragAndDropData, SimpleFileResourceDragAndDrop } from 'vs/base/parts/tree/browser/treeDnd';
import { ClickBehavior, DefaultController } from 'vs/base/parts/tree/browser/treeDefaults';
import { ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults';
import { FileStat, NewStatPlaceholder, Model } from 'vs/workbench/parts/files/common/explorerModel';
import { DragMouseEvent, IMouseEvent } from 'vs/base/browser/mouseEvent';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -56,7 +56,7 @@ import { extractResources } from 'vs/workbench/browser/editor';
import { relative } from 'path';
import { DataTransfers } from 'vs/base/browser/dnd';
import { distinctParents } from 'vs/base/common/resources';
import { WorkbenchTree, multiSelectModifierSettingKey } from 'vs/platform/list/browser/listService';
import { WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser/listService';
export class FileDataSource implements IDataSource {
constructor(
@@ -325,12 +325,11 @@ export class FileAccessibilityProvider implements IAccessibilityProvider {
}
// Explorer Controller
export class FileController extends DefaultController implements IDisposable {
export class FileController extends WorkbenchTreeController implements IDisposable {
private contributedContextMenu: IMenu;
private toDispose: IDisposable[];
private previousSelectionRangeStop: FileStat;
private useAltAsMultiSelectModifier: boolean;
constructor(
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@@ -338,25 +337,14 @@ export class FileController extends DefaultController implements IDisposable {
@ITelemetryService private telemetryService: ITelemetryService,
@IMenuService private menuService: IMenuService,
@IContextKeyService contextKeyService: IContextKeyService,
@IConfigurationService private configurationService: IConfigurationService
@IConfigurationService configurationService: IConfigurationService
) {
super({ clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change to not break DND */, keyboardSupport: false /* handled via IListService */ });
super({ clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change to not break DND */, keyboardSupport: false /* handled via IListService */ }, configurationService);
this.useAltAsMultiSelectModifier = configurationService.getValue(multiSelectModifierSettingKey) === 'alt';
this.toDispose = [];
this.registerListeners();
}
private registerListeners(): void {
this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
this.useAltAsMultiSelectModifier = this.configurationService.getValue(multiSelectModifierSettingKey) === 'alt';
}
}));
}
public onLeftClick(tree: ITree, stat: FileStat | Model, event: IMouseEvent, origin: string = 'mouse'): boolean {
public onLeftClick(tree: WorkbenchTree, stat: FileStat | Model, event: IMouseEvent, origin: string = 'mouse'): boolean {
const payload = { origin: origin };
const isDoubleClick = (origin === 'mouse' && event.detail === 2);
@@ -394,7 +382,7 @@ export class FileController extends DefaultController implements IDisposable {
}
// Allow to multiselect
if ((this.useAltAsMultiSelectModifier && event.altKey) || !this.useAltAsMultiSelectModifier && (event.ctrlKey || event.metaKey)) {
if ((tree.useAltAsMultipleSelectionModifier && event.altKey) || !tree.useAltAsMultipleSelectionModifier && (event.ctrlKey || event.metaKey)) {
const selection = tree.getSelection();
this.previousSelectionRangeStop = undefined;
if (selection.indexOf(stat) >= 0) {
@@ -419,9 +407,12 @@ export class FileController extends DefaultController implements IDisposable {
// Select, Focus and open files
else {
// Expand / Collapse
tree.toggleExpansion(stat, event.altKey);
this.previousSelectionRangeStop = undefined;
if (isDoubleClick || this.openOnSingleClick) {
tree.toggleExpansion(stat, event.altKey);
this.previousSelectionRangeStop = undefined;
}
const preserveFocus = !isDoubleClick;
tree.setFocus(stat, payload);
@@ -432,10 +423,10 @@ export class FileController extends DefaultController implements IDisposable {
tree.setSelection([stat], payload);
if (!stat.isDirectory) {
if (!stat.isDirectory && (isDoubleClick || this.openOnSingleClick)) {
let sideBySide = false;
if (event) {
sideBySide = this.useAltAsMultiSelectModifier ? (event.ctrlKey || event.metaKey) : event.altKey;
sideBySide = tree.useAltAsMultipleSelectionModifier ? (event.ctrlKey || event.metaKey) : event.altKey;
}
this.openEditor(stat, { preserveFocus, sideBySide, pinned: isDoubleClick });
@@ -28,15 +28,13 @@ import { EditorGroup } from 'vs/workbench/common/editor/editorStacksModel';
import { attachStylerCallback } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { WorkbenchList, useAltAsMultipleSelectionModifier } from 'vs/platform/list/browser/listService';
import { IDelegate, IRenderer, IListContextMenuEvent, IListMouseEvent } from 'vs/base/browser/ui/list/list';
import { WorkbenchList } from 'vs/platform/list/browser/listService';
import { IDelegate, IRenderer, IListContextMenuEvent } from 'vs/base/browser/ui/list/list';
import { EditorLabel } from 'vs/workbench/browser/labels';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { TPromise } from 'vs/base/common/winjs.base';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem';
import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions';
@@ -153,7 +151,6 @@ export class OpenEditorsView extends ViewsViewletPanel {
new EditorGroupRenderer(this.keybindingService, this.instantiationService, this.editorGroupService),
new OpenEditorRenderer(getSelectedElements, this.instantiationService, this.keybindingService, this.configurationService, this.editorGroupService)
], {
keyboardSupport: false,
identityProvider: element => element instanceof OpenEditor ? element.getId() : element.id.toString()
});
@@ -185,15 +182,28 @@ export class OpenEditorsView extends ViewsViewletPanel {
});
// Open when selecting via keyboard
this.disposables.push(this.list.onMouseClick(e => this.onMouseClick(e, false)));
this.disposables.push(this.list.onMouseDblClick(e => this.onMouseClick(e, true)));
this.disposables.push(this.list.onKeyDown(e => {
const event = new StandardKeyboardEvent(e);
if (event.keyCode === KeyCode.Enter) {
const focused = this.list.getFocusedElements();
const element = focused.length ? focused[0] : undefined;
if (element instanceof OpenEditor) {
this.openEditor(element, { pinned: false, sideBySide: !!(event.altKey || event.ctrlKey || event.metaKey), preserveFocus: false });
this.disposables.push(this.list.onOpen(e => {
const browserEvent = e.browserEvent;
let openToSide = false;
let isSingleClick = false;
let isDoubleClick = false;
let isMiddleClick = false;
if (browserEvent instanceof MouseEvent) {
isSingleClick = browserEvent.detail === 1;
isDoubleClick = browserEvent.detail === 2;
isMiddleClick = browserEvent.button === 1 /* middle button */;
openToSide = this.list.useAltAsMultipleSelectionModifier ? (browserEvent.ctrlKey || browserEvent.metaKey) : browserEvent.altKey;
}
const focused = this.list.getFocusedElements();
const element = focused.length ? focused[0] : undefined;
if (element instanceof OpenEditor) {
if (isMiddleClick) {
const position = this.model.positionOfGroup(element.group);
this.editorService.closeEditor(position, element.editor).done(null, errors.onUnexpectedError);
} else {
this.openEditor(element, { preserveFocus: isSingleClick, pinned: isDoubleClick, sideBySide: openToSide });
}
}
}));
@@ -268,21 +278,6 @@ export class OpenEditorsView extends ViewsViewletPanel {
return -1;
}
private onMouseClick(event: IListMouseEvent<OpenEditor | IEditorGroup>, isDoubleClick: boolean): void {
const element = event.element;
if (!(element instanceof OpenEditor)) {
return;
}
if (event.browserEvent && event.browserEvent.button === 1 /* Middle Button */) {
const position = this.model.positionOfGroup(element.group);
this.editorService.closeEditor(position, element.editor).done(null, errors.onUnexpectedError);
} else {
const sideBySide = useAltAsMultipleSelectionModifier(this.configurationService) ? event.browserEvent.altKey : (event.browserEvent.ctrlKey || event.browserEvent.metaKey);
this.openEditor(element, { preserveFocus: !isDoubleClick, pinned: isDoubleClick, sideBySide });
}
}
private openEditor(element: OpenEditor, options: { preserveFocus: boolean; pinned: boolean; sideBySide: boolean; }): void {
if (element) {
/* __GDPR__
@@ -311,7 +306,7 @@ export class OpenEditorsView extends ViewsViewletPanel {
fillInActions(this.contributedContextMenu, { shouldForwardArgs: true, arg: element instanceof OpenEditor ? element.editor.getResource() : {} }, actions, this.contextMenuService);
return TPromise.as(actions);
},
getActionsContext: () => element instanceof OpenEditor ? { group: element.group, editor: element.editor } : { group: element }
getActionsContext: () => element instanceof OpenEditor ? { groupId: element.group.id, editorIndex: element.editorIndex } : { groupId: element.id }
});
}
@@ -493,7 +488,7 @@ class EditorGroupRenderer implements IRenderer<IEditorGroup, IEditorGroupTemplat
renderElement(editorGroup: IEditorGroup, index: number, templateData: IEditorGroupTemplateData): void {
templateData.editorGroup = editorGroup;
templateData.name.textContent = editorGroup.label;
templateData.actionBar.context = { group: editorGroup };
templateData.actionBar.context = { groupId: editorGroup.id };
}
disposeTemplate(templateData: IEditorGroupTemplateData): void {
@@ -597,7 +592,7 @@ class OpenEditorRenderer implements IRenderer<OpenEditor, IOpenEditorTemplateDat
extraClasses: ['open-editor'],
fileDecorations: this.configurationService.getValue<IFilesConfiguration>().explorer.decorations
});
templateData.actionBar.context = { group: editor.group, editor: editor.editor };
templateData.actionBar.context = { groupId: editor.group.id, editorIndex: editor.editorIndex };
}
disposeTemplate(templateData: IOpenEditorTemplateData): void {
@@ -27,11 +27,10 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import Messages from 'vs/workbench/parts/markers/common/messages';
import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import FileResultsNavigation from 'vs/workbench/parts/files/browser/fileResultsNavigation';
import { debounceEvent } from 'vs/base/common/event';
import { SimpleFileResourceDragAndDrop } from 'vs/base/parts/tree/browser/treeDnd';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { WorkbenchTree } from 'vs/platform/list/browser/listService';
import ResourceResultsNavigation, { WorkbenchTree } from 'vs/platform/list/browser/listService';
import { IMarkersWorkbenchService } from 'vs/workbench/parts/markers/common/markers';
export class MarkersPanel extends Panel {
@@ -206,14 +205,13 @@ export class MarkersPanel extends Panel {
}, {
indentPixels: 0,
twistiePixels: 20,
ariaLabel: Messages.MARKERS_PANEL_ARIA_LABEL_PROBLEMS_TREE,
keyboardSupport: false
ariaLabel: Messages.MARKERS_PANEL_ARIA_LABEL_PROBLEMS_TREE
});
Constants.MarkerFocusContextKey.bindTo(this.tree.contextKeyService);
const fileResultsNavigation = this._register(new FileResultsNavigation(this.tree, { openOnFocus: true }));
this._register(debounceEvent(fileResultsNavigation.openFile, (last, event) => event, 75, true)(options => {
const fileResultsNavigation = this._register(new ResourceResultsNavigation(this.tree, { openOnFocus: true }));
this._register(debounceEvent(fileResultsNavigation.openResource, (last, event) => event, 75, true)(options => {
this.openFileAtElement(options.element, options.editorOptions.preserveFocus, options.sideBySide, options.editorOptions.pinned);
}));
}
@@ -14,16 +14,18 @@ import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
import { IAction } from 'vs/base/common/actions';
import { ActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { WorkbenchTree } from 'vs/platform/list/browser/listService';
import { WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser/listService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export class Controller extends treedefaults.DefaultController {
export class Controller extends WorkbenchTreeController {
constructor(
@IContextMenuService private contextMenuService: IContextMenuService,
@IMenuService private menuService: IMenuService,
@IKeybindingService private _keybindingService: IKeybindingService
@IKeybindingService private _keybindingService: IKeybindingService,
@IConfigurationService configurationService: IConfigurationService
) {
super({ clickBehavior: treedefaults.ClickBehavior.ON_MOUSE_DOWN, keyboardSupport: false });
super({ clickBehavior: treedefaults.ClickBehavior.ON_MOUSE_DOWN, keyboardSupport: false }, configurationService);
}
protected onLeftClick(tree: tree.ITree, element: any, event: mouse.IMouseEvent): boolean {
@@ -333,7 +333,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor
this.keybindingsListContainer = DOM.append(parent, $('.keybindings-list-container'));
this.keybindingsList = this._register(this.instantiationService.createInstance(WorkbenchList, this.keybindingsListContainer, new Delegate(), [new KeybindingHeaderRenderer(), new KeybindingItemRenderer(this, this.keybindingsService)],
{ identityProvider: e => e.id, keyboardSupport: false, mouseSupport: true, ariaLabel: localize('keybindingsLabel', "Keybindings") }));
{ identityProvider: e => e.id, mouseSupport: true, ariaLabel: localize('keybindingsLabel', "Keybindings") }));
this._register(this.keybindingsList.onContextMenu(e => this.onContextMenu(e)));
this._register(this.keybindingsList.onFocusChange(e => this.onFocusChange(e)));
this._register(this.keybindingsList.onDidFocus(() => {
@@ -854,8 +854,7 @@ export class RepositoryPanel extends ViewletPanel {
];
this.list = this.instantiationService.createInstance(WorkbenchList, this.listContainer, delegate, renderers, {
identityProvider: scmResourceIdentityProvider,
keyboardSupport: false
identityProvider: scmResourceIdentityProvider
});
chain(this.list.onOpen)
@@ -941,6 +940,7 @@ export class RepositoryPanel extends ViewletPanel {
}
this.editorGroupService.pinEditor(activeEditor.position, activeEditorInput);
activeEditor.focus();
}
private onListContextMenu(e: IListContextMenuEvent<ISCMResourceGroup | ISCMResource>): void {
@@ -53,12 +53,11 @@ import { OpenFolderAction, OpenFileFolderAction } from 'vs/workbench/browser/act
import * as Constants from 'vs/workbench/parts/search/common/constants';
import { IThemeService, ITheme, ICssStyleCollector, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { editorFindMatchHighlight, diffInserted, diffRemoved, diffInsertedOutline, diffRemovedOutline, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
import FileResultsNavigation from 'vs/workbench/parts/files/browser/fileResultsNavigation';
import { getOutOfWorkspaceEditorResources } from 'vs/workbench/parts/search/common/search';
import { PreferencesEditor } from 'vs/workbench/parts/preferences/browser/preferencesEditor';
import { SimpleFileResourceDragAndDrop } from 'vs/base/parts/tree/browser/treeDnd';
import { isDiffEditor, isCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { WorkbenchTree } from 'vs/platform/list/browser/listService';
import ResourceResultsNavigation, { WorkbenchTree } from 'vs/platform/list/browser/listService';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
export class SearchViewlet extends Viewlet {
@@ -84,7 +83,7 @@ export class SearchViewlet extends Viewlet {
private searching: boolean;
private actions: SearchAction[] = [];
private tree: ITree;
private tree: WorkbenchTree;
private viewletSettings: any;
private messages: Builder;
private searchWidgetsContainer: Builder;
@@ -505,15 +504,14 @@ export class SearchViewlet extends Viewlet {
accessibilityProvider: this.instantiationService.createInstance(SearchAccessibilityProvider),
dnd
}, {
ariaLabel: nls.localize('treeAriaLabel', "Search Results"),
keyboardSupport: false
ariaLabel: nls.localize('treeAriaLabel', "Search Results")
});
this.tree.setInput(this.viewModel.searchResult);
this.toUnbind.push(renderer);
const fileResultsNavigation = this._register(new FileResultsNavigation(this.tree, { openOnFocus: true }));
this._register(debounceEvent(fileResultsNavigation.openFile, (last, event) => event, 75, true)(options => {
const fileResultsNavigation = this._register(new ResourceResultsNavigation(this.tree, { openOnFocus: true }));
this._register(debounceEvent(fileResultsNavigation.openResource, (last, event) => event, 75, true)(options => {
if (options.element instanceof Match) {
let selectedMatch: Match = options.element;
if (this.currentSelectedFileMatch) {
@@ -289,6 +289,25 @@ export class ExtensionScannerInput {
}
}
export interface IExtensionReference {
name: string;
path: string;
}
export interface IExtensionResolver {
resolveExtensions(): TPromise<IExtensionReference[]>;
}
class DefaultExtensionResolver implements IExtensionResolver {
constructor(private root: string) { }
resolveExtensions(): TPromise<IExtensionReference[]> {
return pfs.readDirsInDir(this.root)
.then(folders => folders.map(name => ({ name, path: join(this.root, name) })));
}
}
export class ExtensionScanner {
/**
@@ -318,10 +337,14 @@ export class ExtensionScanner {
/**
* Scan a list of extensions defined in `absoluteFolderPath`
*/
public static async scanExtensions(input: ExtensionScannerInput, log: ILog): TPromise<IExtensionDescription[]> {
public static async scanExtensions(input: ExtensionScannerInput, log: ILog, resolver?: IExtensionResolver): TPromise<IExtensionDescription[]> {
const absoluteFolderPath = input.absoluteFolderPath;
const isBuiltin = input.isBuiltin;
if (!resolver) {
resolver = new DefaultExtensionResolver(absoluteFolderPath);
}
try {
let obsolete: { [folderName: string]: boolean; } = {};
if (!isBuiltin) {
@@ -333,38 +356,35 @@ export class ExtensionScanner {
}
}
const rawFolders = await pfs.readDirsInDir(absoluteFolderPath);
let refs = await resolver.resolveExtensions();
// Ensure the same extension order
rawFolders.sort();
refs.sort((a, b) => a.name < b.name ? -1 : 1);
let folders: string[] = null;
if (isBuiltin) {
folders = rawFolders;
} else {
if (!isBuiltin) {
// TODO: align with extensionsService
const nonGallery: string[] = [];
const gallery: string[] = [];
const nonGallery: IExtensionReference[] = [];
const gallery: IExtensionReference[] = [];
rawFolders.forEach(folder => {
if (obsolete[folder]) {
refs.forEach(ref => {
if (obsolete[ref.name]) {
return;
}
const { id, version } = getIdAndVersionFromLocalExtensionId(folder);
const { id, version } = getIdAndVersionFromLocalExtensionId(ref.name);
if (!id || !version) {
nonGallery.push(folder);
nonGallery.push(ref);
} else {
gallery.push(folder);
gallery.push(ref);
}
});
folders = [...nonGallery, ...gallery];
refs = [...nonGallery, ...gallery];
}
const nlsConfig = ExtensionScannerInput.createNLSConfig(input);
let extensionDescriptions = await TPromise.join(folders.map(f => this.scanExtension(input.ourVersion, log, join(absoluteFolderPath, f), isBuiltin, nlsConfig)));
let extensionDescriptions = await TPromise.join(refs.map(r => this.scanExtension(input.ourVersion, log, r.path, isBuiltin, nlsConfig)));
extensionDescriptions = extensionDescriptions.filter(item => item !== null);
if (!isBuiltin) {
@@ -19,7 +19,7 @@ import { IMessage, IExtensionDescription, IExtensionsStatus, IExtensionService,
import { IExtensionEnablementService, IExtensionIdentifier, EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions, BetterMergeId, BetterMergeDisabledNowKey } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionsRegistry, ExtensionPoint, IExtensionPointUser, ExtensionMessageCollector, IExtensionPoint } from 'vs/platform/extensions/common/extensionsRegistry';
import { ExtensionScanner, ILog, ExtensionScannerInput } from 'vs/workbench/services/extensions/electron-browser/extensionPoints';
import { ExtensionScanner, ILog, ExtensionScannerInput, IExtensionResolver, IExtensionReference } from 'vs/workbench/services/extensions/electron-browser/extensionPoints';
import { IMessageService, CloseAction } from 'vs/platform/message/common/message';
import { ProxyIdentifier } from 'vs/workbench/services/extensions/node/proxyIdentifier';
import { ExtHostContext, ExtHostExtensionServiceShape, IExtHostContext, MainContext } from 'vs/workbench/api/node/extHost.protocol';
@@ -46,6 +46,42 @@ import { RPCProtocol } from 'vs/workbench/services/extensions/node/rpcProtocol';
const SystemExtensionsRoot = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', 'extensions'));
const ExtraDevSystemExtensionsRoot = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', '.build', 'builtInExtensions'));
interface IBuiltInExtension {
name: string;
version: string;
repo: string;
}
interface IBuiltInExtensionControl {
[name: string]: 'marketplace' | 'disabled' | string;
}
class ExtraBuiltInExtensionResolver implements IExtensionResolver {
constructor(private builtInExtensions: IBuiltInExtension[], private control: IBuiltInExtensionControl) { }
resolveExtensions(): TPromise<IExtensionReference[]> {
const result: IExtensionReference[] = [];
for (const ext of this.builtInExtensions) {
const controlState = this.control[ext.name] || 'marketplace';
switch (controlState) {
case 'disabled':
break;
case 'marketplace':
result.push({ name: ext.name, path: path.join(ExtraDevSystemExtensionsRoot, ext.name) });
break;
default:
result.push({ name: ext.name, path: controlState });
break;
}
}
return TPromise.as(result);
}
}
// Enable to see detailed message communication between window and extension host
const logExtensionHostCommunication = false;
@@ -632,7 +668,19 @@ export class ExtensionService extends Disposable implements IExtensionService {
let finalBuiltinExtensions: TPromise<IExtensionDescription[]> = builtinExtensions;
if (devMode) {
const extraBuiltinExtensions = ExtensionScanner.scanExtensions(new ExtensionScannerInput(version, commit, locale, devMode, ExtraDevSystemExtensionsRoot, true), log);
const builtInExtensionsFilePath = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', 'build', 'builtInExtensions.json'));
const builtInExtensions = pfs.readFile(builtInExtensionsFilePath, 'utf8')
.then<IBuiltInExtension[]>(raw => JSON.parse(raw));
const controlFilePath = path.join(process.env['HOME'], '.vscode-oss-dev', 'extensions', 'control.json');
const controlFile = pfs.readFile(controlFilePath, 'utf8')
.then<IBuiltInExtensionControl>(raw => JSON.parse(raw), () => ({} as any));
const input = new ExtensionScannerInput(version, commit, locale, devMode, ExtraDevSystemExtensionsRoot, true);
const extraBuiltinExtensions = TPromise.join([builtInExtensions, controlFile])
.then(([builtInExtensions, control]) => new ExtraBuiltInExtensionResolver(builtInExtensions, control))
.then(resolver => ExtensionScanner.scanExtensions(input, log, resolver));
finalBuiltinExtensions = TPromise.join([builtinExtensions, extraBuiltinExtensions]).then(([builtinExtensions, extraBuiltinExtensions]) => {
let resultMap: { [id: string]: IExtensionDescription; } = Object.create(null);
for (let i = 0, len = builtinExtensions.length; i < len; i++) {