mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-08 01:28:26 -05:00
Merge VS Code 1.26.1 (#2394)
* Squash merge commits for 1.26 (#1) (#2323) * Polish tag search as per feedback (#55269) * Polish tag search as per feedback * Updated regex * Allow users to opt-out of features that send online requests in the background (#55097) * settings sweep #54690 * Minor css tweaks to enable eoverflow elipsis in more places (#55277) * fix an issue with titlebarheight when not scaling with zoom * Settings descriptions update #54690 * fixes #55209 * Settings editor - many padding fixes * More space above level 2 label * Fixing Cannot debug npm script using Yarn #55103 * Settings editor - show ellipsis when description overflows * Settings editor - ... fix measuring around links, relayout * Setting descriptions * Settings editor - fix ... for some short lines, fix select container width * Settings editor - overlay trees so scrollable shadow is full width * Fix #54133 - missing extension settings after reload * Settings color token description tweak * Settings editor - disable overflow indicator temporarily, needs to be faster * Added command to Run the selected npm script * fixes #54452 * fixes #54929 * fixes #55248 * prefix command with extension name * Contribute run selected to the context menu * node-debug@1.26.6 * Allow terminal rendererType to be swapped out at runtime Part of #53274 Fixes #55344 * Settings editor - fix not focusing search when restoring editor setInput must be actually async. Will be fixed naturally when we aren't using winJS promises... * Settings editor - TOC should only expand the section with a selected item * Bump node-debug2 * Settings editor - Tree focus outlines * Settings editor - don't blink the scrollbar when toc selection changes And hide TOC correctly when the editor is narrow * Settings editor - header rows should not be selectable * fixes #54877 * change debug assignee to isi * Settings sweep (#54690) * workaround for #55051 * Settings sweep (#54690) * settings sweep #54690 * Don't try closing tags when you type > after another > * Describe what implementation code lens does Fixes #55370 * fix javadoc formatter setting description * fixes #55325 * update to officical TS version * Settings editor - Even more padding, use semibold instead of bold * Fix #55357 - fix TOC twistie * fixes #55288 * explorer: refresh on di change file system provider registration fixes #53256 * Disable push to Linux repo to test standalone publisher * New env var to notify log level to extensions #54001 * Disable snippets in extension search (when not in suggest dropdown) (#55281) * Disable snippits in extension search (when not in suggest dropdown) * Add monaco input contributions * Fix bug preventing snippetSuggestions from taking effect in sub-editors * Latest emmet helper to fix #52366 * Fix comment updates for threads within same file * Allow extensions to log telemetry to log files #54001 * Pull latest css grammar * files.exclude control - use same style for "add" vs "edit" * files.exclude control - focus/keyboard behavior * don't show menubar too early * files.exclude - better styling * Place cursor at end of extensions search box on autofill (#55254) * Place cursor at end of extensions search box on autofill * Use position instead of selection * fix linux build issue (empty if block) * Settings editor - fix extension category prefixes * Settings editor - add simple ellipsis for first line that overflows, doesn't cover case when first line does not overflow but there is more text, TODO * File/Text search provider docs * Fixes #52655 * Include epoch (#55008) * Fixes #53385 * Fixes #49480 * VS Code Insiders (Users) not opening Fixes #55353 * Better handling of the case when the extension host fails to start * Fixes #53966 * Remove confusing Start from wordPartLeft commands ID * vscode-xterm@3.6.0-beta12 Fixes #55488 * Initial size is set to infinity!! Fixes #55461 * Polish embeddedEditorBackground * configuration service misses event * Fix #55224 - fix duplicate results in multiroot workspace from splitting the diskseach query * Select all not working in issue reporter on mac, fixes #55424 * Disable fuzzy matching for extensions autosuggest (#55498) * Fix clipping of extensions search border in some third party themes (#55504) * fixes #55538 * Fix bug causing an aria alert to not be shown the third time (and odd numbers thereafter) * Settings editor - work around rendering glitch with webkit-line-clamp * Settings editor - revert earlier '...' changes * Settings editor - move enumDescription to its own div, because it disturbs -webkit-line-clamp for some reason * Settings editor - better overflow indicator * Don't show existing filters in autocomplete (#55495) * Dont show existing filters in autocomplete * Simplify * Settings Editor: Add aria labels for input elements Fixes: #54836 (#55543) * fixes #55223 * Update vscode-css-languageservice to 3.0.10-next.1 * Fix #55509 - settings navigation * Fix #55519 * Fix #55520 * FIx #55524 * Fix #55556 - include wordSeparators in all search queries, so findTextInFiles can respect isWordMatch correctly * oss updates for endgame * Fix unit tests * fixes #55522 * Avoid missing manifest error from bubbling up #54757 * Settings format crawl * Search provider - Fix FileSearchProvider to return array, not progress * Fix #55598 * Settings editor - fix NPE rendering settings with no description * dont render inden guides in search box (#55600) * fixes #55454 * More settings crawl * Another change for #55598 - maxResults applies to FileSearch and TextSearch but not FileIndex * Fix FileSearchProvider unit tests for progress change * fixes #55561 * Settings description update for #54690 * Update setting descriptions for online services * Minor edits * fixes #55513 * fixes #55451 * Fix #55612 - fix findTextInFiles cancellation * fixes #55539 * More setting description tweaks * Setting to disable online experiments #54354 * fixes #55507 * fixes #55515 * Show online services action only in Insiders for now * Settings editor - change toc behavior default to 'filter' * Settings editor - nicer filter count style during search * Fix #55617 - search viewlet icons * Settings editor - better styling for element count indicator * SearchProvider - fix NPE when searching extraFileResources * Allow extends to work without json suffix Fixes #16905 * Remove accessability options logic entirely Follow up on #55451 * use latest version of DAP * fixes #55490 * fixes #55122 * fixes #52332 * Avoid assumptions about git: URIs (fixes #36236) * relative path for descriptions * resourece: get rid of isFile context key fixes #48275 * Register previous ids for compatibility (#53497) * more tuning for #48275 * no need to always re-read "files explorer" fixes #52003 * read out active composites properly fixes #51967 * Update link colors for hc theme to meet color contrast ratio, fixes #55651 Also updated link color for `textLinkActiveForeground` to be the same as `textLinkForeground` as it wasn't properly updated * detect 'winpty-agent.exe'; fixes #55672 * node-debug@1.26.7 * reset counter on new label * Settings editor - fix multiple setting links in one description * Settings editor - color code blocks in setting descriptions, fix #55532 * Settings editor - hover color in TOC * Settings editor - fix navigation NPE * Settings editor - fix text control width * Settings editor - maybe fix #55684 * Fix bug causing cursor to not move on paste * fixes #53582 * Use ctrlCmd instead of ctrl for go down from search box * fixes #55264 * fixes #55456 * filter for spcaes before triggering search (#55611) * Fix #55698 - don't lose filtered TOC counts when refreshing TOC * fixes #55421 * fixes #28979 * fixes #55576 * only add check for updates to windows/linux help * readonly files: append decoration to label fixes #53022 * debug: do not show toolbar while initialising fixes #55026 * Opening launch.json should not activate debug extensions fixes #55029 * fixes #55435 * fixes #55434 * fixes #55439 * trigger menu only on altkey up * Fix #50555 - fix settings editor memory leak * Fix #55712 - no need to focus 'a' anymore when restoring control focus after tree render * fixes #55335 * proper fix for readonly model fixes #53022 * improve FoldingRangeKind spec (for #55686) * Use class with static fields (fixes #55494) * Fixes #53671 * fixes #54630 * [html] should disable ionic suggestions by default. Currently forces deprecated Ionic v1 suggestions in .html files while typing. Fixes #53324 * cleanup deps * debug issues back to andre * update electron for smoketest * Fix #55757 - prevent settings tabs from overflowing * Fix #53897 - revert setting menu defaults to old editor * Add enum descriptions to `typescript.preferences.importModuleSpecifier` * Fix #55767 - leaking style elements from settings editor * Fix #55521 - prevent flashing when clicking in exclude control * Update Git modified color for contrast ratio, fixes #53140 * Revert "Merge branch 'master' of github.com:Microsoft/vscode" This reverts commit bf46b6bfbae0cab99c2863e1244a916181fa9fbc, reversing changes made to e275a424483dfb4ed33b428c97d5e2c441d6b917. * Revert "Revert "Merge branch 'master' of github.com:Microsoft/vscode"" This reverts commit 53949d963f39e40757557c6526332354a31d9154. * don't ask to install an incomplete menu * Fix NPE in terminal AccessibilityManager Fixes #55744 * don't display fallback menu unless we've closed the last window * fixes #55547 * Fix smoke tests for extension search box * Update OSSREADME.json for Electron 2.0.5 * Update distro Includes Chromium license changes * fix #55455 * fix #55865 * fixes #55893 * Fix bug causing workspace recommendations to go away upon ignoring a recommendation (#55805) * Fix bug causing workspace recommendations to go away upon ignoring a recommendation * ONly show on @recommended or @recommended:workspace * Make more consistant * Fix #55911 * Understand json activity (#55926) * Understand json file activity * Refactoring * adding composer.json * Distro update for experiments * use terminal.processId for auto-attach; fixes #55918 * Reject invalid URI with vscode.openFolder (for #55891) * improve win32 setup system vs user detection fixes #55840 fixes #55840 delay winreg import related to #55840 show notification earlier related to #55840 fix #55840 update inno setup message related to #55840 * Fix #55593 - this code only operates on local paths, so use fsPath and Uri.file instead * Bring back the old menu due to electron 2.0 issues (#55913) * add the old menu back for native menus * make menu labels match * `vscode.openFolder`: treat missing URI schema gracefully (for #55891) * delay EH reattach; fixes #55955 * Mark all json files under appSettingsHome as settings * Use localized strings for telemetry opt-out * Exception when saving file editor opened from remote file provider (fixes #55051) * Remove terminal menu from stable Fixes 56003 * VSCode Insiders crashes on open with TypeError: Cannot read property 'lastIndexOf' of undefined. Fixes #54933 * improve fix for #55891 * fix #55916 * Improve #55891 * increase EH debugging restart delay; fixes #55955 * Revert "Don't include non-resource entries in history quick pick" This reverts commit 37209a838e9f7e9abe6dc53ed73cdf1e03b72060. * Diff editor: horizontal scrollbar height is smaller (fixes #56062) * improve openFolder uri fix (correctly treat backslashes) * fixes #56116 repair ipc for native menubar keybindings * Fix #56240 - Open the JSON settings editor instead of the UI editor * Fix #55536 * uriDisplay: if no formatter is registered fall back to getPathlabel fixes #56104 * VSCode hangs when opening python file. Fixes #56377 * VS Code Hangs When Opening Specific PowerShell File. Fixes #56430 * Fix #56433 - search extraFileResources even when no folders open * Workaround #55649 * Fix in master #56371 * Fix tests #56371 * Fix in master #56317 * increase version to 1.26.1 * Fixes #56387: Handle SIGPIPE in extension host * fixes #56185 * Fix merge issues (part 1) * Fix build breaks (part 1) * Build breaks (part 2) * Build breaks (part 3) * More build breaks (part 4) * Fix build breaks (part 5) * WIP * Fix menus * Render query result and message panels (#2363) * Put back query editor hot exit changes * Fix grid changes that broke profiler (#2365) * Update APIs for saving query editor state * Fix restore view state for profiler and edit data * Updating custom default themes to support 4.5:1 contrast ratio * Test updates * Fix Extension Manager and Windows Setup * Update license headers * Add appveyor and travis files back * Fix hidden modal dropdown issue
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
import { localize } from 'vs/nls';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { join } from 'vs/base/common/paths';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import { createCSSRule } from 'vs/base/browser/dom';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
@@ -17,19 +17,19 @@ import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IExtensionService, IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ViewLocation } from 'vs/workbench/common/views';
|
||||
import { PersistentViewsViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { Extensions as ViewContainerExtensions, IViewContainersRegistry, TEST_VIEW_CONTAINER_ID } from 'vs/workbench/common/views';
|
||||
import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { forEach } from 'vs/base/common/collections';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
export interface IUserFriendlyViewsContainerDescriptor {
|
||||
id: string;
|
||||
@@ -37,6 +37,12 @@ export interface IUserFriendlyViewsContainerDescriptor {
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export interface IUserFriendlyViewsContainerDescriptor2 {
|
||||
id: string;
|
||||
title: string;
|
||||
icon: URI;
|
||||
}
|
||||
|
||||
const viewsContainerSchema: IJSONSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@@ -50,7 +56,7 @@ const viewsContainerSchema: IJSONSchema = {
|
||||
type: 'string'
|
||||
},
|
||||
icon: {
|
||||
description: localize('vscode.extension.contributes.views.containers.icon', "Path to the container icon. Icons are 24x24 centered on a 50x40 square and have a fill color of 'rgb(215, 218, 224)' or '#d7dae0'. It is recommended that icons be in SVG, though any image file type is accepted."),
|
||||
description: localize('vscode.extension.contributes.views.containers.icon', "Path to the container icon. Icons are 24x24 centered on a 50x40 block and have a fill color of 'rgb(215, 218, 224)' or '#d7dae0'. It is recommended that icons be in SVG, though any image file type is accepted."),
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
@@ -80,12 +86,11 @@ class ViewsContainersExtensionHandler implements IWorkbenchContribution {
|
||||
}
|
||||
|
||||
private registerTestViewContainer(): void {
|
||||
const id = 'test';
|
||||
const title = localize('test', "Test");
|
||||
const cssClass = `extensionViewlet-${id}`;
|
||||
const icon = require.toUrl('./media/test.svg');
|
||||
const cssClass = `extensionViewlet-test`;
|
||||
const icon = URI.parse(require.toUrl('./media/test.svg'));
|
||||
|
||||
this.registerCustomViewlet({ id, title, icon }, TEST_VIEW_CONTAINER_ORDER, cssClass);
|
||||
this.registerCustomViewlet({ id: TEST_VIEW_CONTAINER_ID, title, icon }, TEST_VIEW_CONTAINER_ORDER, cssClass);
|
||||
}
|
||||
|
||||
private handleAndRegisterCustomViewContainers() {
|
||||
@@ -137,34 +142,34 @@ class ViewsContainersExtensionHandler implements IWorkbenchContribution {
|
||||
private registerCustomViewContainers(containers: IUserFriendlyViewsContainerDescriptor[], extension: IExtensionDescription) {
|
||||
containers.forEach((descriptor, index) => {
|
||||
const cssClass = `extensionViewlet-${descriptor.id}`;
|
||||
const icon = join(extension.extensionFolderPath, descriptor.icon);
|
||||
this.registerCustomViewlet({ id: descriptor.id, title: descriptor.title, icon }, TEST_VIEW_CONTAINER_ORDER + index + 1, cssClass);
|
||||
const icon = resources.joinPath(extension.extensionLocation, descriptor.icon);
|
||||
this.registerCustomViewlet({ id: `workbench.view.extension.${descriptor.id}`, title: descriptor.title, icon }, TEST_VIEW_CONTAINER_ORDER + index + 1, cssClass);
|
||||
});
|
||||
}
|
||||
|
||||
private registerCustomViewlet(descriptor: IUserFriendlyViewsContainerDescriptor, order: number, cssClass: string): void {
|
||||
private registerCustomViewlet(descriptor: IUserFriendlyViewsContainerDescriptor2, order: number, cssClass: string): void {
|
||||
const viewContainersRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
|
||||
const viewletRegistry = Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets);
|
||||
const id = `workbench.view.extension.${descriptor.id}`;
|
||||
const id = descriptor.id;
|
||||
|
||||
if (!viewletRegistry.getViewlet(id)) {
|
||||
|
||||
const location: ViewLocation = ViewLocation.register(id);
|
||||
viewContainersRegistry.registerViewContainer(id);
|
||||
|
||||
// Register as viewlet
|
||||
class CustomViewlet extends PersistentViewsViewlet {
|
||||
class CustomViewlet extends ViewContainerViewlet {
|
||||
constructor(
|
||||
@IPartService partService: IPartService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IWorkspaceContextService contextService: IWorkspaceContextService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
|
||||
@IEditorService editorService: IEditorService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IExtensionService extensionService: IExtensionService
|
||||
) {
|
||||
super(id, location, `${id}.state`, true, partService, telemetryService, storageService, instantiationService, themeService, contextService, contextKeyService, contextMenuService, extensionService);
|
||||
super(id, `${id}.state`, true, partService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
|
||||
}
|
||||
}
|
||||
const viewletDescriptor = new ViewletDescriptor(
|
||||
@@ -172,7 +177,8 @@ class ViewsContainersExtensionHandler implements IWorkbenchContribution {
|
||||
id,
|
||||
descriptor.title,
|
||||
cssClass,
|
||||
order
|
||||
order,
|
||||
descriptor.icon
|
||||
);
|
||||
|
||||
viewletRegistry.registerViewlet(viewletDescriptor);
|
||||
@@ -182,9 +188,9 @@ class ViewsContainersExtensionHandler implements IWorkbenchContribution {
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IViewletService viewletService: IViewletService,
|
||||
@IWorkbenchEditorService editorService: IWorkbenchEditorService
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(id, label, id, viewletService, editorService);
|
||||
super(id, label, id, viewletService, editorGroupService);
|
||||
}
|
||||
}
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
|
||||
@@ -203,4 +209,4 @@ class ViewsContainersExtensionHandler implements IWorkbenchContribution {
|
||||
}
|
||||
|
||||
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
|
||||
workbenchRegistry.registerWorkbenchContribution(ViewsContainersExtensionHandler, LifecyclePhase.Starting);
|
||||
workbenchRegistry.registerWorkbenchContribution(ViewsContainersExtensionHandler, LifecyclePhase.Starting);
|
||||
|
||||
@@ -7,22 +7,144 @@
|
||||
import { localize } from 'vs/nls';
|
||||
import { forEach } from 'vs/base/common/collections';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { ExtensionMessageCollector, ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { ViewLocation, ViewsRegistry, ICustomViewDescriptor } from 'vs/workbench/common/views';
|
||||
import { CustomTreeViewPanel } from 'vs/workbench/browser/parts/views/customViewPanel';
|
||||
import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { ViewContainer, ViewsRegistry, ICustomViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views';
|
||||
import { CustomTreeViewPanel, CustomTreeViewer } from 'vs/workbench/browser/parts/views/customView';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { coalesce, } from 'vs/base/common/arrays';
|
||||
import { viewsContainersExtensionPoint } from 'vs/workbench/api/browser/viewsContainersExtensionPoint';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { VIEWLET_ID as EXPLORER } from 'vs/workbench/parts/files/common/files';
|
||||
import { VIEWLET_ID as SCM } from 'vs/workbench/parts/scm/common/scm';
|
||||
import { VIEWLET_ID as DEBUG } from 'vs/workbench/parts/debug/common/debug';
|
||||
|
||||
namespace schema {
|
||||
interface IUserFriendlyViewDescriptor {
|
||||
id: string;
|
||||
name: string;
|
||||
when?: string;
|
||||
}
|
||||
|
||||
export interface IUserFriendlyViewDescriptor {
|
||||
id: string;
|
||||
name: string;
|
||||
when?: string;
|
||||
const viewDescriptor: IJSONSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
description: localize('vscode.extension.contributes.view.id', 'Identifier of the view. Use this to register a data provider through `vscode.window.registerTreeDataProviderForView` API. Also to trigger activating your extension by registering `onView:${id}` event to `activationEvents`.'),
|
||||
type: 'string'
|
||||
},
|
||||
name: {
|
||||
description: localize('vscode.extension.contributes.view.name', 'The human-readable name of the view. Will be shown'),
|
||||
type: 'string'
|
||||
},
|
||||
when: {
|
||||
description: localize('vscode.extension.contributes.view.when', 'Condition which must be true to show this view'),
|
||||
type: 'string'
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const viewsContribution: IJSONSchema = {
|
||||
description: localize('vscode.extension.contributes.views', "Contributes views to the editor"),
|
||||
type: 'object',
|
||||
properties: {
|
||||
'explorer': {
|
||||
description: localize('views.explorer', "Contributes views to Explorer container in the Activity bar"),
|
||||
type: 'array',
|
||||
items: viewDescriptor,
|
||||
default: []
|
||||
},
|
||||
'debug': {
|
||||
description: localize('views.debug', "Contributes views to Debug container in the Activity bar"),
|
||||
type: 'array',
|
||||
items: viewDescriptor,
|
||||
default: []
|
||||
},
|
||||
'scm': {
|
||||
description: localize('views.scm', "Contributes views to SCM container in the Activity bar"),
|
||||
type: 'array',
|
||||
items: viewDescriptor,
|
||||
default: []
|
||||
},
|
||||
'test': {
|
||||
description: localize('views.test', "Contributes views to Test container in the Activity bar"),
|
||||
type: 'array',
|
||||
items: viewDescriptor,
|
||||
default: []
|
||||
}
|
||||
},
|
||||
additionalProperties: {
|
||||
description: localize('views.contributed', "Contributes views to contributed views container"),
|
||||
type: 'array',
|
||||
items: viewDescriptor,
|
||||
default: []
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const viewsExtensionPoint: IExtensionPoint<{ [loc: string]: IUserFriendlyViewDescriptor[] }> = ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: IUserFriendlyViewDescriptor[] }>('views', [viewsContainersExtensionPoint], viewsContribution);
|
||||
|
||||
class ViewsContainersExtensionHandler implements IWorkbenchContribution {
|
||||
|
||||
private viewContainersRegistry: IViewContainersRegistry;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private instantiationService: IInstantiationService
|
||||
) {
|
||||
this.viewContainersRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
|
||||
this.handleAndRegisterCustomViews();
|
||||
}
|
||||
|
||||
export function isValidViewDescriptors(viewDescriptors: IUserFriendlyViewDescriptor[], collector: ExtensionMessageCollector): boolean {
|
||||
private handleAndRegisterCustomViews() {
|
||||
viewsExtensionPoint.setHandler(extensions => {
|
||||
for (let extension of extensions) {
|
||||
const { value, collector } = extension;
|
||||
|
||||
forEach(value, entry => {
|
||||
if (!this.isValidViewDescriptors(entry.value, collector)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let container = this.getViewContainer(entry.key);
|
||||
if (!container) {
|
||||
collector.warn(localize('ViewContainerDoesnotExist', "View container '{0}' does not exist and all views registered to it will be added to 'Explorer'.", entry.key));
|
||||
container = this.viewContainersRegistry.get(EXPLORER);
|
||||
}
|
||||
const registeredViews = ViewsRegistry.getViews(container);
|
||||
const viewIds = [];
|
||||
const viewDescriptors = coalesce(entry.value.map(item => {
|
||||
// validate
|
||||
if (viewIds.indexOf(item.id) !== -1) {
|
||||
collector.error(localize('duplicateView1', "Cannot register multiple views with same id `{0}` in the view container `{1}`", item.id, container.id));
|
||||
return null;
|
||||
}
|
||||
if (registeredViews.some(v => v.id === item.id)) {
|
||||
collector.error(localize('duplicateView2', "A view with id `{0}` is already registered in the view container `{1}`", item.id, container.id));
|
||||
return null;
|
||||
}
|
||||
|
||||
const viewDescriptor = <ICustomViewDescriptor>{
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
ctor: CustomTreeViewPanel,
|
||||
container,
|
||||
when: ContextKeyExpr.deserialize(item.when),
|
||||
canToggleVisibility: true,
|
||||
collapsed: this.showCollapsed(container),
|
||||
treeViewer: this.instantiationService.createInstance(CustomTreeViewer, item.id, container)
|
||||
};
|
||||
|
||||
viewIds.push(viewDescriptor.id);
|
||||
return viewDescriptor;
|
||||
}));
|
||||
ViewsRegistry.registerViews(viewDescriptors);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private isValidViewDescriptors(viewDescriptors: IUserFriendlyViewDescriptor[], collector: ExtensionMessageCollector): boolean {
|
||||
if (!Array.isArray(viewDescriptors)) {
|
||||
collector.error(localize('requirearray', "views must be an array"));
|
||||
return false;
|
||||
@@ -46,114 +168,26 @@ namespace schema {
|
||||
return true;
|
||||
}
|
||||
|
||||
const viewDescriptor: IJSONSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
description: localize('vscode.extension.contributes.view.id', 'Identifier of the view. Use this to register a data provider through `vscode.window.registerTreeDataProviderForView` API. Also to trigger activating your extension by registering `onView:${id}` event to `activationEvents`.'),
|
||||
type: 'string'
|
||||
},
|
||||
name: {
|
||||
description: localize('vscode.extension.contributes.view.name', 'The human-readable name of the view. Will be shown'),
|
||||
type: 'string'
|
||||
},
|
||||
when: {
|
||||
description: localize('vscode.extension.contributes.view.when', 'Condition which must be true to show this view'),
|
||||
type: 'string'
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export const viewsContribution: IJSONSchema = {
|
||||
description: localize('vscode.extension.contributes.views', "Contributes views to the editor"),
|
||||
type: 'object',
|
||||
properties: {
|
||||
'explorer': {
|
||||
description: localize('views.explorer', "Contributes views to Explorer container in the Activity bar"),
|
||||
type: 'array',
|
||||
items: viewDescriptor,
|
||||
default: []
|
||||
},
|
||||
'debug': {
|
||||
description: localize('views.debug', "Contributes views to Debug container in the Activity bar"),
|
||||
type: 'array',
|
||||
items: viewDescriptor,
|
||||
default: []
|
||||
},
|
||||
'scm': {
|
||||
description: localize('views.scm', "Contributes views to SCM container in the Activity bar"),
|
||||
type: 'array',
|
||||
items: viewDescriptor,
|
||||
default: []
|
||||
},
|
||||
'test': {
|
||||
description: localize('views.test', "Contributes views to Test container in the Activity bar"),
|
||||
type: 'array',
|
||||
items: viewDescriptor,
|
||||
default: []
|
||||
}
|
||||
},
|
||||
additionalProperties: {
|
||||
description: localize('views.contributed', "Contributes views to contributed views container"),
|
||||
type: 'array',
|
||||
items: viewDescriptor,
|
||||
default: []
|
||||
private getViewContainer(value: string): ViewContainer {
|
||||
switch (value) {
|
||||
case 'explorer': return this.viewContainersRegistry.get(EXPLORER);
|
||||
case 'debug': return this.viewContainersRegistry.get(DEBUG);
|
||||
case 'scm': return this.viewContainersRegistry.get(SCM);
|
||||
default: return this.viewContainersRegistry.get(`workbench.view.extension.${value}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getViewLocation(value: string): ViewLocation {
|
||||
switch (value) {
|
||||
case 'explorer': return ViewLocation.Explorer;
|
||||
case 'debug': return ViewLocation.Debug;
|
||||
case 'scm': return ViewLocation.SCM;
|
||||
default: return ViewLocation.get(`workbench.view.extension.${value}`);
|
||||
private showCollapsed(container: ViewContainer): boolean {
|
||||
switch (container.id) {
|
||||
case EXPLORER:
|
||||
case SCM:
|
||||
case DEBUG:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyViewDescriptor[] }>('views', [viewsContainersExtensionPoint], schema.viewsContribution)
|
||||
.setHandler((extensions) => {
|
||||
for (let extension of extensions) {
|
||||
const { value, collector } = extension;
|
||||
|
||||
forEach(value, entry => {
|
||||
if (!schema.isValidViewDescriptors(entry.value, collector)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let location = getViewLocation(entry.key);
|
||||
if (!location) {
|
||||
collector.warn(localize('ViewContainerDoesnotExist', "View container '{0}' does not exist and all views registered to it will be added to 'Explorer'.", entry.key));
|
||||
location = ViewLocation.Explorer;
|
||||
}
|
||||
const registeredViews = ViewsRegistry.getViews(location);
|
||||
const viewIds = [];
|
||||
const viewDescriptors = coalesce(entry.value.map(item => {
|
||||
const viewDescriptor = <ICustomViewDescriptor>{
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
ctor: CustomTreeViewPanel,
|
||||
location,
|
||||
when: ContextKeyExpr.deserialize(item.when),
|
||||
canToggleVisibility: true,
|
||||
collapsed: true,
|
||||
treeView: true
|
||||
};
|
||||
|
||||
// validate
|
||||
if (viewIds.indexOf(viewDescriptor.id) !== -1) {
|
||||
collector.error(localize('duplicateView1', "Cannot register multiple views with same id `{0}` in the location `{1}`", viewDescriptor.id, viewDescriptor.location.id));
|
||||
return null;
|
||||
}
|
||||
if (registeredViews.some(v => v.id === viewDescriptor.id)) {
|
||||
collector.error(localize('duplicateView2', "A view with id `{0}` is already registered in the location `{1}`", viewDescriptor.id, viewDescriptor.location.id));
|
||||
return null;
|
||||
}
|
||||
|
||||
viewIds.push(viewDescriptor.id);
|
||||
return viewDescriptor;
|
||||
}));
|
||||
ViewsRegistry.registerViews(viewDescriptors);
|
||||
});
|
||||
}
|
||||
});
|
||||
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
|
||||
workbenchRegistry.registerWorkbenchContribution(ViewsContainersExtensionHandler, LifecyclePhase.Starting);
|
||||
@@ -53,6 +53,7 @@ import './mainThreadTerminalService';
|
||||
import './mainThreadTreeViews';
|
||||
import './mainThreadLogService';
|
||||
import './mainThreadWebview';
|
||||
import './mainThreadComments';
|
||||
import './mainThreadUrls';
|
||||
import './mainThreadWindow';
|
||||
import './mainThreadWorkspace';
|
||||
|
||||
@@ -94,11 +94,11 @@ function _generateMarkdown(description: string | ICommandHandlerDescription): st
|
||||
parts.push('\n\n');
|
||||
if (description.args) {
|
||||
for (let arg of description.args) {
|
||||
parts.push(`* _${arg.name}_ ${arg.description || ''}\n`);
|
||||
parts.push(`* _${arg.name}_ - ${arg.description || ''}\n`);
|
||||
}
|
||||
}
|
||||
if (description.returns) {
|
||||
parts.push(`* _(returns)_ ${description.returns}`);
|
||||
parts.push(`* _(returns)_ - ${description.returns}`);
|
||||
}
|
||||
parts.push('\n\n');
|
||||
return parts.join('');
|
||||
|
||||
156
src/vs/workbench/api/electron-browser/mainThreadComments.ts
Normal file
156
src/vs/workbench/api/electron-browser/mainThreadComments.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { ICodeEditor, isCodeEditor, isDiffEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { keys } from 'vs/base/common/map';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { ExtHostCommentsShape, ExtHostContext, IExtHostContext, MainContext, MainThreadCommentsShape } from '../node/extHost.protocol';
|
||||
|
||||
import { ICommentService } from 'vs/workbench/parts/comments/electron-browser/commentService';
|
||||
import { COMMENTS_PANEL_ID } from 'vs/workbench/parts/comments/electron-browser/commentsPanel';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { ReviewController } from 'vs/workbench/parts/comments/electron-browser/commentsEditorContribution';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadComments)
|
||||
export class MainThreadComments extends Disposable implements MainThreadCommentsShape {
|
||||
private _disposables: IDisposable[];
|
||||
private _proxy: ExtHostCommentsShape;
|
||||
private _documentProviders = new Map<number, IDisposable>();
|
||||
private _workspaceProviders = new Map<number, IDisposable>();
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IEditorService private _editorService: IEditorService,
|
||||
@ICommentService private _commentService: ICommentService,
|
||||
@IPanelService private _panelService: IPanelService,
|
||||
@ICodeEditorService private _codeEditorService: ICodeEditorService
|
||||
) {
|
||||
super();
|
||||
this._disposables = [];
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostComments);
|
||||
this._disposables.push(this._editorService.onDidActiveEditorChange(e => {
|
||||
const editors = this.getFocusedEditors();
|
||||
if (!editors || !editors.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
editors.forEach(editor => {
|
||||
const controller = ReviewController.get(editor);
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!editor.getModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const outerEditorURI = editor.getModel().uri;
|
||||
this.provideDocumentComments(outerEditorURI).then(commentInfos => {
|
||||
this._commentService.setDocumentComments(outerEditorURI, commentInfos.filter(info => info !== null));
|
||||
});
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
$registerDocumentCommentProvider(handle: number): void {
|
||||
this._documentProviders.set(handle, undefined);
|
||||
|
||||
this._commentService.registerDataProvider(
|
||||
handle,
|
||||
{
|
||||
provideDocumentComments: async (uri, token) => {
|
||||
return this._proxy.$provideDocumentComments(handle, uri);
|
||||
},
|
||||
onDidChangeCommentThreads: null,
|
||||
createNewCommentThread: async (uri, range, text, token) => {
|
||||
return this._proxy.$createNewCommentThread(handle, uri, range, text);
|
||||
},
|
||||
replyToCommentThread: async (uri, range, thread, text, token) => {
|
||||
return this._proxy.$replyToCommentThread(handle, uri, range, thread, text);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$registerWorkspaceCommentProvider(handle: number): void {
|
||||
this._workspaceProviders.set(handle, undefined);
|
||||
this._panelService.setPanelEnablement(COMMENTS_PANEL_ID, true);
|
||||
this._panelService.openPanel(COMMENTS_PANEL_ID);
|
||||
this._proxy.$provideWorkspaceComments(handle).then(commentThreads => {
|
||||
if (commentThreads) {
|
||||
this._commentService.setWorkspaceComments(handle, commentThreads);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$unregisterDocumentCommentProvider(handle: number): void {
|
||||
this._documentProviders.delete(handle);
|
||||
this._commentService.unregisterDataProvider(handle);
|
||||
}
|
||||
|
||||
$unregisterWorkspaceCommentProvider(handle: number): void {
|
||||
this._workspaceProviders.delete(handle);
|
||||
if (this._workspaceProviders.size === 0) {
|
||||
this._panelService.setPanelEnablement(COMMENTS_PANEL_ID, false);
|
||||
}
|
||||
this._commentService.removeWorkspaceComments(handle);
|
||||
}
|
||||
|
||||
$onDidCommentThreadsChange(handle: number, event: modes.CommentThreadChangedEvent) {
|
||||
// notify comment service
|
||||
this._commentService.updateComments(event);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._disposables = dispose(this._disposables);
|
||||
this._workspaceProviders.forEach(value => dispose(value));
|
||||
this._workspaceProviders.clear();
|
||||
this._documentProviders.forEach(value => dispose(value));
|
||||
this._documentProviders.clear();
|
||||
}
|
||||
|
||||
getFocusedEditors(): ICodeEditor[] {
|
||||
let activeControl = this._editorService.activeControl;
|
||||
if (activeControl) {
|
||||
if (isCodeEditor(activeControl.getControl())) {
|
||||
return [this._editorService.activeControl.getControl() as ICodeEditor];
|
||||
}
|
||||
|
||||
if (isDiffEditor(activeControl.getControl())) {
|
||||
let diffEditor = activeControl.getControl() as IDiffEditor;
|
||||
return [diffEditor.getOriginalEditor(), diffEditor.getModifiedEditor()];
|
||||
}
|
||||
}
|
||||
|
||||
let editor = this._codeEditorService.getFocusedCodeEditor();
|
||||
|
||||
if (editor) {
|
||||
return [editor];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
async provideWorkspaceComments(): Promise<modes.CommentThread[]> {
|
||||
const result: modes.CommentThread[] = [];
|
||||
for (const handle of keys(this._workspaceProviders)) {
|
||||
result.push(...await this._proxy.$provideWorkspaceComments(handle));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async provideDocumentComments(resource: URI): Promise<modes.CommentInfo[]> {
|
||||
const result: modes.CommentInfo[] = [];
|
||||
for (const handle of keys(this._documentProviders)) {
|
||||
result.push(await this._proxy.$provideDocumentComments(handle, resource));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,13 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import {
|
||||
ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext,
|
||||
IExtHostContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto
|
||||
} from '../node/extHost.protocol';
|
||||
} from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import severity from 'vs/base/common/severity';
|
||||
import { AbstractDebugAdapter, convertToVSCPaths, convertToDAPaths } from 'vs/workbench/parts/debug/node/debugAdapter';
|
||||
import { AbstractDebugAdapter } from 'vs/workbench/parts/debug/node/debugAdapter';
|
||||
import * as paths from 'vs/base/common/paths';
|
||||
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { convertToVSCPaths, convertToDAPaths } from 'vs/workbench/parts/debug/common/debugUtils';
|
||||
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadDebugService)
|
||||
@@ -38,9 +39,9 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
|
||||
) {
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDebugService);
|
||||
this._toDispose = [];
|
||||
this._toDispose.push(debugService.onDidNewProcess(proc => this._proxy.$acceptDebugSessionStarted(<DebugSessionUUID>proc.getId(), proc.configuration.type, proc.getName(false))));
|
||||
this._toDispose.push(debugService.onDidEndProcess(proc => this._proxy.$acceptDebugSessionTerminated(<DebugSessionUUID>proc.getId(), proc.configuration.type, proc.getName(false))));
|
||||
this._toDispose.push(debugService.getViewModel().onDidFocusProcess(proc => {
|
||||
this._toDispose.push(debugService.onDidNewSession(proc => this._proxy.$acceptDebugSessionStarted(<DebugSessionUUID>proc.getId(), proc.configuration.type, proc.getName(false))));
|
||||
this._toDispose.push(debugService.onDidEndSession(proc => this._proxy.$acceptDebugSessionTerminated(<DebugSessionUUID>proc.getId(), proc.configuration.type, proc.getName(false))));
|
||||
this._toDispose.push(debugService.getViewModel().onDidFocusSession(proc => {
|
||||
if (proc) {
|
||||
this._proxy.$acceptDebugSessionActiveChanged(<DebugSessionUUID>proc.getId(), proc.configuration.type, proc.getName(false));
|
||||
} else {
|
||||
@@ -50,7 +51,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
|
||||
|
||||
this._toDispose.push(debugService.onDidCustomEvent(event => {
|
||||
if (event && event.sessionId) {
|
||||
const process = this.debugService.getModel().getProcesses().filter(p => p.getId() === event.sessionId).pop();
|
||||
const process = this.debugService.getModel().getSessions().filter(p => p.getId() === event.sessionId).pop();
|
||||
if (process) {
|
||||
this._proxy.$acceptDebugSessionCustomEvent(event.sessionId, process.configuration.type, process.configuration.name, event);
|
||||
}
|
||||
@@ -63,15 +64,15 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
|
||||
this._toDispose.push(this.debugService.getConfigurationManager().registerDebugAdapterProvider(debugTypes, this));
|
||||
}
|
||||
|
||||
createDebugAdapter(debugType: string, adapterInfo): IDebugAdapter {
|
||||
createDebugAdapter(debugType: string, adapterInfo, debugPort: number): IDebugAdapter {
|
||||
const handle = this._debugAdaptersHandleCounter++;
|
||||
const da = new ExtensionHostDebugAdapter(handle, this._proxy, debugType, adapterInfo);
|
||||
const da = new ExtensionHostDebugAdapter(handle, this._proxy, debugType, adapterInfo, debugPort);
|
||||
this._debugAdapters.set(handle, da);
|
||||
return da;
|
||||
}
|
||||
|
||||
substituteVariables(folder: IWorkspaceFolder, config: IConfig): TPromise<IConfig> {
|
||||
return this._proxy.$substituteVariables(folder.uri, config);
|
||||
return this._proxy.$substituteVariables(folder ? folder.uri : undefined, config);
|
||||
}
|
||||
|
||||
runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise<void> {
|
||||
@@ -89,7 +90,8 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
|
||||
|
||||
// set up a handler to send more
|
||||
this._toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(e => {
|
||||
if (e) {
|
||||
// Ignore session only breakpoint events since they should only reflect in the UI
|
||||
if (e && !e.sessionOnly) {
|
||||
const delta: IBreakpointsDeltaDto = {};
|
||||
if (e.added) {
|
||||
delta.added = this.convertToDto(e.added);
|
||||
@@ -157,10 +159,10 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
|
||||
type: 'function',
|
||||
id: fbp.getId(),
|
||||
enabled: fbp.enabled,
|
||||
functionName: fbp.name,
|
||||
hitCondition: bp.hitCondition,
|
||||
// {{SQL CARBON EDIT}}
|
||||
// condition: bp.condition
|
||||
condition: fbp.condition,
|
||||
hitCondition: fbp.hitCondition,
|
||||
logMessage: fbp.logMessage,
|
||||
functionName: fbp.name
|
||||
};
|
||||
} else {
|
||||
const sbp = <IBreakpoint>bp;
|
||||
@@ -220,9 +222,9 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
|
||||
}
|
||||
|
||||
public $customDebugAdapterRequest(sessionId: DebugSessionUUID, request: string, args: any): TPromise<any> {
|
||||
const process = this.debugService.getModel().getProcesses().filter(p => p.getId() === sessionId).pop();
|
||||
const process = this.debugService.getModel().getSessions().filter(p => p.getId() === sessionId).pop();
|
||||
if (process) {
|
||||
return process.session.custom(request, args).then(response => {
|
||||
return process.raw.custom(request, args).then(response => {
|
||||
if (response && response.success) {
|
||||
return response.body;
|
||||
} else {
|
||||
@@ -267,7 +269,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
|
||||
/*
|
||||
class ExtensionHostDebugAdapter extends AbstractDebugAdapter {
|
||||
|
||||
constructor(private _handle: number, private _proxy: ExtHostDebugServiceShape, private _debugType: string, private _adapterExecutable: IAdapterExecutable | null) {
|
||||
constructor(private _handle: number, private _proxy: ExtHostDebugServiceShape, private _debugType: string, private _adapterExecutable: IAdapterExecutable | null, private _debugPort: number) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -280,7 +282,7 @@ class ExtensionHostDebugAdapter extends AbstractDebugAdapter {
|
||||
}
|
||||
|
||||
public startSession(): TPromise<void> {
|
||||
return this._proxy.$startDASession(this._handle, this._debugType, this._adapterExecutable);
|
||||
return this._proxy.$startDASession(this._handle, this._debugType, this._adapterExecutable, this._debugPort);
|
||||
}
|
||||
|
||||
public sendMessage(message: DebugProtocol.ProtocolMessage): void {
|
||||
|
||||
@@ -10,14 +10,14 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { ExtHostContext, MainContext, IExtHostContext, MainThreadDecorationsShape, ExtHostDecorationsShape, DecorationData, DecorationRequest } from '../node/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { IDecorationsService, IDecorationData } from 'vs/workbench/services/decorations/browser/decorations';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { values } from 'vs/base/common/collections';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
class DecorationRequestsQueue {
|
||||
|
||||
private _idPool = 0;
|
||||
private _requests: { [id: number]: DecorationRequest } = Object.create(null);
|
||||
private _resolver: { [id: number]: Function } = Object.create(null);
|
||||
private _resolver: { [id: number]: (data: DecorationData) => any } = Object.create(null);
|
||||
|
||||
private _timer: number;
|
||||
|
||||
@@ -27,16 +27,18 @@ class DecorationRequestsQueue {
|
||||
//
|
||||
}
|
||||
|
||||
enqueue(handle: number, uri: URI): TPromise<DecorationData> {
|
||||
enqueue(handle: number, uri: URI, token: CancellationToken): Promise<DecorationData> {
|
||||
const id = ++this._idPool;
|
||||
return new TPromise((resolve, reject) => {
|
||||
const result = new Promise<DecorationData>(resolve => {
|
||||
this._requests[id] = { id, handle, uri };
|
||||
this._resolver[id] = resolve;
|
||||
this._processQueue();
|
||||
}, () => {
|
||||
});
|
||||
token.onCancellationRequested(() => {
|
||||
delete this._requests[id];
|
||||
delete this._resolver[id];
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private _processQueue(): void {
|
||||
@@ -87,8 +89,8 @@ export class MainThreadDecorations implements MainThreadDecorationsShape {
|
||||
const registration = this._decorationsService.registerDecorationsProvider({
|
||||
label,
|
||||
onDidChange: emitter.event,
|
||||
provideDecorations: (uri) => {
|
||||
return this._requestQueue.enqueue(handle, uri).then(data => {
|
||||
provideDecorations: (uri, token) => {
|
||||
return this._requestQueue.enqueue(handle, uri, token).then(data => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -4,18 +4,20 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ITextModel, DefaultEndOfLine } from 'vs/editor/common/model';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { MainThreadDocumentContentProvidersShape, ExtHostContext, ExtHostDocumentContentProvidersShape, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { createTextBuffer } from 'vs/editor/common/model/textModel';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { ExtHostContext, ExtHostDocumentContentProvidersShape, IExtHostContext, MainContext, MainThreadDocumentContentProvidersShape } from '../node/extHost.protocol';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadDocumentContentProviders)
|
||||
export class MainThreadDocumentContentProviders implements MainThreadDocumentContentProvidersShape {
|
||||
@@ -28,8 +30,8 @@ export class MainThreadDocumentContentProviders implements MainThreadDocumentCon
|
||||
@ITextModelService private readonly _textModelResolverService: ITextModelService,
|
||||
@IModeService private readonly _modeService: IModeService,
|
||||
@IModelService private readonly _modelService: IModelService,
|
||||
@ICodeEditorService codeEditorService: ICodeEditorService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService
|
||||
@IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService,
|
||||
@ICodeEditorService codeEditorService: ICodeEditorService
|
||||
) {
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentContentProviders);
|
||||
}
|
||||
@@ -69,10 +71,11 @@ export class MainThreadDocumentContentProviders implements MainThreadDocumentCon
|
||||
return;
|
||||
}
|
||||
|
||||
const textBuffer = createTextBuffer(value, DefaultEndOfLine.CRLF);
|
||||
|
||||
if (!model.equalsTextBuffer(textBuffer)) {
|
||||
model.setValueFromTextBuffer(textBuffer);
|
||||
}
|
||||
this._editorWorkerService.computeMoreMinimalEdits(model.uri, [{ text: value, range: model.getFullModelRange() }]).then(edits => {
|
||||
if (edits.length > 0) {
|
||||
// use the evil-edit as these models show in readonly-editor only
|
||||
model.applyEdits(edits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)));
|
||||
}
|
||||
}, onUnexpectedError);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,11 +220,18 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
|
||||
return this._fileService.resolveFile(asFileUri).then(stats => {
|
||||
// don't create a new file ontop of an existing file
|
||||
return TPromise.wrapError<boolean>(new Error('file already exists on disk'));
|
||||
}, err => this._doCreateUntitled(asFileUri).then(resource => !!resource));
|
||||
}, err => {
|
||||
return this._doCreateUntitled(uri).then(resource => !!resource);
|
||||
});
|
||||
}
|
||||
|
||||
private _doCreateUntitled(resource?: URI, modeId?: string, initialValue?: string): TPromise<URI> {
|
||||
return this._untitledEditorService.loadOrCreate({ resource, modeId, initialValue }).then(model => {
|
||||
return this._untitledEditorService.loadOrCreate({
|
||||
resource,
|
||||
modeId,
|
||||
initialValue,
|
||||
useResourcePath: Boolean(resource && resource.path)
|
||||
}).then(model => {
|
||||
const resource = model.getResource();
|
||||
|
||||
if (!this._modelIsSynced[resource.toString()]) {
|
||||
|
||||
@@ -12,8 +12,9 @@ import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ExtHostContext, ExtHostDocumentsAndEditorsShape, IModelAddedData, ITextEditorAddData, IDocumentsAndEditorsDelta, IExtHostContext, MainContext } from '../node/extHost.protocol';
|
||||
import { MainThreadTextEditor } from './mainThreadEditor';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { Position as EditorPosition, IEditor } from 'vs/platform/editor/common/editor';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { EditorViewColumn, editorGroupToViewColumn } from 'vs/workbench/api/shared/editor';
|
||||
import { IEditor } from 'vs/workbench/common/editor';
|
||||
import { extHostCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { MainThreadDocuments } from 'vs/workbench/api/electron-browser/mainThreadDocuments';
|
||||
import { MainThreadTextEditors } from 'vs/workbench/api/electron-browser/mainThreadEditors';
|
||||
@@ -21,9 +22,10 @@ import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { isCodeEditor, isDiffEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { isDiffEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
|
||||
|
||||
namespace mapset {
|
||||
|
||||
@@ -159,7 +161,7 @@ class MainThreadDocumentAndEditorStateComputer {
|
||||
private readonly _onDidChangeState: (delta: DocumentAndEditorStateDelta) => void,
|
||||
@IModelService private readonly _modelService: IModelService,
|
||||
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
|
||||
@IWorkbenchEditorService private readonly _workbenchEditorService: IWorkbenchEditorService
|
||||
@IEditorService private readonly _editorService: IEditorService
|
||||
) {
|
||||
this._modelService.onModelAdded(this._updateStateOnModelAdd, this, this._toDispose);
|
||||
this._modelService.onModelRemoved(this._updateState, this, this._toDispose);
|
||||
@@ -177,8 +179,8 @@ class MainThreadDocumentAndEditorStateComputer {
|
||||
|
||||
private _onDidAddEditor(e: ICodeEditor): void {
|
||||
this._toDisposeOnEditorRemove.set(e.getId(), e.onDidChangeModel(() => this._updateState()));
|
||||
this._toDisposeOnEditorRemove.set(e.getId(), e.onDidFocusEditor(() => this._updateState()));
|
||||
this._toDisposeOnEditorRemove.set(e.getId(), e.onDidBlurEditor(() => this._updateState()));
|
||||
this._toDisposeOnEditorRemove.set(e.getId(), e.onDidFocusEditorText(() => this._updateState()));
|
||||
this._toDisposeOnEditorRemove.set(e.getId(), e.onDidBlurEditorText(() => this._updateState()));
|
||||
this._updateState();
|
||||
}
|
||||
|
||||
@@ -243,32 +245,26 @@ class MainThreadDocumentAndEditorStateComputer {
|
||||
) {
|
||||
const apiEditor = new TextEditorSnapshot(editor);
|
||||
editors.set(apiEditor.id, apiEditor);
|
||||
if (editor.isFocused()) {
|
||||
if (editor.hasTextFocus()) {
|
||||
activeEditor = apiEditor.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// active editor: if none of the previous editors had focus we try
|
||||
// to match the action workbench editor with one of editor we have
|
||||
// to match the active workbench editor with one of editor we have
|
||||
// just computed
|
||||
if (!activeEditor) {
|
||||
const workbenchEditor = this._workbenchEditorService.getActiveEditor();
|
||||
if (workbenchEditor) {
|
||||
const workbenchEditorControl = workbenchEditor.getControl();
|
||||
let candidate: ICodeEditor;
|
||||
if (isCodeEditor(workbenchEditorControl)) {
|
||||
candidate = workbenchEditorControl;
|
||||
} else if (isDiffEditor(workbenchEditorControl)) {
|
||||
candidate = workbenchEditorControl.getModifiedEditor();
|
||||
}
|
||||
if (candidate) {
|
||||
editors.forEach(snapshot => {
|
||||
if (candidate === snapshot.editor) {
|
||||
activeEditor = snapshot.id;
|
||||
}
|
||||
});
|
||||
}
|
||||
let candidate = this._editorService.activeTextEditorWidget;
|
||||
if (isDiffEditor(candidate)) {
|
||||
candidate = candidate.getModifiedEditor();
|
||||
}
|
||||
if (candidate) {
|
||||
editors.forEach(snapshot => {
|
||||
if (candidate === snapshot.editor) {
|
||||
activeEditor = snapshot.id;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,24 +300,26 @@ export class MainThreadDocumentsAndEditors {
|
||||
extHostContext: IExtHostContext,
|
||||
@IModelService private readonly _modelService: IModelService,
|
||||
@ITextFileService private readonly _textFileService: ITextFileService,
|
||||
@IWorkbenchEditorService private readonly _workbenchEditorService: IWorkbenchEditorService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@ICodeEditorService codeEditorService: ICodeEditorService,
|
||||
@IModeService modeService: IModeService,
|
||||
@IFileService fileService: IFileService,
|
||||
@ITextModelService textModelResolverService: ITextModelService,
|
||||
@IUntitledEditorService untitledEditorService: IUntitledEditorService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService
|
||||
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
|
||||
@IBulkEditService bulkEditService: IBulkEditService
|
||||
|
||||
) {
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentsAndEditors);
|
||||
|
||||
const mainThreadDocuments = new MainThreadDocuments(this, extHostContext, this._modelService, modeService, this._textFileService, fileService, textModelResolverService, untitledEditorService);
|
||||
extHostContext.set(MainContext.MainThreadDocuments, mainThreadDocuments);
|
||||
|
||||
const mainThreadTextEditors = new MainThreadTextEditors(this, extHostContext, codeEditorService, this._workbenchEditorService, editorGroupService, textModelResolverService, fileService, this._modelService);
|
||||
const mainThreadTextEditors = new MainThreadTextEditors(this, extHostContext, codeEditorService, bulkEditService, this._editorService, this._editorGroupService);
|
||||
extHostContext.set(MainContext.MainThreadTextEditors, mainThreadTextEditors);
|
||||
|
||||
// It is expected that the ctor of the state computer calls our `_onDelta`.
|
||||
this._stateComputer = new MainThreadDocumentAndEditorStateComputer(delta => this._onDelta(delta), _modelService, codeEditorService, _workbenchEditorService);
|
||||
this._stateComputer = new MainThreadDocumentAndEditorStateComputer(delta => this._onDelta(delta), _modelService, codeEditorService, this._editorService);
|
||||
|
||||
this._toDispose = [
|
||||
mainThreadDocuments,
|
||||
@@ -423,10 +421,10 @@ export class MainThreadDocumentsAndEditors {
|
||||
};
|
||||
}
|
||||
|
||||
private _findEditorPosition(editor: MainThreadTextEditor): EditorPosition {
|
||||
for (let workbenchEditor of this._workbenchEditorService.getVisibleEditors()) {
|
||||
private _findEditorPosition(editor: MainThreadTextEditor): EditorViewColumn {
|
||||
for (let workbenchEditor of this._editorService.visibleControls) {
|
||||
if (editor.matches(workbenchEditor)) {
|
||||
return workbenchEditor.position;
|
||||
return editorGroupToViewColumn(this._editorGroupService, workbenchEditor.group);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IEditor } from 'vs/platform/editor/common/editor';
|
||||
import { IEditor } from 'vs/workbench/common/editor';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Range, IRange } from 'vs/editor/common/core/range';
|
||||
@@ -266,10 +266,10 @@ export class MainThreadTextEditor {
|
||||
this.setCodeEditor(null);
|
||||
}));
|
||||
|
||||
this._codeEditorListeners.push(this._codeEditor.onDidFocusEditor(() => {
|
||||
this._codeEditorListeners.push(this._codeEditor.onDidFocusEditorWidget(() => {
|
||||
this._focusTracker.onGainedFocus();
|
||||
}));
|
||||
this._codeEditorListeners.push(this._codeEditor.onDidBlurEditor(() => {
|
||||
this._codeEditorListeners.push(this._codeEditor.onDidBlurEditorWidget(() => {
|
||||
this._focusTracker.onLostFocus();
|
||||
}));
|
||||
|
||||
@@ -423,7 +423,7 @@ export class MainThreadTextEditor {
|
||||
|
||||
public isFocused(): boolean {
|
||||
if (this._codeEditor) {
|
||||
return this._codeEditor.isFocused();
|
||||
return this._codeEditor.hasTextFocus();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -448,9 +448,9 @@ export class MainThreadTextEditor {
|
||||
}
|
||||
|
||||
if (opts.setEndOfLine === EndOfLine.CRLF) {
|
||||
this._model.setEOL(EndOfLineSequence.CRLF);
|
||||
this._model.pushEOL(EndOfLineSequence.CRLF);
|
||||
} else if (opts.setEndOfLine === EndOfLine.LF) {
|
||||
this._model.setEOL(EndOfLineSequence.LF);
|
||||
this._model.pushEOL(EndOfLineSequence.LF);
|
||||
}
|
||||
|
||||
let transformedEdits = edits.map((edit): IIdentifiedSingleEditOperation => {
|
||||
|
||||
@@ -4,35 +4,37 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { localize } from 'vs/nls';
|
||||
import { disposed } from 'vs/base/common/errors';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IDecorationRenderOptions, IDecorationOptions, ILineChange } from 'vs/editor/common/editorCommon';
|
||||
import { ISingleEditOperation } from 'vs/editor/common/model';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { Position as EditorPosition, ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { MainThreadTextEditor } from './mainThreadEditor';
|
||||
import { ITextEditorConfigurationUpdate, TextEditorRevealType, IApplyEditsOptions, IUndoStopOptions, WorkspaceEditDto, reviveWorkspaceEditDto } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { MainThreadDocumentsAndEditors } from './mainThreadDocumentsAndEditors';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { equals as objectEquals } from 'vs/base/common/objects';
|
||||
import { ExtHostContext, MainThreadTextEditorsShape, ExtHostEditorsShape, ITextDocumentShowOptions, ITextEditorPositionData, IExtHostContext } from '../node/extHost.protocol';
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { ISelection } from 'vs/editor/common/core/selection';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { BulkEdit } from 'vs/editor/browser/services/bulkEdit';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { isCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { isResourceFileEdit } from 'vs/editor/common/modes';
|
||||
import { IDecorationOptions, IDecorationRenderOptions, ILineChange } from 'vs/editor/common/editorCommon';
|
||||
import { ISingleEditOperation } from 'vs/editor/common/model';
|
||||
import { EditorViewColumn, viewColumnToEditorGroup, editorGroupToViewColumn } from 'vs/workbench/api/shared/editor';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { IApplyEditsOptions, ITextEditorConfigurationUpdate, IUndoStopOptions, TextEditorRevealType, WorkspaceEditDto, reviveWorkspaceEditDto } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { ExtHostContext, ExtHostEditorsShape, IExtHostContext, ITextDocumentShowOptions, ITextEditorPositionData, MainThreadTextEditorsShape } from '../node/extHost.protocol';
|
||||
import { MainThreadDocumentsAndEditors } from './mainThreadDocumentsAndEditors';
|
||||
import { MainThreadTextEditor } from './mainThreadEditor';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
|
||||
export class MainThreadTextEditors implements MainThreadTextEditorsShape {
|
||||
|
||||
private static INSTANCE_COUNT: number = 0;
|
||||
|
||||
private _instanceId: string;
|
||||
private _proxy: ExtHostEditorsShape;
|
||||
private _documentsAndEditors: MainThreadDocumentsAndEditors;
|
||||
private _workbenchEditorService: IWorkbenchEditorService;
|
||||
private _toDispose: IDisposable[];
|
||||
private _textEditorsListenersMap: { [editorId: string]: IDisposable[]; };
|
||||
private _editorPositionData: ITextEditorPositionData;
|
||||
@@ -42,15 +44,13 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
|
||||
documentsAndEditors: MainThreadDocumentsAndEditors,
|
||||
extHostContext: IExtHostContext,
|
||||
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
|
||||
@IWorkbenchEditorService workbenchEditorService: IWorkbenchEditorService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService,
|
||||
@ITextModelService private readonly _textModelResolverService: ITextModelService,
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
@IModelService private readonly _modelService: IModelService,
|
||||
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
this._instanceId = String(++MainThreadTextEditors.INSTANCE_COUNT);
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostEditors);
|
||||
this._documentsAndEditors = documentsAndEditors;
|
||||
this._workbenchEditorService = workbenchEditorService;
|
||||
this._toDispose = [];
|
||||
this._textEditorsListenersMap = Object.create(null);
|
||||
this._editorPositionData = null;
|
||||
@@ -58,8 +58,9 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
|
||||
this._toDispose.push(documentsAndEditors.onTextEditorAdd(editors => editors.forEach(this._onTextEditorAdd, this)));
|
||||
this._toDispose.push(documentsAndEditors.onTextEditorRemove(editors => editors.forEach(this._onTextEditorRemove, this)));
|
||||
|
||||
this._toDispose.push(editorGroupService.onEditorsChanged(() => this._updateActiveAndVisibleTextEditors()));
|
||||
this._toDispose.push(editorGroupService.onEditorGroupMoved(() => this._updateActiveAndVisibleTextEditors()));
|
||||
this._toDispose.push(this._editorService.onDidVisibleEditorsChange(() => this._updateActiveAndVisibleTextEditors()));
|
||||
this._toDispose.push(this._editorGroupService.onDidRemoveGroup(() => this._updateActiveAndVisibleTextEditors()));
|
||||
this._toDispose.push(this._editorGroupService.onDidMoveGroup(() => this._updateActiveAndVisibleTextEditors()));
|
||||
|
||||
this._registeredDecorationTypes = Object.create(null);
|
||||
}
|
||||
@@ -103,10 +104,10 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
|
||||
|
||||
private _getTextEditorPositionData(): ITextEditorPositionData {
|
||||
let result: ITextEditorPositionData = Object.create(null);
|
||||
for (let workbenchEditor of this._workbenchEditorService.getVisibleEditors()) {
|
||||
for (let workbenchEditor of this._editorService.visibleControls) {
|
||||
const id = this._documentsAndEditors.findTextEditorIdFor(workbenchEditor);
|
||||
if (id) {
|
||||
result[id] = workbenchEditor.position;
|
||||
result[id] = editorGroupToViewColumn(this._editorGroupService, workbenchEditor.group);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -128,7 +129,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
|
||||
options: editorOptions
|
||||
};
|
||||
|
||||
return this._workbenchEditorService.openEditor(input, options.position).then(editor => {
|
||||
return this._editorService.openEditor(input, viewColumnToEditorGroup(this._editorGroupService, options.position)).then(editor => {
|
||||
if (!editor) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -136,14 +137,14 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
|
||||
});
|
||||
}
|
||||
|
||||
$tryShowEditor(id: string, position: EditorPosition): TPromise<void> {
|
||||
$tryShowEditor(id: string, position?: EditorViewColumn): TPromise<void> {
|
||||
let mainThreadEditor = this._documentsAndEditors.getEditor(id);
|
||||
if (mainThreadEditor) {
|
||||
let model = mainThreadEditor.getModel();
|
||||
return this._workbenchEditorService.openEditor({
|
||||
return this._editorService.openEditor({
|
||||
resource: model.uri,
|
||||
options: { preserveFocus: false }
|
||||
}, position).then(() => { return; });
|
||||
}, viewColumnToEditorGroup(this._editorGroupService, position)).then(() => { return; });
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -151,10 +152,10 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
|
||||
$tryHideEditor(id: string): TPromise<void> {
|
||||
let mainThreadEditor = this._documentsAndEditors.getEditor(id);
|
||||
if (mainThreadEditor) {
|
||||
let editors = this._workbenchEditorService.getVisibleEditors();
|
||||
let editors = this._editorService.visibleControls;
|
||||
for (let editor of editors) {
|
||||
if (mainThreadEditor.matches(editor)) {
|
||||
return this._workbenchEditorService.closeEditor(editor.position, editor.input).then(() => { return; });
|
||||
return editor.group.closeEditor(editor.input).then(() => { return; });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,6 +171,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
|
||||
}
|
||||
|
||||
$trySetDecorations(id: string, key: string, ranges: IDecorationOptions[]): TPromise<void> {
|
||||
key = `${this._instanceId}-${key}`;
|
||||
if (!this._documentsAndEditors.getEditor(id)) {
|
||||
return TPromise.wrapError(disposed(`TextEditor(${id})`));
|
||||
}
|
||||
@@ -178,6 +180,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
|
||||
}
|
||||
|
||||
$trySetDecorationsFast(id: string, key: string, ranges: number[]): TPromise<void> {
|
||||
key = `${this._instanceId}-${key}`;
|
||||
if (!this._documentsAndEditors.getEditor(id)) {
|
||||
return TPromise.wrapError(disposed(`TextEditor(${id})`));
|
||||
}
|
||||
@@ -209,31 +212,8 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
|
||||
}
|
||||
|
||||
$tryApplyWorkspaceEdit(dto: WorkspaceEditDto): TPromise<boolean> {
|
||||
|
||||
const { edits } = reviveWorkspaceEditDto(dto);
|
||||
|
||||
// First check if loaded models were not changed in the meantime
|
||||
for (let i = 0, len = edits.length; i < len; i++) {
|
||||
const edit = edits[i];
|
||||
if (!isResourceFileEdit(edit) && edit.modelVersionId) {
|
||||
let model = this._modelService.getModel(edit.resource);
|
||||
if (model && model.getVersionId() !== edit.modelVersionId) {
|
||||
// model changed in the meantime
|
||||
return TPromise.as(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let codeEditor: ICodeEditor;
|
||||
let editor = this._workbenchEditorService.getActiveEditor();
|
||||
if (editor) {
|
||||
let candidate = editor.getControl();
|
||||
if (isCodeEditor(candidate)) {
|
||||
codeEditor = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return BulkEdit.perform(edits, this._textModelResolverService, this._fileService, codeEditor).then(() => true);
|
||||
return this._bulkEditService.apply({ edits }, undefined).then(() => true, err => false);
|
||||
}
|
||||
|
||||
$tryInsertSnippet(id: string, template: string, ranges: IRange[], opts: IUndoStopOptions): TPromise<boolean> {
|
||||
@@ -244,11 +224,13 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
|
||||
}
|
||||
|
||||
$registerTextEditorDecorationType(key: string, options: IDecorationRenderOptions): void {
|
||||
key = `${this._instanceId}-${key}`;
|
||||
this._registeredDecorationTypes[key] = true;
|
||||
this._codeEditorService.registerDecorationType(key, options);
|
||||
}
|
||||
|
||||
$removeTextEditorDecorationType(key: string): void {
|
||||
key = `${this._instanceId}-${key}`;
|
||||
delete this._registeredDecorationTypes[key];
|
||||
this._codeEditorService.removeDecorationType(key);
|
||||
}
|
||||
@@ -272,3 +254,46 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
|
||||
return TPromise.as(diffEditor.getLineChanges());
|
||||
}
|
||||
}
|
||||
|
||||
// --- commands
|
||||
|
||||
CommandsRegistry.registerCommand('_workbench.open', function (accessor: ServicesAccessor, args: [URI, IEditorOptions, EditorViewColumn]) {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
const openerService = accessor.get(IOpenerService);
|
||||
|
||||
const [resource, options, position] = args;
|
||||
|
||||
if (options || typeof position === 'number') {
|
||||
// use editor options or editor view column as a hint to use the editor service for opening
|
||||
return editorService.openEditor({ resource, options }, viewColumnToEditorGroup(editorGroupService, position)).then(_ => void 0);
|
||||
}
|
||||
|
||||
if (resource && resource.scheme === 'command') {
|
||||
// do not allow to execute commands from here
|
||||
return TPromise.as(void 0);
|
||||
}
|
||||
|
||||
// finally, delegate to opener service
|
||||
return openerService.open(resource).then(_ => void 0);
|
||||
});
|
||||
|
||||
|
||||
CommandsRegistry.registerCommand('_workbench.diff', function (accessor: ServicesAccessor, args: [URI, URI, string, string, IEditorOptions, EditorViewColumn]) {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
|
||||
let [leftResource, rightResource, label, description, options, position] = args;
|
||||
|
||||
if (!options || typeof options !== 'object') {
|
||||
options = {
|
||||
preserveFocus: false
|
||||
};
|
||||
}
|
||||
|
||||
if (!label) {
|
||||
label = localize('diffLeftRightLabel', "{0} ⟷ {1}", leftResource.toString(true), rightResource.toString(true));
|
||||
}
|
||||
|
||||
return editorService.openEditor({ leftResource, rightResource, label, description, options }, viewColumnToEditorGroup(editorGroupService, position)).then(() => void 0);
|
||||
});
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
'use strict';
|
||||
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { FileWriteOptions, FileSystemProviderCapabilities, IFileChange, IFileService, IFileSystemProvider, IStat, IWatchOptions, FileType, FileOverwriteOptions } from 'vs/platform/files/common/files';
|
||||
import { FileWriteOptions, FileSystemProviderCapabilities, IFileChange, IFileService, IFileSystemProvider, IStat, IWatchOptions, FileType, FileOverwriteOptions, FileDeleteOptions } from 'vs/platform/files/common/files';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { ExtHostContext, ExtHostFileSystemShape, IExtHostContext, IFileChangeDto, MainContext, MainThreadFileSystemShape } from '../node/extHost.protocol';
|
||||
|
||||
@@ -71,11 +71,9 @@ class RemoteFileSystemProvider implements IFileSystemProvider {
|
||||
watch(resource: URI, opts: IWatchOptions) {
|
||||
const session = Math.random();
|
||||
this._proxy.$watch(this._handle, session, resource, opts);
|
||||
return {
|
||||
dispose: () => {
|
||||
this._proxy.$unwatch(this._handle, session);
|
||||
}
|
||||
};
|
||||
return toDisposable(() => {
|
||||
this._proxy.$unwatch(this._handle, session);
|
||||
});
|
||||
}
|
||||
|
||||
$onFileSystemChange(changes: IFileChangeDto[]): void {
|
||||
@@ -107,8 +105,8 @@ class RemoteFileSystemProvider implements IFileSystemProvider {
|
||||
return this._proxy.$writeFile(this._handle, resource, encoded, opts);
|
||||
}
|
||||
|
||||
delete(resource: URI): TPromise<void, any> {
|
||||
return this._proxy.$delete(this._handle, resource);
|
||||
delete(resource: URI, opts: FileDeleteOptions): TPromise<void, any> {
|
||||
return this._proxy.$delete(this._handle, resource, opts);
|
||||
}
|
||||
|
||||
mkdir(resource: URI): TPromise<void, any> {
|
||||
|
||||
@@ -4,29 +4,32 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { FileChangeType, IFileService } from 'vs/platform/files/common/files';
|
||||
import { ExtHostContext, ExtHostFileSystemEventServiceShape, FileSystemEvents, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { FileChangeType, IFileService, FileOperation } from 'vs/platform/files/common/files';
|
||||
import { extHostCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { ExtHostContext, FileSystemEvents, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
|
||||
@extHostCustomer
|
||||
export class MainThreadFileSystemEventService {
|
||||
|
||||
private readonly _listener: IDisposable;
|
||||
private readonly _listener = new Array<IDisposable>();
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IFileService fileService: IFileService
|
||||
@IFileService fileService: IFileService,
|
||||
@ITextFileService textfileService: ITextFileService,
|
||||
) {
|
||||
|
||||
const proxy: ExtHostFileSystemEventServiceShape = extHostContext.getProxy(ExtHostContext.ExtHostFileSystemEventService);
|
||||
const proxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystemEventService);
|
||||
|
||||
// file system events - (changes the editor and other make)
|
||||
const events: FileSystemEvents = {
|
||||
created: [],
|
||||
changed: [],
|
||||
deleted: []
|
||||
};
|
||||
|
||||
this._listener = fileService.onFileChanges(event => {
|
||||
fileService.onFileChanges(event => {
|
||||
for (let change of event.changes) {
|
||||
switch (change.type) {
|
||||
case FileChangeType.ADDED:
|
||||
@@ -45,10 +48,22 @@ export class MainThreadFileSystemEventService {
|
||||
events.created.length = 0;
|
||||
events.changed.length = 0;
|
||||
events.deleted.length = 0;
|
||||
});
|
||||
}, undefined, this._listener);
|
||||
|
||||
// file operation events - (changes the editor makes)
|
||||
fileService.onAfterOperation(e => {
|
||||
if (e.operation === FileOperation.MOVE) {
|
||||
proxy.$onFileRename(e.resource, e.target.resource);
|
||||
}
|
||||
}, undefined, this._listener);
|
||||
|
||||
textfileService.onWillMove(e => {
|
||||
let promise = proxy.$onWillRename(e.oldResource, e.newResource);
|
||||
e.waitUntil(promise);
|
||||
}, undefined, this._listener);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._listener.dispose();
|
||||
dispose(this._listener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,19 +9,20 @@ import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { ITextModel, ISingleEditOperation } from 'vs/editor/common/model';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { WorkspaceSymbolProviderRegistry, IWorkspaceSymbolProvider } from 'vs/workbench/parts/search/common/search';
|
||||
import * as search from 'vs/workbench/parts/search/common/search';
|
||||
import { wireCancellationToken } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Position as EditorPosition } from 'vs/editor/common/core/position';
|
||||
import { Range as EditorRange } from 'vs/editor/common/core/range';
|
||||
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, SymbolInformationDto, CodeActionDto, reviveWorkspaceEditDto, ISerializedDocumentFilter } from '../node/extHost.protocol';
|
||||
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, WorkspaceSymbolDto, CodeActionDto, reviveWorkspaceEditDto, ISerializedDocumentFilter, DefinitionLinkDto } from '../node/extHost.protocol';
|
||||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { IHeapService } from './mainThreadHeapService';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { toLanguageSelector } from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadLanguageFeatures)
|
||||
export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape {
|
||||
@@ -71,17 +72,31 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
}
|
||||
}
|
||||
|
||||
private static _reviveSymbolInformationDto(data: SymbolInformationDto): modes.SymbolInformation;
|
||||
private static _reviveSymbolInformationDto(data: SymbolInformationDto[]): modes.SymbolInformation[];
|
||||
private static _reviveSymbolInformationDto(data: SymbolInformationDto | SymbolInformationDto[]): modes.SymbolInformation | modes.SymbolInformation[] {
|
||||
private static _reviveDefinitionLinkDto(data: DefinitionLinkDto): modes.DefinitionLink;
|
||||
private static _reviveDefinitionLinkDto(data: DefinitionLinkDto[]): modes.DefinitionLink[];
|
||||
private static _reviveDefinitionLinkDto(data: DefinitionLinkDto | DefinitionLinkDto[]): modes.DefinitionLink | modes.DefinitionLink[] {
|
||||
if (!data) {
|
||||
return <modes.SymbolInformation>data;
|
||||
return <modes.DefinitionLink>data;
|
||||
} else if (Array.isArray(data)) {
|
||||
data.forEach(MainThreadLanguageFeatures._reviveSymbolInformationDto);
|
||||
return <modes.SymbolInformation[]>data;
|
||||
data.forEach(l => MainThreadLanguageFeatures._reviveDefinitionLinkDto(l));
|
||||
return <modes.DefinitionLink[]>data;
|
||||
} else {
|
||||
data.uri = URI.revive(data.uri);
|
||||
return <modes.DefinitionLink>data;
|
||||
}
|
||||
}
|
||||
|
||||
private static _reviveWorkspaceSymbolDto(data: WorkspaceSymbolDto): search.IWorkspaceSymbol;
|
||||
private static _reviveWorkspaceSymbolDto(data: WorkspaceSymbolDto[]): search.IWorkspaceSymbol[];
|
||||
private static _reviveWorkspaceSymbolDto(data: WorkspaceSymbolDto | WorkspaceSymbolDto[]): search.IWorkspaceSymbol | search.IWorkspaceSymbol[] {
|
||||
if (!data) {
|
||||
return <search.IWorkspaceSymbol>data;
|
||||
} else if (Array.isArray(data)) {
|
||||
data.forEach(MainThreadLanguageFeatures._reviveWorkspaceSymbolDto);
|
||||
return <search.IWorkspaceSymbol[]>data;
|
||||
} else {
|
||||
data.location = MainThreadLanguageFeatures._reviveLocationDto(data.location);
|
||||
return <modes.SymbolInformation>data;
|
||||
return <search.IWorkspaceSymbol>data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,10 +111,11 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
|
||||
// --- outline
|
||||
|
||||
$registerOutlineSupport(handle: number, selector: ISerializedDocumentFilter[]): void {
|
||||
this._registrations[handle] = modes.DocumentSymbolProviderRegistry.register(toLanguageSelector(selector), <modes.DocumentSymbolProvider>{
|
||||
provideDocumentSymbols: (model: ITextModel, token: CancellationToken): Thenable<modes.SymbolInformation[]> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideDocumentSymbols(handle, model.uri)).then(MainThreadLanguageFeatures._reviveSymbolInformationDto);
|
||||
$registerOutlineSupport(handle: number, selector: ISerializedDocumentFilter[], displayName: string): void {
|
||||
this._registrations[handle] = modes.DocumentSymbolProviderRegistry.register(typeConverters.LanguageSelector.from(selector), <modes.DocumentSymbolProvider>{
|
||||
displayName,
|
||||
provideDocumentSymbols: (model: ITextModel, token: CancellationToken): Thenable<modes.DocumentSymbol[]> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideDocumentSymbols(handle, model.uri));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -123,7 +139,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
provider.onDidChange = emitter.event;
|
||||
}
|
||||
|
||||
this._registrations[handle] = modes.CodeLensProviderRegistry.register(toLanguageSelector(selector), provider);
|
||||
this._registrations[handle] = modes.CodeLensProviderRegistry.register(typeConverters.LanguageSelector.from(selector), provider);
|
||||
}
|
||||
|
||||
$emitCodeLensEvent(eventHandle: number, event?: any): void {
|
||||
@@ -136,25 +152,25 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
// --- declaration
|
||||
|
||||
$registerDeclaractionSupport(handle: number, selector: ISerializedDocumentFilter[]): void {
|
||||
this._registrations[handle] = modes.DefinitionProviderRegistry.register(toLanguageSelector(selector), <modes.DefinitionProvider>{
|
||||
provideDefinition: (model, position, token): Thenable<modes.Definition> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideDefinition(handle, model.uri, position)).then(MainThreadLanguageFeatures._reviveLocationDto);
|
||||
this._registrations[handle] = modes.DefinitionProviderRegistry.register(typeConverters.LanguageSelector.from(selector), <modes.DefinitionProvider>{
|
||||
provideDefinition: (model, position, token): Thenable<modes.DefinitionLink[]> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideDefinition(handle, model.uri, position)).then(MainThreadLanguageFeatures._reviveDefinitionLinkDto);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$registerImplementationSupport(handle: number, selector: ISerializedDocumentFilter[]): void {
|
||||
this._registrations[handle] = modes.ImplementationProviderRegistry.register(toLanguageSelector(selector), <modes.ImplementationProvider>{
|
||||
this._registrations[handle] = modes.ImplementationProviderRegistry.register(typeConverters.LanguageSelector.from(selector), <modes.ImplementationProvider>{
|
||||
provideImplementation: (model, position, token): Thenable<modes.Definition> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideImplementation(handle, model.uri, position)).then(MainThreadLanguageFeatures._reviveLocationDto);
|
||||
return wireCancellationToken(token, this._proxy.$provideImplementation(handle, model.uri, position)).then(MainThreadLanguageFeatures._reviveDefinitionLinkDto);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$registerTypeDefinitionSupport(handle: number, selector: ISerializedDocumentFilter[]): void {
|
||||
this._registrations[handle] = modes.TypeDefinitionProviderRegistry.register(toLanguageSelector(selector), <modes.TypeDefinitionProvider>{
|
||||
this._registrations[handle] = modes.TypeDefinitionProviderRegistry.register(typeConverters.LanguageSelector.from(selector), <modes.TypeDefinitionProvider>{
|
||||
provideTypeDefinition: (model, position, token): Thenable<modes.Definition> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideTypeDefinition(handle, model.uri, position)).then(MainThreadLanguageFeatures._reviveLocationDto);
|
||||
return wireCancellationToken(token, this._proxy.$provideTypeDefinition(handle, model.uri, position)).then(MainThreadLanguageFeatures._reviveDefinitionLinkDto);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -162,7 +178,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
// --- extra info
|
||||
|
||||
$registerHoverProvider(handle: number, selector: ISerializedDocumentFilter[]): void {
|
||||
this._registrations[handle] = modes.HoverProviderRegistry.register(toLanguageSelector(selector), <modes.HoverProvider>{
|
||||
this._registrations[handle] = modes.HoverProviderRegistry.register(typeConverters.LanguageSelector.from(selector), <modes.HoverProvider>{
|
||||
provideHover: (model: ITextModel, position: EditorPosition, token: CancellationToken): Thenable<modes.Hover> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideHover(handle, model.uri, position));
|
||||
}
|
||||
@@ -172,7 +188,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
// --- occurrences
|
||||
|
||||
$registerDocumentHighlightProvider(handle: number, selector: ISerializedDocumentFilter[]): void {
|
||||
this._registrations[handle] = modes.DocumentHighlightProviderRegistry.register(toLanguageSelector(selector), <modes.DocumentHighlightProvider>{
|
||||
this._registrations[handle] = modes.DocumentHighlightProviderRegistry.register(typeConverters.LanguageSelector.from(selector), <modes.DocumentHighlightProvider>{
|
||||
provideDocumentHighlights: (model: ITextModel, position: EditorPosition, token: CancellationToken): Thenable<modes.DocumentHighlight[]> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideDocumentHighlights(handle, model.uri, position));
|
||||
}
|
||||
@@ -182,7 +198,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
// --- references
|
||||
|
||||
$registerReferenceSupport(handle: number, selector: ISerializedDocumentFilter[]): void {
|
||||
this._registrations[handle] = modes.ReferenceProviderRegistry.register(toLanguageSelector(selector), <modes.ReferenceProvider>{
|
||||
this._registrations[handle] = modes.ReferenceProviderRegistry.register(typeConverters.LanguageSelector.from(selector), <modes.ReferenceProvider>{
|
||||
provideReferences: (model: ITextModel, position: EditorPosition, context: modes.ReferenceContext, token: CancellationToken): Thenable<modes.Location[]> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideReferences(handle, model.uri, position, context)).then(MainThreadLanguageFeatures._reviveLocationDto);
|
||||
}
|
||||
@@ -192,9 +208,9 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
// --- quick fix
|
||||
|
||||
$registerQuickFixSupport(handle: number, selector: ISerializedDocumentFilter[], providedCodeActionKinds?: string[]): void {
|
||||
this._registrations[handle] = modes.CodeActionProviderRegistry.register(toLanguageSelector(selector), <modes.CodeActionProvider>{
|
||||
provideCodeActions: (model: ITextModel, range: EditorRange, context: modes.CodeActionContext, token: CancellationToken): Thenable<modes.CodeAction[]> => {
|
||||
return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$provideCodeActions(handle, model.uri, range, context))).then(MainThreadLanguageFeatures._reviveCodeActionDto);
|
||||
this._registrations[handle] = modes.CodeActionProviderRegistry.register(typeConverters.LanguageSelector.from(selector), <modes.CodeActionProvider>{
|
||||
provideCodeActions: (model: ITextModel, rangeOrSelection: EditorRange | Selection, context: modes.CodeActionContext, token: CancellationToken): Thenable<modes.CodeAction[]> => {
|
||||
return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$provideCodeActions(handle, model.uri, rangeOrSelection, context))).then(MainThreadLanguageFeatures._reviveCodeActionDto);
|
||||
},
|
||||
providedCodeActionKinds
|
||||
});
|
||||
@@ -203,7 +219,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
// --- formatting
|
||||
|
||||
$registerDocumentFormattingSupport(handle: number, selector: ISerializedDocumentFilter[]): void {
|
||||
this._registrations[handle] = modes.DocumentFormattingEditProviderRegistry.register(toLanguageSelector(selector), <modes.DocumentFormattingEditProvider>{
|
||||
this._registrations[handle] = modes.DocumentFormattingEditProviderRegistry.register(typeConverters.LanguageSelector.from(selector), <modes.DocumentFormattingEditProvider>{
|
||||
provideDocumentFormattingEdits: (model: ITextModel, options: modes.FormattingOptions, token: CancellationToken): Thenable<ISingleEditOperation[]> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideDocumentFormattingEdits(handle, model.uri, options));
|
||||
}
|
||||
@@ -211,7 +227,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
}
|
||||
|
||||
$registerRangeFormattingSupport(handle: number, selector: ISerializedDocumentFilter[]): void {
|
||||
this._registrations[handle] = modes.DocumentRangeFormattingEditProviderRegistry.register(toLanguageSelector(selector), <modes.DocumentRangeFormattingEditProvider>{
|
||||
this._registrations[handle] = modes.DocumentRangeFormattingEditProviderRegistry.register(typeConverters.LanguageSelector.from(selector), <modes.DocumentRangeFormattingEditProvider>{
|
||||
provideDocumentRangeFormattingEdits: (model: ITextModel, range: EditorRange, options: modes.FormattingOptions, token: CancellationToken): Thenable<ISingleEditOperation[]> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideDocumentRangeFormattingEdits(handle, model.uri, range, options));
|
||||
}
|
||||
@@ -219,7 +235,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
}
|
||||
|
||||
$registerOnTypeFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], autoFormatTriggerCharacters: string[]): void {
|
||||
this._registrations[handle] = modes.OnTypeFormattingEditProviderRegistry.register(toLanguageSelector(selector), <modes.OnTypeFormattingEditProvider>{
|
||||
this._registrations[handle] = modes.OnTypeFormattingEditProviderRegistry.register(typeConverters.LanguageSelector.from(selector), <modes.OnTypeFormattingEditProvider>{
|
||||
|
||||
autoFormatTriggerCharacters,
|
||||
|
||||
@@ -233,18 +249,18 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
|
||||
$registerNavigateTypeSupport(handle: number): void {
|
||||
let lastResultId: number;
|
||||
this._registrations[handle] = WorkspaceSymbolProviderRegistry.register(<IWorkspaceSymbolProvider>{
|
||||
provideWorkspaceSymbols: (search: string): TPromise<modes.SymbolInformation[]> => {
|
||||
this._registrations[handle] = search.WorkspaceSymbolProviderRegistry.register(<search.IWorkspaceSymbolProvider>{
|
||||
provideWorkspaceSymbols: (search: string): TPromise<search.IWorkspaceSymbol[]> => {
|
||||
return this._proxy.$provideWorkspaceSymbols(handle, search).then(result => {
|
||||
if (lastResultId !== undefined) {
|
||||
this._proxy.$releaseWorkspaceSymbols(handle, lastResultId);
|
||||
}
|
||||
lastResultId = result._id;
|
||||
return MainThreadLanguageFeatures._reviveSymbolInformationDto(result.symbols);
|
||||
return MainThreadLanguageFeatures._reviveWorkspaceSymbolDto(result.symbols);
|
||||
});
|
||||
},
|
||||
resolveWorkspaceSymbol: (item: modes.SymbolInformation): TPromise<modes.SymbolInformation> => {
|
||||
return this._proxy.$resolveWorkspaceSymbol(handle, item).then(i => MainThreadLanguageFeatures._reviveSymbolInformationDto(i));
|
||||
resolveWorkspaceSymbol: (item: search.IWorkspaceSymbol): TPromise<search.IWorkspaceSymbol> => {
|
||||
return this._proxy.$resolveWorkspaceSymbol(handle, item).then(i => MainThreadLanguageFeatures._reviveWorkspaceSymbolDto(i));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -253,7 +269,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
|
||||
$registerRenameSupport(handle: number, selector: ISerializedDocumentFilter[], supportResolveLocation: boolean): void {
|
||||
|
||||
this._registrations[handle] = modes.RenameProviderRegistry.register(toLanguageSelector(selector), <modes.RenameProvider>{
|
||||
this._registrations[handle] = modes.RenameProviderRegistry.register(typeConverters.LanguageSelector.from(selector), <modes.RenameProvider>{
|
||||
provideRenameEdits: (model: ITextModel, position: EditorPosition, newName: string, token: CancellationToken): Thenable<modes.WorkspaceEdit> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideRenameEdits(handle, model.uri, position, newName)).then(reviveWorkspaceEditDto);
|
||||
},
|
||||
@@ -266,7 +282,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
// --- suggest
|
||||
|
||||
$registerSuggestSupport(handle: number, selector: ISerializedDocumentFilter[], triggerCharacters: string[], supportsResolveDetails: boolean): void {
|
||||
this._registrations[handle] = modes.SuggestRegistry.register(toLanguageSelector(selector), <modes.ISuggestSupport>{
|
||||
this._registrations[handle] = modes.SuggestRegistry.register(typeConverters.LanguageSelector.from(selector), <modes.ISuggestSupport>{
|
||||
triggerCharacters,
|
||||
provideCompletionItems: (model: ITextModel, position: EditorPosition, context: modes.SuggestContext, token: CancellationToken): Thenable<modes.ISuggestResult> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideCompletionItems(handle, model.uri, position, context)).then(result => {
|
||||
@@ -289,7 +305,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
// --- parameter hints
|
||||
|
||||
$registerSignatureHelpProvider(handle: number, selector: ISerializedDocumentFilter[], triggerCharacter: string[]): void {
|
||||
this._registrations[handle] = modes.SignatureHelpProviderRegistry.register(toLanguageSelector(selector), <modes.SignatureHelpProvider>{
|
||||
this._registrations[handle] = modes.SignatureHelpProviderRegistry.register(typeConverters.LanguageSelector.from(selector), <modes.SignatureHelpProvider>{
|
||||
|
||||
signatureHelpTriggerCharacters: triggerCharacter,
|
||||
|
||||
@@ -303,7 +319,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
// --- links
|
||||
|
||||
$registerDocumentLinkProvider(handle: number, selector: ISerializedDocumentFilter[]): void {
|
||||
this._registrations[handle] = modes.LinkProviderRegistry.register(toLanguageSelector(selector), <modes.LinkProvider>{
|
||||
this._registrations[handle] = modes.LinkProviderRegistry.register(typeConverters.LanguageSelector.from(selector), <modes.LinkProvider>{
|
||||
provideLinks: (model, token) => {
|
||||
return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$provideDocumentLinks(handle, model.uri)));
|
||||
},
|
||||
@@ -317,7 +333,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
|
||||
$registerDocumentColorProvider(handle: number, selector: ISerializedDocumentFilter[]): void {
|
||||
const proxy = this._proxy;
|
||||
this._registrations[handle] = modes.ColorProviderRegistry.register(toLanguageSelector(selector), <modes.DocumentColorProvider>{
|
||||
this._registrations[handle] = modes.ColorProviderRegistry.register(typeConverters.LanguageSelector.from(selector), <modes.DocumentColorProvider>{
|
||||
provideDocumentColors: (model, token) => {
|
||||
return wireCancellationToken(token, proxy.$provideDocumentColors(handle, model.uri))
|
||||
.then(documentColors => {
|
||||
@@ -351,7 +367,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
|
||||
$registerFoldingRangeProvider(handle: number, selector: ISerializedDocumentFilter[]): void {
|
||||
const proxy = this._proxy;
|
||||
this._registrations[handle] = modes.FoldingRangeProviderRegistry.register(toLanguageSelector(selector), <modes.FoldingRangeProvider>{
|
||||
this._registrations[handle] = modes.FoldingRangeProviderRegistry.register(typeConverters.LanguageSelector.from(selector), <modes.FoldingRangeProvider>{
|
||||
provideFoldingRanges: (model, context, token) => {
|
||||
return wireCancellationToken(token, proxy.$provideFoldingRanges(handle, model.uri, context));
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { once } from 'vs/base/common/event';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadMessageService)
|
||||
@@ -24,8 +23,7 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape {
|
||||
extHostContext: IExtHostContext,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IDialogService private readonly _dialogService: IDialogService,
|
||||
@IEnvironmentService private readonly _environmentService: IEnvironmentService
|
||||
@IDialogService private readonly _dialogService: IDialogService
|
||||
) {
|
||||
//
|
||||
}
|
||||
@@ -79,7 +77,7 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape {
|
||||
}
|
||||
|
||||
const secondaryActions: IAction[] = [];
|
||||
if (extension && extension.extensionFolderPath !== this._environmentService.extensionDevelopmentPath) {
|
||||
if (extension && !extension.isUnderDevelopment) {
|
||||
secondaryActions.push(new ManageExtensionAction(extension.id, nls.localize('manageExtension', "Manage Extension"), this._commandService));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { IProgressService2, IProgress, IProgressOptions, IProgressStep } from 'vs/platform/progress/common/progress';
|
||||
import { IProgress } from 'vs/platform/progress/common/progress';
|
||||
import { MainThreadProgressShape, MainContext, IExtHostContext, ExtHostProgressShape, ExtHostContext } from '../node/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { IProgressService2, IProgressStep, IProgressOptions } from 'vs/workbench/services/progress/common/progress';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadProgress)
|
||||
export class MainThreadProgress implements MainThreadProgressShape {
|
||||
|
||||
@@ -6,41 +6,42 @@
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { asWinJsPromise } from 'vs/base/common/async';
|
||||
import { IQuickOpenService, IPickOptions, IInputOptions } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IPickOptions, IInputOptions, IQuickInputService, IQuickInput } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { InputBoxOptions } from 'vscode';
|
||||
import { ExtHostContext, MainThreadQuickOpenShape, ExtHostQuickOpenShape, MyQuickPickItems, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { ExtHostContext, MainThreadQuickOpenShape, ExtHostQuickOpenShape, TransferQuickPickItems, MainContext, IExtHostContext, TransferQuickInput, TransferQuickInputButton } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
interface QuickInputSession {
|
||||
input: IQuickInput;
|
||||
handlesToItems: Map<number, TransferQuickPickItems>;
|
||||
}
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadQuickOpen)
|
||||
export class MainThreadQuickOpen implements MainThreadQuickOpenShape {
|
||||
|
||||
private _proxy: ExtHostQuickOpenShape;
|
||||
private _quickOpenService: IQuickOpenService;
|
||||
private _quickInputService: IQuickInputService;
|
||||
private _doSetItems: (items: MyQuickPickItems[]) => any;
|
||||
private _doSetItems: (items: TransferQuickPickItems[]) => any;
|
||||
private _doSetError: (error: Error) => any;
|
||||
private _contents: TPromise<MyQuickPickItems[]>;
|
||||
private _contents: TPromise<TransferQuickPickItems[]>;
|
||||
private _token: number = 0;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IQuickOpenService quickOpenService: IQuickOpenService,
|
||||
@IQuickInputService quickInputService: IQuickInputService
|
||||
) {
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostQuickOpen);
|
||||
this._quickOpenService = quickOpenService;
|
||||
this._quickInputService = quickInputService;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
}
|
||||
|
||||
$show(options: IPickOptions): TPromise<number | number[]> {
|
||||
|
||||
$show(options: IPickOptions<TransferQuickPickItems>): TPromise<number | number[]> {
|
||||
const myToken = ++this._token;
|
||||
|
||||
this._contents = new TPromise<MyQuickPickItems[]>((c, e) => {
|
||||
this._contents = new TPromise<TransferQuickPickItems[]>((c, e) => {
|
||||
this._doSetItems = (items) => {
|
||||
if (myToken === this._token) {
|
||||
c(items);
|
||||
@@ -54,32 +55,33 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape {
|
||||
};
|
||||
});
|
||||
|
||||
if (options.canSelectMany) {
|
||||
return asWinJsPromise(token => this._quickInputService.pick(this._contents, options, token)).then(items => {
|
||||
options = {
|
||||
...options,
|
||||
onDidFocus: el => {
|
||||
if (el) {
|
||||
this._proxy.$onItemSelected((<TransferQuickPickItems>el).handle);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (options.canPickMany) {
|
||||
return asWinJsPromise(token => this._quickInputService.pick(this._contents, options as { canPickMany: true }, token)).then(items => {
|
||||
if (items) {
|
||||
return items.map(item => item.handle);
|
||||
}
|
||||
return undefined;
|
||||
}, undefined, progress => {
|
||||
if (progress) {
|
||||
this._proxy.$onItemSelected((<MyQuickPickItems>progress).handle);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return asWinJsPromise(token => this._quickOpenService.pick(this._contents, options, token)).then(item => {
|
||||
return asWinJsPromise(token => this._quickInputService.pick(this._contents, options, token)).then(item => {
|
||||
if (item) {
|
||||
return item.handle;
|
||||
}
|
||||
return undefined;
|
||||
}, undefined, progress => {
|
||||
if (progress) {
|
||||
this._proxy.$onItemSelected((<MyQuickPickItems>progress).handle);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$setItems(items: MyQuickPickItems[]): TPromise<any> {
|
||||
$setItems(items: TransferQuickPickItems[]): TPromise<any> {
|
||||
if (this._doSetItems) {
|
||||
this._doSetItems(items);
|
||||
}
|
||||
@@ -96,7 +98,6 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape {
|
||||
// ---- input
|
||||
|
||||
$input(options: InputBoxOptions, validateInput: boolean): TPromise<string> {
|
||||
|
||||
const inputOptions: IInputOptions = Object.create(null);
|
||||
|
||||
if (options) {
|
||||
@@ -116,4 +117,109 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape {
|
||||
|
||||
return asWinJsPromise(token => this._quickInputService.input(inputOptions, token));
|
||||
}
|
||||
|
||||
// ---- QuickInput
|
||||
|
||||
private sessions = new Map<number, QuickInputSession>();
|
||||
|
||||
$createOrUpdate(params: TransferQuickInput): TPromise<void> {
|
||||
const sessionId = params.id;
|
||||
let session = this.sessions.get(sessionId);
|
||||
if (!session) {
|
||||
if (params.type === 'quickPick') {
|
||||
const input = this._quickInputService.createQuickPick();
|
||||
input.onDidAccept(() => {
|
||||
this._proxy.$onDidAccept(sessionId);
|
||||
});
|
||||
input.onDidChangeActive(items => {
|
||||
this._proxy.$onDidChangeActive(sessionId, items.map(item => (item as TransferQuickPickItems).handle));
|
||||
});
|
||||
input.onDidChangeSelection(items => {
|
||||
this._proxy.$onDidChangeSelection(sessionId, items.map(item => (item as TransferQuickPickItems).handle));
|
||||
});
|
||||
input.onDidTriggerButton(button => {
|
||||
this._proxy.$onDidTriggerButton(sessionId, (button as TransferQuickInputButton).handle);
|
||||
});
|
||||
input.onDidChangeValue(value => {
|
||||
this._proxy.$onDidChangeValue(sessionId, value);
|
||||
});
|
||||
input.onDidHide(() => {
|
||||
this._proxy.$onDidHide(sessionId);
|
||||
});
|
||||
session = {
|
||||
input,
|
||||
handlesToItems: new Map()
|
||||
};
|
||||
} else {
|
||||
const input = this._quickInputService.createInputBox();
|
||||
input.onDidAccept(() => {
|
||||
this._proxy.$onDidAccept(sessionId);
|
||||
});
|
||||
input.onDidTriggerButton(button => {
|
||||
this._proxy.$onDidTriggerButton(sessionId, (button as TransferQuickInputButton).handle);
|
||||
});
|
||||
input.onDidChangeValue(value => {
|
||||
this._proxy.$onDidChangeValue(sessionId, value);
|
||||
});
|
||||
input.onDidHide(() => {
|
||||
this._proxy.$onDidHide(sessionId);
|
||||
});
|
||||
session = {
|
||||
input,
|
||||
handlesToItems: new Map()
|
||||
};
|
||||
}
|
||||
this.sessions.set(sessionId, session);
|
||||
}
|
||||
const { input, handlesToItems } = session;
|
||||
for (const param in params) {
|
||||
if (param === 'id' || param === 'type') {
|
||||
continue;
|
||||
}
|
||||
if (param === 'visible') {
|
||||
if (params.visible) {
|
||||
input.show();
|
||||
} else {
|
||||
input.hide();
|
||||
}
|
||||
} else if (param === 'items') {
|
||||
handlesToItems.clear();
|
||||
params[param].forEach(item => {
|
||||
handlesToItems.set(item.handle, item);
|
||||
});
|
||||
input[param] = params[param];
|
||||
} else if (param === 'activeItems' || param === 'selectedItems') {
|
||||
input[param] = params[param]
|
||||
.filter(handle => handlesToItems.has(handle))
|
||||
.map(handle => handlesToItems.get(handle));
|
||||
} else if (param === 'buttons') {
|
||||
input[param] = params.buttons.map(button => {
|
||||
if (button.handle === -1) {
|
||||
return this._quickInputService.backButton;
|
||||
}
|
||||
const { iconPath, tooltip, handle } = button;
|
||||
return {
|
||||
iconPath: {
|
||||
dark: URI.revive(iconPath.dark),
|
||||
light: iconPath.light && URI.revive(iconPath.light)
|
||||
},
|
||||
tooltip,
|
||||
handle
|
||||
};
|
||||
});
|
||||
} else {
|
||||
input[param] = params[param];
|
||||
}
|
||||
}
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
$dispose(sessionId: number): TPromise<void> {
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (session) {
|
||||
session.input.dispose();
|
||||
this.sessions.delete(sessionId);
|
||||
}
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,17 +401,17 @@ export class MainThreadSCM implements MainThreadSCMShape {
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
repository.input.validateInput = async (value, pos): TPromise<IInputValidation | undefined> => {
|
||||
const result = await this._proxy.$validateInput(sourceControlHandle, value, pos);
|
||||
repository.input.validateInput = (value, pos): TPromise<IInputValidation | undefined> => {
|
||||
return this._proxy.$validateInput(sourceControlHandle, value, pos).then(result => {
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
message: result[0],
|
||||
type: result[1]
|
||||
};
|
||||
return {
|
||||
message: result[0],
|
||||
type: result[1]
|
||||
};
|
||||
});
|
||||
};
|
||||
} else {
|
||||
repository.input.validateInput = () => TPromise.as(undefined);
|
||||
|
||||
@@ -16,7 +16,7 @@ import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { trimTrailingWhitespace } from 'vs/editor/common/commands/trimTrailingWhitespaceCommand';
|
||||
import { getDocumentFormattingEdits, NoProviderError } from 'vs/editor/contrib/format/format';
|
||||
import { EditOperationsCommand } from 'vs/editor/contrib/format/formatCommand';
|
||||
import { FormattingEdit } from 'vs/editor/contrib/format/formattingEdit';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
|
||||
import { ExtHostContext, ExtHostDocumentSaveParticipantShape, IExtHostContext } from '../node/extHost.protocol';
|
||||
@@ -24,20 +24,19 @@ import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { extHostCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress';
|
||||
import { IProgressService2, ProgressLocation } from 'vs/workbench/services/progress/common/progress';
|
||||
import { localize } from 'vs/nls';
|
||||
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { shouldSynchronizeModel } from 'vs/editor/common/services/modelService';
|
||||
import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger';
|
||||
import { CodeAction } from 'vs/editor/common/modes';
|
||||
import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands';
|
||||
import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction';
|
||||
import { ICodeActionsOnSaveOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
|
||||
|
||||
export interface ISaveParticipantParticipant extends ISaveParticipant {
|
||||
// progressMessage: string;
|
||||
@@ -93,7 +92,7 @@ function findEditor(model: ITextModel, codeEditorService: ICodeEditorService): I
|
||||
if (model.isAttachedToEditor()) {
|
||||
for (const editor of codeEditorService.listCodeEditors()) {
|
||||
if (editor.getModel() === model) {
|
||||
if (editor.isFocused()) {
|
||||
if (editor.hasTextFocus()) {
|
||||
return editor; // favour focused editor if there are multiple
|
||||
}
|
||||
|
||||
@@ -216,16 +215,20 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant {
|
||||
const timeout = this._configurationService.getValue('editor.formatOnSaveTimeout', { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.getResource() });
|
||||
|
||||
return new Promise<ISingleEditOperation[]>((resolve, reject) => {
|
||||
setTimeout(() => reject(localize('timeout.formatOnSave', "Aborted format on save after {0}ms", timeout)), timeout);
|
||||
getDocumentFormattingEdits(model, { tabSize, insertSpaces })
|
||||
.then(edits => this._editorWorkerService.computeMoreMinimalEdits(model.uri, edits))
|
||||
.then(resolve, err => {
|
||||
if (!(err instanceof Error) || err.name !== NoProviderError.Name) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
let request = getDocumentFormattingEdits(model, { tabSize, insertSpaces });
|
||||
|
||||
setTimeout(() => {
|
||||
reject(localize('timeout.formatOnSave', "Aborted format on save after {0}ms", timeout));
|
||||
request.cancel();
|
||||
}, timeout);
|
||||
|
||||
request.then(edits => this._editorWorkerService.computeMoreMinimalEdits(model.uri, edits)).then(resolve, err => {
|
||||
if (!(err instanceof Error) || err.name !== NoProviderError.Name) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
}).then(edits => {
|
||||
if (!isFalsyOrEmpty(edits) && versionNow === model.getVersionId()) {
|
||||
@@ -240,7 +243,7 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant {
|
||||
}
|
||||
|
||||
private _editsWithEditor(editor: ICodeEditor, edits: ISingleEditOperation[]): void {
|
||||
EditOperationsCommand.execute(editor, edits);
|
||||
FormattingEdit.execute(editor, edits);
|
||||
}
|
||||
|
||||
private _editWithModel(model: ITextModel, edits: ISingleEditOperation[]): void {
|
||||
@@ -270,10 +273,8 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant {
|
||||
class CodeActionOnParticipant implements ISaveParticipant {
|
||||
|
||||
constructor(
|
||||
@ITextModelService private readonly _textModelService: ITextModelService,
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
@IBulkEditService private readonly _bulkEditService: IBulkEditService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService
|
||||
) { }
|
||||
|
||||
@@ -283,10 +284,6 @@ class CodeActionOnParticipant implements ISaveParticipant {
|
||||
}
|
||||
|
||||
const model = editorModel.textEditorModel;
|
||||
const editor = findEditor(model, this._codeEditorService);
|
||||
if (!editor) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const settingsOverrides = { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.getResource() };
|
||||
const setting = this._configurationService.getValue<ICodeActionsOnSaveOptions>('editor.codeActionsOnSave', settingsOverrides);
|
||||
@@ -304,17 +301,20 @@ class CodeActionOnParticipant implements ISaveParticipant {
|
||||
return new Promise<CodeAction[]>((resolve, reject) => {
|
||||
setTimeout(() => reject(localize('codeActionsOnSave.didTimeout', "Aborted codeActionsOnSave after {0}ms", timeout)), timeout);
|
||||
this.getActionsToRun(model, codeActionsOnSave).then(resolve);
|
||||
}).then(actionsToRun => this.applyCodeActions(actionsToRun, editor));
|
||||
}).then(actionsToRun => this.applyCodeActions(actionsToRun));
|
||||
}
|
||||
|
||||
private async applyCodeActions(actionsToRun: CodeAction[], editor: ICodeEditor) {
|
||||
private async applyCodeActions(actionsToRun: CodeAction[]) {
|
||||
for (const action of actionsToRun) {
|
||||
await applyCodeAction(action, this._textModelService, this._fileService, this._commandService, editor);
|
||||
await applyCodeAction(action, this._bulkEditService, this._commandService);
|
||||
}
|
||||
}
|
||||
|
||||
private async getActionsToRun(model: ITextModel, codeActionsOnSave: CodeActionKind[]) {
|
||||
const actions = await getCodeActions(model, model.getFullModelRange(), { kind: CodeActionKind.Source, includeSourceActions: true });
|
||||
const actions = await getCodeActions(model, model.getFullModelRange(), {
|
||||
type: 'auto',
|
||||
filter: { kind: CodeActionKind.Source, includeSourceActions: true },
|
||||
});
|
||||
const actionsToRun = actions.filter(returnedAction => returnedAction.kind && codeActionsOnSave.some(onSaveKind => onSaveKind.contains(returnedAction.kind)));
|
||||
return actionsToRun;
|
||||
}
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
'use strict';
|
||||
|
||||
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { values } from 'vs/base/common/map';
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { PPromise, TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IFileMatch, ILineMatch, ISearchComplete, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, QueryType } from 'vs/platform/search/common/search';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IFileMatch, IRawFileMatch2, ISearchComplete, ISearchCompleteStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, QueryType, SearchProviderType } from 'vs/platform/search/common/search';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { ExtHostContext, ExtHostSearchShape, IExtHostContext, MainContext, MainThreadSearchShape } from '../node/extHost.protocol';
|
||||
|
||||
@@ -21,18 +22,27 @@ export class MainThreadSearch implements MainThreadSearchShape {
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@ISearchService private readonly _searchService: ISearchService
|
||||
@ISearchService private readonly _searchService: ISearchService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService
|
||||
) {
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostSearch);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._searchProvider.forEach(value => dispose());
|
||||
this._searchProvider.forEach(value => value.dispose());
|
||||
this._searchProvider.clear();
|
||||
}
|
||||
|
||||
$registerSearchProvider(handle: number, scheme: string): void {
|
||||
this._searchProvider.set(handle, new RemoteSearchProvider(this._searchService, scheme, handle, this._proxy));
|
||||
$registerTextSearchProvider(handle: number, scheme: string): void {
|
||||
this._searchProvider.set(handle, new RemoteSearchProvider(this._searchService, SearchProviderType.text, scheme, handle, this._proxy));
|
||||
}
|
||||
|
||||
$registerFileSearchProvider(handle: number, scheme: string): void {
|
||||
this._searchProvider.set(handle, new RemoteSearchProvider(this._searchService, SearchProviderType.file, scheme, handle, this._proxy));
|
||||
}
|
||||
|
||||
$registerFileIndexProvider(handle: number, scheme: string): void {
|
||||
this._searchProvider.set(handle, new RemoteSearchProvider(this._searchService, SearchProviderType.fileIndex, scheme, handle, this._proxy));
|
||||
}
|
||||
|
||||
$unregisterProvider(handle: number): void {
|
||||
@@ -40,9 +50,17 @@ export class MainThreadSearch implements MainThreadSearchShape {
|
||||
this._searchProvider.delete(handle);
|
||||
}
|
||||
|
||||
$handleFindMatch(handle: number, session, data: UriComponents | [UriComponents, ILineMatch]): void {
|
||||
$handleFileMatch(handle: number, session, data: UriComponents[]): void {
|
||||
this._searchProvider.get(handle).handleFindMatch(session, data);
|
||||
}
|
||||
|
||||
$handleTextMatch(handle: number, session, data: IRawFileMatch2[]): void {
|
||||
this._searchProvider.get(handle).handleFindMatch(session, data);
|
||||
}
|
||||
|
||||
$handleTelemetry(eventName: string, data: any): void {
|
||||
this._telemetryService.publicLog(eventName, data);
|
||||
}
|
||||
}
|
||||
|
||||
class SearchOperation {
|
||||
@@ -57,66 +75,57 @@ class SearchOperation {
|
||||
//
|
||||
}
|
||||
|
||||
addMatch(resource: URI, match: ILineMatch): void {
|
||||
if (!this.matches.has(resource.toString())) {
|
||||
this.matches.set(resource.toString(), { resource, lineMatches: [] });
|
||||
addMatch(match: IFileMatch): void {
|
||||
if (this.matches.has(match.resource.toString())) {
|
||||
// Merge with previous IFileMatches
|
||||
this.matches.get(match.resource.toString()).lineMatches.push(...match.lineMatches);
|
||||
} else {
|
||||
this.matches.set(match.resource.toString(), match);
|
||||
}
|
||||
if (match) {
|
||||
this.matches.get(resource.toString()).lineMatches.push(match);
|
||||
}
|
||||
this.progress(this.matches.get(resource.toString()));
|
||||
|
||||
this.progress(this.matches.get(match.resource.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
class RemoteSearchProvider implements ISearchResultProvider {
|
||||
class RemoteSearchProvider implements ISearchResultProvider, IDisposable {
|
||||
|
||||
private readonly _registrations: IDisposable[];
|
||||
private readonly _searches = new Map<number, SearchOperation>();
|
||||
|
||||
|
||||
constructor(
|
||||
searchService: ISearchService,
|
||||
type: SearchProviderType,
|
||||
private readonly _scheme: string,
|
||||
private readonly _handle: number,
|
||||
private readonly _proxy: ExtHostSearchShape
|
||||
) {
|
||||
this._registrations = [searchService.registerSearchResultProvider(this)];
|
||||
this._registrations = [searchService.registerSearchResultProvider(this._scheme, type, this)];
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose(this._registrations);
|
||||
}
|
||||
|
||||
search(query: ISearchQuery): PPromise<ISearchComplete, ISearchProgressItem> {
|
||||
search(query: ISearchQuery, onProgress?: (p: ISearchProgressItem) => void): TPromise<ISearchComplete> {
|
||||
|
||||
if (isFalsyOrEmpty(query.folderQueries)) {
|
||||
return PPromise.as(undefined);
|
||||
}
|
||||
|
||||
let includes = { ...query.includePattern };
|
||||
let excludes = { ...query.excludePattern };
|
||||
|
||||
for (const folderQuery of query.folderQueries) {
|
||||
if (folderQuery.folder.scheme === this._scheme) {
|
||||
includes = { ...includes, ...folderQuery.includePattern };
|
||||
excludes = { ...excludes, ...folderQuery.excludePattern };
|
||||
}
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
let outer: TPromise;
|
||||
|
||||
return new PPromise((resolve, reject, report) => {
|
||||
return new TPromise((resolve, reject) => {
|
||||
|
||||
const search = new SearchOperation(report);
|
||||
const search = new SearchOperation(onProgress);
|
||||
this._searches.set(search.id, search);
|
||||
|
||||
outer = query.type === QueryType.File
|
||||
? this._proxy.$provideFileSearchResults(this._handle, search.id, query.filePattern)
|
||||
: this._proxy.$provideTextSearchResults(this._handle, search.id, query.contentPattern, { excludes: Object.keys(excludes), includes: Object.keys(includes) });
|
||||
? this._proxy.$provideFileSearchResults(this._handle, search.id, query)
|
||||
: this._proxy.$provideTextSearchResults(this._handle, search.id, query.contentPattern, query);
|
||||
|
||||
outer.then(() => {
|
||||
outer.then((result: ISearchCompleteStats) => {
|
||||
this._searches.delete(search.id);
|
||||
resolve(({ results: values(search.matches), stats: undefined }));
|
||||
resolve(({ results: values(search.matches), stats: result.stats, limitHit: result.limitHit }));
|
||||
}, err => {
|
||||
this._searches.delete(search.id);
|
||||
reject(err);
|
||||
@@ -128,21 +137,28 @@ class RemoteSearchProvider implements ISearchResultProvider {
|
||||
});
|
||||
}
|
||||
|
||||
handleFindMatch(session: number, dataOrUri: UriComponents | [UriComponents, ILineMatch]): void {
|
||||
clearCache(cacheKey: string): TPromise<void> {
|
||||
return this._proxy.$clearCache(cacheKey);
|
||||
}
|
||||
|
||||
handleFindMatch(session: number, dataOrUri: (UriComponents | IRawFileMatch2)[]): void {
|
||||
if (!this._searches.has(session)) {
|
||||
// ignore...
|
||||
return;
|
||||
}
|
||||
let resource: URI;
|
||||
let match: ILineMatch;
|
||||
|
||||
if (Array.isArray(dataOrUri)) {
|
||||
resource = URI.revive(dataOrUri[0]);
|
||||
match = dataOrUri[1];
|
||||
} else {
|
||||
resource = URI.revive(dataOrUri);
|
||||
}
|
||||
|
||||
this._searches.get(session).addMatch(resource, match);
|
||||
const searchOp = this._searches.get(session);
|
||||
dataOrUri.forEach(result => {
|
||||
if ((<IRawFileMatch2>result).lineMatches) {
|
||||
searchOp.addMatch({
|
||||
resource: URI.revive((<IRawFileMatch2>result).resource),
|
||||
lineMatches: (<IRawFileMatch2>result).lineMatches
|
||||
});
|
||||
} else {
|
||||
searchOp.addMatch({
|
||||
resource: URI.revive(result)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,33 +4,34 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import * as Objects from 'vs/base/common/objects';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as Types from 'vs/base/common/types';
|
||||
import * as Platform from 'vs/base/common/platform';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
|
||||
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
|
||||
import {
|
||||
ContributedTask, ExtensionTaskSourceTransfer, TaskIdentifier, TaskExecution, Task, TaskEvent, TaskEventKind,
|
||||
ContributedTask, ExtensionTaskSourceTransfer, KeyedTaskIdentifier, TaskExecution, Task, TaskEvent, TaskEventKind,
|
||||
PresentationOptions, CommandOptions, CommandConfiguration, RuntimeType, CustomTask, TaskScope, TaskSource, TaskSourceKind, ExtensionTaskSource, RevealKind, PanelKind
|
||||
} from 'vs/workbench/parts/tasks/common/tasks';
|
||||
import { ITaskService, TaskFilter } from 'vs/workbench/parts/tasks/common/taskService';
|
||||
|
||||
import { TaskDefinition } from 'vs/workbench/parts/tasks/node/tasks';
|
||||
|
||||
import { ITaskService, TaskFilter } from 'vs/workbench/parts/tasks/common/taskService';
|
||||
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { ExtHostContext, MainThreadTaskShape, ExtHostTaskShape, MainContext, IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import {
|
||||
TaskDefinitionDTO, TaskExecutionDTO, ProcessExecutionOptionsDTO, TaskPresentationOptionsDTO,
|
||||
ProcessExecutionDTO, ShellExecutionDTO, ShellExecutionOptionsDTO, TaskDTO, TaskSourceDTO, TaskHandleDTO, TaskFilterDTO
|
||||
ProcessExecutionDTO, ShellExecutionDTO, ShellExecutionOptionsDTO, TaskDTO, TaskSourceDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO
|
||||
} from 'vs/workbench/api/shared/tasks';
|
||||
|
||||
export { TaskDTO, TaskHandleDTO, TaskExecutionDTO, TaskFilterDTO };
|
||||
|
||||
namespace TaskExecutionDTO {
|
||||
export function from(value: TaskExecution): TaskExecutionDTO {
|
||||
return {
|
||||
@@ -38,25 +39,46 @@ namespace TaskExecutionDTO {
|
||||
task: TaskDTO.from(value.task)
|
||||
};
|
||||
}
|
||||
export function to(value: TaskExecutionDTO, workspace: IWorkspaceContextService): TaskExecution {
|
||||
export function to(value: TaskExecutionDTO, workspace: IWorkspaceContextService, executeOnly: boolean): TaskExecution {
|
||||
return {
|
||||
id: value.id,
|
||||
task: TaskDTO.to(value.task, workspace)
|
||||
task: TaskDTO.to(value.task, workspace, executeOnly)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
namespace TaskProcessStartedDTO {
|
||||
export function from(value: TaskExecution, processId: number): TaskProcessStartedDTO {
|
||||
return {
|
||||
id: value.id,
|
||||
processId
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
namespace TaskProcessEndedDTO {
|
||||
export function from(value: TaskExecution, exitCode: number): TaskProcessEndedDTO {
|
||||
return {
|
||||
id: value.id,
|
||||
exitCode
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
namespace TaskDefinitionDTO {
|
||||
export function from(value: TaskIdentifier): TaskDefinitionDTO {
|
||||
export function from(value: KeyedTaskIdentifier): TaskDefinitionDTO {
|
||||
let result = Objects.assign(Object.create(null), value);
|
||||
delete result._key;
|
||||
return result;
|
||||
}
|
||||
export function to(value: TaskDefinitionDTO): TaskIdentifier {
|
||||
const hash = crypto.createHash('md5');
|
||||
hash.update(JSON.stringify(value));
|
||||
let result = Objects.assign(Object.create(null), value);
|
||||
result._key = hash.digest('hex');
|
||||
export function to(value: TaskDefinitionDTO, executeOnly: boolean): KeyedTaskIdentifier {
|
||||
let result = TaskDefinition.createTaskIdentifier(value, console);
|
||||
if (result === void 0 && executeOnly) {
|
||||
result = {
|
||||
_key: generateUuid(),
|
||||
type: '$executeOnly'
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -288,7 +310,7 @@ namespace TaskDTO {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function to(task: TaskDTO, workspace: IWorkspaceContextService): Task {
|
||||
export function to(task: TaskDTO, workspace: IWorkspaceContextService, executeOnly: boolean): Task {
|
||||
if (typeof task.name !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
@@ -307,10 +329,10 @@ namespace TaskDTO {
|
||||
let source = TaskSourceDTO.to(task.source, workspace);
|
||||
|
||||
let label = nls.localize('task.label', '{0}: {1}', source.label, task.name);
|
||||
let definition = TaskDefinitionDTO.to(task.definition);
|
||||
let definition = TaskDefinitionDTO.to(task.definition, executeOnly);
|
||||
let id = `${task.source.extensionId}.${definition._key}`;
|
||||
let result: ContributedTask = {
|
||||
_id: id, // uuidMap.getUUID(identifier),
|
||||
_id: id, // uuidMap.getUUID(identifier)
|
||||
_source: source,
|
||||
_label: label,
|
||||
type: definition.type,
|
||||
@@ -339,6 +361,7 @@ namespace TaskFilterDTO {
|
||||
@extHostNamedCustomer(MainContext.MainThreadTask)
|
||||
export class MainThreadTask implements MainThreadTaskShape {
|
||||
|
||||
private _extHostContext: IExtHostContext;
|
||||
private _proxy: ExtHostTaskShape;
|
||||
private _activeHandles: { [handle: number]: boolean; };
|
||||
|
||||
@@ -352,9 +375,13 @@ export class MainThreadTask implements MainThreadTaskShape {
|
||||
this._taskService.onDidStateChange((event: TaskEvent) => {
|
||||
let task = event.__task;
|
||||
if (event.kind === TaskEventKind.Start) {
|
||||
this._proxy.$taskStarted(TaskExecutionDTO.from(Task.getTaskExecution(task)));
|
||||
this._proxy.$onDidStartTask(TaskExecutionDTO.from(Task.getTaskExecution(task)));
|
||||
} else if (event.kind === TaskEventKind.ProcessStarted) {
|
||||
this._proxy.$onDidStartTaskProcess(TaskProcessStartedDTO.from(Task.getTaskExecution(task), event.processId));
|
||||
} else if (event.kind === TaskEventKind.ProcessEnded) {
|
||||
this._proxy.$onDidEndTaskProcess(TaskProcessEndedDTO.from(Task.getTaskExecution(task), event.exitCode));
|
||||
} else if (event.kind === TaskEventKind.End) {
|
||||
this._proxy.$taskEnded(TaskExecutionDTO.from(Task.getTaskExecution(task)));
|
||||
this._proxy.$OnDidEndTask(TaskExecutionDTO.from(Task.getTaskExecution(task)));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -368,17 +395,26 @@ export class MainThreadTask implements MainThreadTaskShape {
|
||||
|
||||
public $registerTaskProvider(handle: number): TPromise<void> {
|
||||
this._taskService.registerTaskProvider(handle, {
|
||||
provideTasks: () => {
|
||||
return this._proxy.$provideTasks(handle).then((value) => {
|
||||
provideTasks: (validTypes: IStringDictionary<boolean>) => {
|
||||
return this._proxy.$provideTasks(handle, validTypes).then((value) => {
|
||||
let tasks: Task[] = [];
|
||||
for (let task of value.tasks) {
|
||||
if (ContributedTask.is(task)) {
|
||||
let uri = (task._source as any as ExtensionTaskSourceTransfer).__workspaceFolder;
|
||||
if (uri) {
|
||||
delete (task._source as any as ExtensionTaskSourceTransfer).__workspaceFolder;
|
||||
(task._source as any).workspaceFolder = this._workspaceContextServer.getWorkspaceFolder(URI.revive(uri));
|
||||
let taskTransfer = task._source as any as ExtensionTaskSourceTransfer;
|
||||
if (taskTransfer.__workspaceFolder !== void 0 && taskTransfer.__definition !== void 0) {
|
||||
(task._source as any).workspaceFolder = this._workspaceContextServer.getWorkspaceFolder(URI.revive(taskTransfer.__workspaceFolder));
|
||||
delete taskTransfer.__workspaceFolder;
|
||||
let taskIdentifier = TaskDefinition.createTaskIdentifier(taskTransfer.__definition, console);
|
||||
delete taskTransfer.__definition;
|
||||
if (taskIdentifier !== void 0) {
|
||||
(task as ContributedTask).defines = taskIdentifier;
|
||||
task._id = `${task._id}.${taskIdentifier._key}`;
|
||||
tasks.push(task);
|
||||
}
|
||||
} else {
|
||||
console.warn(`Dropping task ${task.name}. Missing workspace folder and task definition`);
|
||||
}
|
||||
}
|
||||
value.tasks = tasks;
|
||||
return value;
|
||||
});
|
||||
}
|
||||
@@ -417,11 +453,11 @@ export class MainThreadTask implements MainThreadTaskShape {
|
||||
task: TaskDTO.from(task)
|
||||
};
|
||||
resolve(result);
|
||||
}, (error) => {
|
||||
}, (_error) => {
|
||||
reject(new Error('Task not found'));
|
||||
});
|
||||
} else {
|
||||
let task = TaskDTO.to(value, this._workspaceContextServer);
|
||||
let task = TaskDTO.to(value, this._workspaceContextServer, true);
|
||||
this._taskService.run(task);
|
||||
let result: TaskExecutionDTO = {
|
||||
id: task._id,
|
||||
@@ -449,4 +485,37 @@ export class MainThreadTask implements MainThreadTaskShape {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public $registerTaskSystem(key: string, info: TaskSystemInfoDTO): void {
|
||||
let platform: Platform.Platform;
|
||||
switch (info.platform) {
|
||||
case 'win32':
|
||||
platform = Platform.Platform.Windows;
|
||||
break;
|
||||
case 'darwin':
|
||||
platform = Platform.Platform.Mac;
|
||||
break;
|
||||
case 'linux':
|
||||
platform = Platform.Platform.Linux;
|
||||
break;
|
||||
default:
|
||||
platform = Platform.platform;
|
||||
}
|
||||
this._taskService.registerTaskSystem(key, {
|
||||
platform: platform,
|
||||
uriProvider: (path: string): URI => {
|
||||
return URI.parse(`${info.scheme}://${info.host}:${info.port}${path}`);
|
||||
},
|
||||
context: this._extHostContext,
|
||||
resolveVariables: (workspaceFolder: IWorkspaceFolder, variables: Set<string>): TPromise<Map<string, string>> => {
|
||||
let vars: string[] = [];
|
||||
variables.forEach(item => vars.push(item));
|
||||
return this._proxy.$resolveVariables(workspaceFolder.uri, vars).then(values => {
|
||||
let result = new Map<string, string>();
|
||||
Object.keys(values).forEach(key => result.set(key, values[key]));
|
||||
return result;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
'use strict';
|
||||
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest } from 'vs/workbench/parts/terminal/common/terminal';
|
||||
import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY } from 'vs/workbench/parts/terminal/common/terminal';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, ShellLaunchConfigDto } from '../node/extHost.protocol';
|
||||
import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, ShellLaunchConfigDto } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTerminalService)
|
||||
@@ -16,28 +16,35 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
private _proxy: ExtHostTerminalServiceShape;
|
||||
private _toDispose: IDisposable[] = [];
|
||||
private _terminalProcesses: { [id: number]: ITerminalProcessExtHostProxy } = {};
|
||||
private _dataListeners: { [id: number]: IDisposable } = {};
|
||||
private _terminalOnDidWriteDataListeners: { [id: number]: IDisposable } = {};
|
||||
private _terminalOnDidAcceptInputListeners: { [id: number]: IDisposable } = {};
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@ITerminalService private terminalService: ITerminalService
|
||||
) {
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalService);
|
||||
this._toDispose.push(terminalService.onInstanceCreated((terminalInstance) => {
|
||||
this._toDispose.push(terminalService.onInstanceCreated((instance) => {
|
||||
// Delay this message so the TerminalInstance constructor has a chance to finish and
|
||||
// return the ID normally to the extension host. The ID that is passed here will be used
|
||||
// to register non-extension API terminals in the extension host.
|
||||
setTimeout(() => this._onTerminalOpened(terminalInstance), 100);
|
||||
setTimeout(() => this._onTerminalOpened(instance), EXT_HOST_CREATION_DELAY);
|
||||
}));
|
||||
this._toDispose.push(terminalService.onInstanceDisposed(terminalInstance => this._onTerminalDisposed(terminalInstance)));
|
||||
this._toDispose.push(terminalService.onInstanceProcessIdReady(terminalInstance => this._onTerminalProcessIdReady(terminalInstance)));
|
||||
this._toDispose.push(terminalService.onInstanceDisposed(instance => this._onTerminalDisposed(instance)));
|
||||
this._toDispose.push(terminalService.onInstanceProcessIdReady(instance => this._onTerminalProcessIdReady(instance)));
|
||||
this._toDispose.push(terminalService.onInstanceDimensionsChanged(instance => this._onInstanceDimensionsChanged(instance)));
|
||||
this._toDispose.push(terminalService.onInstanceRequestExtHostProcess(request => this._onTerminalRequestExtHostProcess(request)));
|
||||
this._toDispose.push(terminalService.onActiveInstanceChanged(instance => this._onActiveTerminalChanged(instance ? instance.id : undefined)));
|
||||
|
||||
// Set initial ext host state
|
||||
this.terminalService.terminalInstances.forEach(t => {
|
||||
this._onTerminalOpened(t);
|
||||
t.processReady.then(() => this._onTerminalProcessIdReady(t));
|
||||
});
|
||||
const activeInstance = this.terminalService.getActiveInstance();
|
||||
if (activeInstance) {
|
||||
this._proxy.$acceptActiveTerminalChanged(activeInstance.id);
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
@@ -60,8 +67,13 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
return TPromise.as(this.terminalService.createTerminal(shellLaunchConfig).id);
|
||||
}
|
||||
|
||||
public $createTerminalRenderer(name: string): TPromise<number> {
|
||||
const instance = this.terminalService.createTerminalRenderer(name);
|
||||
return TPromise.as(instance.id);
|
||||
}
|
||||
|
||||
public $show(terminalId: number, preserveFocus: boolean): void {
|
||||
let terminalInstance = this.terminalService.getInstanceFromId(terminalId);
|
||||
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
|
||||
if (terminalInstance) {
|
||||
this.terminalService.setActiveInstance(terminalInstance);
|
||||
this.terminalService.showPanel(!preserveFocus);
|
||||
@@ -75,31 +87,86 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
}
|
||||
|
||||
public $dispose(terminalId: number): void {
|
||||
let terminalInstance = this.terminalService.getInstanceFromId(terminalId);
|
||||
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
|
||||
if (terminalInstance) {
|
||||
terminalInstance.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public $terminalRendererWrite(terminalId: number, text: string): void {
|
||||
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
|
||||
if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) {
|
||||
terminalInstance.write(text);
|
||||
}
|
||||
}
|
||||
|
||||
public $terminalRendererSetName(terminalId: number, name: string): void {
|
||||
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
|
||||
if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) {
|
||||
terminalInstance.setTitle(name, false);
|
||||
}
|
||||
}
|
||||
|
||||
public $terminalRendererSetDimensions(terminalId: number, dimensions: ITerminalDimensions): void {
|
||||
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
|
||||
if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) {
|
||||
terminalInstance.setDimensions(dimensions);
|
||||
}
|
||||
}
|
||||
|
||||
public $terminalRendererRegisterOnInputListener(terminalId: number): void {
|
||||
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
|
||||
if (!terminalInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Listener already registered
|
||||
if (this._terminalOnDidAcceptInputListeners.hasOwnProperty(terminalId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register
|
||||
this._terminalOnDidAcceptInputListeners[terminalId] = terminalInstance.onRendererInput(data => this._onTerminalRendererInput(terminalId, data));
|
||||
terminalInstance.addDisposable(this._terminalOnDidAcceptInputListeners[terminalId]);
|
||||
}
|
||||
|
||||
public $sendText(terminalId: number, text: string, addNewLine: boolean): void {
|
||||
let terminalInstance = this.terminalService.getInstanceFromId(terminalId);
|
||||
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
|
||||
if (terminalInstance) {
|
||||
terminalInstance.sendText(text, addNewLine);
|
||||
}
|
||||
}
|
||||
|
||||
public $registerOnDataListener(terminalId: number): void {
|
||||
let terminalInstance = this.terminalService.getInstanceFromId(terminalId);
|
||||
if (terminalInstance) {
|
||||
this._dataListeners[terminalId] = terminalInstance.onData(data => this._onTerminalData(terminalId, data));
|
||||
terminalInstance.onDisposed(instance => delete this._dataListeners[terminalId]);
|
||||
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
|
||||
if (!terminalInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Listener already registered
|
||||
if (this._terminalOnDidWriteDataListeners[terminalId]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register
|
||||
this._terminalOnDidWriteDataListeners[terminalId] = terminalInstance.onData(data => {
|
||||
this._onTerminalData(terminalId, data);
|
||||
});
|
||||
terminalInstance.addDisposable(this._terminalOnDidWriteDataListeners[terminalId]);
|
||||
}
|
||||
|
||||
private _onActiveTerminalChanged(terminalId: number | undefined): void {
|
||||
this._proxy.$acceptActiveTerminalChanged(terminalId);
|
||||
}
|
||||
|
||||
private _onTerminalData(terminalId: number, data: string): void {
|
||||
this._proxy.$acceptTerminalProcessData(terminalId, data);
|
||||
}
|
||||
|
||||
private _onTerminalRendererInput(terminalId: number, data: string): void {
|
||||
this._proxy.$acceptTerminalRendererInput(terminalId, data);
|
||||
}
|
||||
|
||||
private _onTerminalDisposed(terminalInstance: ITerminalInstance): void {
|
||||
this._proxy.$acceptTerminalClosed(terminalInstance.id);
|
||||
}
|
||||
@@ -112,6 +179,14 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
this._proxy.$acceptTerminalProcessId(terminalInstance.id, terminalInstance.processId);
|
||||
}
|
||||
|
||||
private _onInstanceDimensionsChanged(instance: ITerminalInstance): void {
|
||||
// Only send the dimensions if the terminal is a renderer only as there is no API to access
|
||||
// dimensions on a plain Terminal.
|
||||
if (instance.shellLaunchConfig.isRendererOnly) {
|
||||
this._proxy.$acceptTerminalRendererDimensions(instance.id, instance.cols, instance.rows);
|
||||
}
|
||||
}
|
||||
|
||||
private _onTerminalRequestExtHostProcess(request: ITerminalProcessExtHostRequest): void {
|
||||
this._terminalProcesses[request.proxy.terminalId] = request.proxy;
|
||||
const shellLaunchConfigDto: ShellLaunchConfigDto = {
|
||||
@@ -123,7 +198,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
};
|
||||
this._proxy.$createProcess(request.proxy.terminalId, shellLaunchConfigDto, request.cols, request.rows);
|
||||
request.proxy.onInput(data => this._proxy.$acceptProcessInput(request.proxy.terminalId, data));
|
||||
request.proxy.onResize((cols, rows) => this._proxy.$acceptProcessResize(request.proxy.terminalId, cols, rows));
|
||||
request.proxy.onResize(dimensions => this._proxy.$acceptProcessResize(request.proxy.terminalId, dimensions.cols, dimensions.rows));
|
||||
request.proxy.onShutdown(() => this._proxy.$acceptProcessShutdown(request.proxy.terminalId));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,10 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ExtHostContext, MainThreadTreeViewsShape, ExtHostTreeViewsShape, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { ITreeViewDataProvider, ITreeItem, IViewsService } from 'vs/workbench/common/views';
|
||||
import { ITreeViewDataProvider, ITreeItem, IViewsService, ITreeViewer, ViewsRegistry, ICustomViewDescriptor } from 'vs/workbench/common/views';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { distinct } from 'vs/base/common/arrays';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
@@ -29,32 +28,55 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
|
||||
}
|
||||
|
||||
$registerTreeViewDataProvider(treeViewId: string): void {
|
||||
const dataProvider = this._register(new TreeViewDataProvider(treeViewId, this._proxy, this.notificationService));
|
||||
const dataProvider = new TreeViewDataProvider(treeViewId, this._proxy, this.notificationService);
|
||||
this._dataProviders.set(treeViewId, dataProvider);
|
||||
const treeViewer = this.viewsService.getTreeViewer(treeViewId);
|
||||
if (treeViewer) {
|
||||
treeViewer.dataProvider = dataProvider;
|
||||
const viewer = this.getTreeViewer(treeViewId);
|
||||
if (viewer) {
|
||||
viewer.dataProvider = dataProvider;
|
||||
this.registerListeners(treeViewId, viewer);
|
||||
this._proxy.$setVisible(treeViewId, viewer.visible);
|
||||
} else {
|
||||
this.notificationService.error('No view is registered with id: ' + treeViewId);
|
||||
}
|
||||
}
|
||||
|
||||
$reveal(treeViewId: string, item: ITreeItem, parentChain: ITreeItem[], options?: { select?: boolean }): TPromise<void> {
|
||||
return this.viewsService.openView(treeViewId)
|
||||
$reveal(treeViewId: string, item: ITreeItem, parentChain: ITreeItem[], options: { select: boolean, focus: boolean }): TPromise<void> {
|
||||
return this.viewsService.openView(treeViewId, options.focus)
|
||||
.then(() => {
|
||||
const viewer = this.viewsService.getTreeViewer(treeViewId);
|
||||
const viewer = this.getTreeViewer(treeViewId);
|
||||
return viewer ? viewer.reveal(item, parentChain, options) : null;
|
||||
});
|
||||
}
|
||||
|
||||
$refresh(treeViewId: string, itemsToRefresh: { [treeItemHandle: string]: ITreeItem }): void {
|
||||
$refresh(treeViewId: string, itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeItem }): TPromise<void> {
|
||||
const viewer = this.getTreeViewer(treeViewId);
|
||||
const dataProvider = this._dataProviders.get(treeViewId);
|
||||
if (dataProvider) {
|
||||
dataProvider.refresh(itemsToRefresh);
|
||||
if (viewer && dataProvider) {
|
||||
const itemsToRefresh = dataProvider.getItemsToRefresh(itemsToRefreshByHandle);
|
||||
return viewer.refresh(itemsToRefresh.length ? itemsToRefresh : void 0);
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
private registerListeners(treeViewId: string, treeViewer: ITreeViewer): void {
|
||||
this._register(treeViewer.onDidExpandItem(item => this._proxy.$setExpanded(treeViewId, item.handle, true)));
|
||||
this._register(treeViewer.onDidCollapseItem(item => this._proxy.$setExpanded(treeViewId, item.handle, false)));
|
||||
this._register(treeViewer.onDidChangeSelection(items => this._proxy.$setSelection(treeViewId, items.map(({ handle }) => handle))));
|
||||
this._register(treeViewer.onDidChangeVisibility(isVisible => this._proxy.$setVisible(treeViewId, isVisible)));
|
||||
}
|
||||
|
||||
private getTreeViewer(treeViewId: string): ITreeViewer {
|
||||
const viewDescriptor: ICustomViewDescriptor = <ICustomViewDescriptor>ViewsRegistry.getView(treeViewId);
|
||||
return viewDescriptor ? viewDescriptor.treeViewer : null;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._dataProviders.forEach((dataProvider, treeViewId) => {
|
||||
const treeViewer = this.getTreeViewer(treeViewId);
|
||||
if (treeViewer) {
|
||||
treeViewer.dataProvider = null;
|
||||
}
|
||||
});
|
||||
this._dataProviders.clear();
|
||||
super.dispose();
|
||||
}
|
||||
@@ -66,12 +88,6 @@ export type TreeItemHandle = string;
|
||||
// {{SQL CARBON EDIT}}
|
||||
export class TreeViewDataProvider implements ITreeViewDataProvider {
|
||||
|
||||
private readonly _onDidChange: Emitter<ITreeItem[] | undefined | null> = new Emitter<ITreeItem[] | undefined | null>();
|
||||
readonly onDidChange: Event<ITreeItem[] | undefined | null> = this._onDidChange.event;
|
||||
|
||||
private readonly _onDispose: Emitter<void> = new Emitter<void>();
|
||||
readonly onDispose: Event<void> = this._onDispose.event;
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
protected itemsMap: Map<TreeItemHandle, ITreeItem> = new Map<TreeItemHandle, ITreeItem>();
|
||||
|
||||
@@ -95,7 +111,7 @@ export class TreeViewDataProvider implements ITreeViewDataProvider {
|
||||
});
|
||||
}
|
||||
|
||||
refresh(itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeItem }) {
|
||||
getItemsToRefresh(itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeItem }): ITreeItem[] {
|
||||
const itemsToRefresh: ITreeItem[] = [];
|
||||
if (itemsToRefreshByHandle) {
|
||||
for (const treeItemHandle of Object.keys(itemsToRefreshByHandle)) {
|
||||
@@ -118,11 +134,7 @@ export class TreeViewDataProvider implements ITreeViewDataProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (itemsToRefresh.length) {
|
||||
this._onDidChange.fire(itemsToRefresh);
|
||||
} else {
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
return itemsToRefresh;
|
||||
}
|
||||
|
||||
private postGetChildren(elements: ITreeItem[]): ITreeItem[] {
|
||||
@@ -145,8 +157,4 @@ export class TreeViewDataProvider implements ITreeViewDataProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._onDispose.fire();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export class MainThreadUrls implements MainThreadUrlsShape {
|
||||
this.proxy = context.getProxy(ExtHostContext.ExtHostUrls);
|
||||
}
|
||||
|
||||
$registerProtocolHandler(handle: number, extensionId: string): TPromise<void> {
|
||||
$registerUriHandler(handle: number, extensionId: string): TPromise<void> {
|
||||
const handler = new ExtensionUrlHandler(this.proxy, handle, extensionId);
|
||||
const disposable = this.urlService.registerHandler(handler);
|
||||
|
||||
@@ -52,7 +52,7 @@ export class MainThreadUrls implements MainThreadUrlsShape {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
$unregisterProtocolHandler(handle: number): TPromise<void> {
|
||||
$unregisterUriHandler(handle: number): TPromise<void> {
|
||||
const tuple = this.handlers.get(handle);
|
||||
|
||||
if (!tuple) {
|
||||
|
||||
@@ -2,29 +2,27 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as map from 'vs/base/common/map';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { Position } from 'vs/platform/editor/common/editor';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { ExtHostContext, ExtHostWebviewsShape, IExtHostContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { ExtHostContext, ExtHostWebviewsShape, IExtHostContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelShowOptions } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/shared/editor';
|
||||
import { WebviewEditor } from 'vs/workbench/parts/webview/electron-browser/webviewEditor';
|
||||
import { WebviewEditorInput } from 'vs/workbench/parts/webview/electron-browser/webviewEditorInput';
|
||||
import { IWebviewEditorService, WebviewInputOptions, WebviewReviver } from 'vs/workbench/parts/webview/electron-browser/webviewEditorService';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { ICreateWebViewShowOptions, IWebviewEditorService, WebviewInputOptions, WebviewReviver } from 'vs/workbench/parts/webview/electron-browser/webviewEditorService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import * as vscode from 'vscode';
|
||||
import { extHostNamedCustomer } from './extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadWebviews)
|
||||
export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviver {
|
||||
|
||||
private static readonly serializeTimeout = 500; // ms
|
||||
|
||||
private static readonly viewType = 'mainThreadWebview';
|
||||
|
||||
private static readonly standardSupportedLinkSchemes = ['http', 'https', 'mailto'];
|
||||
@@ -41,18 +39,17 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv
|
||||
|
||||
constructor(
|
||||
context: IExtHostContext,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService,
|
||||
@ILifecycleService lifecycleService: ILifecycleService,
|
||||
@IWorkbenchEditorService private readonly _editorService: IWorkbenchEditorService,
|
||||
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@IWebviewEditorService private readonly _webviewService: IWebviewEditorService,
|
||||
@IOpenerService private readonly _openerService: IOpenerService,
|
||||
@IExtensionService private readonly _extensionService: IExtensionService,
|
||||
|
||||
) {
|
||||
this._proxy = context.getProxy(ExtHostContext.ExtHostWebviews);
|
||||
editorGroupService.onEditorsChanged(this.onEditorsChanged, this, this._toDispose);
|
||||
editorGroupService.onEditorGroupMoved(this.onEditorGroupMoved, this, this._toDispose);
|
||||
_editorService.onDidActiveEditorChange(this.onActiveEditorChanged, this, this._toDispose);
|
||||
_editorService.onDidVisibleEditorsChange(this.onVisibleEditorsChanged, this, this._toDispose);
|
||||
|
||||
this._toDispose.push(_webviewService.registerReviver(MainThreadWebviews.viewType, this));
|
||||
|
||||
@@ -61,19 +58,25 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv
|
||||
}, this, this._toDispose);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
public dispose(): void {
|
||||
this._toDispose = dispose(this._toDispose);
|
||||
}
|
||||
|
||||
$createWebviewPanel(
|
||||
public $createWebviewPanel(
|
||||
handle: WebviewPanelHandle,
|
||||
viewType: string,
|
||||
title: string,
|
||||
column: Position,
|
||||
showOptions: { viewColumn: EditorViewColumn | null, preserveFocus: boolean },
|
||||
options: WebviewInputOptions,
|
||||
extensionFolderPath: string
|
||||
extensionLocation: UriComponents
|
||||
): void {
|
||||
const webview = this._webviewService.createWebview(MainThreadWebviews.viewType, title, column, options, extensionFolderPath, this.createWebviewEventDelegate(handle));
|
||||
const mainThreadShowOptions: ICreateWebViewShowOptions = Object.create(null);
|
||||
if (showOptions) {
|
||||
mainThreadShowOptions.preserveFocus = showOptions.preserveFocus;
|
||||
mainThreadShowOptions.group = viewColumnToEditorGroup(this._editorGroupService, showOptions.viewColumn);
|
||||
}
|
||||
|
||||
const webview = this._webviewService.createWebview(MainThreadWebviews.viewType, title, mainThreadShowOptions, reviveWebviewOptions(options), URI.revive(extensionLocation), this.createWebviewEventDelegate(handle));
|
||||
webview.state = {
|
||||
viewType: viewType,
|
||||
state: undefined
|
||||
@@ -83,33 +86,45 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv
|
||||
this._activeWebview = handle;
|
||||
}
|
||||
|
||||
$disposeWebview(handle: WebviewPanelHandle): void {
|
||||
public $disposeWebview(handle: WebviewPanelHandle): void {
|
||||
const webview = this.getWebview(handle);
|
||||
webview.dispose();
|
||||
}
|
||||
|
||||
$setTitle(handle: WebviewPanelHandle, value: string): void {
|
||||
public $setTitle(handle: WebviewPanelHandle, value: string): void {
|
||||
const webview = this.getWebview(handle);
|
||||
webview.setName(value);
|
||||
}
|
||||
|
||||
$setHtml(handle: WebviewPanelHandle, value: string): void {
|
||||
public $setIconPath(handle: WebviewPanelHandle, value: { light: UriComponents, dark: UriComponents } | undefined): void {
|
||||
const webview = this.getWebview(handle);
|
||||
webview.iconPath = reviveWebviewIcon(value);
|
||||
}
|
||||
|
||||
public $setHtml(handle: WebviewPanelHandle, value: string): void {
|
||||
const webview = this.getWebview(handle);
|
||||
webview.html = value;
|
||||
}
|
||||
|
||||
$reveal(handle: WebviewPanelHandle, column: Position | null): void {
|
||||
public $setOptions(handle: WebviewPanelHandle, options: vscode.WebviewOptions): void {
|
||||
const webview = this.getWebview(handle);
|
||||
webview.setOptions(reviveWebviewOptions(options));
|
||||
}
|
||||
|
||||
public $reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void {
|
||||
const webview = this.getWebview(handle);
|
||||
if (webview.isDisposed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._webviewService.revealWebview(webview, column);
|
||||
const targetGroup = this._editorGroupService.getGroup(viewColumnToEditorGroup(this._editorGroupService, showOptions.viewColumn));
|
||||
|
||||
this._webviewService.revealWebview(webview, targetGroup || this._editorGroupService.activeGroup, showOptions.preserveFocus);
|
||||
}
|
||||
|
||||
async $postMessage(handle: WebviewPanelHandle, message: any): TPromise<boolean> {
|
||||
public $postMessage(handle: WebviewPanelHandle, message: any): TPromise<boolean> {
|
||||
const webview = this.getWebview(handle);
|
||||
const editors = this._editorService.getVisibleEditors()
|
||||
const editors = this._editorService.visibleControls
|
||||
.filter(e => e instanceof WebviewEditor)
|
||||
.map(e => e as WebviewEditor)
|
||||
.filter(e => e.input.matches(webview));
|
||||
@@ -118,68 +133,56 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv
|
||||
editor.sendMessage(message);
|
||||
}
|
||||
|
||||
return (editors.length > 0);
|
||||
return TPromise.as(editors.length > 0);
|
||||
}
|
||||
|
||||
$registerSerializer(viewType: string): void {
|
||||
public $registerSerializer(viewType: string): void {
|
||||
this._revivers.add(viewType);
|
||||
}
|
||||
|
||||
$unregisterSerializer(viewType: string): void {
|
||||
public $unregisterSerializer(viewType: string): void {
|
||||
this._revivers.delete(viewType);
|
||||
}
|
||||
|
||||
reviveWebview(webview: WebviewEditorInput): TPromise<void> {
|
||||
public reviveWebview(webview: WebviewEditorInput): TPromise<void> {
|
||||
const viewType = webview.state.viewType;
|
||||
return this._extensionService.activateByEvent(`onView:${viewType}`).then(() => {
|
||||
return this._extensionService.activateByEvent(`onWebviewPanel:${viewType}`).then(() => {
|
||||
const handle = 'revival-' + MainThreadWebviews.revivalPool++;
|
||||
this._webviews.set(handle, webview);
|
||||
webview._events = this.createWebviewEventDelegate(handle);
|
||||
|
||||
return this._proxy.$deserializeWebviewPanel(handle, webview.state.viewType, webview.getTitle(), webview.state.state, webview.position, webview.options)
|
||||
let state = undefined;
|
||||
if (webview.state.state) {
|
||||
try {
|
||||
state = JSON.parse(webview.state.state);
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
return this._proxy.$deserializeWebviewPanel(handle, webview.state.viewType, webview.getTitle(), state, editorGroupToViewColumn(this._editorGroupService, webview.group), webview.options)
|
||||
.then(undefined, () => {
|
||||
webview.html = MainThreadWebviews.getDeserializationFailedContents(viewType);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
canRevive(webview: WebviewEditorInput): boolean {
|
||||
if (webview.isDisposed()) {
|
||||
public canRevive(webview: WebviewEditorInput): boolean {
|
||||
if (webview.isDisposed() || !webview.state) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this._revivers.has(webview.viewType) || webview.reviver !== null;
|
||||
return this._revivers.has(webview.state.viewType) || !!webview.reviver;
|
||||
}
|
||||
|
||||
private _onWillShutdown(): TPromise<boolean> {
|
||||
const toRevive: WebviewPanelHandle[] = [];
|
||||
this._webviews.forEach((view, key) => {
|
||||
this._webviews.forEach((view) => {
|
||||
if (this.canRevive(view)) {
|
||||
toRevive.push(key);
|
||||
view.state.state = view.webviewState;
|
||||
}
|
||||
});
|
||||
|
||||
const reviveResponses = toRevive.map(handle =>
|
||||
TPromise.any([
|
||||
this._proxy.$serializeWebviewPanel(handle).then(
|
||||
state => ({ handle, state }),
|
||||
() => ({ handle, state: null })),
|
||||
TPromise.timeout(MainThreadWebviews.serializeTimeout).then(() => ({ handle, state: null }))
|
||||
]).then(x => x.value));
|
||||
|
||||
return TPromise.join(reviveResponses).then(results => {
|
||||
for (const result of results) {
|
||||
const view = this._webviews.get(result.handle);
|
||||
if (view) {
|
||||
if (result.state) {
|
||||
view.state.state = result.state;
|
||||
} else {
|
||||
view.state = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false; // Don't veto shutdown
|
||||
});
|
||||
return TPromise.as(false); // Don't veto shutdown
|
||||
}
|
||||
|
||||
private createWebviewEventDelegate(handle: WebviewPanelHandle) {
|
||||
@@ -187,23 +190,18 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv
|
||||
onDidClickLink: uri => this.onDidClickLink(handle, uri),
|
||||
onMessage: message => this._proxy.$onMessage(handle, message),
|
||||
onDispose: () => {
|
||||
const cleanUp = () => {
|
||||
this._webviews.delete(handle);
|
||||
};
|
||||
this._proxy.$onDidDisposeWebviewPanel(handle).then(
|
||||
() => this._webviews.delete(handle),
|
||||
() => this._webviews.delete(handle));
|
||||
cleanUp,
|
||||
cleanUp);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private getWebview(handle: WebviewPanelHandle): WebviewEditorInput {
|
||||
const webview = this._webviews.get(handle);
|
||||
if (!webview) {
|
||||
throw new Error('Unknown webview handle:' + handle);
|
||||
}
|
||||
return webview;
|
||||
}
|
||||
|
||||
private onEditorsChanged() {
|
||||
const activeEditor = this._editorService.getActiveEditor();
|
||||
private onActiveEditorChanged() {
|
||||
const activeEditor = this._editorService.activeControl;
|
||||
let newActiveWebview: { input: WebviewEditorInput, handle: WebviewPanelHandle } | undefined = undefined;
|
||||
if (activeEditor && activeEditor.input instanceof WebviewEditorInput) {
|
||||
for (const handle of map.keys(this._webviews)) {
|
||||
@@ -217,7 +215,11 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv
|
||||
|
||||
if (newActiveWebview && newActiveWebview.handle === this._activeWebview) {
|
||||
// Webview itself unchanged but position may have changed
|
||||
this._proxy.$onDidChangeWebviewPanelViewState(newActiveWebview.handle, true, newActiveWebview.input.position);
|
||||
this._proxy.$onDidChangeWebviewPanelViewState(newActiveWebview.handle, {
|
||||
active: true,
|
||||
visible: true,
|
||||
position: editorGroupToViewColumn(this._editorGroupService, newActiveWebview.input.group)
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -225,33 +227,45 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv
|
||||
if (typeof this._activeWebview !== 'undefined') {
|
||||
const oldActiveWebview = this._webviews.get(this._activeWebview);
|
||||
if (oldActiveWebview) {
|
||||
this._proxy.$onDidChangeWebviewPanelViewState(this._activeWebview, false, oldActiveWebview.position);
|
||||
this._proxy.$onDidChangeWebviewPanelViewState(this._activeWebview, {
|
||||
active: false,
|
||||
visible: this._editorService.visibleControls.some(editor => editor.input && editor.input.matches(oldActiveWebview)),
|
||||
position: editorGroupToViewColumn(this._editorGroupService, oldActiveWebview.group),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Then for newly active
|
||||
if (newActiveWebview) {
|
||||
this._proxy.$onDidChangeWebviewPanelViewState(newActiveWebview.handle, true, activeEditor.position);
|
||||
this._proxy.$onDidChangeWebviewPanelViewState(newActiveWebview.handle, {
|
||||
active: true,
|
||||
visible: true,
|
||||
position: editorGroupToViewColumn(this._editorGroupService, activeEditor.group)
|
||||
});
|
||||
this._activeWebview = newActiveWebview.handle;
|
||||
} else {
|
||||
this._activeWebview = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private onEditorGroupMoved(): void {
|
||||
for (const workbenchEditor of this._editorService.getVisibleEditors()) {
|
||||
if (!workbenchEditor.input) {
|
||||
return;
|
||||
}
|
||||
private onVisibleEditorsChanged(): void {
|
||||
this._webviews.forEach((input, handle) => {
|
||||
for (const workbenchEditor of this._editorService.visibleControls) {
|
||||
if (workbenchEditor.input && workbenchEditor.input.matches(input)) {
|
||||
const editorPosition = editorGroupToViewColumn(this._editorGroupService, workbenchEditor.group);
|
||||
|
||||
this._webviews.forEach((input, handle) => {
|
||||
if (workbenchEditor.input.matches(input) && input.position !== workbenchEditor.position) {
|
||||
input.updatePosition(workbenchEditor.position);
|
||||
this._proxy.$onDidChangeWebviewPanelViewState(handle, handle === this._activeWebview, workbenchEditor.position);
|
||||
input.updateGroup(workbenchEditor.group.id);
|
||||
this._proxy.$onDidChangeWebviewPanelViewState(handle, {
|
||||
active: handle === this._activeWebview,
|
||||
visible: true,
|
||||
position: editorPosition
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private onDidClickLink(handle: WebviewPanelHandle, link: URI): void {
|
||||
if (!link) {
|
||||
return;
|
||||
@@ -264,6 +278,14 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv
|
||||
}
|
||||
}
|
||||
|
||||
private getWebview(handle: WebviewPanelHandle): WebviewEditorInput {
|
||||
const webview = this._webviews.get(handle);
|
||||
if (!webview) {
|
||||
throw new Error('Unknown webview handle:' + handle);
|
||||
}
|
||||
return webview;
|
||||
}
|
||||
|
||||
private static getDeserializationFailedContents(viewType: string) {
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
@@ -276,3 +298,23 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv
|
||||
</html>`;
|
||||
}
|
||||
}
|
||||
|
||||
function reviveWebviewOptions(options: WebviewInputOptions): WebviewInputOptions {
|
||||
return {
|
||||
...options,
|
||||
localResourceRoots: Array.isArray(options.localResourceRoots) ? options.localResourceRoots.map(URI.revive) : undefined
|
||||
};
|
||||
}
|
||||
|
||||
function reviveWebviewIcon(
|
||||
value: { light: UriComponents, dark: UriComponents } | undefined
|
||||
): { light: URI, dark: URI } | undefined {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
light: URI.revive(value.light),
|
||||
dark: URI.revive(value.dark)
|
||||
};
|
||||
}
|
||||
@@ -5,18 +5,24 @@
|
||||
'use strict';
|
||||
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { ISearchService, QueryType, ISearchQuery, IFolderQuery, ISearchConfiguration } from 'vs/platform/search/common/search';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { MainThreadWorkspaceShape, ExtHostWorkspaceShape, ExtHostContext, MainContext, IExtHostContext } from '../node/extHost.protocol';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IFileMatch, IFolderQuery, IPatternInfo, IQueryOptions, ISearchConfiguration, ISearchQuery, ISearchService, QueryType, ISearchProgressItem } from 'vs/platform/search/common/search';
|
||||
import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { QueryBuilder } from 'vs/workbench/parts/search/common/queryBuilder';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
|
||||
import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, MainContext, MainThreadWorkspaceShape } from '../node/extHost.protocol';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadWorkspace)
|
||||
export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
||||
@@ -32,7 +38,8 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
||||
@ITextFileService private readonly _textFileService: ITextFileService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@IWorkspaceEditingService private readonly _workspaceEditingService: IWorkspaceEditingService,
|
||||
@IStatusbarService private readonly _statusbarService: IStatusbarService
|
||||
@IStatusbarService private readonly _statusbarService: IStatusbarService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
) {
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostWorkspace);
|
||||
this._contextService.onDidChangeWorkspaceFolders(this._onDidChangeWorkspace, this, this._toDispose);
|
||||
@@ -97,7 +104,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
||||
|
||||
// --- search ---
|
||||
|
||||
$startSearch(includePattern: string, includeFolder: string, excludePatternOrDisregardExcludes: string | false, maxResults: number, requestId: number): Thenable<URI[]> {
|
||||
$startFileSearch(includePattern: string, includeFolder: string, excludePatternOrDisregardExcludes: string | false, maxResults: number, requestId: number): Thenable<URI[]> {
|
||||
const workspace = this._contextService.getWorkspace();
|
||||
if (!workspace.folders.length) {
|
||||
return undefined;
|
||||
@@ -128,12 +135,18 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
||||
folderQueries,
|
||||
type: QueryType.File,
|
||||
maxResults,
|
||||
includePattern: { [typeof includePattern === 'string' ? includePattern : undefined]: true },
|
||||
excludePattern: { [typeof excludePatternOrDisregardExcludes === 'string' ? excludePatternOrDisregardExcludes : undefined]: true },
|
||||
disregardExcludeSettings: excludePatternOrDisregardExcludes === false,
|
||||
useRipgrep,
|
||||
ignoreSymlinks
|
||||
};
|
||||
if (typeof includePattern === 'string') {
|
||||
query.includePattern = { [includePattern]: true };
|
||||
}
|
||||
|
||||
if (typeof excludePatternOrDisregardExcludes === 'string') {
|
||||
query.excludePattern = { [excludePatternOrDisregardExcludes]: true };
|
||||
}
|
||||
|
||||
this._searchService.extendQuery(query);
|
||||
|
||||
const search = this._searchService.search(query).then(result => {
|
||||
@@ -152,6 +165,38 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
||||
return search;
|
||||
}
|
||||
|
||||
$startTextSearch(pattern: IPatternInfo, options: IQueryOptions, requestId: number): TPromise<void, IFileMatch> {
|
||||
const workspace = this._contextService.getWorkspace();
|
||||
const folders = workspace.folders.map(folder => folder.uri);
|
||||
|
||||
const queryBuilder = this._instantiationService.createInstance(QueryBuilder);
|
||||
const query = queryBuilder.text(pattern, folders, options);
|
||||
|
||||
const onProgress = (p: ISearchProgressItem) => {
|
||||
if (p.lineMatches) {
|
||||
this._proxy.$handleTextSearchResult(p, requestId);
|
||||
}
|
||||
};
|
||||
|
||||
const search = this._searchService.search(query, onProgress).then(
|
||||
() => {
|
||||
delete this._activeSearches[requestId];
|
||||
return null;
|
||||
},
|
||||
err => {
|
||||
delete this._activeSearches[requestId];
|
||||
if (!isPromiseCanceledError(err)) {
|
||||
return TPromise.wrapError(err);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
|
||||
this._activeSearches[requestId] = search;
|
||||
|
||||
return search;
|
||||
}
|
||||
|
||||
$cancelSearch(requestId: number): Thenable<boolean> {
|
||||
const search = this._activeSearches[requestId];
|
||||
if (search) {
|
||||
@@ -170,3 +215,19 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
CommandsRegistry.registerCommand('_workbench.enterWorkspace', async function (accessor: ServicesAccessor, workspace: URI, disableExtensions: string[]) {
|
||||
const workspaceEditingService = accessor.get(IWorkspaceEditingService);
|
||||
const extensionService = accessor.get(IExtensionService);
|
||||
const windowService = accessor.get(IWindowService);
|
||||
|
||||
if (disableExtensions && disableExtensions.length) {
|
||||
const runningExtensions = await extensionService.getExtensions();
|
||||
// If requested extension to disable is running, then reload window with given workspace
|
||||
if (disableExtensions && runningExtensions.some(runningExtension => disableExtensions.some(id => areSameExtensions({ id }, { id: runningExtension.id })))) {
|
||||
return windowService.openWindow([URI.file(workspace.fsPath)], { args: { _: [], 'disable-extension': disableExtensions } });
|
||||
}
|
||||
}
|
||||
|
||||
return workspaceEditingService.enterWorkspace(workspace.fsPath);
|
||||
});
|
||||
@@ -8,7 +8,10 @@ import URI from 'vs/base/common/uri';
|
||||
import * as vscode from 'vscode';
|
||||
import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { CommandsRegistry, ICommandService, ICommandHandler } from 'vs/platform/commands/common/commands';
|
||||
import { Position as EditorPosition, ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { EditorViewColumn } from 'vs/workbench/api/shared/editor';
|
||||
import { EditorGroupLayout } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// The following commands are registered on both sides separately.
|
||||
@@ -32,7 +35,7 @@ export class PreviewHTMLAPICommand {
|
||||
public static execute(executor: ICommandsExecutor, uri: URI, position?: vscode.ViewColumn, label?: string, options?: any): Thenable<any> {
|
||||
return executor.executeCommand('_workbench.previewHtml',
|
||||
uri,
|
||||
typeof position === 'number' && typeConverters.fromViewColumn(position),
|
||||
typeof position === 'number' && typeConverters.ViewColumn.from(position),
|
||||
label,
|
||||
options
|
||||
);
|
||||
@@ -46,8 +49,13 @@ export class OpenFolderAPICommand {
|
||||
if (!uri) {
|
||||
return executor.executeCommand('_files.pickFolderAndOpen', forceNewWindow);
|
||||
}
|
||||
if (!uri.scheme || isWindows && uri.scheme.match(/^[a-zA-Z]$/)) {
|
||||
// workaround for #55916 and #55891, will be removed in 1.28
|
||||
console.warn(`'vscode.openFolder' command invoked with an invalid URI (file:// scheme missing): '${uri}'. Converted to a 'file://' URI.`);
|
||||
uri = URI.file((uri.scheme ? uri.scheme + ':' : '') + uri.path);
|
||||
}
|
||||
|
||||
return executor.executeCommand('_files.windowOpen', [uri.fsPath], forceNewWindow);
|
||||
return executor.executeCommand('_files.windowOpen', [uri], forceNewWindow);
|
||||
}
|
||||
}
|
||||
CommandsRegistry.registerCommand(OpenFolderAPICommand.ID, adjustHandler(OpenFolderAPICommand.execute));
|
||||
@@ -59,8 +67,8 @@ export class DiffAPICommand {
|
||||
left, right,
|
||||
label,
|
||||
undefined,
|
||||
typeConverters.toTextEditorOptions(options),
|
||||
options ? typeConverters.fromViewColumn(options.viewColumn) : undefined
|
||||
typeConverters.TextEditorOptions.from(options),
|
||||
options ? typeConverters.ViewColumn.from(options.viewColumn) : undefined
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -70,21 +78,21 @@ export class OpenAPICommand {
|
||||
public static ID = 'vscode.open';
|
||||
public static execute(executor: ICommandsExecutor, resource: URI, columnOrOptions?: vscode.ViewColumn | vscode.TextDocumentShowOptions): Thenable<any> {
|
||||
let options: ITextEditorOptions;
|
||||
let column: EditorPosition;
|
||||
let position: EditorViewColumn;
|
||||
|
||||
if (columnOrOptions) {
|
||||
if (typeof columnOrOptions === 'number') {
|
||||
column = typeConverters.fromViewColumn(columnOrOptions);
|
||||
position = typeConverters.ViewColumn.from(columnOrOptions);
|
||||
} else {
|
||||
options = typeConverters.toTextEditorOptions(columnOrOptions);
|
||||
column = typeConverters.fromViewColumn(columnOrOptions.viewColumn);
|
||||
options = typeConverters.TextEditorOptions.from(columnOrOptions);
|
||||
position = typeConverters.ViewColumn.from(columnOrOptions.viewColumn);
|
||||
}
|
||||
}
|
||||
|
||||
return executor.executeCommand('_workbench.open', [
|
||||
resource,
|
||||
options,
|
||||
column
|
||||
position
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -97,3 +105,11 @@ export class RemoveFromRecentlyOpenedAPICommand {
|
||||
}
|
||||
}
|
||||
CommandsRegistry.registerCommand(RemoveFromRecentlyOpenedAPICommand.ID, adjustHandler(RemoveFromRecentlyOpenedAPICommand.execute));
|
||||
|
||||
export class SetEditorLayoutAPICommand {
|
||||
public static ID = 'vscode.setEditorLayout';
|
||||
public static execute(executor: ICommandsExecutor, layout: EditorGroupLayout): Thenable<any> {
|
||||
return executor.executeCommand('layoutEditorGroups', layout);
|
||||
}
|
||||
}
|
||||
CommandsRegistry.registerCommand(SetEditorLayoutAPICommand.ID, adjustHandler(SetEditorLayoutAPICommand.execute));
|
||||
@@ -7,7 +7,7 @@
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { score } from 'vs/editor/common/modes/languageSelector';
|
||||
import * as Platform from 'vs/base/common/platform';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import product from 'vs/platform/node/product';
|
||||
import pkg from 'vs/platform/node/package';
|
||||
@@ -31,7 +31,7 @@ import { ExtHostTerminalService } from 'vs/workbench/api/node/extHostTerminalSer
|
||||
import { ExtHostMessageService } from 'vs/workbench/api/node/extHostMessageService';
|
||||
import { ExtHostEditors } from 'vs/workbench/api/node/extHostTextEditors';
|
||||
import { ExtHostLanguages } from 'vs/workbench/api/node/extHostLanguages';
|
||||
import { ExtHostLanguageFeatures } from 'vs/workbench/api/node/extHostLanguageFeatures';
|
||||
import { ExtHostLanguageFeatures, ISchemeTransformer } from 'vs/workbench/api/node/extHostLanguageFeatures';
|
||||
import { ExtHostApiCommands } from 'vs/workbench/api/node/extHostApiCommands';
|
||||
import { ExtHostTask } from 'vs/workbench/api/node/extHostTask';
|
||||
// {{SQL CARBON EDIT}}
|
||||
@@ -47,18 +47,19 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import * as vscode from 'vscode';
|
||||
import * as paths from 'vs/base/common/paths';
|
||||
import * as files from 'vs/platform/files/common/files';
|
||||
import { MainContext, ExtHostContext, IInitData, IExtHostContext } from './extHost.protocol';
|
||||
import { MainContext, ExtHostContext, IInitData, IMainContext } from './extHost.protocol';
|
||||
import * as languageConfiguration from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions';
|
||||
import { ProxyIdentifier } from 'vs/workbench/services/extensions/node/proxyIdentifier';
|
||||
import { ExtHostDialogs } from 'vs/workbench/api/node/extHostDialogs';
|
||||
import { ExtHostFileSystem } from 'vs/workbench/api/node/extHostFileSystem';
|
||||
import { ExtHostDecorations } from 'vs/workbench/api/node/extHostDecorations';
|
||||
import { toGlobPattern, toLanguageSelector } from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { ExtensionActivatedByAPI } from 'vs/workbench/api/node/extHostExtensionActivator';
|
||||
import { OverviewRulerLane } from 'vs/editor/common/model';
|
||||
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
|
||||
import { ExtHostWebviews } from 'vs/workbench/api/node/extHostWebview';
|
||||
import { ExtHostComments } from './extHostComments';
|
||||
import { ExtHostSearch } from './extHostSearch';
|
||||
import { ExtHostUrls } from './extHostUrls';
|
||||
|
||||
@@ -80,7 +81,7 @@ function proposedApiFunction<T>(extension: IExtensionDescription, fn: T): T {
|
||||
if (extension.enableProposedApi) {
|
||||
return fn;
|
||||
} else {
|
||||
return <any>throwProposedApiError;
|
||||
return throwProposedApiError.bind(null, extension);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,13 +90,15 @@ function proposedApiFunction<T>(extension: IExtensionDescription, fn: T): T {
|
||||
*/
|
||||
export function createApiFactory(
|
||||
initData: IInitData,
|
||||
rpcProtocol: IExtHostContext,
|
||||
rpcProtocol: IMainContext,
|
||||
extHostWorkspace: ExtHostWorkspace,
|
||||
extHostConfiguration: ExtHostConfiguration,
|
||||
extensionService: ExtHostExtensionService,
|
||||
extHostLogService: ExtHostLogService
|
||||
): IExtensionApiFactory {
|
||||
|
||||
let schemeTransformer: ISchemeTransformer = null;
|
||||
|
||||
// Addressable instances
|
||||
rpcProtocol.set(ExtHostContext.ExtHostLogService, extHostLogService);
|
||||
const extHostHeapService = rpcProtocol.set(ExtHostContext.ExtHostHeapService, new ExtHostHeapService());
|
||||
@@ -108,26 +111,27 @@ export function createApiFactory(
|
||||
const extHostDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(extHostLogService, extHostDocuments, rpcProtocol.getProxy(MainContext.MainThreadTextEditors)));
|
||||
const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors));
|
||||
const extHostCommands = rpcProtocol.set(ExtHostContext.ExtHostCommands, new ExtHostCommands(rpcProtocol, extHostHeapService, extHostLogService));
|
||||
const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands));
|
||||
const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService));
|
||||
rpcProtocol.set(ExtHostContext.ExtHostWorkspace, extHostWorkspace);
|
||||
// {{SQL CARBON EDIT}}
|
||||
//const extHostDebugService = rpcProtocol.set(ExtHostContext.ExtHostDebugService, new ExtHostDebugService(threadService, extHostWorkspace));
|
||||
rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration);
|
||||
const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol));
|
||||
const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, null, extHostDocuments, extHostCommands, extHostHeapService, extHostDiagnostics));
|
||||
const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, schemeTransformer, extHostDocuments, extHostCommands, extHostHeapService, extHostDiagnostics, extHostLogService));
|
||||
const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures));
|
||||
const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService());
|
||||
const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpcProtocol, extHostDocumentsAndEditors));
|
||||
const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, new ExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands));
|
||||
const extHostTerminalService = rpcProtocol.set(ExtHostContext.ExtHostTerminalService, new ExtHostTerminalService(rpcProtocol, extHostConfiguration, extHostLogService));
|
||||
// {{SQL CARBON EDIT}}
|
||||
//const extHostDebugService = rpcProtocol.set(ExtHostContext.ExtHostDebugService, new ExtHostDebugService(rpcProtocol, extHostWorkspace, extensionService, extHostDocumentsAndEditors, extHostConfiguration, extHostTerminalService));
|
||||
const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, extHostLogService));
|
||||
const extHostSearch = rpcProtocol.set(ExtHostContext.ExtHostSearch, new ExtHostSearch(rpcProtocol));
|
||||
const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, new ExtHostTask(rpcProtocol, extHostWorkspace));
|
||||
const extHostSearch = rpcProtocol.set(ExtHostContext.ExtHostSearch, new ExtHostSearch(rpcProtocol, schemeTransformer));
|
||||
const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, new ExtHostTask(rpcProtocol, extHostWorkspace, extHostDocumentsAndEditors, extHostConfiguration));
|
||||
const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol));
|
||||
rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService);
|
||||
const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress)));
|
||||
const exthostCommentProviders = rpcProtocol.set(ExtHostContext.ExtHostComments, new ExtHostComments(rpcProtocol, extHostCommands.converter, extHostDocuments));
|
||||
|
||||
// Check that no named customers are missing
|
||||
const expected: ProxyIdentifier<any>[] = Object.keys(ExtHostContext).map((key) => ExtHostContext[key]);
|
||||
const expected: ProxyIdentifier<any>[] = Object.keys(ExtHostContext).map((key) => (<any>ExtHostContext)[key]);
|
||||
rpcProtocol.assertRegistered(expected);
|
||||
|
||||
// Other instances
|
||||
@@ -138,7 +142,7 @@ export function createApiFactory(
|
||||
const extHostLanguages = new ExtHostLanguages(rpcProtocol);
|
||||
|
||||
// Register API-ish commands
|
||||
ExtHostApiCommands.register(extHostCommands, extHostTask);
|
||||
ExtHostApiCommands.register(extHostCommands);
|
||||
|
||||
return function (extension: IExtensionDescription): typeof vscode {
|
||||
|
||||
@@ -148,7 +152,7 @@ export function createApiFactory(
|
||||
// We only inform once, it is not a warning because we just want to raise awareness and because
|
||||
// we cannot say if the extension is doing it right or wrong...
|
||||
let checkSelector = (function () {
|
||||
let done = initData.environment.extensionDevelopmentPath !== extension.extensionFolderPath;
|
||||
let done = (!extension.isUnderDevelopment);
|
||||
function informOnce(selector: vscode.DocumentSelector) {
|
||||
if (!done) {
|
||||
console.info(`Extension '${extension.id}' uses a document selector without scheme. Learn more about this: https://go.microsoft.com/fwlink/?linkid=872305`);
|
||||
@@ -222,7 +226,7 @@ export function createApiFactory(
|
||||
const env: typeof vscode.env = Object.freeze({
|
||||
get machineId() { return initData.telemetryInfo.machineId; },
|
||||
get sessionId() { return initData.telemetryInfo.sessionId; },
|
||||
get language() { return Platform.language; },
|
||||
get language() { return platform.language; },
|
||||
get appName() { return product.nameLong; },
|
||||
get appRoot() { return initData.environment.appRoot; },
|
||||
get logLevel() { return extHostLogService.getLevel(); }
|
||||
@@ -250,17 +254,17 @@ export function createApiFactory(
|
||||
get onDidChangeDiagnostics() {
|
||||
return extHostDiagnostics.onDidChangeDiagnostics;
|
||||
},
|
||||
getDiagnostics: (resource?) => {
|
||||
getDiagnostics: (resource?: vscode.Uri) => {
|
||||
return <any>extHostDiagnostics.getDiagnostics(resource);
|
||||
},
|
||||
getLanguages(): TPromise<string[]> {
|
||||
return extHostLanguages.getLanguages();
|
||||
},
|
||||
match(selector: vscode.DocumentSelector, document: vscode.TextDocument): number {
|
||||
return score(toLanguageSelector(selector), document.uri, document.languageId, true);
|
||||
return score(typeConverters.LanguageSelector.from(selector), document.uri, document.languageId, true);
|
||||
},
|
||||
registerCodeActionsProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider, metadata?: vscode.CodeActionProviderMetadata): vscode.Disposable {
|
||||
return extHostLanguageFeatures.registerCodeActionProvider(checkSelector(selector), provider, metadata);
|
||||
return extHostLanguageFeatures.registerCodeActionProvider(checkSelector(selector), provider, extension, metadata);
|
||||
},
|
||||
registerCodeLensProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable {
|
||||
return extHostLanguageFeatures.registerCodeLensProvider(checkSelector(selector), provider);
|
||||
@@ -287,7 +291,7 @@ export function createApiFactory(
|
||||
return extHostLanguageFeatures.registerRenameProvider(checkSelector(selector), provider);
|
||||
},
|
||||
registerDocumentSymbolProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentSymbolProvider): vscode.Disposable {
|
||||
return extHostLanguageFeatures.registerDocumentSymbolProvider(checkSelector(selector), provider);
|
||||
return extHostLanguageFeatures.registerDocumentSymbolProvider(checkSelector(selector), provider, extension);
|
||||
},
|
||||
registerWorkspaceSymbolProvider(provider: vscode.WorkspaceSymbolProvider): vscode.Disposable {
|
||||
return extHostLanguageFeatures.registerWorkspaceSymbolProvider(provider);
|
||||
@@ -329,8 +333,11 @@ export function createApiFactory(
|
||||
get visibleTextEditors() {
|
||||
return extHostEditors.getVisibleTextEditors();
|
||||
},
|
||||
get activeTerminal() {
|
||||
return proposedApiFunction(extension, extHostTerminalService.activeTerminal);
|
||||
},
|
||||
get terminals() {
|
||||
return proposedApiFunction(extension, extHostTerminalService.terminals);
|
||||
return extHostTerminalService.terminals;
|
||||
},
|
||||
showTextDocument(documentOrUri: vscode.TextDocument | vscode.Uri, columnOrOptions?: vscode.ViewColumn | vscode.TextDocumentShowOptions, preserveFocus?: boolean): TPromise<vscode.TextEditor> {
|
||||
let documentPromise: TPromise<vscode.TextDocument>;
|
||||
@@ -367,8 +374,11 @@ export function createApiFactory(
|
||||
onDidCloseTerminal(listener, thisArg?, disposables?) {
|
||||
return extHostTerminalService.onDidCloseTerminal(listener, thisArg, disposables);
|
||||
},
|
||||
onDidOpenTerminal: proposedApiFunction(extension, (listener, thisArg?, disposables?) => {
|
||||
onDidOpenTerminal(listener, thisArg?, disposables?) {
|
||||
return extHostTerminalService.onDidOpenTerminal(listener, thisArg, disposables);
|
||||
},
|
||||
onDidChangeActiveTerminal: proposedApiFunction(extension, (listener, thisArg?, disposables?) => {
|
||||
return extHostTerminalService.onDidChangeActiveTerminal(listener, thisArg, disposables);
|
||||
}),
|
||||
get state() {
|
||||
return extHostWindow.state;
|
||||
@@ -416,8 +426,8 @@ export function createApiFactory(
|
||||
createOutputChannel(name: string): vscode.OutputChannel {
|
||||
return extHostOutputService.createOutputChannel(name);
|
||||
},
|
||||
createWebviewPanel(viewType: string, title: string, column: vscode.ViewColumn, options: vscode.WebviewPanelOptions & vscode.WebviewOptions): vscode.WebviewPanel {
|
||||
return extHostWebviews.createWebview(viewType, title, column, options, extension.extensionFolderPath);
|
||||
createWebviewPanel(viewType: string, title: string, showOptions: vscode.ViewColumn | { viewColumn: vscode.ViewColumn, preserveFocus?: boolean }, options: vscode.WebviewPanelOptions & vscode.WebviewOptions): vscode.WebviewPanel {
|
||||
return extHostWebviews.createWebview(extension.extensionLocation, viewType, title, showOptions, options);
|
||||
},
|
||||
createTerminal(nameOrOptions: vscode.TerminalOptions | string, shellPath?: string, shellArgs?: string[]): vscode.Terminal {
|
||||
if (typeof nameOrOptions === 'object') {
|
||||
@@ -425,12 +435,18 @@ export function createApiFactory(
|
||||
}
|
||||
return extHostTerminalService.createTerminal(<string>nameOrOptions, shellPath, shellArgs);
|
||||
},
|
||||
createTerminalRenderer(name: string): vscode.TerminalRenderer {
|
||||
return extHostTerminalService.createTerminalRenderer(name);
|
||||
},
|
||||
registerTreeDataProvider(viewId: string, treeDataProvider: vscode.TreeDataProvider<any>): vscode.Disposable {
|
||||
return extHostTreeViews.registerTreeDataProvider(viewId, treeDataProvider);
|
||||
},
|
||||
createTreeView(viewId: string, options: { treeDataProvider: vscode.TreeDataProvider<any> }): vscode.TreeView<any> {
|
||||
return extHostTreeViews.createTreeView(viewId, options);
|
||||
},
|
||||
registerWebviewPanelSerializer: (viewType: string, serializer: vscode.WebviewPanelSerializer) => {
|
||||
return extHostWebviews.registerWebviewPanelSerializer(viewType, serializer);
|
||||
},
|
||||
// proposed API
|
||||
sampleFunction: proposedApiFunction(extension, () => {
|
||||
return extHostMessageService.showMessage(extension, Severity.Info, 'Hello Proposed Api!', {}, []);
|
||||
@@ -438,12 +454,15 @@ export function createApiFactory(
|
||||
registerDecorationProvider: proposedApiFunction(extension, (provider: vscode.DecorationProvider) => {
|
||||
return extHostDecorations.registerDecorationProvider(provider, extension.id);
|
||||
}),
|
||||
registerWebviewPanelSerializer: proposedApiFunction(extension, (viewType: string, serializer: vscode.WebviewPanelSerializer) => {
|
||||
return extHostWebviews.registerWebviewPanelSerializer(viewType, serializer);
|
||||
}),
|
||||
registerProtocolHandler: proposedApiFunction(extension, (handler: vscode.ProtocolHandler) => {
|
||||
return extHostUrls.registerProtocolHandler(extension.id, handler);
|
||||
})
|
||||
registerUriHandler(handler: vscode.UriHandler) {
|
||||
return extHostUrls.registerUriHandler(extension.id, handler);
|
||||
},
|
||||
createQuickPick<T extends vscode.QuickPickItem>(): vscode.QuickPick<T> {
|
||||
return extHostQuickOpen.createQuickPick(extension.id);
|
||||
},
|
||||
createInputBox(): vscode.InputBox {
|
||||
return extHostQuickOpen.createInputBox(extension.id);
|
||||
},
|
||||
};
|
||||
|
||||
// namespace: workspace
|
||||
@@ -476,7 +495,22 @@ export function createApiFactory(
|
||||
return extHostWorkspace.getRelativePath(pathOrUri, includeWorkspace);
|
||||
},
|
||||
findFiles: (include, exclude, maxResults?, token?) => {
|
||||
return extHostWorkspace.findFiles(toGlobPattern(include), toGlobPattern(exclude), maxResults, extension.id, token);
|
||||
return extHostWorkspace.findFiles(typeConverters.GlobPattern.from(include), typeConverters.GlobPattern.from(exclude), maxResults, extension.id, token);
|
||||
},
|
||||
findTextInFiles: (query: vscode.TextSearchQuery, optionsOrCallback, callbackOrToken?, token?: vscode.CancellationToken) => {
|
||||
let options: vscode.FindTextInFilesOptions;
|
||||
let callback: (result: vscode.TextSearchResult) => void;
|
||||
|
||||
if (typeof optionsOrCallback === 'object') {
|
||||
options = optionsOrCallback;
|
||||
callback = callbackOrToken;
|
||||
} else {
|
||||
options = {};
|
||||
callback = optionsOrCallback;
|
||||
token = callbackOrToken;
|
||||
}
|
||||
|
||||
return extHostWorkspace.findTextInFiles(query, options || {}, callback, extension.id, token);
|
||||
},
|
||||
saveAll: (includeUntitled?) => {
|
||||
return extHostWorkspace.saveAll(includeUntitled);
|
||||
@@ -485,7 +519,7 @@ export function createApiFactory(
|
||||
return extHostEditors.applyWorkspaceEdit(edit);
|
||||
},
|
||||
createFileSystemWatcher: (pattern, ignoreCreate, ignoreChange, ignoreDelete): vscode.FileSystemWatcher => {
|
||||
return extHostFileSystemEvent.createFileSystemWatcher(toGlobPattern(pattern), ignoreCreate, ignoreChange, ignoreDelete);
|
||||
return extHostFileSystemEvent.createFileSystemWatcher(typeConverters.GlobPattern.from(pattern), ignoreCreate, ignoreChange, ignoreDelete);
|
||||
},
|
||||
get textDocuments() {
|
||||
return extHostDocuments.getAllDocumentData().map(data => data.document);
|
||||
@@ -542,29 +576,33 @@ export function createApiFactory(
|
||||
registerTaskProvider: (type: string, provider: vscode.TaskProvider) => {
|
||||
return extHostTask.registerTaskProvider(extension, provider);
|
||||
},
|
||||
fetchTasks: proposedApiFunction(extension, (filter?: vscode.TaskFilter): Thenable<vscode.Task[]> => {
|
||||
return extHostTask.fetchTasks(filter);
|
||||
}),
|
||||
executeTask: proposedApiFunction(extension, (task: vscode.Task): Thenable<vscode.TaskExecution> => {
|
||||
return extHostTask.executeTask(extension, task);
|
||||
}),
|
||||
get taskExecutions(): vscode.TaskExecution[] {
|
||||
return extHostTask.taskExecutions;
|
||||
},
|
||||
onDidStartTask: (listeners, thisArgs?, disposables?) => {
|
||||
return extHostTask.onDidStartTask(listeners, thisArgs, disposables);
|
||||
},
|
||||
onDidEndTask: (listeners, thisArgs?, disposables?) => {
|
||||
return extHostTask.onDidEndTask(listeners, thisArgs, disposables);
|
||||
},
|
||||
registerFileSystemProvider(scheme, provider, options) {
|
||||
return extHostFileSystem.registerFileSystemProvider(scheme, provider, options);
|
||||
},
|
||||
registerDeprecatedFileSystemProvider: proposedApiFunction(extension, (scheme, provider) => {
|
||||
return extHostFileSystem.registerDeprecatedFileSystemProvider(scheme, provider);
|
||||
registerFileSearchProvider: proposedApiFunction(extension, (scheme, provider) => {
|
||||
return extHostSearch.registerFileSearchProvider(scheme, provider);
|
||||
}),
|
||||
registerSearchProvider: proposedApiFunction(extension, (scheme, provider) => {
|
||||
return extHostSearch.registerSearchProvider(scheme, provider);
|
||||
registerSearchProvider: proposedApiFunction(extension, () => {
|
||||
// Temp for live share in Insiders
|
||||
return { dispose: () => { } };
|
||||
}),
|
||||
registerTextSearchProvider: proposedApiFunction(extension, (scheme, provider) => {
|
||||
return extHostSearch.registerTextSearchProvider(scheme, provider);
|
||||
}),
|
||||
registerFileIndexProvider: proposedApiFunction(extension, (scheme, provider) => {
|
||||
return extHostSearch.registerFileIndexProvider(scheme, provider);
|
||||
}),
|
||||
registerDocumentCommentProvider: proposedApiFunction(extension, (provider: vscode.DocumentCommentProvider) => {
|
||||
return exthostCommentProviders.registerDocumentCommentProvider(provider);
|
||||
}),
|
||||
registerWorkspaceCommentProvider: proposedApiFunction(extension, (provider: vscode.WorkspaceCommentProvider) => {
|
||||
return exthostCommentProviders.registerWorkspaceCommentProvider(provider);
|
||||
}),
|
||||
onDidRenameFile: proposedApiFunction(extension, (listener, thisArg?, disposables?) => {
|
||||
return extHostFileSystemEvent.onDidRenameFile(listener, thisArg, disposables);
|
||||
}),
|
||||
onWillRenameFile: proposedApiFunction(extension, (listener, thisArg?, disposables?) => {
|
||||
return extHostFileSystemEvent.getOnWillRenameFileEvent(extension)(listener, thisArg, disposables);
|
||||
})
|
||||
};
|
||||
|
||||
@@ -580,6 +618,32 @@ export function createApiFactory(
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
// delete namespace: debug
|
||||
const tasks: typeof vscode.tasks = {
|
||||
registerTaskProvider: (type: string, provider: vscode.TaskProvider) => {
|
||||
return extHostTask.registerTaskProvider(extension, provider);
|
||||
},
|
||||
fetchTasks: (filter?: vscode.TaskFilter): Thenable<vscode.Task[]> => {
|
||||
return extHostTask.fetchTasks(filter);
|
||||
},
|
||||
executeTask: (task: vscode.Task): Thenable<vscode.TaskExecution> => {
|
||||
return extHostTask.executeTask(extension, task);
|
||||
},
|
||||
get taskExecutions(): vscode.TaskExecution[] {
|
||||
return extHostTask.taskExecutions;
|
||||
},
|
||||
onDidStartTask: (listeners, thisArgs?, disposables?) => {
|
||||
return extHostTask.onDidStartTask(listeners, thisArgs, disposables);
|
||||
},
|
||||
onDidEndTask: (listeners, thisArgs?, disposables?) => {
|
||||
return extHostTask.onDidEndTask(listeners, thisArgs, disposables);
|
||||
},
|
||||
onDidStartTaskProcess: (listeners, thisArgs?, disposables?) => {
|
||||
return extHostTask.onDidStartTaskProcess(listeners, thisArgs, disposables);
|
||||
},
|
||||
onDidEndTaskProcess: (listeners, thisArgs?, disposables?) => {
|
||||
return extHostTask.onDidEndTaskProcess(listeners, thisArgs, disposables);
|
||||
}
|
||||
};
|
||||
|
||||
return <typeof vscode>{
|
||||
version: pkg.version,
|
||||
@@ -593,6 +657,7 @@ export function createApiFactory(
|
||||
scm,
|
||||
// {{SQL CARBON EDIT}}
|
||||
// debug,
|
||||
tasks,
|
||||
// types
|
||||
Breakpoint: extHostTypes.Breakpoint,
|
||||
CancellationTokenSource: CancellationTokenSource,
|
||||
@@ -602,6 +667,7 @@ export function createApiFactory(
|
||||
Color: extHostTypes.Color,
|
||||
ColorPresentation: extHostTypes.ColorPresentation,
|
||||
ColorInformation: extHostTypes.ColorInformation,
|
||||
CodeActionTrigger: extHostTypes.CodeActionTrigger,
|
||||
EndOfLine: extHostTypes.EndOfLine,
|
||||
CompletionItem: extHostTypes.CompletionItem,
|
||||
CompletionItemKind: extHostTypes.CompletionItemKind,
|
||||
@@ -611,6 +677,7 @@ export function createApiFactory(
|
||||
// DebugAdapterExecutable: extHostTypes.DebugAdapterExecutable,
|
||||
Diagnostic: extHostTypes.Diagnostic,
|
||||
DiagnosticRelatedInformation: extHostTypes.DiagnosticRelatedInformation,
|
||||
DiagnosticTag: extHostTypes.DiagnosticTag,
|
||||
DiagnosticSeverity: extHostTypes.DiagnosticSeverity,
|
||||
Disposable: extHostTypes.Disposable,
|
||||
DocumentHighlight: extHostTypes.DocumentHighlight,
|
||||
@@ -626,6 +693,7 @@ export function createApiFactory(
|
||||
OverviewRulerLane: OverviewRulerLane,
|
||||
ParameterInformation: extHostTypes.ParameterInformation,
|
||||
Position: extHostTypes.Position,
|
||||
QuickInputButtons: extHostTypes.QuickInputButtons,
|
||||
Range: extHostTypes.Range,
|
||||
Selection: extHostTypes.Selection,
|
||||
SignatureHelp: extHostTypes.SignatureHelp,
|
||||
@@ -634,12 +702,7 @@ export function createApiFactory(
|
||||
SourceBreakpoint: extHostTypes.SourceBreakpoint,
|
||||
StatusBarAlignment: extHostTypes.StatusBarAlignment,
|
||||
SymbolInformation: extHostTypes.SymbolInformation,
|
||||
HierarchicalSymbolInformation: class extends extHostTypes.HierarchicalSymbolInformation {
|
||||
constructor(name, kind, keyof, range) {
|
||||
checkProposedApiEnabled(extension);
|
||||
super(name, kind, keyof, range);
|
||||
}
|
||||
},
|
||||
DocumentSymbol: extHostTypes.DocumentSymbol,
|
||||
SymbolKind: extHostTypes.SymbolKind,
|
||||
SourceControlInputBoxValidationType: extHostTypes.SourceControlInputBoxValidationType,
|
||||
TextDocumentSaveReason: extHostTypes.TextDocumentSaveReason,
|
||||
@@ -669,17 +732,29 @@ export function createApiFactory(
|
||||
ConfigurationTarget: extHostTypes.ConfigurationTarget,
|
||||
RelativePattern: extHostTypes.RelativePattern,
|
||||
|
||||
DeprecatedFileChangeType: extHostTypes.DeprecatedFileChangeType,
|
||||
DeprecatedFileType: extHostTypes.DeprecatedFileType,
|
||||
FileChangeType: extHostTypes.FileChangeType,
|
||||
FileType: files.FileType,
|
||||
FileSystemError: extHostTypes.FileSystemError,
|
||||
FoldingRange: extHostTypes.FoldingRange,
|
||||
FoldingRangeKind: extHostTypes.FoldingRangeKind
|
||||
FoldingRangeKind: extHostTypes.FoldingRangeKind,
|
||||
|
||||
CommentThreadCollapsibleState: extHostTypes.CommentThreadCollapsibleState
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the original fs path (using the original casing for the drive letter)
|
||||
*/
|
||||
export function originalFSPath(uri: URI): string {
|
||||
const result = uri.fsPath;
|
||||
if (/^[a-zA-Z]:/.test(result) && uri.path.charAt(1).toLowerCase() === result.charAt(0)) {
|
||||
// Restore original drive letter casing
|
||||
return uri.path.charAt(1) + result.substr(1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
class Extension<T> implements vscode.Extension<T> {
|
||||
|
||||
private _extensionService: ExtHostExtensionService;
|
||||
@@ -691,7 +766,7 @@ class Extension<T> implements vscode.Extension<T> {
|
||||
constructor(extensionService: ExtHostExtensionService, description: IExtensionDescription) {
|
||||
this._extensionService = extensionService;
|
||||
this.id = description.id;
|
||||
this.extensionPath = paths.normalize(description.extensionFolderPath, true);
|
||||
this.extensionPath = paths.normalize(originalFSPath(description.extensionLocation), true);
|
||||
this.packageJSON = description;
|
||||
}
|
||||
|
||||
@@ -720,13 +795,13 @@ function defineAPI(factory: IExtensionApiFactory, extensionPaths: TernarySearchT
|
||||
|
||||
const node_module = <any>require.__$__nodeRequire('module');
|
||||
const original = node_module._load;
|
||||
node_module._load = function load(request, parent, isMain) {
|
||||
node_module._load = function load(request: string, parent: any, isMain: any) {
|
||||
if (request !== 'vscode') {
|
||||
return original.apply(this, arguments);
|
||||
}
|
||||
|
||||
// get extension id from filename and api for extension
|
||||
const ext = extensionPaths.findSubstr(parent.filename);
|
||||
const ext = extensionPaths.findSubstr(URI.file(parent.filename).fsPath);
|
||||
if (ext) {
|
||||
let apiImpl = extApiImpl.get(ext.id);
|
||||
if (!apiImpl) {
|
||||
@@ -738,6 +813,9 @@ function defineAPI(factory: IExtensionApiFactory, extensionPaths: TernarySearchT
|
||||
|
||||
// fall back to a default implementation
|
||||
if (!defaultApiImpl) {
|
||||
let extensionPathsPretty = '';
|
||||
extensionPaths.forEach((value, index) => extensionPathsPretty += `\t${index} -> ${value.id}\n`);
|
||||
console.warn(`Could not identify extension for 'vscode' require call from ${parent.filename}. These are the extension path mappings: \n${extensionPathsPretty}`);
|
||||
defaultApiImpl = factory(nullExtensionDescription);
|
||||
}
|
||||
return defaultApiImpl;
|
||||
@@ -753,8 +831,9 @@ const nullExtensionDescription: IExtensionDescription = {
|
||||
enableProposedApi: false,
|
||||
engines: undefined,
|
||||
extensionDependencies: undefined,
|
||||
extensionFolderPath: undefined,
|
||||
extensionLocation: undefined,
|
||||
isBuiltin: false,
|
||||
isUnderDevelopment: false,
|
||||
main: undefined,
|
||||
version: undefined
|
||||
};
|
||||
|
||||
@@ -4,57 +4,48 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { createMainContextProxyIdentifier as createMainId, createExtHostContextProxyIdentifier as createExtId, ProxyIdentifier, IRPCProtocol } from 'vs/workbench/services/extensions/node/proxyIdentifier';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { SerializedError } from 'vs/base/common/errors';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
import { IMarkerData } from 'vs/platform/markers/common/markers';
|
||||
import { Position as EditorPosition } from 'vs/platform/editor/common/editor';
|
||||
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
|
||||
import { IProgressOptions, IProgressStep } from 'vs/platform/progress/common/progress';
|
||||
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
|
||||
import { IConfigurationData, ConfigurationTarget, IConfigurationModel } from 'vs/platform/configuration/common/configuration';
|
||||
import { IConfig, IAdapterExecutable, ITerminalSettings } from 'vs/workbench/parts/debug/common/debug';
|
||||
|
||||
import { IPickOpenEntry, IPickOptions } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions';
|
||||
import { EndOfLine, TextEditorLineNumbersStyle } from 'vs/workbench/api/node/extHostTypes';
|
||||
|
||||
|
||||
import { TaskSet } from 'vs/workbench/parts/tasks/common/tasks';
|
||||
import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { ISelection, Selection } from 'vs/editor/common/core/selection';
|
||||
|
||||
import { ITreeItem } from 'vs/workbench/common/views';
|
||||
import { ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { SerializedError } from 'vs/base/common/errors';
|
||||
import { IStat, FileChangeType, IWatchOptions, FileSystemProviderCapabilities, FileWriteOptions, FileType, FileOverwriteOptions } from 'vs/platform/files/common/files';
|
||||
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { CommentRule, CharacterPair, EnterAction } from 'vs/editor/common/modes/languageConfiguration';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { ISingleEditOperation } from 'vs/editor/common/model';
|
||||
import { ILineMatch, IPatternInfo } from 'vs/platform/search/common/search';
|
||||
import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { CharacterPair, CommentRule, EnterAction } from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
|
||||
import { ConfigurationTarget, IConfigurationData, IConfigurationModel } from 'vs/platform/configuration/common/configuration';
|
||||
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { FileChangeType, FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, FileWriteOptions, IStat, IWatchOptions } from 'vs/platform/files/common/files';
|
||||
import { LogLevel } from 'vs/platform/log/common/log';
|
||||
import { TaskExecutionDTO, TaskDTO, TaskHandleDTO, TaskFilterDTO } from 'vs/workbench/api/shared/tasks';
|
||||
import { IMarkerData } from 'vs/platform/markers/common/markers';
|
||||
import { IPickOptions, IQuickInputButton, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IPatternInfo, IQueryOptions, IRawFileMatch2, IRawSearchQuery, ISearchCompleteStats } from 'vs/platform/search/common/search';
|
||||
import { StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
import { EndOfLine, IFileOperationOptions, TextEditorLineNumbersStyle } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { EditorViewColumn } from 'vs/workbench/api/shared/editor';
|
||||
import { TaskDTO, TaskExecutionDTO, TaskFilterDTO, TaskHandleDTO, TaskProcessEndedDTO, TaskProcessStartedDTO, TaskSystemInfoDTO } from 'vs/workbench/api/shared/tasks';
|
||||
import { ITreeItem } from 'vs/workbench/common/views';
|
||||
import { IAdapterExecutable, IConfig, ITerminalSettings } from 'vs/workbench/parts/debug/common/debug';
|
||||
import { TaskSet } from 'vs/workbench/parts/tasks/common/tasks';
|
||||
import { ITerminalDimensions } from 'vs/workbench/parts/terminal/common/terminal';
|
||||
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId, IRPCProtocol, ProxyIdentifier } from 'vs/workbench/services/extensions/node/proxyIdentifier';
|
||||
import { IProgressOptions, IProgressStep } from 'vs/workbench/services/progress/common/progress';
|
||||
import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export interface IEnvironment {
|
||||
isExtensionDevelopmentDebug: boolean;
|
||||
appRoot: string;
|
||||
appSettingsHome: string;
|
||||
disableExtensions: boolean;
|
||||
extensionDevelopmentPath: string;
|
||||
extensionTestsPath: string;
|
||||
}
|
||||
@@ -102,6 +93,14 @@ export interface MainThreadCommandsShape extends IDisposable {
|
||||
$getCommands(): Thenable<string[]>;
|
||||
}
|
||||
|
||||
export interface MainThreadCommentsShape extends IDisposable {
|
||||
$registerDocumentCommentProvider(handle: number): void;
|
||||
$unregisterDocumentCommentProvider(handle: number): void;
|
||||
$registerWorkspaceCommentProvider(handle: number): void;
|
||||
$unregisterWorkspaceCommentProvider(handle: number): void;
|
||||
$onDidCommentThreadsChange(handle: number, event: modes.CommentThreadChangedEvent): void;
|
||||
}
|
||||
|
||||
export interface MainThreadConfigurationShape extends IDisposable {
|
||||
$updateConfigurationOption(target: ConfigurationTarget, key: string, value: any, resource: UriComponents): TPromise<void>;
|
||||
$removeConfigurationOption(target: ConfigurationTarget, key: string, resource: UriComponents): TPromise<void>;
|
||||
@@ -181,7 +180,7 @@ export interface IApplyEditsOptions extends IUndoStopOptions {
|
||||
}
|
||||
|
||||
export interface ITextDocumentShowOptions {
|
||||
position?: EditorPosition;
|
||||
position?: EditorViewColumn;
|
||||
preserveFocus?: boolean;
|
||||
pinned?: boolean;
|
||||
selection?: IRange;
|
||||
@@ -191,7 +190,7 @@ export interface MainThreadTextEditorsShape extends IDisposable {
|
||||
$tryShowTextDocument(resource: UriComponents, options: ITextDocumentShowOptions): TPromise<string>;
|
||||
$registerTextEditorDecorationType(key: string, options: editorCommon.IDecorationRenderOptions): void;
|
||||
$removeTextEditorDecorationType(key: string): void;
|
||||
$tryShowEditor(id: string, position: EditorPosition): TPromise<void>;
|
||||
$tryShowEditor(id: string, position: EditorViewColumn): TPromise<void>;
|
||||
$tryHideEditor(id: string): TPromise<void>;
|
||||
$trySetOptions(id: string, options: ITextEditorConfigurationUpdate): TPromise<void>;
|
||||
$trySetDecorations(id: string, key: string, ranges: editorCommon.IDecorationOptions[]): TPromise<void>;
|
||||
@@ -206,8 +205,8 @@ export interface MainThreadTextEditorsShape extends IDisposable {
|
||||
|
||||
export interface MainThreadTreeViewsShape extends IDisposable {
|
||||
$registerTreeViewDataProvider(treeViewId: string): void;
|
||||
$refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem }): void;
|
||||
$reveal(treeViewId: string, treeItem: ITreeItem, parentChain: ITreeItem[], options?: { select?: boolean }): TPromise<void>;
|
||||
$refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem }): TPromise<void>;
|
||||
$reveal(treeViewId: string, treeItem: ITreeItem, parentChain: ITreeItem[], options: { select: boolean, focus: boolean }): TPromise<void>;
|
||||
}
|
||||
|
||||
export interface MainThreadErrorsShape extends IDisposable {
|
||||
@@ -263,7 +262,7 @@ export interface ISerializedDocumentFilter {
|
||||
|
||||
export interface MainThreadLanguageFeaturesShape extends IDisposable {
|
||||
$unregister(handle: number): void;
|
||||
$registerOutlineSupport(handle: number, selector: ISerializedDocumentFilter[]): void;
|
||||
$registerOutlineSupport(handle: number, selector: ISerializedDocumentFilter[], extensionId: string): void;
|
||||
$registerCodeLensSupport(handle: number, selector: ISerializedDocumentFilter[], eventHandle: number): void;
|
||||
$emitCodeLensEvent(eventHandle: number, event?: any): void;
|
||||
$registerDeclaractionSupport(handle: number, selector: ISerializedDocumentFilter[]): void;
|
||||
@@ -316,26 +315,98 @@ export interface MainThreadProgressShape extends IDisposable {
|
||||
|
||||
export interface MainThreadTerminalServiceShape extends IDisposable {
|
||||
$createTerminal(name?: string, shellPath?: string, shellArgs?: string[], cwd?: string, env?: { [key: string]: string }, waitOnExit?: boolean): TPromise<number>;
|
||||
$createTerminalRenderer(name: string): TPromise<number>;
|
||||
$dispose(terminalId: number): void;
|
||||
$hide(terminalId: number): void;
|
||||
$sendText(terminalId: number, text: string, addNewLine: boolean): void;
|
||||
$show(terminalId: number, preserveFocus: boolean): void;
|
||||
$registerOnDataListener(terminalId: number): void;
|
||||
|
||||
// Process
|
||||
$sendProcessTitle(terminalId: number, title: string): void;
|
||||
$sendProcessData(terminalId: number, data: string): void;
|
||||
$sendProcessPid(terminalId: number, pid: number): void;
|
||||
$sendProcessExit(terminalId: number, exitCode: number): void;
|
||||
|
||||
// Renderer
|
||||
$terminalRendererSetName(terminalId: number, name: string): void;
|
||||
$terminalRendererSetDimensions(terminalId: number, dimensions: ITerminalDimensions): void;
|
||||
$terminalRendererWrite(terminalId: number, text: string): void;
|
||||
$terminalRendererRegisterOnInputListener(terminalId: number): void;
|
||||
}
|
||||
|
||||
export interface MyQuickPickItems extends IPickOpenEntry {
|
||||
export interface TransferQuickPickItems extends IQuickPickItem {
|
||||
handle: number;
|
||||
}
|
||||
|
||||
export interface TransferQuickInputButton extends IQuickInputButton {
|
||||
handle: number;
|
||||
}
|
||||
|
||||
export type TransferQuickInput = TransferQuickPick | TransferInputBox;
|
||||
|
||||
export interface BaseTransferQuickInput {
|
||||
|
||||
id: number;
|
||||
|
||||
type?: 'quickPick' | 'inputBox';
|
||||
|
||||
enabled?: boolean;
|
||||
|
||||
busy?: boolean;
|
||||
|
||||
visible?: boolean;
|
||||
}
|
||||
|
||||
export interface TransferQuickPick extends BaseTransferQuickInput {
|
||||
|
||||
type?: 'quickPick';
|
||||
|
||||
value?: string;
|
||||
|
||||
placeholder?: string;
|
||||
|
||||
buttons?: TransferQuickInputButton[];
|
||||
|
||||
items?: TransferQuickPickItems[];
|
||||
|
||||
activeItems?: number[];
|
||||
|
||||
selectedItems?: number[];
|
||||
|
||||
canSelectMany?: boolean;
|
||||
|
||||
ignoreFocusOut?: boolean;
|
||||
|
||||
matchOnDescription?: boolean;
|
||||
|
||||
matchOnDetail?: boolean;
|
||||
}
|
||||
|
||||
export interface TransferInputBox extends BaseTransferQuickInput {
|
||||
|
||||
type?: 'inputBox';
|
||||
|
||||
value?: string;
|
||||
|
||||
placeholder?: string;
|
||||
|
||||
password?: boolean;
|
||||
|
||||
buttons?: TransferQuickInputButton[];
|
||||
|
||||
prompt?: string;
|
||||
|
||||
validationMessage?: string;
|
||||
}
|
||||
|
||||
export interface MainThreadQuickOpenShape extends IDisposable {
|
||||
$show(options: IPickOptions): TPromise<number | number[]>;
|
||||
$setItems(items: MyQuickPickItems[]): TPromise<any>;
|
||||
$show(options: IPickOptions<TransferQuickPickItems>): TPromise<number | number[]>;
|
||||
$setItems(items: TransferQuickPickItems[]): TPromise<any>;
|
||||
$setError(error: Error): TPromise<any>;
|
||||
$input(options: vscode.InputBoxOptions, validateInput: boolean): TPromise<string>;
|
||||
$createOrUpdate(params: TransferQuickInput): TPromise<void>;
|
||||
$dispose(id: number): TPromise<void>;
|
||||
}
|
||||
|
||||
export interface MainThreadStatusBarShape extends IDisposable {
|
||||
@@ -354,29 +425,41 @@ export interface MainThreadTelemetryShape extends IDisposable {
|
||||
|
||||
export type WebviewPanelHandle = string;
|
||||
|
||||
export interface WebviewPanelShowOptions {
|
||||
readonly viewColumn?: EditorViewColumn;
|
||||
readonly preserveFocus?: boolean;
|
||||
}
|
||||
|
||||
export interface MainThreadWebviewsShape extends IDisposable {
|
||||
$createWebviewPanel(handle: WebviewPanelHandle, viewType: string, title: string, column: EditorPosition, options: vscode.WebviewPanelOptions & vscode.WebviewOptions, extensionFolderPath: string): void;
|
||||
$createWebviewPanel(handle: WebviewPanelHandle, viewType: string, title: string, showOptions: WebviewPanelShowOptions, options: vscode.WebviewPanelOptions & vscode.WebviewOptions, extensionLocation: UriComponents): void;
|
||||
$disposeWebview(handle: WebviewPanelHandle): void;
|
||||
$reveal(handle: WebviewPanelHandle, column: EditorPosition | undefined): void;
|
||||
$reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void;
|
||||
$setTitle(handle: WebviewPanelHandle, value: string): void;
|
||||
$setIconPath(handle: WebviewPanelHandle, value: { light: UriComponents, dark: UriComponents } | undefined): void;
|
||||
$setHtml(handle: WebviewPanelHandle, value: string): void;
|
||||
$setOptions(handle: WebviewPanelHandle, options: vscode.WebviewOptions): void;
|
||||
$postMessage(handle: WebviewPanelHandle, value: any): Thenable<boolean>;
|
||||
|
||||
$registerSerializer(viewType: string): void;
|
||||
$unregisterSerializer(viewType: string): void;
|
||||
}
|
||||
|
||||
export interface WebviewPanelViewState {
|
||||
readonly active: boolean;
|
||||
readonly visible: boolean;
|
||||
readonly position: EditorViewColumn;
|
||||
}
|
||||
|
||||
export interface ExtHostWebviewsShape {
|
||||
$onMessage(handle: WebviewPanelHandle, message: any): void;
|
||||
$onDidChangeWebviewPanelViewState(handle: WebviewPanelHandle, active: boolean, position: EditorPosition): void;
|
||||
$onDidChangeWebviewPanelViewState(handle: WebviewPanelHandle, newState: WebviewPanelViewState): void;
|
||||
$onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Thenable<void>;
|
||||
$deserializeWebviewPanel(newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorPosition, options: vscode.WebviewOptions): Thenable<void>;
|
||||
$serializeWebviewPanel(webviewHandle: WebviewPanelHandle): Thenable<any>;
|
||||
$deserializeWebviewPanel(newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: vscode.WebviewOptions): Thenable<void>;
|
||||
}
|
||||
|
||||
export interface MainThreadUrlsShape extends IDisposable {
|
||||
$registerProtocolHandler(handle: number, extensionId: string): TPromise<void>;
|
||||
$unregisterProtocolHandler(handle: number): TPromise<void>;
|
||||
$registerUriHandler(handle: number, extensionId: string): TPromise<void>;
|
||||
$unregisterUriHandler(handle: number): TPromise<void>;
|
||||
}
|
||||
|
||||
export interface ExtHostUrlsShape {
|
||||
@@ -384,7 +467,8 @@ export interface ExtHostUrlsShape {
|
||||
}
|
||||
|
||||
export interface MainThreadWorkspaceShape extends IDisposable {
|
||||
$startSearch(includePattern: string, includeFolder: string, excludePatternOrDisregardExcludes: string | false, maxResults: number, requestId: number): Thenable<UriComponents[]>;
|
||||
$startFileSearch(includePattern: string, includeFolder: string, excludePatternOrDisregardExcludes: string | false, maxResults: number, requestId: number): Thenable<UriComponents[]>;
|
||||
$startTextSearch(query: IPatternInfo, options: IQueryOptions, requestId: number): TPromise<void>;
|
||||
$cancelSearch(requestId: number): Thenable<boolean>;
|
||||
$saveAll(includeUntitled?: boolean): Thenable<boolean>;
|
||||
$updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, workspaceFoldersToAdd: { uri: UriComponents, name?: string }[]): Thenable<void>;
|
||||
@@ -402,17 +486,22 @@ export interface MainThreadFileSystemShape extends IDisposable {
|
||||
}
|
||||
|
||||
export interface MainThreadSearchShape extends IDisposable {
|
||||
$registerSearchProvider(handle: number, scheme: string): void;
|
||||
$registerFileSearchProvider(handle: number, scheme: string): void;
|
||||
$registerTextSearchProvider(handle: number, scheme: string): void;
|
||||
$registerFileIndexProvider(handle: number, scheme: string): void;
|
||||
$unregisterProvider(handle: number): void;
|
||||
$handleFindMatch(handle: number, session, data: UriComponents | [UriComponents, ILineMatch]): void;
|
||||
$handleFileMatch(handle: number, session: number, data: UriComponents[]): void;
|
||||
$handleTextMatch(handle: number, session: number, data: IRawFileMatch2[]): void;
|
||||
$handleTelemetry(eventName: string, data: any): void;
|
||||
}
|
||||
|
||||
export interface MainThreadTaskShape extends IDisposable {
|
||||
$registerTaskProvider(handle: number): TPromise<void>;
|
||||
$unregisterTaskProvider(handle: number): TPromise<void>;
|
||||
$fetchTasks(filter?: TaskFilterDTO): TPromise<TaskDTO[]>;
|
||||
$executeTask(task: TaskHandleDTO | TaskDTO): TPromise<TaskExecutionDTO>;
|
||||
$terminateTask(id: string): TPromise<void>;
|
||||
$unregisterTaskProvider(handle: number): TPromise<void>;
|
||||
$registerTaskSystem(scheme: string, info: TaskSystemInfoDTO): void;
|
||||
}
|
||||
|
||||
export interface MainThreadExtensionServiceShape extends IDisposable {
|
||||
@@ -482,10 +571,10 @@ export interface MainThreadSCMShape extends IDisposable {
|
||||
export type DebugSessionUUID = string;
|
||||
|
||||
export interface MainThreadDebugServiceShape extends IDisposable {
|
||||
$registerDebugTypes(debugTypes: string[]);
|
||||
$acceptDAMessage(handle: number, message: DebugProtocol.ProtocolMessage);
|
||||
$acceptDAError(handle: number, name: string, message: string, stack: string);
|
||||
$acceptDAExit(handle: number, code: number, signal: string);
|
||||
$registerDebugTypes(debugTypes: string[]): void;
|
||||
$acceptDAMessage(handle: number, message: DebugProtocol.ProtocolMessage): void;
|
||||
$acceptDAError(handle: number, name: string, message: string, stack: string): void;
|
||||
$acceptDAExit(handle: number, code: number, signal: string): void;
|
||||
$registerDebugConfigurationProvider(type: string, hasProvideMethod: boolean, hasResolveMethod: boolean, hasDebugAdapterExecutable: boolean, handle: number): TPromise<any>;
|
||||
$unregisterDebugConfigurationProvider(handle: number): TPromise<any>;
|
||||
$startDebugging(folder: UriComponents | undefined, nameOrConfig: string | vscode.DebugConfiguration): TPromise<boolean>;
|
||||
@@ -546,10 +635,10 @@ export interface ITextEditorAddData {
|
||||
options: IResolvedTextEditorConfiguration;
|
||||
selections: ISelection[];
|
||||
visibleRanges: IRange[];
|
||||
editorPosition: EditorPosition;
|
||||
editorPosition: EditorViewColumn;
|
||||
}
|
||||
export interface ITextEditorPositionData {
|
||||
[id: string]: EditorPosition;
|
||||
[id: string]: EditorViewColumn;
|
||||
}
|
||||
export interface IEditorPropertiesChangeData {
|
||||
options: IResolvedTextEditorConfiguration | null;
|
||||
@@ -580,10 +669,14 @@ export interface ExtHostDocumentsAndEditorsShape {
|
||||
|
||||
export interface ExtHostTreeViewsShape {
|
||||
$getChildren(treeViewId: string, treeItemHandle?: string): TPromise<ITreeItem[]>;
|
||||
$setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void;
|
||||
$setSelection(treeViewId: string, treeItemHandles: string[]): void;
|
||||
$setVisible(treeViewId: string, visible: boolean): void;
|
||||
}
|
||||
|
||||
export interface ExtHostWorkspaceShape {
|
||||
$acceptWorkspaceData(workspace: IWorkspaceData): void;
|
||||
$handleTextSearchResult(result: IRawFileMatch2, requestId: number): void;
|
||||
}
|
||||
|
||||
export interface ExtHostFileSystemShape {
|
||||
@@ -594,14 +687,15 @@ export interface ExtHostFileSystemShape {
|
||||
$rename(handle: number, resource: UriComponents, target: UriComponents, opts: FileOverwriteOptions): TPromise<void>;
|
||||
$copy(handle: number, resource: UriComponents, target: UriComponents, opts: FileOverwriteOptions): TPromise<void>;
|
||||
$mkdir(handle: number, resource: UriComponents): TPromise<void>;
|
||||
$delete(handle: number, resource: UriComponents): TPromise<void>;
|
||||
$delete(handle: number, resource: UriComponents, opts: FileDeleteOptions): TPromise<void>;
|
||||
$watch(handle: number, session: number, resource: UriComponents, opts: IWatchOptions): void;
|
||||
$unwatch(handle: number, session: number): void;
|
||||
}
|
||||
|
||||
export interface ExtHostSearchShape {
|
||||
$provideFileSearchResults(handle: number, session: number, query: string): TPromise<void>;
|
||||
$provideTextSearchResults(handle: number, session: number, pattern: IPatternInfo, options: { includes: string[], excludes: string[] }): TPromise<void>;
|
||||
$provideFileSearchResults(handle: number, session: number, query: IRawSearchQuery): TPromise<ISearchCompleteStats>;
|
||||
$clearCache(cacheKey: string): TPromise<void>;
|
||||
$provideTextSearchResults(handle: number, session: number, pattern: IPatternInfo, query: IRawSearchQuery): TPromise<ISearchCompleteStats>;
|
||||
}
|
||||
|
||||
export interface ExtHostExtensionServiceShape {
|
||||
@@ -615,6 +709,8 @@ export interface FileSystemEvents {
|
||||
}
|
||||
export interface ExtHostFileSystemEventServiceShape {
|
||||
$onFileEvent(events: FileSystemEvents): void;
|
||||
$onFileRename(oldUri: UriComponents, newUri: UriComponents): void;
|
||||
$onWillRename(oldUri: UriComponents, newUri: UriComponents): TPromise<any>;
|
||||
}
|
||||
|
||||
export interface ObjectIdentifier {
|
||||
@@ -664,7 +760,14 @@ export interface LocationDto {
|
||||
range: IRange;
|
||||
}
|
||||
|
||||
export interface SymbolInformationDto extends IdObject {
|
||||
export interface DefinitionLinkDto {
|
||||
origin?: IRange;
|
||||
uri: UriComponents;
|
||||
range: IRange;
|
||||
selectionRange?: IRange;
|
||||
}
|
||||
|
||||
export interface WorkspaceSymbolDto extends IdObject {
|
||||
name: string;
|
||||
containerName?: string;
|
||||
kind: modes.SymbolKind;
|
||||
@@ -672,12 +775,13 @@ export interface SymbolInformationDto extends IdObject {
|
||||
}
|
||||
|
||||
export interface WorkspaceSymbolsDto extends IdObject {
|
||||
symbols: SymbolInformationDto[];
|
||||
symbols: WorkspaceSymbolDto[];
|
||||
}
|
||||
|
||||
export interface ResourceFileEditDto {
|
||||
oldUri: UriComponents;
|
||||
newUri: UriComponents;
|
||||
options: IFileOperationOptions;
|
||||
}
|
||||
|
||||
export interface ResourceTextEditDto {
|
||||
@@ -716,21 +820,21 @@ export interface CodeActionDto {
|
||||
}
|
||||
|
||||
export interface ExtHostLanguageFeaturesShape {
|
||||
$provideDocumentSymbols(handle: number, resource: UriComponents): TPromise<SymbolInformationDto[]>;
|
||||
$provideDocumentSymbols(handle: number, resource: UriComponents): TPromise<modes.DocumentSymbol[]>;
|
||||
$provideCodeLenses(handle: number, resource: UriComponents): TPromise<modes.ICodeLensSymbol[]>;
|
||||
$resolveCodeLens(handle: number, resource: UriComponents, symbol: modes.ICodeLensSymbol): TPromise<modes.ICodeLensSymbol>;
|
||||
$provideDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise<LocationDto | LocationDto[]>;
|
||||
$provideImplementation(handle: number, resource: UriComponents, position: IPosition): TPromise<LocationDto | LocationDto[]>;
|
||||
$provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise<LocationDto | LocationDto[]>;
|
||||
$provideDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise<DefinitionLinkDto[]>;
|
||||
$provideImplementation(handle: number, resource: UriComponents, position: IPosition): TPromise<DefinitionLinkDto[]>;
|
||||
$provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise<DefinitionLinkDto[]>;
|
||||
$provideHover(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.Hover>;
|
||||
$provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.DocumentHighlight[]>;
|
||||
$provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext): TPromise<LocationDto[]>;
|
||||
$provideCodeActions(handle: number, resource: UriComponents, range: IRange, context: modes.CodeActionContext): TPromise<CodeActionDto[]>;
|
||||
$provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext): TPromise<CodeActionDto[]>;
|
||||
$provideDocumentFormattingEdits(handle: number, resource: UriComponents, options: modes.FormattingOptions): TPromise<ISingleEditOperation[]>;
|
||||
$provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: IRange, options: modes.FormattingOptions): TPromise<ISingleEditOperation[]>;
|
||||
$provideOnTypeFormattingEdits(handle: number, resource: UriComponents, position: IPosition, ch: string, options: modes.FormattingOptions): TPromise<ISingleEditOperation[]>;
|
||||
$provideWorkspaceSymbols(handle: number, search: string): TPromise<WorkspaceSymbolsDto>;
|
||||
$resolveWorkspaceSymbol(handle: number, symbol: SymbolInformationDto): TPromise<SymbolInformationDto>;
|
||||
$resolveWorkspaceSymbol(handle: number, symbol: WorkspaceSymbolDto): TPromise<WorkspaceSymbolDto>;
|
||||
$releaseWorkspaceSymbols(handle: number, id: number): void;
|
||||
$provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string): TPromise<WorkspaceEditDto>;
|
||||
$resolveRenameLocation(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.RenameLocation>;
|
||||
@@ -748,6 +852,12 @@ export interface ExtHostLanguageFeaturesShape {
|
||||
export interface ExtHostQuickOpenShape {
|
||||
$onItemSelected(handle: number): void;
|
||||
$validateInput(input: string): TPromise<string>;
|
||||
$onDidChangeActive(sessionId: number, handles: number[]): void;
|
||||
$onDidChangeSelection(sessionId: number, handles: number[]): void;
|
||||
$onDidAccept(sessionId: number): void;
|
||||
$onDidChangeValue(sessionId: number, value: string): void;
|
||||
$onDidTriggerButton(sessionId: number, handle: number): void;
|
||||
$onDidHide(sessionId: number): void;
|
||||
}
|
||||
|
||||
export interface ShellLaunchConfigDto {
|
||||
@@ -761,8 +871,11 @@ export interface ShellLaunchConfigDto {
|
||||
export interface ExtHostTerminalServiceShape {
|
||||
$acceptTerminalClosed(id: number): void;
|
||||
$acceptTerminalOpened(id: number, name: string): void;
|
||||
$acceptActiveTerminalChanged(id: number | null): void;
|
||||
$acceptTerminalProcessId(id: number, processId: number): void;
|
||||
$acceptTerminalProcessData(id: number, data: string);
|
||||
$acceptTerminalProcessData(id: number, data: string): void;
|
||||
$acceptTerminalRendererInput(id: number, data: string): void;
|
||||
$acceptTerminalRendererDimensions(id: number, cols: number, rows: number): void;
|
||||
$createProcess(id: number, shellLaunchConfig: ShellLaunchConfigDto, cols: number, rows: number): void;
|
||||
$acceptProcessInput(id: number, data: string): void;
|
||||
$acceptProcessResize(id: number, cols: number, rows: number): void;
|
||||
@@ -777,9 +890,12 @@ export interface ExtHostSCMShape {
|
||||
}
|
||||
|
||||
export interface ExtHostTaskShape {
|
||||
$provideTasks(handle: number): TPromise<TaskSet>;
|
||||
$taskStarted(execution: TaskExecutionDTO): void;
|
||||
$taskEnded(execution: TaskExecutionDTO): void;
|
||||
$provideTasks(handle: number, validTypes: { [key: string]: boolean; }): TPromise<TaskSet>;
|
||||
$onDidStartTask(execution: TaskExecutionDTO): void;
|
||||
$onDidStartTaskProcess(value: TaskProcessStartedDTO): void;
|
||||
$onDidEndTaskProcess(value: TaskProcessEndedDTO): void;
|
||||
$OnDidEndTask(execution: TaskExecutionDTO): void;
|
||||
$resolveVariables(workspaceFolder: UriComponents, variables: string[]): TPromise<any>;
|
||||
}
|
||||
|
||||
export interface IBreakpointDto {
|
||||
@@ -828,7 +944,7 @@ export interface ISourceMultiBreakpointDto {
|
||||
export interface ExtHostDebugServiceShape {
|
||||
$substituteVariables(folder: UriComponents | undefined, config: IConfig): TPromise<IConfig>;
|
||||
$runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise<void>;
|
||||
$startDASession(handle: number, debugType: string, adapterExecutableInfo: IAdapterExecutable | null): TPromise<void>;
|
||||
$startDASession(handle: number, debugType: string, adapterExecutableInfo: IAdapterExecutable | null, debugPort: number): TPromise<void>;
|
||||
$stopDASession(handle: number): TPromise<void>;
|
||||
$sendDAMessage(handle: number, message: DebugProtocol.ProtocolMessage): TPromise<void>;
|
||||
$resolveDebugConfiguration(handle: number, folder: UriComponents | undefined, debugConfiguration: IConfig): TPromise<IConfig>;
|
||||
@@ -861,17 +977,25 @@ export interface ExtHostWindowShape {
|
||||
}
|
||||
|
||||
export interface ExtHostLogServiceShape {
|
||||
$setLevel(level: LogLevel);
|
||||
$setLevel(level: LogLevel): void;
|
||||
}
|
||||
|
||||
export interface ExtHostProgressShape {
|
||||
$acceptProgressCanceled(handle: number): void;
|
||||
}
|
||||
|
||||
export interface ExtHostCommentsShape {
|
||||
$provideDocumentComments(handle: number, document: UriComponents): TPromise<modes.CommentInfo>;
|
||||
$createNewCommentThread?(handle: number, document: UriComponents, range: IRange, text: string): TPromise<modes.CommentThread>;
|
||||
$replyToCommentThread?(handle: number, document: UriComponents, range: IRange, commentThread: modes.CommentThread, text: string): TPromise<modes.CommentThread>;
|
||||
$provideWorkspaceComments(handle: number): TPromise<modes.CommentThread[]>;
|
||||
}
|
||||
|
||||
// --- proxy identifiers
|
||||
|
||||
export const MainContext = {
|
||||
MainThreadCommands: <ProxyIdentifier<MainThreadCommandsShape>>createMainId<MainThreadCommandsShape>('MainThreadCommands'),
|
||||
MainThreadComments: createMainId<MainThreadCommentsShape>('MainThreadComments'),
|
||||
MainThreadConfiguration: createMainId<MainThreadConfigurationShape>('MainThreadConfiguration'),
|
||||
// {{SQL CARBON EDIT}}
|
||||
// MainThreadDebugService: createMainId<MainThreadDebugServiceShape>('MainThreadDebugService'),
|
||||
@@ -931,6 +1055,7 @@ export const ExtHostContext = {
|
||||
ExtHostWorkspace: createExtId<ExtHostWorkspaceShape>('ExtHostWorkspace'),
|
||||
ExtHostWindow: createExtId<ExtHostWindowShape>('ExtHostWindow'),
|
||||
ExtHostWebviews: createExtId<ExtHostWebviewsShape>('ExtHostWebviews'),
|
||||
ExtHostUrls: createExtId<ExtHostUrlsShape>('ExtHostUrls'),
|
||||
ExtHostProgress: createMainId<ExtHostProgressShape>('ExtHostProgress')
|
||||
ExtHostProgress: createMainId<ExtHostProgressShape>('ExtHostProgress'),
|
||||
ExtHostComments: createMainId<ExtHostCommentsShape>('ExtHostComments'),
|
||||
ExtHostUrls: createExtId<ExtHostUrlsShape>('ExtHostUrls')
|
||||
};
|
||||
|
||||
@@ -10,30 +10,28 @@ import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as vscode from 'vscode';
|
||||
import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import * as types from 'vs/workbench/api/node/extHostTypes';
|
||||
import { IRawColorInfo } from 'vs/workbench/api/node/extHost.protocol';
|
||||
|
||||
import { IRawColorInfo, WorkspaceEditDto } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { ISingleEditOperation } from 'vs/editor/common/model';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import * as search from 'vs/workbench/parts/search/common/search';
|
||||
import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
|
||||
import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands';
|
||||
import { IWorkspaceSymbolProvider } from 'vs/workbench/parts/search/common/search';
|
||||
import { CustomCodeAction } from 'vs/workbench/api/node/extHostLanguageFeatures';
|
||||
import { ExtHostTask } from './extHostTask';
|
||||
import { ICommandsExecutor, PreviewHTMLAPICommand, OpenFolderAPICommand, DiffAPICommand, OpenAPICommand, RemoveFromRecentlyOpenedAPICommand } from './apiCommands';
|
||||
import { ICommandsExecutor, PreviewHTMLAPICommand, OpenFolderAPICommand, DiffAPICommand, OpenAPICommand, RemoveFromRecentlyOpenedAPICommand, SetEditorLayoutAPICommand } from './apiCommands';
|
||||
import { EditorGroupLayout } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
|
||||
|
||||
export class ExtHostApiCommands {
|
||||
|
||||
static register(commands: ExtHostCommands, workspace: ExtHostTask) {
|
||||
return new ExtHostApiCommands(commands, workspace).registerCommands();
|
||||
static register(commands: ExtHostCommands) {
|
||||
return new ExtHostApiCommands(commands).registerCommands();
|
||||
}
|
||||
|
||||
private _commands: ExtHostCommands;
|
||||
private _tasks: ExtHostTask;
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
private constructor(commands: ExtHostCommands, task: ExtHostTask) {
|
||||
private constructor(commands: ExtHostCommands) {
|
||||
this._commands = commands;
|
||||
this._tasks = task;
|
||||
}
|
||||
|
||||
registerCommands() {
|
||||
@@ -105,7 +103,7 @@ export class ExtHostApiCommands {
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position in a text document', constraint: types.Position },
|
||||
{ name: 'triggerCharacter', description: '(optional) Trigger signature help when the user types the character, like `,` or `(`', constraint: value => value === void 0 || typeof value === 'string' }
|
||||
{ name: 'triggerCharacter', description: '(optional) Trigger signature help when the user types the character, like `,` or `(`', constraint: (value: any) => value === void 0 || typeof value === 'string' }
|
||||
],
|
||||
returns: 'A promise that resolves to SignatureHelp.'
|
||||
});
|
||||
@@ -121,8 +119,8 @@ export class ExtHostApiCommands {
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position in a text document', constraint: types.Position },
|
||||
{ name: 'triggerCharacter', description: '(optional) Trigger completion when the user types the character, like `,` or `(`', constraint: value => value === void 0 || typeof value === 'string' },
|
||||
{ name: 'itemResolveCount', description: '(optional) Number of completions to resolve (too large numbers slow down completions)', constraint: value => value === void 0 || typeof value === 'number' }
|
||||
{ name: 'triggerCharacter', description: '(optional) Trigger completion when the user types the character, like `,` or `(`', constraint: (value: any) => value === void 0 || typeof value === 'string' },
|
||||
{ name: 'itemResolveCount', description: '(optional) Number of completions to resolve (too large numbers slow down completions)', constraint: (value: any) => value === void 0 || typeof value === 'number' }
|
||||
],
|
||||
returns: 'A promise that resolves to a CompletionList-instance.'
|
||||
});
|
||||
@@ -138,7 +136,7 @@ export class ExtHostApiCommands {
|
||||
description: 'Execute CodeLens provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'itemResolveCount', description: '(optional) Number of lenses that should be resolved and returned. Will only retrun resolved lenses, will impact performance)', constraint: value => value === void 0 || typeof value === 'number' }
|
||||
{ name: 'itemResolveCount', description: '(optional) Number of lenses that should be resolved and returned. Will only retrun resolved lenses, will impact performance)', constraint: (value: any) => value === void 0 || typeof value === 'number' }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of CodeLens-instances.'
|
||||
});
|
||||
@@ -176,11 +174,6 @@ export class ExtHostApiCommands {
|
||||
],
|
||||
returns: 'A promise that resolves to an array of DocumentLink-instances.'
|
||||
});
|
||||
this._register('vscode.executeTaskProvider', this._executeTaskProvider, {
|
||||
description: 'Execute task provider',
|
||||
args: [],
|
||||
returns: 'An array of task handles'
|
||||
});
|
||||
this._register('vscode.executeDocumentColorProvider', this._executeDocumentColorProvider, {
|
||||
description: 'Execute document color provider.',
|
||||
args: [
|
||||
@@ -219,18 +212,18 @@ export class ExtHostApiCommands {
|
||||
See [working with the HTML preview](https://code.visualstudio.com/docs/extensionAPI/vscode-api-commands#working-with-the-html-preview) for more information about the HTML preview's integration with the editor and for best practices for extension authors.
|
||||
`,
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of the resource to preview.', constraint: value => value instanceof URI || typeof value === 'string' },
|
||||
{ name: 'column', description: '(optional) Column in which to preview.', constraint: value => typeof value === 'undefined' || (typeof value === 'number' && typeof types.ViewColumn[value] === 'string') },
|
||||
{ name: 'label', description: '(optional) An human readable string that is used as title for the preview.', constraint: v => typeof v === 'string' || typeof v === 'undefined' },
|
||||
{ name: 'options', description: '(optional) Options for controlling webview environment.', constraint: v => typeof v === 'object' || typeof v === 'undefined' }
|
||||
{ name: 'uri', description: 'Uri of the resource to preview.', constraint: (value: any) => value instanceof URI || typeof value === 'string' },
|
||||
{ name: 'column', description: '(optional) Column in which to preview.', constraint: (value: any) => typeof value === 'undefined' || (typeof value === 'number' && typeof types.ViewColumn[value] === 'string') },
|
||||
{ name: 'label', description: '(optional) An human readable string that is used as title for the preview.', constraint: (v: any) => typeof v === 'string' || typeof v === 'undefined' },
|
||||
{ name: 'options', description: '(optional) Options for controlling webview environment.', constraint: (v: any) => typeof v === 'object' || typeof v === 'undefined' }
|
||||
]
|
||||
});
|
||||
|
||||
this._register(OpenFolderAPICommand.ID, adjustHandler(OpenFolderAPICommand.execute), {
|
||||
description: 'Open a folder or workspace in the current window or new window depending on the newWindow argument. Note that opening in the same window will shutdown the current extension host process and start a new one on the given folder/workspace unless the newWindow parameter is set to true.',
|
||||
args: [
|
||||
{ name: 'uri', description: '(optional) Uri of the folder or workspace file to open. If not provided, a native dialog will ask the user for the folder', constraint: value => value === void 0 || value instanceof URI },
|
||||
{ name: 'newWindow', description: '(optional) Whether to open the folder/workspace in a new window or the same. Defaults to opening in the same window.', constraint: value => value === void 0 || typeof value === 'boolean' }
|
||||
{ name: 'uri', description: '(optional) Uri of the folder or workspace file to open. If not provided, a native dialog will ask the user for the folder', constraint: (value: any) => value === void 0 || value instanceof URI },
|
||||
{ name: 'newWindow', description: '(optional) Whether to open the folder/workspace in a new window or the same. Defaults to opening in the same window.', constraint: (value: any) => value === void 0 || typeof value === 'boolean' }
|
||||
]
|
||||
});
|
||||
|
||||
@@ -239,7 +232,7 @@ export class ExtHostApiCommands {
|
||||
args: [
|
||||
{ name: 'left', description: 'Left-hand side resource of the diff editor', constraint: URI },
|
||||
{ name: 'right', description: 'Right-hand side resource of the diff editor', constraint: URI },
|
||||
{ name: 'title', description: '(optional) Human readable title for the diff editor', constraint: v => v === void 0 || typeof v === 'string' },
|
||||
{ name: 'title', description: '(optional) Human readable title for the diff editor', constraint: (v: any) => v === void 0 || typeof v === 'string' },
|
||||
{ name: 'options', description: '(optional) Editor options, see vscode.TextDocumentShowOptions' }
|
||||
]
|
||||
});
|
||||
@@ -248,14 +241,21 @@ export class ExtHostApiCommands {
|
||||
description: 'Opens the provided resource in the editor. Can be a text or binary file, or a http(s) url. If you need more control over the options for opening a text file, use vscode.window.showTextDocument instead.',
|
||||
args: [
|
||||
{ name: 'resource', description: 'Resource to open', constraint: URI },
|
||||
{ name: 'columnOrOptions', description: '(optional) Either the column in which to open or editor options, see vscode.TextDocumentShowOptions', constraint: v => v === void 0 || typeof v === 'number' || typeof v === 'object' }
|
||||
{ name: 'columnOrOptions', description: '(optional) Either the column in which to open or editor options, see vscode.TextDocumentShowOptions', constraint: (v: any) => v === void 0 || typeof v === 'number' || typeof v === 'object' }
|
||||
]
|
||||
});
|
||||
|
||||
this._register(RemoveFromRecentlyOpenedAPICommand.ID, adjustHandler(RemoveFromRecentlyOpenedAPICommand.execute), {
|
||||
description: 'Removes an entry with the given path from the recently opened list.',
|
||||
args: [
|
||||
{ name: 'path', description: 'Path to remove from recently opened.', constraint: value => typeof value === 'string' }
|
||||
{ name: 'path', description: 'Path to remove from recently opened.', constraint: (value: any) => typeof value === 'string' }
|
||||
]
|
||||
});
|
||||
|
||||
this._register(SetEditorLayoutAPICommand.ID, adjustHandler(SetEditorLayoutAPICommand.execute), {
|
||||
description: 'Sets the editor layout. The layout is described as object with an initial (optional) orientation (0 = horizontal, 1 = vertical) and an array of editor groups within. Each editor group can have a size and another array of editor groups that will be laid out orthogonal to the orientation. If editor group sizes are provided, their sum must be 1 to be applied per row or column. Example for a 2x2 grid: `{ orientation: 0, groups: [{ groups: [{}, {}], size: 0.5 }, { groups: [{}, {}], size: 0.5 }] }`',
|
||||
args: [
|
||||
{ name: 'layout', description: 'The editor layout to set.', constraint: (value: EditorGroupLayout) => typeof value === 'object' && Array.isArray(value.groups) }
|
||||
]
|
||||
});
|
||||
}
|
||||
@@ -274,11 +274,11 @@ export class ExtHostApiCommands {
|
||||
* @return A promise that resolves to an array of symbol information.
|
||||
*/
|
||||
private _executeWorkspaceSymbolProvider(query: string): Thenable<types.SymbolInformation[]> {
|
||||
return this._commands.executeCommand<[IWorkspaceSymbolProvider, modes.SymbolInformation[]][]>('_executeWorkspaceSymbolProvider', { query }).then(value => {
|
||||
return this._commands.executeCommand<[search.IWorkspaceSymbolProvider, search.IWorkspaceSymbol[]][]>('_executeWorkspaceSymbolProvider', { query }).then(value => {
|
||||
const result: types.SymbolInformation[] = [];
|
||||
if (Array.isArray(value)) {
|
||||
for (let tuple of value) {
|
||||
result.push(...tuple[1].map(typeConverters.toSymbolInformation));
|
||||
result.push(...tuple[1].map(typeConverters.WorkspaceSymbol.to));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -288,7 +288,7 @@ export class ExtHostApiCommands {
|
||||
private _executeDefinitionProvider(resource: URI, position: types.Position): Thenable<types.Location[]> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.fromPosition(position)
|
||||
position: position && typeConverters.Position.from(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.Location[]>('_executeDefinitionProvider', args)
|
||||
.then(tryMapWith(typeConverters.location.to));
|
||||
@@ -297,7 +297,7 @@ export class ExtHostApiCommands {
|
||||
private _executeTypeDefinitionProvider(resource: URI, position: types.Position): Thenable<types.Location[]> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.fromPosition(position)
|
||||
position: position && typeConverters.Position.from(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.Location[]>('_executeTypeDefinitionProvider', args)
|
||||
.then(tryMapWith(typeConverters.location.to));
|
||||
@@ -306,7 +306,7 @@ export class ExtHostApiCommands {
|
||||
private _executeImplementationProvider(resource: URI, position: types.Position): Thenable<types.Location[]> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.fromPosition(position)
|
||||
position: position && typeConverters.Position.from(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.Location[]>('_executeImplementationProvider', args)
|
||||
.then(tryMapWith(typeConverters.location.to));
|
||||
@@ -315,25 +315,25 @@ export class ExtHostApiCommands {
|
||||
private _executeHoverProvider(resource: URI, position: types.Position): Thenable<types.Hover[]> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.fromPosition(position)
|
||||
position: position && typeConverters.Position.from(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.Hover[]>('_executeHoverProvider', args)
|
||||
.then(tryMapWith(typeConverters.toHover));
|
||||
.then(tryMapWith(typeConverters.Hover.to));
|
||||
}
|
||||
|
||||
private _executeDocumentHighlights(resource: URI, position: types.Position): Thenable<types.DocumentHighlight[]> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.fromPosition(position)
|
||||
position: position && typeConverters.Position.from(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.DocumentHighlight[]>('_executeDocumentHighlights', args)
|
||||
.then(tryMapWith(typeConverters.toDocumentHighlight));
|
||||
.then(tryMapWith(typeConverters.DocumentHighlight.to));
|
||||
}
|
||||
|
||||
private _executeReferenceProvider(resource: URI, position: types.Position): Thenable<types.Location[]> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.fromPosition(position)
|
||||
position: position && typeConverters.Position.from(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.Location[]>('_executeReferenceProvider', args)
|
||||
.then(tryMapWith(typeConverters.location.to));
|
||||
@@ -342,10 +342,10 @@ export class ExtHostApiCommands {
|
||||
private _executeDocumentRenameProvider(resource: URI, position: types.Position, newName: string): Thenable<types.WorkspaceEdit> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.fromPosition(position),
|
||||
position: position && typeConverters.Position.from(position),
|
||||
newName
|
||||
};
|
||||
return this._commands.executeCommand<modes.WorkspaceEdit>('_executeDocumentRenameProvider', args).then(value => {
|
||||
return this._commands.executeCommand<WorkspaceEditDto>('_executeDocumentRenameProvider', args).then(value => {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -359,7 +359,7 @@ export class ExtHostApiCommands {
|
||||
private _executeSignatureHelpProvider(resource: URI, position: types.Position, triggerCharacter: string): Thenable<types.SignatureHelp> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.fromPosition(position),
|
||||
position: position && typeConverters.Position.from(position),
|
||||
triggerCharacter
|
||||
};
|
||||
return this._commands.executeCommand<modes.SignatureHelp>('_executeSignatureHelpProvider', args).then(value => {
|
||||
@@ -373,7 +373,7 @@ export class ExtHostApiCommands {
|
||||
private _executeCompletionItemProvider(resource: URI, position: types.Position, triggerCharacter: string, maxItemsToResolve: number): Thenable<types.CompletionList> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.fromPosition(position),
|
||||
position: position && typeConverters.Position.from(position),
|
||||
triggerCharacter,
|
||||
maxItemsToResolve
|
||||
};
|
||||
@@ -393,7 +393,7 @@ export class ExtHostApiCommands {
|
||||
};
|
||||
return this._commands.executeCommand<IRawColorInfo[]>('_executeDocumentColorProvider', args).then(result => {
|
||||
if (result) {
|
||||
return result.map(ci => ({ range: typeConverters.toRange(ci.range), color: typeConverters.Color.to(ci.color) }));
|
||||
return result.map(ci => ({ range: typeConverters.Range.to(ci.range), color: typeConverters.Color.to(ci.color) }));
|
||||
}
|
||||
return [];
|
||||
});
|
||||
@@ -403,7 +403,7 @@ export class ExtHostApiCommands {
|
||||
const args = {
|
||||
resource: context.uri,
|
||||
color: typeConverters.Color.from(color),
|
||||
range: typeConverters.fromRange(context.range),
|
||||
range: typeConverters.Range.from(context.range),
|
||||
};
|
||||
return this._commands.executeCommand<modes.IColorPresentation[]>('_executeColorPresentationProvider', args).then(result => {
|
||||
if (result) {
|
||||
@@ -413,22 +413,31 @@ export class ExtHostApiCommands {
|
||||
});
|
||||
}
|
||||
|
||||
private _executeDocumentSymbolProvider(resource: URI): Thenable<types.SymbolInformation[]> {
|
||||
private _executeDocumentSymbolProvider(resource: URI): Thenable<vscode.SymbolInformation[]> {
|
||||
const args = {
|
||||
resource
|
||||
};
|
||||
return this._commands.executeCommand<modes.IOutline>('_executeDocumentSymbolProvider', args).then(value => {
|
||||
if (value && Array.isArray(value.entries)) {
|
||||
return value.entries.map(typeConverters.toSymbolInformation);
|
||||
return this._commands.executeCommand<modes.DocumentSymbol[]>('_executeDocumentSymbolProvider', args).then(value => {
|
||||
if (isFalsyOrEmpty(value)) {
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
let result: vscode.SymbolInformation[] = [];
|
||||
for (const symbol of value) {
|
||||
result.push(new types.SymbolInformation(
|
||||
symbol.name,
|
||||
typeConverters.SymbolKind.to(symbol.kind),
|
||||
symbol.containerName,
|
||||
new types.Location(resource, typeConverters.Range.to(symbol.range))
|
||||
));
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
private _executeCodeActionProvider(resource: URI, range: types.Range): Thenable<(vscode.CodeAction | vscode.Command)[]> {
|
||||
const args = {
|
||||
resource,
|
||||
range: typeConverters.fromRange(range)
|
||||
range: typeConverters.Range.from(range)
|
||||
};
|
||||
return this._commands.executeCommand<CustomCodeAction[]>('_executeCodeActionProvider', args)
|
||||
.then(tryMapWith(codeAction => {
|
||||
@@ -455,7 +464,7 @@ export class ExtHostApiCommands {
|
||||
return this._commands.executeCommand<modes.ICodeLensSymbol[]>('_executeCodeLensProvider', args)
|
||||
.then(tryMapWith(item => {
|
||||
return new types.CodeLens(
|
||||
typeConverters.toRange(item.range),
|
||||
typeConverters.Range.to(item.range),
|
||||
this._commands.converter.fromInternal(item.command));
|
||||
}));
|
||||
|
||||
@@ -467,38 +476,34 @@ export class ExtHostApiCommands {
|
||||
options
|
||||
};
|
||||
return this._commands.executeCommand<ISingleEditOperation[]>('_executeFormatDocumentProvider', args)
|
||||
.then(tryMapWith(edit => new types.TextEdit(typeConverters.toRange(edit.range), edit.text)));
|
||||
.then(tryMapWith(edit => new types.TextEdit(typeConverters.Range.to(edit.range), edit.text)));
|
||||
}
|
||||
|
||||
private _executeFormatRangeProvider(resource: URI, range: types.Range, options: vscode.FormattingOptions): Thenable<vscode.TextEdit[]> {
|
||||
const args = {
|
||||
resource,
|
||||
range: typeConverters.fromRange(range),
|
||||
range: typeConverters.Range.from(range),
|
||||
options
|
||||
};
|
||||
return this._commands.executeCommand<ISingleEditOperation[]>('_executeFormatRangeProvider', args)
|
||||
.then(tryMapWith(edit => new types.TextEdit(typeConverters.toRange(edit.range), edit.text)));
|
||||
.then(tryMapWith(edit => new types.TextEdit(typeConverters.Range.to(edit.range), edit.text)));
|
||||
}
|
||||
|
||||
private _executeFormatOnTypeProvider(resource: URI, position: types.Position, ch: string, options: vscode.FormattingOptions): Thenable<vscode.TextEdit[]> {
|
||||
const args = {
|
||||
resource,
|
||||
position: typeConverters.fromPosition(position),
|
||||
position: typeConverters.Position.from(position),
|
||||
ch,
|
||||
options
|
||||
};
|
||||
return this._commands.executeCommand<ISingleEditOperation[]>('_executeFormatOnTypeProvider', args)
|
||||
.then(tryMapWith(edit => new types.TextEdit(typeConverters.toRange(edit.range), edit.text)));
|
||||
.then(tryMapWith(edit => new types.TextEdit(typeConverters.Range.to(edit.range), edit.text)));
|
||||
}
|
||||
|
||||
private _executeDocumentLinkProvider(resource: URI): Thenable<vscode.DocumentLink[]> {
|
||||
return this._commands.executeCommand<modes.ILink[]>('_executeLinkProvider', resource)
|
||||
.then(tryMapWith(typeConverters.DocumentLink.to));
|
||||
}
|
||||
|
||||
private _executeTaskProvider(): Thenable<vscode.Task[]> {
|
||||
return this._tasks.fetchTasks();
|
||||
}
|
||||
}
|
||||
|
||||
function tryMapWith<T, R>(f: (x: T) => R) {
|
||||
|
||||
@@ -92,10 +92,10 @@ export class ExtHostCommands implements ExtHostCommandsShape {
|
||||
|
||||
args = cloneAndChange(args, function (value) {
|
||||
if (value instanceof extHostTypes.Position) {
|
||||
return extHostTypeConverter.fromPosition(value);
|
||||
return extHostTypeConverter.Position.from(value);
|
||||
}
|
||||
if (value instanceof extHostTypes.Range) {
|
||||
return extHostTypeConverter.fromRange(value);
|
||||
return extHostTypeConverter.Range.from(value);
|
||||
}
|
||||
if (value instanceof extHostTypes.Location) {
|
||||
return extHostTypeConverter.location.from(value);
|
||||
@@ -133,6 +133,8 @@ export class ExtHostCommands implements ExtHostCommandsShape {
|
||||
}
|
||||
|
||||
$executeContributedCommand<T>(id: string, ...args: any[]): Thenable<T> {
|
||||
this._logService.trace('ExtHostCommands#$executeContributedCommand', id);
|
||||
|
||||
if (!this._commands.has(id)) {
|
||||
// {{ SQL CARBON EDIT }} - Add type assertion to fix build break
|
||||
return <any>Promise.reject(new Error(`Contributed command '${id}' does not exist.`));
|
||||
|
||||
179
src/vs/workbench/api/node/extHostComments.ts
Normal file
179
src/vs/workbench/api/node/extHostComments.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { asWinJsPromise } from 'vs/base/common/async';
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments';
|
||||
import * as extHostTypeConverter from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import * as vscode from 'vscode';
|
||||
import { ExtHostCommentsShape, IMainContext, MainContext, MainThreadCommentsShape } from './extHost.protocol';
|
||||
import { CommandsConverter } from './extHostCommands';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
|
||||
export class ExtHostComments implements ExtHostCommentsShape {
|
||||
private static handlePool = 0;
|
||||
|
||||
private _proxy: MainThreadCommentsShape;
|
||||
|
||||
private _documentProviders = new Map<number, vscode.DocumentCommentProvider>();
|
||||
private _workspaceProviders = new Map<number, vscode.WorkspaceCommentProvider>();
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
private readonly _commandsConverter: CommandsConverter,
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadComments);
|
||||
}
|
||||
|
||||
registerWorkspaceCommentProvider(
|
||||
provider: vscode.WorkspaceCommentProvider
|
||||
): vscode.Disposable {
|
||||
const handle = ExtHostComments.handlePool++;
|
||||
this._workspaceProviders.set(handle, provider);
|
||||
this._proxy.$registerWorkspaceCommentProvider(handle);
|
||||
this.registerListeners(handle, provider);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
this._proxy.$unregisterWorkspaceCommentProvider(handle);
|
||||
this._workspaceProviders.delete(handle);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
registerDocumentCommentProvider(
|
||||
provider: vscode.DocumentCommentProvider
|
||||
): vscode.Disposable {
|
||||
const handle = ExtHostComments.handlePool++;
|
||||
this._documentProviders.set(handle, provider);
|
||||
this._proxy.$registerDocumentCommentProvider(handle);
|
||||
this.registerListeners(handle, provider);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
this._proxy.$unregisterDocumentCommentProvider(handle);
|
||||
this._documentProviders.delete(handle);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$createNewCommentThread(handle: number, uri: UriComponents, range: IRange, text: string): TPromise<modes.CommentThread> {
|
||||
const data = this._documents.getDocumentData(URI.revive(uri));
|
||||
const ran = <vscode.Range>extHostTypeConverter.Range.to(range);
|
||||
|
||||
if (!data || !data.document) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
return asWinJsPromise(token => {
|
||||
let provider = this._documentProviders.get(handle);
|
||||
return provider.createNewCommentThread(data.document, ran, text, token);
|
||||
}).then(commentThread => commentThread ? convertToCommentThread(commentThread, this._commandsConverter) : null);
|
||||
}
|
||||
|
||||
$replyToCommentThread(handle: number, uri: UriComponents, range: IRange, thread: modes.CommentThread, text: string): TPromise<modes.CommentThread> {
|
||||
const data = this._documents.getDocumentData(URI.revive(uri));
|
||||
const ran = <vscode.Range>extHostTypeConverter.Range.to(range);
|
||||
|
||||
if (!data || !data.document) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
return asWinJsPromise(token => {
|
||||
let provider = this._documentProviders.get(handle);
|
||||
return provider.replyToCommentThread(data.document, ran, convertFromCommentThread(thread), text, token);
|
||||
}).then(commentThread => commentThread ? convertToCommentThread(commentThread, this._commandsConverter) : null);
|
||||
}
|
||||
|
||||
$provideDocumentComments(handle: number, uri: UriComponents): TPromise<modes.CommentInfo> {
|
||||
const data = this._documents.getDocumentData(URI.revive(uri));
|
||||
if (!data || !data.document) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
return asWinJsPromise(token => {
|
||||
let provider = this._documentProviders.get(handle);
|
||||
return provider.provideDocumentComments(data.document, token);
|
||||
})
|
||||
.then(commentInfo => commentInfo ? convertCommentInfo(handle, commentInfo, this._commandsConverter) : null);
|
||||
}
|
||||
|
||||
$provideWorkspaceComments(handle: number): TPromise<modes.CommentThread[]> {
|
||||
const provider = this._workspaceProviders.get(handle);
|
||||
if (!provider) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
return asWinJsPromise(token => {
|
||||
return provider.provideWorkspaceComments(token);
|
||||
}).then(comments =>
|
||||
comments.map(x => convertToCommentThread(x, this._commandsConverter)
|
||||
));
|
||||
}
|
||||
|
||||
private registerListeners(handle: number, provider: vscode.DocumentCommentProvider | vscode.WorkspaceCommentProvider) {
|
||||
provider.onDidChangeCommentThreads(event => {
|
||||
|
||||
this._proxy.$onDidCommentThreadsChange(handle, {
|
||||
owner: handle,
|
||||
changed: event.changed.map(x => convertToCommentThread(x, this._commandsConverter)),
|
||||
added: event.added.map(x => convertToCommentThread(x, this._commandsConverter)),
|
||||
removed: event.removed.map(x => convertToCommentThread(x, this._commandsConverter))
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function convertCommentInfo(owner: number, vscodeCommentInfo: vscode.CommentInfo, commandsConverter: CommandsConverter): modes.CommentInfo {
|
||||
return {
|
||||
owner: owner,
|
||||
threads: vscodeCommentInfo.threads.map(x => convertToCommentThread(x, commandsConverter)),
|
||||
commentingRanges: vscodeCommentInfo.commentingRanges ? vscodeCommentInfo.commentingRanges.map(range => extHostTypeConverter.Range.from(range)) : []
|
||||
};
|
||||
}
|
||||
|
||||
function convertToCommentThread(vscodeCommentThread: vscode.CommentThread, commandsConverter: CommandsConverter): modes.CommentThread {
|
||||
return {
|
||||
threadId: vscodeCommentThread.threadId,
|
||||
resource: vscodeCommentThread.resource.toString(),
|
||||
range: extHostTypeConverter.Range.from(vscodeCommentThread.range),
|
||||
comments: vscodeCommentThread.comments.map(comment => convertToComment(comment, commandsConverter)),
|
||||
collapsibleState: vscodeCommentThread.collapsibleState
|
||||
};
|
||||
}
|
||||
|
||||
function convertFromCommentThread(commentThread: modes.CommentThread): vscode.CommentThread {
|
||||
return {
|
||||
threadId: commentThread.threadId,
|
||||
resource: URI.parse(commentThread.resource),
|
||||
range: extHostTypeConverter.Range.to(commentThread.range),
|
||||
comments: commentThread.comments.map(convertFromComment),
|
||||
collapsibleState: commentThread.collapsibleState
|
||||
};
|
||||
}
|
||||
|
||||
function convertFromComment(comment: modes.Comment): vscode.Comment {
|
||||
return {
|
||||
commentId: comment.commentId,
|
||||
body: extHostTypeConverter.MarkdownString.to(comment.body),
|
||||
userName: comment.userName,
|
||||
gravatar: comment.gravatar
|
||||
};
|
||||
}
|
||||
|
||||
function convertToComment(vscodeComment: vscode.Comment, commandsConverter: CommandsConverter): modes.Comment {
|
||||
return {
|
||||
commentId: vscodeComment.commentId,
|
||||
body: extHostTypeConverter.MarkdownString.from(vscodeComment.body),
|
||||
userName: vscodeComment.userName,
|
||||
gravatar: vscodeComment.gravatar,
|
||||
command: vscodeComment.command ? commandsConverter.toInternal(vscodeComment.command) : null
|
||||
};
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import { IConfigurationData, ConfigurationTarget, IConfigurationModel } from 'vs
|
||||
import { Configuration, ConfigurationChangeEvent, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels';
|
||||
import { WorkspaceConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels';
|
||||
import { ResourceMap } from 'vs/base/common/map';
|
||||
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { isObject } from 'vs/base/common/types';
|
||||
|
||||
declare var Proxy: any; // TODO@TypeScript
|
||||
@@ -121,17 +121,17 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape {
|
||||
}
|
||||
return result;
|
||||
},
|
||||
set: (target: any, property: string, value: any) => {
|
||||
set: (_target: any, property: string, value: any) => {
|
||||
cloneTarget();
|
||||
clonedTarget[property] = value;
|
||||
return true;
|
||||
},
|
||||
deleteProperty: (target: any, property: string) => {
|
||||
deleteProperty: (_target: any, property: string) => {
|
||||
cloneTarget();
|
||||
delete clonedTarget[property];
|
||||
return true;
|
||||
},
|
||||
defineProperty: (target: any, property: string, descriptor: any) => {
|
||||
defineProperty: (_target: any, property: string, descriptor: any) => {
|
||||
cloneTarget();
|
||||
Object.defineProperty(clonedTarget, property, descriptor);
|
||||
return true;
|
||||
@@ -179,10 +179,10 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape {
|
||||
return isObject(target) ?
|
||||
new Proxy(target, {
|
||||
get: (target: any, property: string) => readonlyProxy(target[property]),
|
||||
set: (target: any, property: string, value: any) => { throw new Error(`TypeError: Cannot assign to read only property '${property}' of object`); },
|
||||
deleteProperty: (target: any, property: string) => { throw new Error(`TypeError: Cannot delete read only property '${property}' of object`); },
|
||||
defineProperty: (target: any, property: string) => { throw new Error(`TypeError: Cannot define property '${property}' for a readonly object`); },
|
||||
setPrototypeOf: (target: any) => { throw new Error(`TypeError: Cannot set prototype for a readonly object`); },
|
||||
set: (_target: any, property: string, _value: any) => { throw new Error(`TypeError: Cannot assign to read only property '${property}' of object`); },
|
||||
deleteProperty: (_target: any, property: string) => { throw new Error(`TypeError: Cannot delete read only property '${property}' of object`); },
|
||||
defineProperty: (_target: any, property: string) => { throw new Error(`TypeError: Cannot define property '${property}' for a readonly object`); },
|
||||
setPrototypeOf: (_target: any) => { throw new Error(`TypeError: Cannot set prototype for a readonly object`); },
|
||||
isExtensible: () => false,
|
||||
preventExtensions: () => true
|
||||
}) : target;
|
||||
@@ -191,7 +191,7 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape {
|
||||
}
|
||||
|
||||
private _validateConfigurationAccess(key: string, resource: URI, extensionId: string): void {
|
||||
const scope = this._configurationScopes[key];
|
||||
const scope = OVERRIDE_PROPERTY_PATTERN.test(key) ? ConfigurationScope.RESOURCE : this._configurationScopes[key];
|
||||
const extensionIdText = extensionId ? `[${extensionId}] ` : '';
|
||||
if (ConfigurationScope.RESOURCE === scope) {
|
||||
if (resource === void 0) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { asWinJsPromise } from 'vs/base/common/async';
|
||||
import * as nls from 'vs/nls';
|
||||
import {
|
||||
MainContext, MainThreadDebugServiceShape, ExtHostDebugServiceShape, DebugSessionUUID,
|
||||
IMainContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, IFunctionBreakpointDto
|
||||
@@ -16,17 +17,19 @@ import {
|
||||
import * as vscode from 'vscode';
|
||||
import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { DebugAdapter, convertToVSCPaths, convertToDAPaths } from 'vs/workbench/parts/debug/node/debugAdapter';
|
||||
import { DebugAdapter, StreamDebugAdapter, SocketDebugAdapter } from 'vs/workbench/parts/debug/node/debugAdapter';
|
||||
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
|
||||
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors';
|
||||
import { IAdapterExecutable, ITerminalSettings, IDebuggerContribution, IConfig } from 'vs/workbench/parts/debug/common/debug';
|
||||
import { getTerminalLauncher } from 'vs/workbench/parts/debug/node/terminals';
|
||||
import { IAdapterExecutable, ITerminalSettings, IDebuggerContribution, IConfig, IDebugAdapter } from 'vs/workbench/parts/debug/common/debug';
|
||||
import { getTerminalLauncher, hasChildprocesses, prepareCommand } from 'vs/workbench/parts/debug/node/terminals';
|
||||
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { VariableResolver } from 'vs/workbench/services/configurationResolver/node/variableResolver';
|
||||
import { IConfigurationResolverService } from '../../services/configurationResolver/common/configurationResolver';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/node/variableResolver';
|
||||
import { ExtHostConfiguration } from './extHostConfiguration';
|
||||
import { convertToVSCPaths, convertToDAPaths } from 'vs/workbench/parts/debug/common/debugUtils';
|
||||
import { ExtHostTerminalService } from 'vs/workbench/api/node/extHostTerminalService';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
|
||||
|
||||
|
||||
export class ExtHostDebugService implements ExtHostDebugServiceShape {
|
||||
@@ -60,16 +63,20 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
|
||||
|
||||
private readonly _onDidChangeBreakpoints: Emitter<vscode.BreakpointsChangeEvent>;
|
||||
|
||||
private _debugAdapters: Map<number, DebugAdapter>;
|
||||
private _debugAdapters: Map<number, IDebugAdapter>;
|
||||
|
||||
private _variableResolver: IConfigurationResolverService;
|
||||
|
||||
private _integratedTerminalInstance: vscode.Terminal;
|
||||
private _terminalDisposedListener: IDisposable;
|
||||
|
||||
|
||||
constructor(mainContext: IMainContext,
|
||||
private _workspace: ExtHostWorkspace,
|
||||
private _workspaceService: ExtHostWorkspace,
|
||||
private _extensionService: ExtHostExtensionService,
|
||||
private _editorsService: ExtHostDocumentsAndEditors,
|
||||
private _configurationService: ExtHostConfiguration
|
||||
private _configurationService: ExtHostConfiguration,
|
||||
private _terminalService: ExtHostTerminalService
|
||||
) {
|
||||
|
||||
this._handleCounter = 0;
|
||||
@@ -116,37 +123,109 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
|
||||
}
|
||||
|
||||
public $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise<void> {
|
||||
const terminalLauncher = getTerminalLauncher();
|
||||
if (terminalLauncher) {
|
||||
return terminalLauncher.runInTerminal(args, config);
|
||||
|
||||
if (args.kind === 'integrated') {
|
||||
|
||||
if (!this._terminalDisposedListener) {
|
||||
// React on terminal disposed and check if that is the debug terminal #12956
|
||||
this._terminalDisposedListener = this._terminalService.onDidCloseTerminal(terminal => {
|
||||
if (this._integratedTerminalInstance && this._integratedTerminalInstance === terminal) {
|
||||
this._integratedTerminalInstance = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return new TPromise(resolve => {
|
||||
if (this._integratedTerminalInstance) {
|
||||
this._integratedTerminalInstance.processId.then(pid => {
|
||||
resolve(hasChildprocesses(pid));
|
||||
}, err => {
|
||||
resolve(true);
|
||||
});
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
}).then(needNewTerminal => {
|
||||
|
||||
if (needNewTerminal) {
|
||||
this._integratedTerminalInstance = this._terminalService.createTerminal(args.title || nls.localize('debug.terminal.title', "debuggee"));
|
||||
}
|
||||
|
||||
this._integratedTerminalInstance.show();
|
||||
|
||||
return new TPromise((resolve, error) => {
|
||||
setTimeout(_ => {
|
||||
const command = prepareCommand(args, config);
|
||||
this._integratedTerminalInstance.sendText(command, true);
|
||||
resolve(void 0);
|
||||
}, 500);
|
||||
});
|
||||
});
|
||||
|
||||
} else if (args.kind === 'external') {
|
||||
|
||||
const terminalLauncher = getTerminalLauncher();
|
||||
if (terminalLauncher) {
|
||||
return terminalLauncher.runInTerminal(args, config);
|
||||
}
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
|
||||
public $substituteVariables(folderUri: UriComponents | undefined, config: IConfig): TPromise<IConfig> {
|
||||
if (!this._variableResolver) {
|
||||
this._variableResolver = new ExtHostVariableResolverService(this._workspace, this._editorsService, this._configurationService);
|
||||
this._variableResolver = new ExtHostVariableResolverService(this._workspaceService, this._editorsService, this._configurationService);
|
||||
}
|
||||
const folder = <IWorkspaceFolder>this.getFolder(folderUri);
|
||||
return asWinJsPromise(token => DebugAdapter.substituteVariables(folder, config, this._variableResolver));
|
||||
let ws: IWorkspaceFolder;
|
||||
const folder = this.getFolder(folderUri);
|
||||
if (folder) {
|
||||
ws = {
|
||||
uri: folder.uri,
|
||||
name: folder.name,
|
||||
index: folder.index,
|
||||
toResource: () => {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
};
|
||||
}
|
||||
return asWinJsPromise(token => this._variableResolver.resolveAny(ws, config));
|
||||
}
|
||||
|
||||
public $startDASession(handle: number, debugType: string, adpaterExecutable: IAdapterExecutable | null): TPromise<void> {
|
||||
public $startDASession(handle: number, debugType: string, adpaterExecutable: IAdapterExecutable | null, debugPort: number): TPromise<void> {
|
||||
const mythis = this;
|
||||
|
||||
const da = new class extends DebugAdapter {
|
||||
let da: StreamDebugAdapter = null;
|
||||
|
||||
// DA -> VS Code
|
||||
public acceptMessage(message: DebugProtocol.ProtocolMessage) {
|
||||
convertToVSCPaths(message, source => {
|
||||
if (paths.isAbsolute(source.path)) {
|
||||
(<any>source).path = URI.file(source.path);
|
||||
}
|
||||
});
|
||||
mythis._debugServiceProxy.$acceptDAMessage(handle, message);
|
||||
}
|
||||
if (debugPort > 0) {
|
||||
da = new class extends SocketDebugAdapter {
|
||||
|
||||
}(debugType, adpaterExecutable, this._extensionService.getAllExtensionDescriptions());
|
||||
// DA -> VS Code
|
||||
public acceptMessage(message: DebugProtocol.ProtocolMessage) {
|
||||
convertToVSCPaths(message, source => {
|
||||
if (paths.isAbsolute(source.path)) {
|
||||
(<any>source).path = URI.file(source.path);
|
||||
}
|
||||
});
|
||||
mythis._debugServiceProxy.$acceptDAMessage(handle, message);
|
||||
}
|
||||
|
||||
}(debugPort);
|
||||
|
||||
} else {
|
||||
da = new class extends DebugAdapter {
|
||||
|
||||
// DA -> VS Code
|
||||
public acceptMessage(message: DebugProtocol.ProtocolMessage) {
|
||||
convertToVSCPaths(message, source => {
|
||||
if (paths.isAbsolute(source.path)) {
|
||||
(<any>source).path = URI.file(source.path);
|
||||
}
|
||||
});
|
||||
mythis._debugServiceProxy.$acceptDAMessage(handle, message);
|
||||
}
|
||||
|
||||
}(debugType, adpaterExecutable, this._extensionService.getAllExtensionDescriptions());
|
||||
}
|
||||
|
||||
this._debugAdapters.set(handle, da);
|
||||
da.onError(err => this._debugServiceProxy.$acceptDAError(handle, err.name, err.message, err.stack));
|
||||
@@ -346,9 +425,9 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
|
||||
private fireBreakpointChanges(added: vscode.Breakpoint[], removed: vscode.Breakpoint[], changed: vscode.Breakpoint[]) {
|
||||
if (added.length > 0 || removed.length > 0 || changed.length > 0) {
|
||||
this._onDidChangeBreakpoints.fire(Object.freeze({
|
||||
added: Object.freeze<vscode.Breakpoint[]>(added),
|
||||
removed: Object.freeze<vscode.Breakpoint[]>(removed),
|
||||
changed: Object.freeze<vscode.Breakpoint[]>(changed)
|
||||
added,
|
||||
removed,
|
||||
changed,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -458,14 +537,10 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
|
||||
this._onDidReceiveDebugSessionCustomEvent.fire(ee);
|
||||
}
|
||||
|
||||
private getFolder(_folderUri: UriComponents | undefined) {
|
||||
private getFolder(_folderUri: UriComponents | undefined): vscode.WorkspaceFolder | undefined {
|
||||
if (_folderUri) {
|
||||
const folderUriString = URI.revive(_folderUri).toString();
|
||||
const folders = this._workspace.getWorkspaceFolders();
|
||||
const found = folders.filter(f => f.uri.toString() === folderUriString);
|
||||
if (found && found.length > 0) {
|
||||
return found[0];
|
||||
}
|
||||
const folderURI = URI.revive(_folderUri);
|
||||
return this._workspaceService.resolveWorkspaceFolder(folderURI);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -525,5 +600,16 @@ export class ExtHostDebugConsole implements vscode.DebugConsole {
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostVariableResolverService implements IConfigurationResolverService {
|
||||
constructor(workspace: ExtHostWorkspace, editors: ExtHostDocumentsAndEditors, configuration: ExtHostConfiguration) {
|
||||
this._variableResolver = new VariableResolver({
|
||||
const folders = workspace.getWorkspaceFolders();
|
||||
return workspace.getWorkspaceFolders().length;
|
||||
return configuration.getConfiguration(undefined, folderUri).get<string>(section);
|
||||
const activeEditor = editors.activeEditor();
|
||||
const activeEditor = editors.activeEditor();
|
||||
const activeEditor = editors.activeEditor();
|
||||
}
|
||||
}
|
||||
// {{SQL CARBON EDIT}}
|
||||
*/
|
||||
|
||||
@@ -17,17 +17,19 @@ import { keys } from 'vs/base/common/map';
|
||||
|
||||
export class DiagnosticCollection implements vscode.DiagnosticCollection {
|
||||
|
||||
private static readonly _maxDiagnosticsPerFile: number = 250;
|
||||
|
||||
private readonly _name: string;
|
||||
private readonly _owner: string;
|
||||
private readonly _maxDiagnosticsPerFile: number;
|
||||
private readonly _onDidChangeDiagnostics: Emitter<(vscode.Uri | string)[]>;
|
||||
private readonly _proxy: MainThreadDiagnosticsShape;
|
||||
|
||||
private _proxy: MainThreadDiagnosticsShape;
|
||||
private _isDisposed = false;
|
||||
private _data = new Map<string, vscode.Diagnostic[]>();
|
||||
|
||||
constructor(name: string, proxy: MainThreadDiagnosticsShape, onDidChangeDiagnostics: Emitter<(vscode.Uri | string)[]>) {
|
||||
constructor(name: string, owner: string, maxDiagnosticsPerFile: number, proxy: MainThreadDiagnosticsShape, onDidChangeDiagnostics: Emitter<(vscode.Uri | string)[]>) {
|
||||
this._name = name;
|
||||
this._owner = owner;
|
||||
this._maxDiagnosticsPerFile = maxDiagnosticsPerFile;
|
||||
this._proxy = proxy;
|
||||
this._onDidChangeDiagnostics = onDidChangeDiagnostics;
|
||||
}
|
||||
@@ -35,8 +37,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection {
|
||||
dispose(): void {
|
||||
if (!this._isDisposed) {
|
||||
this._onDidChangeDiagnostics.fire(keys(this._data));
|
||||
this._proxy.$clear(this.name);
|
||||
this._proxy = undefined;
|
||||
this._proxy.$clear(this._owner);
|
||||
this._data = undefined;
|
||||
this._isDisposed = true;
|
||||
}
|
||||
@@ -109,15 +110,15 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection {
|
||||
let diagnostics = this._data.get(uri.toString());
|
||||
if (diagnostics) {
|
||||
|
||||
// no more than 250 diagnostics per file
|
||||
if (diagnostics.length > DiagnosticCollection._maxDiagnosticsPerFile) {
|
||||
// no more than N diagnostics per file
|
||||
if (diagnostics.length > this._maxDiagnosticsPerFile) {
|
||||
marker = [];
|
||||
const order = [DiagnosticSeverity.Error, DiagnosticSeverity.Warning, DiagnosticSeverity.Information, DiagnosticSeverity.Hint];
|
||||
orderLoop: for (let i = 0; i < 4; i++) {
|
||||
for (let diagnostic of diagnostics) {
|
||||
if (diagnostic.severity === order[i]) {
|
||||
const len = marker.push(converter.fromDiagnostic(diagnostic));
|
||||
if (len === DiagnosticCollection._maxDiagnosticsPerFile) {
|
||||
const len = marker.push(converter.Diagnostic.from(diagnostic));
|
||||
if (len === this._maxDiagnosticsPerFile) {
|
||||
break orderLoop;
|
||||
}
|
||||
}
|
||||
@@ -126,15 +127,15 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection {
|
||||
|
||||
// add 'signal' marker for showing omitted errors/warnings
|
||||
marker.push({
|
||||
severity: MarkerSeverity.Error,
|
||||
message: localize({ key: 'limitHit', comment: ['amount of errors/warning skipped due to limits'] }, "Not showing {0} further errors and warnings.", diagnostics.length - DiagnosticCollection._maxDiagnosticsPerFile),
|
||||
severity: MarkerSeverity.Info,
|
||||
message: localize({ key: 'limitHit', comment: ['amount of errors/warning skipped due to limits'] }, "Not showing {0} further errors and warnings.", diagnostics.length - this._maxDiagnosticsPerFile),
|
||||
startLineNumber: marker[marker.length - 1].startLineNumber,
|
||||
startColumn: marker[marker.length - 1].startColumn,
|
||||
endLineNumber: marker[marker.length - 1].endLineNumber,
|
||||
endColumn: marker[marker.length - 1].endColumn
|
||||
});
|
||||
} else {
|
||||
marker = diagnostics.map(converter.fromDiagnostic);
|
||||
marker = diagnostics.map(converter.Diagnostic.from);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,20 +143,21 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection {
|
||||
}
|
||||
|
||||
this._onDidChangeDiagnostics.fire(toSync);
|
||||
this._proxy.$changeMany(this.name, entries);
|
||||
this._proxy.$changeMany(this._owner, entries);
|
||||
}
|
||||
|
||||
delete(uri: vscode.Uri): void {
|
||||
this._checkDisposed();
|
||||
this._onDidChangeDiagnostics.fire([uri]);
|
||||
this._data.delete(uri.toString());
|
||||
this._proxy.$changeMany(this.name, [[uri, undefined]]);
|
||||
this._proxy.$changeMany(this._owner, [[uri, undefined]]);
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this._checkDisposed();
|
||||
this._onDidChangeDiagnostics.fire(keys(this._data));
|
||||
this._data.clear();
|
||||
this._proxy.$clear(this.name);
|
||||
this._proxy.$clear(this._owner);
|
||||
}
|
||||
|
||||
forEach(callback: (uri: URI, diagnostics: vscode.Diagnostic[], collection: DiagnosticCollection) => any, thisArg?: any): void {
|
||||
@@ -200,9 +202,10 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection {
|
||||
export class ExtHostDiagnostics implements ExtHostDiagnosticsShape {
|
||||
|
||||
private static _idPool: number = 0;
|
||||
private static readonly _maxDiagnosticsPerFile: number = 1000;
|
||||
|
||||
private readonly _proxy: MainThreadDiagnosticsShape;
|
||||
private readonly _collections: DiagnosticCollection[] = [];
|
||||
private readonly _collections = new Map<string, DiagnosticCollection>();
|
||||
private readonly _onDidChangeDiagnostics = new Emitter<(vscode.Uri | string)[]>();
|
||||
|
||||
static _debouncer(last: (vscode.Uri | string)[], current: (vscode.Uri | string)[]): (vscode.Uri | string)[] {
|
||||
@@ -240,22 +243,28 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape {
|
||||
}
|
||||
|
||||
createDiagnosticCollection(name: string): vscode.DiagnosticCollection {
|
||||
let { _collections, _proxy, _onDidChangeDiagnostics } = this;
|
||||
let owner: string;
|
||||
if (!name) {
|
||||
name = '_generated_diagnostic_collection_name_#' + ExtHostDiagnostics._idPool++;
|
||||
owner = name;
|
||||
} else if (!_collections.has(name)) {
|
||||
owner = name;
|
||||
} else {
|
||||
console.warn(`DiagnosticCollection with name '${name}' does already exist.`);
|
||||
do {
|
||||
owner = name + ExtHostDiagnostics._idPool++;
|
||||
} while (_collections.has(owner));
|
||||
}
|
||||
|
||||
const { _collections, _proxy, _onDidChangeDiagnostics } = this;
|
||||
const result = new class extends DiagnosticCollection {
|
||||
constructor() {
|
||||
super(name, _proxy, _onDidChangeDiagnostics);
|
||||
_collections.push(this);
|
||||
super(name, owner, ExtHostDiagnostics._maxDiagnosticsPerFile, _proxy, _onDidChangeDiagnostics);
|
||||
_collections.set(owner, this);
|
||||
}
|
||||
dispose() {
|
||||
super.dispose();
|
||||
let idx = _collections.indexOf(this);
|
||||
if (idx !== -1) {
|
||||
_collections.splice(idx, 1);
|
||||
}
|
||||
_collections.delete(owner);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -270,7 +279,7 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape {
|
||||
} else {
|
||||
let index = new Map<string, number>();
|
||||
let res: [vscode.Uri, vscode.Diagnostic[]][] = [];
|
||||
for (const collection of this._collections) {
|
||||
this._collections.forEach(collection => {
|
||||
collection.forEach((uri, diagnostics) => {
|
||||
let idx = index.get(uri.toString());
|
||||
if (typeof idx === 'undefined') {
|
||||
@@ -280,18 +289,18 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape {
|
||||
}
|
||||
res[idx][1] = res[idx][1].concat(...diagnostics);
|
||||
});
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
private _getDiagnostics(resource: vscode.Uri): vscode.Diagnostic[] {
|
||||
let res: vscode.Diagnostic[] = [];
|
||||
for (const collection of this._collections) {
|
||||
this._collections.forEach(collection => {
|
||||
if (collection.has(resource)) {
|
||||
res = res.concat(collection.get(resource));
|
||||
}
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { sequence, always } from 'vs/base/common/async';
|
||||
import { illegalState } from 'vs/base/common/errors';
|
||||
import { ExtHostDocumentSaveParticipantShape, MainThreadTextEditorsShape, ResourceTextEditDto } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { TextEdit } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { fromRange, TextDocumentSaveReason, EndOfLine } from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { Range, TextDocumentSaveReason, EndOfLine } from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments';
|
||||
import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import * as vscode from 'vscode';
|
||||
@@ -78,32 +78,32 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
return this._deliverEventAsync(listener, thisArg, stubEvent).then(() => {
|
||||
return this._deliverEventAsync(extension, listener, thisArg, stubEvent).then(() => {
|
||||
// don't send result across the wire
|
||||
return true;
|
||||
|
||||
}, err => {
|
||||
|
||||
this._logService.error('[onWillSaveTextDocument]', extension.id);
|
||||
this._logService.error(`onWillSaveTextDocument-listener from extension '${extension.id}' threw ERROR`);
|
||||
this._logService.error(err);
|
||||
|
||||
if (!(err instanceof Error) || (<Error>err).message !== 'concurrent_edits') {
|
||||
const errors = this._badListeners.get(listener);
|
||||
this._badListeners.set(listener, !errors ? 1 : errors + 1);
|
||||
|
||||
// todo@joh signal to the listener?
|
||||
// if (errors === this._thresholds.errors) {
|
||||
// console.warn('BAD onWillSaveTextDocumentEvent-listener is from now on being ignored');
|
||||
// }
|
||||
if (errors > this._thresholds.errors) {
|
||||
this._logService.info(`onWillSaveTextDocument-listener from extension '${extension.id}' will now be IGNORED because of timeouts and/or errors`);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private _deliverEventAsync(listener: Function, thisArg: any, stubEvent: vscode.TextDocumentWillSaveEvent): Promise<any> {
|
||||
private _deliverEventAsync(extension: IExtensionDescription, listener: Function, thisArg: any, stubEvent: vscode.TextDocumentWillSaveEvent): Promise<any> {
|
||||
|
||||
const promises: Promise<vscode.TextEdit[]>[] = [];
|
||||
|
||||
const t1 = Date.now();
|
||||
const { document, reason } = stubEvent;
|
||||
const { version } = document;
|
||||
|
||||
@@ -133,6 +133,7 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic
|
||||
const handle = setTimeout(() => reject(new Error('timeout')), this._thresholds.timeout);
|
||||
|
||||
return Promise.all(promises).then(edits => {
|
||||
this._logService.debug(`onWillSaveTextDocument-listener from extension '${extension.id}' finished after ${(Date.now() - t1)}ms`);
|
||||
clearTimeout(handle);
|
||||
resolve(edits);
|
||||
}).catch(err => {
|
||||
@@ -151,7 +152,7 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic
|
||||
if (Array.isArray(value) && (<vscode.TextEdit[]>value).every(e => e instanceof TextEdit)) {
|
||||
for (const { newText, newEol, range } of value) {
|
||||
resourceEdit.edits.push({
|
||||
range: range && fromRange(range),
|
||||
range: range && Range.from(range),
|
||||
text: newText,
|
||||
eol: EndOfLine.from(newEol)
|
||||
});
|
||||
|
||||
@@ -136,7 +136,7 @@ export class ExtHostDocuments implements ExtHostDocumentsShape {
|
||||
document: data.document,
|
||||
contentChanges: events.changes.map((change) => {
|
||||
return {
|
||||
range: TypeConverters.toRange(change.range),
|
||||
range: TypeConverters.Range.to(change.range),
|
||||
rangeOffset: change.rangeOffset,
|
||||
rangeLength: change.rangeLength,
|
||||
text: change.text
|
||||
|
||||
@@ -95,10 +95,10 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
|
||||
this._mainContext.getProxy(MainContext.MainThreadTextEditors),
|
||||
data.id,
|
||||
documentData,
|
||||
data.selections.map(typeConverters.toSelection),
|
||||
data.selections.map(typeConverters.Selection.to),
|
||||
data.options,
|
||||
data.visibleRanges.map(typeConverters.toRange),
|
||||
typeConverters.toViewColumn(data.editorPosition)
|
||||
data.visibleRanges.map(typeConverters.Range.to),
|
||||
typeConverters.ViewColumn.to(data.editorPosition)
|
||||
);
|
||||
this._editors.set(data.id, editor);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { ExtHostStorage } from 'vs/workbench/api/node/extHostStorage';
|
||||
// {{SQL CARBON EDIT}}
|
||||
import { createApiFactory, initializeExtensionApi } from 'sql/workbench/api/node/sqlExtHost.api.impl';
|
||||
import { checkProposedApiEnabled } from 'vs/workbench/api/node/extHost.api.impl';
|
||||
import { MainContext, MainThreadExtensionServiceShape, IWorkspaceData, IEnvironment, IInitData, ExtHostExtensionServiceShape, MainThreadTelemetryShape, IExtHostContext } from './extHost.protocol';
|
||||
import { MainContext, MainThreadExtensionServiceShape, IWorkspaceData, IEnvironment, IInitData, ExtHostExtensionServiceShape, MainThreadTelemetryShape, IMainContext } from './extHost.protocol';
|
||||
import { IExtensionMemento, ExtensionsActivator, ActivatedExtension, IExtensionAPI, IExtensionContext, EmptyExtension, IExtensionModule, ExtensionActivationTimesBuilder, ExtensionActivationTimes, ExtensionActivationReason, ExtensionActivatedByEvent } from 'vs/workbench/api/node/extHostExtensionActivator';
|
||||
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
|
||||
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
|
||||
@@ -70,7 +70,7 @@ class ExtensionStoragePath {
|
||||
private readonly _workspace: IWorkspaceData;
|
||||
private readonly _environment: IEnvironment;
|
||||
|
||||
private readonly _ready: TPromise<string>;
|
||||
private readonly _ready: Promise<string>;
|
||||
private _value: string;
|
||||
|
||||
constructor(workspace: IWorkspaceData, environment: IEnvironment) {
|
||||
@@ -79,7 +79,7 @@ class ExtensionStoragePath {
|
||||
this._ready = this._getOrCreateWorkspaceStoragePath().then(value => this._value = value);
|
||||
}
|
||||
|
||||
get whenReady(): TPromise<any> {
|
||||
get whenReady(): Promise<any> {
|
||||
return this._ready;
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ class ExtensionStoragePath {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async _getOrCreateWorkspaceStoragePath(): TPromise<string> {
|
||||
private async _getOrCreateWorkspaceStoragePath(): Promise<string> {
|
||||
if (!this._workspace) {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
@@ -138,7 +138,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
|
||||
* This class is constructed manually because it is a service, so it doesn't use any ctor injection
|
||||
*/
|
||||
constructor(initData: IInitData,
|
||||
extHostContext: IExtHostContext,
|
||||
extHostContext: IMainContext,
|
||||
extHostWorkspace: ExtHostWorkspace,
|
||||
extHostConfiguration: ExtHostConfiguration,
|
||||
extHostLogService: ExtHostLogService
|
||||
@@ -245,8 +245,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
|
||||
if (!ext.main) {
|
||||
return undefined;
|
||||
}
|
||||
return realpath(ext.extensionFolderPath).then(value => tree.set(value, ext));
|
||||
|
||||
return realpath(ext.extensionLocation.fsPath).then(value => tree.set(value, ext));
|
||||
});
|
||||
this._extensionPathIndex = TPromise.join(extensions).then(() => tree);
|
||||
}
|
||||
@@ -361,9 +360,9 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
|
||||
globalState,
|
||||
workspaceState,
|
||||
subscriptions: [],
|
||||
get extensionPath() { return extensionDescription.extensionFolderPath; },
|
||||
get extensionPath() { return extensionDescription.extensionLocation.fsPath; },
|
||||
storagePath: this._storagePath.value(extensionDescription),
|
||||
asAbsolutePath: (relativePath: string) => { return join(extensionDescription.extensionFolderPath, relativePath); },
|
||||
asAbsolutePath: (relativePath: string) => { return join(extensionDescription.extensionLocation.fsPath, relativePath); },
|
||||
get logger() {
|
||||
checkProposedApiEnabled(extensionDescription);
|
||||
return that._extHostLogService.getExtLogger(extensionDescription.id);
|
||||
|
||||
@@ -6,15 +6,13 @@
|
||||
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Event, mapEvent } from 'vs/base/common/event';
|
||||
import { MainContext, IMainContext, ExtHostFileSystemShape, MainThreadFileSystemShape, IFileChangeDto } from './extHost.protocol';
|
||||
import * as vscode from 'vscode';
|
||||
import * as files from 'vs/platform/files/common/files';
|
||||
import * as path from 'path';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { asWinJsPromise } from 'vs/base/common/async';
|
||||
import { values } from 'vs/base/common/map';
|
||||
import { Range, DeprecatedFileType, DeprecatedFileChangeType, FileChangeType } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { Range, FileChangeType } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { ExtHostLanguageFeatures } from 'vs/workbench/api/node/extHostLanguageFeatures';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
@@ -34,7 +32,7 @@ class FsLinkProvider implements vscode.DocumentLinkProvider {
|
||||
}
|
||||
}
|
||||
|
||||
provideDocumentLinks(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.ProviderResult<vscode.DocumentLink[]> {
|
||||
provideDocumentLinks(document: vscode.TextDocument): vscode.ProviderResult<vscode.DocumentLink[]> {
|
||||
if (this._schemes.size === 0) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -49,6 +47,9 @@ class FsLinkProvider implements vscode.DocumentLinkProvider {
|
||||
let m: RegExpMatchArray;
|
||||
while (m = this._regex.exec(textLine.text)) {
|
||||
const target = URI.parse(m[0]);
|
||||
if (target.path[0] !== '/') {
|
||||
continue;
|
||||
}
|
||||
const range = new Range(line, this._regex.lastIndex - m[0].length, line, this._regex.lastIndex);
|
||||
result.push({ target, range });
|
||||
}
|
||||
@@ -57,106 +58,6 @@ class FsLinkProvider implements vscode.DocumentLinkProvider {
|
||||
}
|
||||
}
|
||||
|
||||
class FileSystemProviderShim implements vscode.FileSystemProvider {
|
||||
|
||||
onDidChangeFile: vscode.Event<vscode.FileChangeEvent[]>;
|
||||
|
||||
constructor(private readonly _delegate: vscode.DeprecatedFileSystemProvider) {
|
||||
if (!this._delegate.onDidChange) {
|
||||
this.onDidChangeFile = Event.None;
|
||||
} else {
|
||||
this.onDidChangeFile = mapEvent(this._delegate.onDidChange, old => old.map(FileSystemProviderShim._modernizeFileChange));
|
||||
}
|
||||
}
|
||||
|
||||
watch(uri: vscode.Uri, options: {}): vscode.Disposable {
|
||||
// does nothing because in the old API there was no notion of
|
||||
// watch and provider decide what file events to generate...
|
||||
return { dispose() { } };
|
||||
}
|
||||
|
||||
stat(resource: vscode.Uri): Thenable<vscode.FileStat> {
|
||||
return this._delegate.stat(resource).then(stat => FileSystemProviderShim._modernizeFileStat(stat));
|
||||
}
|
||||
rename(oldUri: vscode.Uri, newUri: vscode.Uri): Thenable<void> {
|
||||
return this._delegate.move(oldUri, newUri).then(stat => void 0);
|
||||
}
|
||||
readDirectory(resource: vscode.Uri): Thenable<[string, vscode.FileType][]> {
|
||||
return this._delegate.readdir(resource).then(tuples => {
|
||||
return tuples.map(tuple => <[string, vscode.FileType]>[path.posix.basename(tuple[0].path), FileSystemProviderShim._modernizeFileStat(tuple[1]).type]);
|
||||
});
|
||||
}
|
||||
|
||||
private static _modernizeFileStat(stat: vscode.DeprecatedFileStat): vscode.FileStat {
|
||||
let { mtime, size, type } = stat;
|
||||
let newType: files.FileType;
|
||||
|
||||
// no support for bitmask, effectively no support for symlinks
|
||||
switch (type) {
|
||||
case DeprecatedFileType.Dir:
|
||||
newType = files.FileType.Directory;
|
||||
break;
|
||||
case DeprecatedFileType.File:
|
||||
newType = files.FileType.File;
|
||||
break;
|
||||
case DeprecatedFileType.Symlink:
|
||||
newType = files.FileType.File & files.FileType.SymbolicLink;
|
||||
break;
|
||||
}
|
||||
return { type: newType, ctime: 0, mtime, size };
|
||||
}
|
||||
|
||||
private static _modernizeFileChange(e: vscode.DeprecatedFileChange): vscode.FileChangeEvent {
|
||||
let { resource, type } = e;
|
||||
let newType: vscode.FileChangeType;
|
||||
switch (type) {
|
||||
case DeprecatedFileChangeType.Updated:
|
||||
newType = FileChangeType.Changed;
|
||||
break;
|
||||
case DeprecatedFileChangeType.Added:
|
||||
newType = FileChangeType.Created;
|
||||
break;
|
||||
case DeprecatedFileChangeType.Deleted:
|
||||
newType = FileChangeType.Deleted;
|
||||
break;
|
||||
|
||||
}
|
||||
return { uri: resource, type: newType };
|
||||
}
|
||||
|
||||
// --- delete/create file or folder
|
||||
|
||||
delete(resource: vscode.Uri): Thenable<void> {
|
||||
return this._delegate.stat(resource).then(stat => {
|
||||
if (stat.type === DeprecatedFileType.Dir) {
|
||||
return this._delegate.rmdir(resource);
|
||||
} else {
|
||||
return this._delegate.unlink(resource);
|
||||
}
|
||||
});
|
||||
}
|
||||
createDirectory(resource: vscode.Uri): Thenable<void> {
|
||||
return this._delegate.mkdir(resource).then(stat => void 0);
|
||||
}
|
||||
|
||||
// --- read/write
|
||||
|
||||
readFile(resource: vscode.Uri): Thenable<Uint8Array> {
|
||||
let chunks: Buffer[] = [];
|
||||
return this._delegate.read(resource, 0, -1, {
|
||||
report(data) {
|
||||
chunks.push(Buffer.from(data));
|
||||
}
|
||||
}).then(() => {
|
||||
return Buffer.concat(chunks);
|
||||
});
|
||||
}
|
||||
|
||||
writeFile(resource: vscode.Uri, content: Uint8Array, options: files.FileWriteOptions): Thenable<void> {
|
||||
return this._delegate.write(resource, content);
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostFileSystem implements ExtHostFileSystemShape {
|
||||
|
||||
private readonly _proxy: MainThreadFileSystemShape;
|
||||
@@ -182,11 +83,7 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape {
|
||||
extHostLanguageFeatures.registerDocumentLinkProvider('*', this._linkProvider);
|
||||
}
|
||||
|
||||
registerDeprecatedFileSystemProvider(scheme: string, provider: vscode.DeprecatedFileSystemProvider) {
|
||||
return this.registerFileSystemProvider(scheme, new FileSystemProviderShim(provider), { isCaseSensitive: false });
|
||||
}
|
||||
|
||||
registerFileSystemProvider(scheme: string, provider: vscode.FileSystemProvider, options: { isCaseSensitive?: boolean }) {
|
||||
registerFileSystemProvider(scheme: string, provider: vscode.FileSystemProvider, options: { isCaseSensitive?: boolean, isReadonly?: boolean } = {}) {
|
||||
|
||||
if (this._usedSchemes.has(scheme)) {
|
||||
throw new Error(`a provider for the scheme '${scheme}' is already registered`);
|
||||
@@ -201,6 +98,9 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape {
|
||||
if (options.isCaseSensitive) {
|
||||
capabilites += files.FileSystemProviderCapabilities.PathCaseSensitive;
|
||||
}
|
||||
if (options.isReadonly) {
|
||||
capabilites += files.FileSystemProviderCapabilities.Readonly;
|
||||
}
|
||||
if (typeof provider.copy === 'function') {
|
||||
capabilites += files.FileSystemProviderCapabilities.FileFolderCopy;
|
||||
}
|
||||
@@ -232,15 +132,13 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape {
|
||||
this._proxy.$onFileSystemChange(handle, mapped);
|
||||
});
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
subscription.dispose();
|
||||
this._linkProvider.delete(scheme);
|
||||
this._usedSchemes.delete(scheme);
|
||||
this._fsProvider.delete(handle);
|
||||
this._proxy.$unregisterProvider(handle);
|
||||
}
|
||||
};
|
||||
return toDisposable(() => {
|
||||
subscription.dispose();
|
||||
this._linkProvider.delete(scheme);
|
||||
this._usedSchemes.delete(scheme);
|
||||
this._fsProvider.delete(handle);
|
||||
this._proxy.$unregisterProvider(handle);
|
||||
});
|
||||
}
|
||||
|
||||
private static _asIStat(stat: vscode.FileStat): files.IStat {
|
||||
@@ -249,15 +147,15 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape {
|
||||
}
|
||||
|
||||
$stat(handle: number, resource: UriComponents): TPromise<files.IStat, any> {
|
||||
return asWinJsPromise(token => this._fsProvider.get(handle).stat(URI.revive(resource))).then(ExtHostFileSystem._asIStat);
|
||||
return asWinJsPromise(() => this._fsProvider.get(handle).stat(URI.revive(resource))).then(ExtHostFileSystem._asIStat);
|
||||
}
|
||||
|
||||
$readdir(handle: number, resource: UriComponents): TPromise<[string, files.FileType][], any> {
|
||||
return asWinJsPromise(token => this._fsProvider.get(handle).readDirectory(URI.revive(resource)));
|
||||
return asWinJsPromise(() => this._fsProvider.get(handle).readDirectory(URI.revive(resource)));
|
||||
}
|
||||
|
||||
$readFile(handle: number, resource: UriComponents): TPromise<string> {
|
||||
return asWinJsPromise(token => {
|
||||
return asWinJsPromise(() => {
|
||||
return this._fsProvider.get(handle).readFile(URI.revive(resource));
|
||||
}).then(data => {
|
||||
return Buffer.isBuffer(data) ? data.toString('base64') : Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString('base64');
|
||||
@@ -265,33 +163,33 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape {
|
||||
}
|
||||
|
||||
$writeFile(handle: number, resource: UriComponents, base64Content: string, opts: files.FileWriteOptions): TPromise<void, any> {
|
||||
return asWinJsPromise(token => this._fsProvider.get(handle).writeFile(URI.revive(resource), Buffer.from(base64Content, 'base64'), opts));
|
||||
return asWinJsPromise(() => this._fsProvider.get(handle).writeFile(URI.revive(resource), Buffer.from(base64Content, 'base64'), opts));
|
||||
}
|
||||
|
||||
$delete(handle: number, resource: UriComponents): TPromise<void, any> {
|
||||
return asWinJsPromise(token => this._fsProvider.get(handle).delete(URI.revive(resource), { recursive: true }));
|
||||
$delete(handle: number, resource: UriComponents, opts: files.FileDeleteOptions): TPromise<void, any> {
|
||||
return asWinJsPromise(() => this._fsProvider.get(handle).delete(URI.revive(resource), opts));
|
||||
}
|
||||
|
||||
$rename(handle: number, oldUri: UriComponents, newUri: UriComponents, opts: files.FileOverwriteOptions): TPromise<void, any> {
|
||||
return asWinJsPromise(token => this._fsProvider.get(handle).rename(URI.revive(oldUri), URI.revive(newUri), opts));
|
||||
return asWinJsPromise(() => this._fsProvider.get(handle).rename(URI.revive(oldUri), URI.revive(newUri), opts));
|
||||
}
|
||||
|
||||
$copy(handle: number, oldUri: UriComponents, newUri: UriComponents, opts: files.FileOverwriteOptions): TPromise<void, any> {
|
||||
return asWinJsPromise(token => this._fsProvider.get(handle).copy(URI.revive(oldUri), URI.revive(newUri), opts));
|
||||
return asWinJsPromise(() => this._fsProvider.get(handle).copy(URI.revive(oldUri), URI.revive(newUri), opts));
|
||||
}
|
||||
|
||||
$mkdir(handle: number, resource: UriComponents): TPromise<void, any> {
|
||||
return asWinJsPromise(token => this._fsProvider.get(handle).createDirectory(URI.revive(resource)));
|
||||
return asWinJsPromise(() => this._fsProvider.get(handle).createDirectory(URI.revive(resource)));
|
||||
}
|
||||
|
||||
$watch(handle: number, session: number, resource: UriComponents, opts: files.IWatchOptions): void {
|
||||
asWinJsPromise(token => {
|
||||
asWinJsPromise(() => {
|
||||
let subscription = this._fsProvider.get(handle).watch(URI.revive(resource), opts);
|
||||
this._watches.set(session, subscription);
|
||||
});
|
||||
}
|
||||
|
||||
$unwatch(handle: number, session: number): void {
|
||||
$unwatch(session: number): void {
|
||||
let subscription = this._watches.get(session);
|
||||
if (subscription) {
|
||||
subscription.dispose();
|
||||
|
||||
@@ -4,18 +4,23 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from './extHostTypes';
|
||||
import { parse, IRelativePattern } from 'vs/base/common/glob';
|
||||
import { Uri, FileSystemWatcher as _FileSystemWatcher } from 'vscode';
|
||||
import { FileSystemEvents, ExtHostFileSystemEventServiceShape } from './extHost.protocol';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { flatten } from 'vs/base/common/arrays';
|
||||
import { AsyncEmitter, Emitter, Event } from 'vs/base/common/event';
|
||||
import { IRelativePattern, parse } from 'vs/base/common/glob';
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors';
|
||||
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import * as vscode from 'vscode';
|
||||
import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, MainContext, ResourceFileEditDto, ResourceTextEditDto, MainThreadTextEditorsShape } from './extHost.protocol';
|
||||
import * as typeConverter from './extHostTypeConverters';
|
||||
import { Disposable, WorkspaceEdit } from './extHostTypes';
|
||||
|
||||
class FileSystemWatcher implements _FileSystemWatcher {
|
||||
class FileSystemWatcher implements vscode.FileSystemWatcher {
|
||||
|
||||
private _onDidCreate = new Emitter<Uri>();
|
||||
private _onDidChange = new Emitter<Uri>();
|
||||
private _onDidDelete = new Emitter<Uri>();
|
||||
private _onDidCreate = new Emitter<vscode.Uri>();
|
||||
private _onDidChange = new Emitter<vscode.Uri>();
|
||||
private _onDidDelete = new Emitter<vscode.Uri>();
|
||||
private _disposable: Disposable;
|
||||
private _config: number;
|
||||
|
||||
@@ -80,31 +85,100 @@ class FileSystemWatcher implements _FileSystemWatcher {
|
||||
this._disposable.dispose();
|
||||
}
|
||||
|
||||
get onDidCreate(): Event<Uri> {
|
||||
get onDidCreate(): Event<vscode.Uri> {
|
||||
return this._onDidCreate.event;
|
||||
}
|
||||
|
||||
get onDidChange(): Event<Uri> {
|
||||
get onDidChange(): Event<vscode.Uri> {
|
||||
return this._onDidChange.event;
|
||||
}
|
||||
|
||||
get onDidDelete(): Event<Uri> {
|
||||
get onDidDelete(): Event<vscode.Uri> {
|
||||
return this._onDidDelete.event;
|
||||
}
|
||||
}
|
||||
|
||||
interface WillRenameListener {
|
||||
extension: IExtensionDescription;
|
||||
(e: vscode.FileWillRenameEvent): any;
|
||||
}
|
||||
|
||||
export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServiceShape {
|
||||
|
||||
private _emitter = new Emitter<FileSystemEvents>();
|
||||
private readonly _onFileEvent = new Emitter<FileSystemEvents>();
|
||||
private readonly _onDidRenameFile = new Emitter<vscode.FileRenameEvent>();
|
||||
private readonly _onWillRenameFile = new AsyncEmitter<vscode.FileWillRenameEvent>();
|
||||
|
||||
constructor() {
|
||||
readonly onDidRenameFile: Event<vscode.FileRenameEvent> = this._onDidRenameFile.event;
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
private readonly _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors,
|
||||
private readonly _mainThreadTextEditors: MainThreadTextEditorsShape = mainContext.getProxy(MainContext.MainThreadTextEditors)
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
public createFileSystemWatcher(globPattern: string | IRelativePattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean): _FileSystemWatcher {
|
||||
return new FileSystemWatcher(this._emitter.event, globPattern, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents);
|
||||
public createFileSystemWatcher(globPattern: string | IRelativePattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean): vscode.FileSystemWatcher {
|
||||
return new FileSystemWatcher(this._onFileEvent.event, globPattern, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents);
|
||||
}
|
||||
|
||||
$onFileEvent(events: FileSystemEvents) {
|
||||
this._emitter.fire(events);
|
||||
this._onFileEvent.fire(events);
|
||||
}
|
||||
|
||||
$onFileRename(oldUri: UriComponents, newUri: UriComponents) {
|
||||
this._onDidRenameFile.fire(Object.freeze({ oldUri: URI.revive(oldUri), newUri: URI.revive(newUri) }));
|
||||
}
|
||||
|
||||
getOnWillRenameFileEvent(extension: IExtensionDescription): Event<vscode.FileWillRenameEvent> {
|
||||
return (listener, thisArg, disposables) => {
|
||||
let wrappedListener = <WillRenameListener><any>function () {
|
||||
listener.apply(thisArg, arguments);
|
||||
};
|
||||
wrappedListener.extension = extension;
|
||||
return this._onWillRenameFile.event(wrappedListener, undefined, disposables);
|
||||
};
|
||||
}
|
||||
|
||||
$onWillRename(oldUriDto: UriComponents, newUriDto: UriComponents): TPromise<any> {
|
||||
const oldUri = URI.revive(oldUriDto);
|
||||
const newUri = URI.revive(newUriDto);
|
||||
|
||||
const edits: WorkspaceEdit[] = [];
|
||||
return TPromise.wrap(this._onWillRenameFile.fireAsync((bucket, listener) => {
|
||||
return {
|
||||
oldUri,
|
||||
newUri,
|
||||
waitUntil: (thenable: Thenable<vscode.WorkspaceEdit>): void => {
|
||||
if (Object.isFrozen(bucket)) {
|
||||
throw new TypeError('waitUntil cannot be called async');
|
||||
}
|
||||
const index = bucket.length;
|
||||
const wrappedThenable = TPromise.as(thenable).then(result => {
|
||||
// ignore all results except for WorkspaceEdits. Those
|
||||
// are stored in a spare array
|
||||
if (result instanceof WorkspaceEdit) {
|
||||
edits[index] = result;
|
||||
}
|
||||
});
|
||||
bucket.push(wrappedThenable);
|
||||
}
|
||||
};
|
||||
}).then(() => {
|
||||
if (edits.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
// flatten all WorkspaceEdits collected via waitUntil-call
|
||||
// and apply them in one go.
|
||||
let allEdits = new Array<(ResourceFileEditDto | ResourceTextEditDto)[]>();
|
||||
for (let edit of edits) {
|
||||
if (edit) { // sparse array
|
||||
let { edits } = typeConverter.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors);
|
||||
allEdits.push(edits);
|
||||
}
|
||||
}
|
||||
return this._mainThreadTextEditors.$tryApplyWorkspaceEdit({ edits: flatten(allEdits) });
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import * as vscode from 'vscode';
|
||||
import * as TypeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, HierarchicalSymbolInformation } from 'vs/workbench/api/node/extHostTypes';
|
||||
import * as typeConvert from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { ISingleEditOperation } from 'vs/editor/common/model';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { ExtHostHeapService } from 'vs/workbench/api/node/extHostHeapService';
|
||||
@@ -17,12 +17,15 @@ import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments';
|
||||
import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands';
|
||||
import { ExtHostDiagnostics } from 'vs/workbench/api/node/extHostDiagnostics';
|
||||
import { asWinJsPromise } from 'vs/base/common/async';
|
||||
import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, SymbolInformationDto, SuggestResultDto, WorkspaceSymbolsDto, SuggestionDto, CodeActionDto, ISerializedDocumentFilter } from './extHost.protocol';
|
||||
import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, WorkspaceSymbolDto, SuggestResultDto, WorkspaceSymbolsDto, SuggestionDto, CodeActionDto, ISerializedDocumentFilter, WorkspaceEditDto } from './extHost.protocol';
|
||||
import { regExpLeadsToEndlessLoop } from 'vs/base/common/strings';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { IRange, Range as EditorRange } from 'vs/editor/common/core/range';
|
||||
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
|
||||
import { isObject } from 'vs/base/common/types';
|
||||
import { ISelection, Selection } from 'vs/editor/common/core/selection';
|
||||
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
// --- adapter
|
||||
|
||||
@@ -36,35 +39,71 @@ class OutlineAdapter {
|
||||
this._provider = provider;
|
||||
}
|
||||
|
||||
provideDocumentSymbols(resource: URI): TPromise<SymbolInformationDto[]> {
|
||||
provideDocumentSymbols(resource: URI): TPromise<modes.DocumentSymbol[]> {
|
||||
let doc = this._documents.getDocumentData(resource).document;
|
||||
return asWinJsPromise(token => this._provider.provideDocumentSymbols(doc, token)).then(value => {
|
||||
if (value instanceof HierarchicalSymbolInformation) {
|
||||
value = HierarchicalSymbolInformation.toFlatSymbolInformation(value);
|
||||
if (isFalsyOrEmpty(value)) {
|
||||
return undefined;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(symbol => IdObject.mixin(TypeConverters.fromSymbolInformation(symbol)));
|
||||
if (value[0] instanceof DocumentSymbol) {
|
||||
return (<DocumentSymbol[]>value).map(typeConvert.DocumentSymbol.from);
|
||||
} else {
|
||||
return OutlineAdapter._asDocumentSymbolTree(resource, <SymbolInformation[]>value);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private static _asDocumentSymbolTree(resource: URI, info: SymbolInformation[]): modes.DocumentSymbol[] {
|
||||
// first sort by start (and end) and then loop over all elements
|
||||
// and build a tree based on containment.
|
||||
info = info.slice(0).sort((a, b) => {
|
||||
let res = a.location.range.start.compareTo(b.location.range.start);
|
||||
if (res === 0) {
|
||||
res = b.location.range.end.compareTo(a.location.range.end);
|
||||
}
|
||||
return res;
|
||||
});
|
||||
let res: modes.DocumentSymbol[] = [];
|
||||
let parentStack: modes.DocumentSymbol[] = [];
|
||||
for (let i = 0; i < info.length; i++) {
|
||||
let element = <modes.DocumentSymbol>{
|
||||
name: info[i].name,
|
||||
kind: typeConvert.SymbolKind.from(info[i].kind),
|
||||
containerName: info[i].containerName,
|
||||
range: typeConvert.Range.from(info[i].location.range),
|
||||
selectionRange: typeConvert.Range.from(info[i].location.range),
|
||||
children: []
|
||||
};
|
||||
|
||||
while (true) {
|
||||
if (parentStack.length === 0) {
|
||||
parentStack.push(element);
|
||||
res.push(element);
|
||||
break;
|
||||
}
|
||||
let parent = parentStack[parentStack.length - 1];
|
||||
if (EditorRange.containsRange(parent.range, element.range) && !EditorRange.equalsRange(parent.range, element.range)) {
|
||||
parent.children.push(element);
|
||||
parentStack.push(element);
|
||||
break;
|
||||
}
|
||||
parentStack.pop();
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
class CodeLensAdapter {
|
||||
|
||||
private static _badCmd: vscode.Command = { command: 'missing', title: '<<MISSING COMMAND>>' };
|
||||
|
||||
private _documents: ExtHostDocuments;
|
||||
private _commands: CommandsConverter;
|
||||
private _heapService: ExtHostHeapService;
|
||||
private _provider: vscode.CodeLensProvider;
|
||||
|
||||
constructor(documents: ExtHostDocuments, commands: CommandsConverter, heapService: ExtHostHeapService, provider: vscode.CodeLensProvider) {
|
||||
this._documents = documents;
|
||||
this._commands = commands;
|
||||
this._heapService = heapService;
|
||||
this._provider = provider;
|
||||
}
|
||||
constructor(
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _commands: CommandsConverter,
|
||||
private readonly _heapService: ExtHostHeapService,
|
||||
private readonly _provider: vscode.CodeLensProvider
|
||||
) { }
|
||||
|
||||
provideCodeLenses(resource: URI): TPromise<modes.ICodeLensSymbol[]> {
|
||||
const doc = this._documents.getDocumentData(resource).document;
|
||||
@@ -74,7 +113,7 @@ class CodeLensAdapter {
|
||||
return lenses.map(lens => {
|
||||
const id = this._heapService.keep(lens);
|
||||
return ObjectIdentifier.mixin({
|
||||
range: TypeConverters.fromRange(lens.range),
|
||||
range: typeConvert.Range.from(lens.range),
|
||||
command: this._commands.toInternal(lens.command)
|
||||
}, id);
|
||||
});
|
||||
@@ -105,89 +144,68 @@ class CodeLensAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
class DefinitionAdapter {
|
||||
private _documents: ExtHostDocuments;
|
||||
private _provider: vscode.DefinitionProvider;
|
||||
|
||||
constructor(documents: ExtHostDocuments, provider: vscode.DefinitionProvider) {
|
||||
this._documents = documents;
|
||||
this._provider = provider;
|
||||
function convertToDefinitionLinks(value: vscode.Definition): modes.DefinitionLink[] {
|
||||
if (Array.isArray(value)) {
|
||||
return (value as (vscode.DefinitionLink | vscode.Location)[]).map(typeConvert.DefinitionLink.from);
|
||||
} else if (value) {
|
||||
return [typeConvert.DefinitionLink.from(value)];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
provideDefinition(resource: URI, position: IPosition): TPromise<modes.Definition> {
|
||||
class DefinitionAdapter {
|
||||
|
||||
constructor(
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _provider: vscode.DefinitionProvider
|
||||
) { }
|
||||
|
||||
provideDefinition(resource: URI, position: IPosition): TPromise<modes.DefinitionLink[]> {
|
||||
let doc = this._documents.getDocumentData(resource).document;
|
||||
let pos = TypeConverters.toPosition(position);
|
||||
return asWinJsPromise(token => this._provider.provideDefinition(doc, pos, token)).then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(TypeConverters.location.from);
|
||||
} else if (value) {
|
||||
return TypeConverters.location.from(value);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
let pos = typeConvert.Position.to(position);
|
||||
return asWinJsPromise(token => this._provider.provideDefinition(doc, pos, token)).then(convertToDefinitionLinks);
|
||||
}
|
||||
}
|
||||
|
||||
class ImplementationAdapter {
|
||||
private _documents: ExtHostDocuments;
|
||||
private _provider: vscode.ImplementationProvider;
|
||||
|
||||
constructor(documents: ExtHostDocuments, provider: vscode.ImplementationProvider) {
|
||||
this._documents = documents;
|
||||
this._provider = provider;
|
||||
}
|
||||
constructor(
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _provider: vscode.ImplementationProvider
|
||||
) { }
|
||||
|
||||
provideImplementation(resource: URI, position: IPosition): TPromise<modes.Definition> {
|
||||
provideImplementation(resource: URI, position: IPosition): TPromise<modes.DefinitionLink[]> {
|
||||
let doc = this._documents.getDocumentData(resource).document;
|
||||
let pos = TypeConverters.toPosition(position);
|
||||
return asWinJsPromise(token => this._provider.provideImplementation(doc, pos, token)).then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(TypeConverters.location.from);
|
||||
} else if (value) {
|
||||
return TypeConverters.location.from(value);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
let pos = typeConvert.Position.to(position);
|
||||
return asWinJsPromise(token => this._provider.provideImplementation(doc, pos, token)).then(convertToDefinitionLinks);
|
||||
}
|
||||
}
|
||||
|
||||
class TypeDefinitionAdapter {
|
||||
private _documents: ExtHostDocuments;
|
||||
private _provider: vscode.TypeDefinitionProvider;
|
||||
|
||||
constructor(documents: ExtHostDocuments, provider: vscode.TypeDefinitionProvider) {
|
||||
this._documents = documents;
|
||||
this._provider = provider;
|
||||
}
|
||||
constructor(
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _provider: vscode.TypeDefinitionProvider
|
||||
) { }
|
||||
|
||||
provideTypeDefinition(resource: URI, position: IPosition): TPromise<modes.Definition> {
|
||||
provideTypeDefinition(resource: URI, position: IPosition): TPromise<modes.DefinitionLink[]> {
|
||||
const doc = this._documents.getDocumentData(resource).document;
|
||||
const pos = TypeConverters.toPosition(position);
|
||||
return asWinJsPromise(token => this._provider.provideTypeDefinition(doc, pos, token)).then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(TypeConverters.location.from);
|
||||
} else if (value) {
|
||||
return TypeConverters.location.from(value);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
const pos = typeConvert.Position.to(position);
|
||||
return asWinJsPromise(token => this._provider.provideTypeDefinition(doc, pos, token)).then(convertToDefinitionLinks);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class HoverAdapter {
|
||||
|
||||
constructor(
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _provider: vscode.HoverProvider,
|
||||
) {
|
||||
//
|
||||
}
|
||||
) { }
|
||||
|
||||
public provideHover(resource: URI, position: IPosition): TPromise<modes.Hover> {
|
||||
|
||||
let doc = this._documents.getDocumentData(resource).document;
|
||||
let pos = TypeConverters.toPosition(position);
|
||||
let pos = typeConvert.Position.to(position);
|
||||
|
||||
return asWinJsPromise(token => this._provider.provideHover(doc, pos, token)).then(value => {
|
||||
if (!value || isFalsyOrEmpty(value.contents)) {
|
||||
@@ -200,59 +218,46 @@ class HoverAdapter {
|
||||
value.range = new Range(pos, pos);
|
||||
}
|
||||
|
||||
return TypeConverters.fromHover(value);
|
||||
return typeConvert.Hover.from(value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentHighlightAdapter {
|
||||
|
||||
private _documents: ExtHostDocuments;
|
||||
private _provider: vscode.DocumentHighlightProvider;
|
||||
|
||||
constructor(documents: ExtHostDocuments, provider: vscode.DocumentHighlightProvider) {
|
||||
this._documents = documents;
|
||||
this._provider = provider;
|
||||
}
|
||||
constructor(
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _provider: vscode.DocumentHighlightProvider
|
||||
) { }
|
||||
|
||||
provideDocumentHighlights(resource: URI, position: IPosition): TPromise<modes.DocumentHighlight[]> {
|
||||
|
||||
let doc = this._documents.getDocumentData(resource).document;
|
||||
let pos = TypeConverters.toPosition(position);
|
||||
let pos = typeConvert.Position.to(position);
|
||||
|
||||
return asWinJsPromise(token => this._provider.provideDocumentHighlights(doc, pos, token)).then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(DocumentHighlightAdapter._convertDocumentHighlight);
|
||||
return value.map(typeConvert.DocumentHighlight.from);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private static _convertDocumentHighlight(documentHighlight: vscode.DocumentHighlight): modes.DocumentHighlight {
|
||||
return {
|
||||
range: TypeConverters.fromRange(documentHighlight.range),
|
||||
kind: documentHighlight.kind
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class ReferenceAdapter {
|
||||
|
||||
private _documents: ExtHostDocuments;
|
||||
private _provider: vscode.ReferenceProvider;
|
||||
|
||||
constructor(documents: ExtHostDocuments, provider: vscode.ReferenceProvider) {
|
||||
this._documents = documents;
|
||||
this._provider = provider;
|
||||
}
|
||||
constructor(
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _provider: vscode.ReferenceProvider
|
||||
) { }
|
||||
|
||||
provideReferences(resource: URI, position: IPosition, context: modes.ReferenceContext): TPromise<modes.Location[]> {
|
||||
let doc = this._documents.getDocumentData(resource).document;
|
||||
let pos = TypeConverters.toPosition(position);
|
||||
let pos = typeConvert.Position.to(position);
|
||||
|
||||
return asWinJsPromise(token => this._provider.provideReferences(doc, pos, context, token)).then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(TypeConverters.location.from);
|
||||
return value.map(typeConvert.location.from);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
@@ -265,27 +270,25 @@ export interface CustomCodeAction extends CodeActionDto {
|
||||
|
||||
class CodeActionAdapter {
|
||||
|
||||
private _documents: ExtHostDocuments;
|
||||
private _commands: CommandsConverter;
|
||||
private _diagnostics: ExtHostDiagnostics;
|
||||
private _provider: vscode.CodeActionProvider;
|
||||
constructor(
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _commands: CommandsConverter,
|
||||
private readonly _diagnostics: ExtHostDiagnostics,
|
||||
private readonly _provider: vscode.CodeActionProvider,
|
||||
private readonly _logService: ILogService,
|
||||
private readonly _extensionId: string
|
||||
) { }
|
||||
|
||||
constructor(documents: ExtHostDocuments, commands: CommandsConverter, diagnostics: ExtHostDiagnostics, provider: vscode.CodeActionProvider) {
|
||||
this._documents = documents;
|
||||
this._commands = commands;
|
||||
this._diagnostics = diagnostics;
|
||||
this._provider = provider;
|
||||
}
|
||||
|
||||
|
||||
provideCodeActions(resource: URI, range: IRange, context: modes.CodeActionContext): TPromise<CodeActionDto[]> {
|
||||
provideCodeActions(resource: URI, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext): TPromise<CodeActionDto[]> {
|
||||
|
||||
const doc = this._documents.getDocumentData(resource).document;
|
||||
const ran = <vscode.Range>TypeConverters.toRange(range);
|
||||
const ran = Selection.isISelection(rangeOrSelection)
|
||||
? <vscode.Selection>typeConvert.Selection.to(rangeOrSelection)
|
||||
: <vscode.Range>typeConvert.Range.to(rangeOrSelection);
|
||||
const allDiagnostics: vscode.Diagnostic[] = [];
|
||||
|
||||
for (const diagnostic of this._diagnostics.getDiagnostics(resource)) {
|
||||
if (ran.contains(diagnostic.range)) {
|
||||
if (ran.intersection(diagnostic.range)) {
|
||||
allDiagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
@@ -294,6 +297,7 @@ class CodeActionAdapter {
|
||||
diagnostics: allDiagnostics,
|
||||
only: context.only ? new CodeActionKind(context.only) : undefined
|
||||
};
|
||||
|
||||
return asWinJsPromise(token =>
|
||||
this._provider.provideCodeActions(doc, ran, codeActionContext, token)
|
||||
).then(commandsOrActions => {
|
||||
@@ -313,12 +317,20 @@ class CodeActionAdapter {
|
||||
command: this._commands.toInternal(candidate),
|
||||
});
|
||||
} else {
|
||||
if (codeActionContext.only) {
|
||||
if (!candidate.kind) {
|
||||
this._logService.warn(`${this._extensionId} - Code actions of kind '${codeActionContext.only.value} 'requested but returned code action does not have a 'kind'. Code action will be dropped. Please set 'CodeAction.kind'.`);
|
||||
} else if (!codeActionContext.only.contains(candidate.kind)) {
|
||||
this._logService.warn(`${this._extensionId} -Code actions of kind '${codeActionContext.only.value} 'requested but returned code action is of kind '${candidate.kind.value}'. Code action will be dropped. Please check 'CodeActionContext.only' to only return requested code actions.`);
|
||||
}
|
||||
}
|
||||
|
||||
// new school: convert code action
|
||||
result.push({
|
||||
title: candidate.title,
|
||||
command: candidate.command && this._commands.toInternal(candidate.command),
|
||||
diagnostics: candidate.diagnostics && candidate.diagnostics.map(TypeConverters.fromDiagnostic),
|
||||
edit: candidate.edit && TypeConverters.WorkspaceEdit.from(candidate.edit),
|
||||
diagnostics: candidate.diagnostics && candidate.diagnostics.map(typeConvert.Diagnostic.from),
|
||||
edit: candidate.edit && typeConvert.WorkspaceEdit.from(candidate.edit),
|
||||
kind: candidate.kind && candidate.kind.value
|
||||
});
|
||||
}
|
||||
@@ -335,13 +347,10 @@ class CodeActionAdapter {
|
||||
|
||||
class DocumentFormattingAdapter {
|
||||
|
||||
private _documents: ExtHostDocuments;
|
||||
private _provider: vscode.DocumentFormattingEditProvider;
|
||||
|
||||
constructor(documents: ExtHostDocuments, provider: vscode.DocumentFormattingEditProvider) {
|
||||
this._documents = documents;
|
||||
this._provider = provider;
|
||||
}
|
||||
constructor(
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _provider: vscode.DocumentFormattingEditProvider
|
||||
) { }
|
||||
|
||||
provideDocumentFormattingEdits(resource: URI, options: modes.FormattingOptions): TPromise<ISingleEditOperation[]> {
|
||||
|
||||
@@ -349,7 +358,7 @@ class DocumentFormattingAdapter {
|
||||
|
||||
return asWinJsPromise(token => this._provider.provideDocumentFormattingEdits(document, <any>options, token)).then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(TypeConverters.TextEdit.from);
|
||||
return value.map(typeConvert.TextEdit.from);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
@@ -358,22 +367,19 @@ class DocumentFormattingAdapter {
|
||||
|
||||
class RangeFormattingAdapter {
|
||||
|
||||
private _documents: ExtHostDocuments;
|
||||
private _provider: vscode.DocumentRangeFormattingEditProvider;
|
||||
|
||||
constructor(documents: ExtHostDocuments, provider: vscode.DocumentRangeFormattingEditProvider) {
|
||||
this._documents = documents;
|
||||
this._provider = provider;
|
||||
}
|
||||
constructor(
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _provider: vscode.DocumentRangeFormattingEditProvider
|
||||
) { }
|
||||
|
||||
provideDocumentRangeFormattingEdits(resource: URI, range: IRange, options: modes.FormattingOptions): TPromise<ISingleEditOperation[]> {
|
||||
|
||||
const { document } = this._documents.getDocumentData(resource);
|
||||
const ran = TypeConverters.toRange(range);
|
||||
const ran = typeConvert.Range.to(range);
|
||||
|
||||
return asWinJsPromise(token => this._provider.provideDocumentRangeFormattingEdits(document, ran, <any>options, token)).then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(TypeConverters.TextEdit.from);
|
||||
return value.map(typeConvert.TextEdit.from);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
@@ -382,24 +388,21 @@ class RangeFormattingAdapter {
|
||||
|
||||
class OnTypeFormattingAdapter {
|
||||
|
||||
private _documents: ExtHostDocuments;
|
||||
private _provider: vscode.OnTypeFormattingEditProvider;
|
||||
|
||||
constructor(documents: ExtHostDocuments, provider: vscode.OnTypeFormattingEditProvider) {
|
||||
this._documents = documents;
|
||||
this._provider = provider;
|
||||
}
|
||||
constructor(
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _provider: vscode.OnTypeFormattingEditProvider
|
||||
) { }
|
||||
|
||||
autoFormatTriggerCharacters: string[] = []; // not here
|
||||
|
||||
provideOnTypeFormattingEdits(resource: URI, position: IPosition, ch: string, options: modes.FormattingOptions): TPromise<ISingleEditOperation[]> {
|
||||
|
||||
const { document } = this._documents.getDocumentData(resource);
|
||||
const pos = TypeConverters.toPosition(position);
|
||||
const pos = typeConvert.Position.to(position);
|
||||
|
||||
return asWinJsPromise(token => this._provider.provideOnTypeFormattingEdits(document, pos, ch, <any>options, token)).then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(TypeConverters.TextEdit.from);
|
||||
return value.map(typeConvert.TextEdit.from);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
@@ -429,7 +432,7 @@ class NavigateTypeAdapter {
|
||||
console.warn('INVALID SymbolInformation, lacks name', item);
|
||||
continue;
|
||||
}
|
||||
const symbol = IdObject.mixin(TypeConverters.fromSymbolInformation(item));
|
||||
const symbol = IdObject.mixin(typeConvert.WorkspaceSymbol.from(item));
|
||||
this._symbolCache[symbol._id] = item;
|
||||
result.symbols.push(symbol);
|
||||
}
|
||||
@@ -442,7 +445,7 @@ class NavigateTypeAdapter {
|
||||
});
|
||||
}
|
||||
|
||||
resolveWorkspaceSymbol(symbol: SymbolInformationDto): TPromise<SymbolInformationDto> {
|
||||
resolveWorkspaceSymbol(symbol: WorkspaceSymbolDto): TPromise<WorkspaceSymbolDto> {
|
||||
|
||||
if (typeof this._provider.resolveWorkspaceSymbol !== 'function') {
|
||||
return TPromise.as(symbol);
|
||||
@@ -451,7 +454,7 @@ class NavigateTypeAdapter {
|
||||
const item = this._symbolCache[symbol._id];
|
||||
if (item) {
|
||||
return asWinJsPromise(token => this._provider.resolveWorkspaceSymbol(item, token)).then(value => {
|
||||
return value && mixin(symbol, TypeConverters.fromSymbolInformation(value), true);
|
||||
return value && mixin(symbol, typeConvert.WorkspaceSymbol.from(value), true);
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
@@ -474,38 +477,35 @@ class RenameAdapter {
|
||||
return typeof provider.prepareRename === 'function';
|
||||
}
|
||||
|
||||
private _documents: ExtHostDocuments;
|
||||
private _provider: vscode.RenameProvider;
|
||||
constructor(
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _provider: vscode.RenameProvider
|
||||
) { }
|
||||
|
||||
constructor(documents: ExtHostDocuments, provider: vscode.RenameProvider) {
|
||||
this._documents = documents;
|
||||
this._provider = provider;
|
||||
}
|
||||
|
||||
provideRenameEdits(resource: URI, position: IPosition, newName: string): TPromise<modes.WorkspaceEdit> {
|
||||
provideRenameEdits(resource: URI, position: IPosition, newName: string): TPromise<WorkspaceEditDto> {
|
||||
|
||||
let doc = this._documents.getDocumentData(resource).document;
|
||||
let pos = TypeConverters.toPosition(position);
|
||||
let pos = typeConvert.Position.to(position);
|
||||
|
||||
return asWinJsPromise(token => this._provider.provideRenameEdits(doc, pos, newName, token)).then(value => {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
return TypeConverters.WorkspaceEdit.from(value);
|
||||
return typeConvert.WorkspaceEdit.from(value);
|
||||
}, err => {
|
||||
if (typeof err === 'string') {
|
||||
return <modes.WorkspaceEdit>{
|
||||
return <WorkspaceEditDto>{
|
||||
edits: undefined,
|
||||
rejectReason: err
|
||||
};
|
||||
} else if (err instanceof Error && typeof err.message === 'string') {
|
||||
return <modes.WorkspaceEdit>{
|
||||
return <WorkspaceEditDto>{
|
||||
edits: undefined,
|
||||
rejectReason: err.message
|
||||
};
|
||||
} else {
|
||||
// generic error
|
||||
return TPromise.wrapError<modes.WorkspaceEdit>(err);
|
||||
return TPromise.wrapError<WorkspaceEditDto>(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -516,7 +516,7 @@ class RenameAdapter {
|
||||
}
|
||||
|
||||
let doc = this._documents.getDocumentData(resource).document;
|
||||
let pos = TypeConverters.toPosition(position);
|
||||
let pos = typeConvert.Position.to(position);
|
||||
|
||||
return asWinJsPromise(token => this._provider.prepareRename(doc, pos, token)).then(rangeOrLocation => {
|
||||
|
||||
@@ -539,7 +539,7 @@ class RenameAdapter {
|
||||
console.warn('INVALID rename location: range must contain position');
|
||||
return undefined;
|
||||
}
|
||||
return { range: TypeConverters.fromRange(range), text };
|
||||
return { range: typeConvert.Range.from(range), text };
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -566,10 +566,10 @@ class SuggestAdapter {
|
||||
provideCompletionItems(resource: URI, position: IPosition, context: modes.SuggestContext): TPromise<SuggestResultDto> {
|
||||
|
||||
const doc = this._documents.getDocumentData(resource).document;
|
||||
const pos = TypeConverters.toPosition(position);
|
||||
const pos = typeConvert.Position.to(position);
|
||||
|
||||
return asWinJsPromise<vscode.CompletionItem[] | vscode.CompletionList>(token => {
|
||||
return this._provider.provideCompletionItems(doc, pos, token, TypeConverters.CompletionContext.from(context));
|
||||
return this._provider.provideCompletionItems(doc, pos, token, typeConvert.CompletionContext.from(context));
|
||||
}).then(value => {
|
||||
|
||||
const _id = this._idPool++;
|
||||
@@ -629,7 +629,7 @@ class SuggestAdapter {
|
||||
}
|
||||
|
||||
const doc = this._documents.getDocumentData(resource).document;
|
||||
const pos = TypeConverters.toPosition(position);
|
||||
const pos = typeConvert.Position.to(position);
|
||||
const wordRangeBeforePos = (doc.getWordRangeAtPosition(pos) as Range || new Range(pos, pos)).with({ end: pos });
|
||||
const newSuggestion = this._convertCompletionItem(resolvedItem, pos, wordRangeBeforePos, _id, _parentId);
|
||||
if (newSuggestion) {
|
||||
@@ -656,14 +656,15 @@ class SuggestAdapter {
|
||||
_parentId,
|
||||
//
|
||||
label: item.label,
|
||||
type: TypeConverters.CompletionItemKind.from(item.kind),
|
||||
type: typeConvert.CompletionItemKind.from(item.kind),
|
||||
detail: item.detail,
|
||||
documentation: item.documentation,
|
||||
filterText: item.filterText,
|
||||
sortText: item.sortText,
|
||||
preselect: item.preselect,
|
||||
//
|
||||
insertText: undefined,
|
||||
additionalTextEdits: item.additionalTextEdits && item.additionalTextEdits.map(TypeConverters.TextEdit.from),
|
||||
additionalTextEdits: item.additionalTextEdits && item.additionalTextEdits.map(typeConvert.TextEdit.from),
|
||||
command: this._commands.toInternal(item.command),
|
||||
commitCharacters: item.commitCharacters
|
||||
};
|
||||
@@ -709,22 +710,19 @@ class SuggestAdapter {
|
||||
|
||||
class SignatureHelpAdapter {
|
||||
|
||||
private _documents: ExtHostDocuments;
|
||||
private _provider: vscode.SignatureHelpProvider;
|
||||
|
||||
constructor(documents: ExtHostDocuments, provider: vscode.SignatureHelpProvider) {
|
||||
this._documents = documents;
|
||||
this._provider = provider;
|
||||
}
|
||||
constructor(
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _provider: vscode.SignatureHelpProvider
|
||||
) { }
|
||||
|
||||
provideSignatureHelp(resource: URI, position: IPosition): TPromise<modes.SignatureHelp> {
|
||||
|
||||
const doc = this._documents.getDocumentData(resource).document;
|
||||
const pos = TypeConverters.toPosition(position);
|
||||
const pos = typeConvert.Position.to(position);
|
||||
|
||||
return asWinJsPromise(token => this._provider.provideSignatureHelp(doc, pos, token)).then(value => {
|
||||
if (value) {
|
||||
return TypeConverters.SignatureHelp.from(value);
|
||||
return typeConvert.SignatureHelp.from(value);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
@@ -733,15 +731,11 @@ class SignatureHelpAdapter {
|
||||
|
||||
class LinkProviderAdapter {
|
||||
|
||||
private _documents: ExtHostDocuments;
|
||||
private _heapService: ExtHostHeapService;
|
||||
private _provider: vscode.DocumentLinkProvider;
|
||||
|
||||
constructor(documents: ExtHostDocuments, heapService: ExtHostHeapService, provider: vscode.DocumentLinkProvider) {
|
||||
this._documents = documents;
|
||||
this._heapService = heapService;
|
||||
this._provider = provider;
|
||||
}
|
||||
constructor(
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _heapService: ExtHostHeapService,
|
||||
private readonly _provider: vscode.DocumentLinkProvider
|
||||
) { }
|
||||
|
||||
provideLinks(resource: URI): TPromise<modes.ILink[]> {
|
||||
const doc = this._documents.getDocumentData(resource).document;
|
||||
@@ -752,7 +746,7 @@ class LinkProviderAdapter {
|
||||
}
|
||||
const result: modes.ILink[] = [];
|
||||
for (const link of links) {
|
||||
let data = TypeConverters.DocumentLink.from(link);
|
||||
let data = typeConvert.DocumentLink.from(link);
|
||||
let id = this._heapService.keep(link);
|
||||
ObjectIdentifier.mixin(data, id);
|
||||
result.push(data);
|
||||
@@ -774,7 +768,7 @@ class LinkProviderAdapter {
|
||||
|
||||
return asWinJsPromise(token => this._provider.resolveDocumentLink(item, token)).then(value => {
|
||||
if (value) {
|
||||
return TypeConverters.DocumentLink.from(value);
|
||||
return typeConvert.DocumentLink.from(value);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
@@ -797,8 +791,8 @@ class ColorProviderAdapter {
|
||||
|
||||
const colorInfos: IRawColorInfo[] = colors.map(ci => {
|
||||
return {
|
||||
color: TypeConverters.Color.from(ci.color),
|
||||
range: TypeConverters.fromRange(ci.range)
|
||||
color: typeConvert.Color.from(ci.color),
|
||||
range: typeConvert.Range.from(ci.range)
|
||||
};
|
||||
});
|
||||
|
||||
@@ -808,10 +802,10 @@ class ColorProviderAdapter {
|
||||
|
||||
provideColorPresentations(resource: URI, raw: IRawColorInfo): TPromise<modes.IColorPresentation[]> {
|
||||
const document = this._documents.getDocumentData(resource).document;
|
||||
const range = TypeConverters.toRange(raw.range);
|
||||
const color = TypeConverters.Color.to(raw.color);
|
||||
const range = typeConvert.Range.to(raw.range);
|
||||
const color = typeConvert.Color.to(raw.color);
|
||||
return asWinJsPromise(token => this._provider.provideColorPresentations(color, { document, range }, token)).then(value => {
|
||||
return value.map(TypeConverters.ColorPresentation.from);
|
||||
return value.map(typeConvert.ColorPresentation.from);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -829,7 +823,7 @@ class FoldingProviderAdapter {
|
||||
if (!Array.isArray(ranges)) {
|
||||
return void 0;
|
||||
}
|
||||
return ranges.map(TypeConverters.FoldingRange.from);
|
||||
return ranges.map(typeConvert.FoldingRange.from);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -855,6 +849,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
|
||||
private _heapService: ExtHostHeapService;
|
||||
private _diagnostics: ExtHostDiagnostics;
|
||||
private _adapter = new Map<number, Adapter>();
|
||||
private readonly _logService: ILogService;
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
@@ -862,7 +857,8 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
|
||||
documents: ExtHostDocuments,
|
||||
commands: ExtHostCommands,
|
||||
heapMonitor: ExtHostHeapService,
|
||||
diagnostics: ExtHostDiagnostics
|
||||
diagnostics: ExtHostDiagnostics,
|
||||
logService: ILogService
|
||||
) {
|
||||
this._schemeTransformer = schemeTransformer;
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadLanguageFeatures);
|
||||
@@ -870,6 +866,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
|
||||
this._commands = commands;
|
||||
this._heapService = heapMonitor;
|
||||
this._diagnostics = diagnostics;
|
||||
this._logService = logService;
|
||||
}
|
||||
|
||||
private _transformDocumentSelector(selector: vscode.DocumentSelector): ISerializedDocumentFilter[] {
|
||||
@@ -935,13 +932,13 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
|
||||
|
||||
// --- outline
|
||||
|
||||
registerDocumentSymbolProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentSymbolProvider): vscode.Disposable {
|
||||
registerDocumentSymbolProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentSymbolProvider, extension?: IExtensionDescription): vscode.Disposable {
|
||||
const handle = this._addNewAdapter(new OutlineAdapter(this._documents, provider));
|
||||
this._proxy.$registerOutlineSupport(handle, this._transformDocumentSelector(selector));
|
||||
this._proxy.$registerOutlineSupport(handle, this._transformDocumentSelector(selector), extension ? extension.displayName || extension.name : undefined);
|
||||
return this._createDisposable(handle);
|
||||
}
|
||||
|
||||
$provideDocumentSymbols(handle: number, resource: UriComponents): TPromise<SymbolInformationDto[]> {
|
||||
$provideDocumentSymbols(handle: number, resource: UriComponents): TPromise<modes.DocumentSymbol[]> {
|
||||
return this._withAdapter(handle, OutlineAdapter, adapter => adapter.provideDocumentSymbols(URI.revive(resource)));
|
||||
}
|
||||
|
||||
@@ -979,7 +976,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
|
||||
return this._createDisposable(handle);
|
||||
}
|
||||
|
||||
$provideDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.Definition> {
|
||||
$provideDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.DefinitionLink[]> {
|
||||
return this._withAdapter(handle, DefinitionAdapter, adapter => adapter.provideDefinition(URI.revive(resource), position));
|
||||
}
|
||||
|
||||
@@ -989,7 +986,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
|
||||
return this._createDisposable(handle);
|
||||
}
|
||||
|
||||
$provideImplementation(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.Definition> {
|
||||
$provideImplementation(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.DefinitionLink[]> {
|
||||
return this._withAdapter(handle, ImplementationAdapter, adapter => adapter.provideImplementation(URI.revive(resource), position));
|
||||
}
|
||||
|
||||
@@ -999,7 +996,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
|
||||
return this._createDisposable(handle);
|
||||
}
|
||||
|
||||
$provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.Definition> {
|
||||
$provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.DefinitionLink[]> {
|
||||
return this._withAdapter(handle, TypeDefinitionAdapter, adapter => adapter.provideTypeDefinition(URI.revive(resource), position));
|
||||
}
|
||||
|
||||
@@ -1041,15 +1038,15 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
|
||||
|
||||
// --- quick fix
|
||||
|
||||
registerCodeActionProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider, metadata?: vscode.CodeActionProviderMetadata): vscode.Disposable {
|
||||
const handle = this._addNewAdapter(new CodeActionAdapter(this._documents, this._commands.converter, this._diagnostics, provider));
|
||||
registerCodeActionProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider, extension?: IExtensionDescription, metadata?: vscode.CodeActionProviderMetadata): vscode.Disposable {
|
||||
const handle = this._addNewAdapter(new CodeActionAdapter(this._documents, this._commands.converter, this._diagnostics, provider, this._logService, extension ? extension.id : ''));
|
||||
this._proxy.$registerQuickFixSupport(handle, this._transformDocumentSelector(selector), metadata && metadata.providedCodeActionKinds ? metadata.providedCodeActionKinds.map(kind => kind.value) : undefined);
|
||||
return this._createDisposable(handle);
|
||||
}
|
||||
|
||||
|
||||
$provideCodeActions(handle: number, resource: UriComponents, range: IRange, context: modes.CodeActionContext): TPromise<CodeActionDto[]> {
|
||||
return this._withAdapter(handle, CodeActionAdapter, adapter => adapter.provideCodeActions(URI.revive(resource), range, context));
|
||||
$provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext): TPromise<CodeActionDto[]> {
|
||||
return this._withAdapter(handle, CodeActionAdapter, adapter => adapter.provideCodeActions(URI.revive(resource), rangeOrSelection, context));
|
||||
}
|
||||
|
||||
// --- formatting
|
||||
@@ -1096,7 +1093,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
|
||||
return this._withAdapter(handle, NavigateTypeAdapter, adapter => adapter.provideWorkspaceSymbols(search));
|
||||
}
|
||||
|
||||
$resolveWorkspaceSymbol(handle: number, symbol: SymbolInformationDto): TPromise<SymbolInformationDto> {
|
||||
$resolveWorkspaceSymbol(handle: number, symbol: WorkspaceSymbolDto): TPromise<WorkspaceSymbolDto> {
|
||||
return this._withAdapter(handle, NavigateTypeAdapter, adapter => adapter.resolveWorkspaceSymbol(symbol));
|
||||
}
|
||||
|
||||
@@ -1112,7 +1109,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
|
||||
return this._createDisposable(handle);
|
||||
}
|
||||
|
||||
$provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string): TPromise<modes.WorkspaceEdit> {
|
||||
$provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string): TPromise<WorkspaceEditDto> {
|
||||
return this._withAdapter(handle, RenameAdapter, adapter => adapter.provideRenameEdits(URI.revive(resource), position, newName));
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,11 @@ import { ProgressOptions } from 'vscode';
|
||||
import { MainThreadProgressShape, ExtHostProgressShape } from './extHost.protocol';
|
||||
import { ProgressLocation } from './extHostTypeConverters';
|
||||
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IProgressStep, Progress } from 'vs/platform/progress/common/progress';
|
||||
import { Progress } from 'vs/platform/progress/common/progress';
|
||||
import { localize } from 'vs/nls';
|
||||
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { debounce } from 'vs/base/common/decorators';
|
||||
import { IProgressStep } from 'vs/workbench/services/progress/common/progress';
|
||||
|
||||
export class ExtHostProgress implements ExtHostProgressShape {
|
||||
|
||||
@@ -70,11 +71,14 @@ export class ExtHostProgress implements ExtHostProgressShape {
|
||||
|
||||
function mergeProgress(result: IProgressStep, currentValue: IProgressStep): IProgressStep {
|
||||
result.message = currentValue.message;
|
||||
if (typeof currentValue.increment === 'number' && typeof result.message === 'number') {
|
||||
result.increment += currentValue.increment;
|
||||
} else if (typeof currentValue.increment === 'number') {
|
||||
result.increment = currentValue.increment;
|
||||
if (typeof currentValue.increment === 'number') {
|
||||
if (typeof result.increment === 'number') {
|
||||
result.increment += currentValue.increment;
|
||||
} else {
|
||||
result.increment = currentValue.increment;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,17 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { wireCancellationToken, asWinJsPromise } from 'vs/base/common/async';
|
||||
import { asWinJsPromise, wireCancellationToken } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { QuickPickOptions, QuickPickItem, InputBoxOptions, WorkspaceFolderPickOptions, WorkspaceFolder } from 'vscode';
|
||||
import { MainContext, MainThreadQuickOpenShape, ExtHostQuickOpenShape, MyQuickPickItems, IMainContext } from './extHost.protocol';
|
||||
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands';
|
||||
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
|
||||
import { InputBox, InputBoxOptions, QuickInput, QuickInputButton, QuickPick, QuickPickItem, QuickPickOptions, WorkspaceFolder, WorkspaceFolderPickOptions } from 'vscode';
|
||||
import { ExtHostQuickOpenShape, IMainContext, MainContext, MainThreadQuickOpenShape, TransferQuickPickItems, TransferQuickInput, TransferQuickInputButton } from './extHost.protocol';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { ThemeIcon, QuickInputButtons } from 'vs/workbench/api/node/extHostTypes';
|
||||
|
||||
export type Item = string | QuickPickItem;
|
||||
|
||||
@@ -23,13 +27,15 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape {
|
||||
private _onDidSelectItem: (handle: number) => void;
|
||||
private _validateInput: (input: string) => string | Thenable<string>;
|
||||
|
||||
private _sessions = new Map<number, ExtHostQuickInput>();
|
||||
|
||||
constructor(mainContext: IMainContext, workspace: ExtHostWorkspace, commands: ExtHostCommands) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadQuickOpen);
|
||||
this._workspace = workspace;
|
||||
this._commands = commands;
|
||||
}
|
||||
|
||||
showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Thenable<QuickPickItem[]>, options: QuickPickOptions & { canSelectMany: true; }, token?: CancellationToken): Thenable<QuickPickItem[] | undefined>;
|
||||
showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Thenable<QuickPickItem[]>, options: QuickPickOptions & { canPickMany: true; }, token?: CancellationToken): Thenable<QuickPickItem[] | undefined>;
|
||||
showQuickPick(itemsOrItemsPromise: string[] | Thenable<string[]>, options?: QuickPickOptions, token?: CancellationToken): Thenable<string | undefined>;
|
||||
showQuickPick(itemsOrItemsPromise: QuickPickItem[] | Thenable<QuickPickItem[]>, options?: QuickPickOptions, token?: CancellationToken): Thenable<QuickPickItem | undefined>;
|
||||
showQuickPick(itemsOrItemsPromise: Item[] | Thenable<Item[]>, options?: QuickPickOptions, token: CancellationToken = CancellationToken.None): Thenable<Item | Item[] | undefined> {
|
||||
@@ -40,12 +46,11 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape {
|
||||
const itemsPromise = <TPromise<Item[]>>TPromise.wrap(itemsOrItemsPromise);
|
||||
|
||||
const quickPickWidget = this._proxy.$show({
|
||||
autoFocus: { autoFocusFirstEntry: true },
|
||||
placeHolder: options && options.placeHolder,
|
||||
matchOnDescription: options && options.matchOnDescription,
|
||||
matchOnDetail: options && options.matchOnDetail,
|
||||
ignoreFocusLost: options && options.ignoreFocusOut,
|
||||
canSelectMany: options && options.canPickMany
|
||||
canPickMany: options && options.canPickMany
|
||||
});
|
||||
|
||||
const promise = TPromise.any(<TPromise<number | Item[]>[]>[quickPickWidget, itemsPromise]).then(values => {
|
||||
@@ -55,7 +60,7 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape {
|
||||
|
||||
return itemsPromise.then(items => {
|
||||
|
||||
let pickItems: MyQuickPickItems[] = [];
|
||||
let pickItems: TransferQuickPickItems[] = [];
|
||||
for (let handle = 0; handle < items.length; handle++) {
|
||||
|
||||
let item = items[handle];
|
||||
@@ -143,4 +148,443 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape {
|
||||
return this._workspace.getWorkspaceFolders().filter(folder => folder.uri.toString() === selectedFolder.uri.toString())[0];
|
||||
});
|
||||
}
|
||||
|
||||
// ---- QuickInput
|
||||
|
||||
createQuickPick<T extends QuickPickItem>(extensionId: string): QuickPick<T> {
|
||||
const session = new ExtHostQuickPick(this._proxy, extensionId, () => this._sessions.delete(session._id));
|
||||
this._sessions.set(session._id, session);
|
||||
return session;
|
||||
}
|
||||
|
||||
createInputBox(extensionId: string): InputBox {
|
||||
const session = new ExtHostInputBox(this._proxy, extensionId, () => this._sessions.delete(session._id));
|
||||
this._sessions.set(session._id, session);
|
||||
return session;
|
||||
}
|
||||
|
||||
$onDidChangeValue(sessionId: number, value: string): void {
|
||||
const session = this._sessions.get(sessionId);
|
||||
if (session) {
|
||||
session._fireDidChangeValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
$onDidAccept(sessionId: number): void {
|
||||
const session = this._sessions.get(sessionId);
|
||||
if (session) {
|
||||
session._fireDidAccept();
|
||||
}
|
||||
}
|
||||
|
||||
$onDidChangeActive(sessionId: number, handles: number[]): void {
|
||||
const session = this._sessions.get(sessionId);
|
||||
if (session instanceof ExtHostQuickPick) {
|
||||
session._fireDidChangeActive(handles);
|
||||
}
|
||||
}
|
||||
|
||||
$onDidChangeSelection(sessionId: number, handles: number[]): void {
|
||||
const session = this._sessions.get(sessionId);
|
||||
if (session instanceof ExtHostQuickPick) {
|
||||
session._fireDidChangeSelection(handles);
|
||||
}
|
||||
}
|
||||
|
||||
$onDidTriggerButton(sessionId: number, handle: number): void {
|
||||
const session = this._sessions.get(sessionId);
|
||||
if (session) {
|
||||
session._fireDidTriggerButton(handle);
|
||||
}
|
||||
}
|
||||
|
||||
$onDidHide(sessionId: number): void {
|
||||
const session = this._sessions.get(sessionId);
|
||||
if (session) {
|
||||
session._fireDidHide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ExtHostQuickInput implements QuickInput {
|
||||
|
||||
private static _nextId = 1;
|
||||
_id = ExtHostQuickPick._nextId++;
|
||||
|
||||
private _title: string;
|
||||
private _steps: number;
|
||||
private _totalSteps: number;
|
||||
private _visible = false;
|
||||
private _enabled = true;
|
||||
private _busy = false;
|
||||
private _ignoreFocusOut = true;
|
||||
private _value = '';
|
||||
private _placeholder: string;
|
||||
private _buttons: QuickInputButton[] = [];
|
||||
private _handlesToButtons = new Map<number, QuickInputButton>();
|
||||
private _onDidAcceptEmitter = new Emitter<void>();
|
||||
private _onDidChangeValueEmitter = new Emitter<string>();
|
||||
private _onDidTriggerButtonEmitter = new Emitter<QuickInputButton>();
|
||||
private _onDidHideEmitter = new Emitter<void>();
|
||||
private _updateTimeout: number;
|
||||
private _pendingUpdate: TransferQuickInput = { id: this._id };
|
||||
|
||||
private _disposed = false;
|
||||
protected _disposables: IDisposable[] = [
|
||||
this._onDidTriggerButtonEmitter,
|
||||
this._onDidHideEmitter,
|
||||
this._onDidAcceptEmitter,
|
||||
this._onDidChangeValueEmitter
|
||||
];
|
||||
|
||||
constructor(protected _proxy: MainThreadQuickOpenShape, protected _extensionId: string, private _onDidDispose: () => void) {
|
||||
}
|
||||
|
||||
get title() {
|
||||
return this._title;
|
||||
}
|
||||
|
||||
set title(title: string) {
|
||||
this._title = title;
|
||||
this.update({ title });
|
||||
}
|
||||
|
||||
get step() {
|
||||
return this._steps;
|
||||
}
|
||||
|
||||
set step(step: number) {
|
||||
this._steps = step;
|
||||
this.update({ step });
|
||||
}
|
||||
|
||||
get totalSteps() {
|
||||
return this._totalSteps;
|
||||
}
|
||||
|
||||
set totalSteps(totalSteps: number) {
|
||||
this._totalSteps = totalSteps;
|
||||
this.update({ totalSteps });
|
||||
}
|
||||
|
||||
get enabled() {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
set enabled(enabled: boolean) {
|
||||
this._enabled = enabled;
|
||||
this.update({ enabled });
|
||||
}
|
||||
|
||||
get busy() {
|
||||
return this._busy;
|
||||
}
|
||||
|
||||
set busy(busy: boolean) {
|
||||
this._busy = busy;
|
||||
this.update({ busy });
|
||||
}
|
||||
|
||||
get ignoreFocusOut() {
|
||||
return this._ignoreFocusOut;
|
||||
}
|
||||
|
||||
set ignoreFocusOut(ignoreFocusOut: boolean) {
|
||||
this._ignoreFocusOut = ignoreFocusOut;
|
||||
this.update({ ignoreFocusOut });
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
set value(value: string) {
|
||||
this._value = value;
|
||||
this.update({ value });
|
||||
}
|
||||
|
||||
get placeholder() {
|
||||
return this._placeholder;
|
||||
}
|
||||
|
||||
set placeholder(placeholder: string) {
|
||||
this._placeholder = placeholder;
|
||||
this.update({ placeholder });
|
||||
}
|
||||
|
||||
onDidChangeValue = this._onDidChangeValueEmitter.event;
|
||||
|
||||
onDidAccept = this._onDidAcceptEmitter.event;
|
||||
|
||||
get buttons() {
|
||||
return this._buttons;
|
||||
}
|
||||
|
||||
set buttons(buttons: QuickInputButton[]) {
|
||||
this._buttons = buttons.slice();
|
||||
this._handlesToButtons.clear();
|
||||
buttons.forEach((button, i) => {
|
||||
const handle = button === QuickInputButtons.Back ? -1 : i;
|
||||
this._handlesToButtons.set(handle, button);
|
||||
});
|
||||
this.update({
|
||||
buttons: buttons.map<TransferQuickInputButton>((button, i) => ({
|
||||
iconPath: getIconUris(button.iconPath),
|
||||
tooltip: button.tooltip,
|
||||
handle: button === QuickInputButtons.Back ? -1 : i,
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
onDidTriggerButton = this._onDidTriggerButtonEmitter.event;
|
||||
|
||||
show(): void {
|
||||
this._visible = true;
|
||||
this.update({ visible: true });
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this._visible = false;
|
||||
this.update({ visible: false });
|
||||
}
|
||||
|
||||
onDidHide = this._onDidHideEmitter.event;
|
||||
|
||||
_fireDidAccept() {
|
||||
this._onDidAcceptEmitter.fire();
|
||||
}
|
||||
|
||||
_fireDidChangeValue(value) {
|
||||
this._value = value;
|
||||
this._onDidChangeValueEmitter.fire(value);
|
||||
}
|
||||
|
||||
_fireDidTriggerButton(handle: number) {
|
||||
const button = this._handlesToButtons.get(handle);
|
||||
this._onDidTriggerButtonEmitter.fire(button);
|
||||
}
|
||||
|
||||
_fireDidHide() {
|
||||
this._onDidHideEmitter.fire();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this._disposed) {
|
||||
return;
|
||||
}
|
||||
this._disposed = true;
|
||||
this._fireDidHide();
|
||||
this._disposables = dispose(this._disposables);
|
||||
if (this._updateTimeout) {
|
||||
clearTimeout(this._updateTimeout);
|
||||
this._updateTimeout = undefined;
|
||||
}
|
||||
this._onDidDispose();
|
||||
this._proxy.$dispose(this._id);
|
||||
}
|
||||
|
||||
protected update(properties: Record<string, any>): void {
|
||||
if (this._disposed) {
|
||||
return;
|
||||
}
|
||||
for (const key of Object.keys(properties)) {
|
||||
const value = properties[key];
|
||||
this._pendingUpdate[key] = value === undefined ? null : value;
|
||||
}
|
||||
|
||||
if ('visible' in this._pendingUpdate) {
|
||||
if (this._updateTimeout) {
|
||||
clearTimeout(this._updateTimeout);
|
||||
this._updateTimeout = undefined;
|
||||
}
|
||||
this.dispatchUpdate();
|
||||
} else if (this._visible && !this._updateTimeout) {
|
||||
// Defer the update so that multiple changes to setters dont cause a redraw each
|
||||
this._updateTimeout = setTimeout(() => {
|
||||
this._updateTimeout = undefined;
|
||||
this.dispatchUpdate();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private dispatchUpdate() {
|
||||
this._proxy.$createOrUpdate(this._pendingUpdate);
|
||||
this._pendingUpdate = { id: this._id };
|
||||
}
|
||||
}
|
||||
|
||||
function getIconUris(iconPath: QuickInputButton['iconPath']) {
|
||||
const light = getLightIconUri(iconPath);
|
||||
return { dark: getDarkIconUri(iconPath) || light, light };
|
||||
}
|
||||
|
||||
function getLightIconUri(iconPath: QuickInputButton['iconPath']) {
|
||||
if (iconPath && !(iconPath instanceof ThemeIcon)) {
|
||||
if (typeof iconPath === 'string'
|
||||
|| iconPath instanceof URI) {
|
||||
return getIconUri(iconPath);
|
||||
}
|
||||
return getIconUri(iconPath['light']);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getDarkIconUri(iconPath: QuickInputButton['iconPath']) {
|
||||
if (iconPath && !(iconPath instanceof ThemeIcon) && iconPath['dark']) {
|
||||
return getIconUri(iconPath['dark']);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getIconUri(iconPath: string | URI) {
|
||||
if (iconPath instanceof URI) {
|
||||
return iconPath;
|
||||
}
|
||||
return URI.file(iconPath);
|
||||
}
|
||||
|
||||
class ExtHostQuickPick<T extends QuickPickItem> extends ExtHostQuickInput implements QuickPick<T> {
|
||||
|
||||
private _items: T[] = [];
|
||||
private _handlesToItems = new Map<number, T>();
|
||||
private _itemsToHandles = new Map<T, number>();
|
||||
private _canSelectMany = false;
|
||||
private _matchOnDescription = true;
|
||||
private _matchOnDetail = true;
|
||||
private _activeItems: T[] = [];
|
||||
private _onDidChangeActiveEmitter = new Emitter<T[]>();
|
||||
private _selectedItems: T[] = [];
|
||||
private _onDidChangeSelectionEmitter = new Emitter<T[]>();
|
||||
|
||||
constructor(proxy: MainThreadQuickOpenShape, extensionId: string, onDispose: () => void) {
|
||||
super(proxy, extensionId, onDispose);
|
||||
this._disposables.push(
|
||||
this._onDidChangeActiveEmitter,
|
||||
this._onDidChangeSelectionEmitter,
|
||||
);
|
||||
this.update({ type: 'quickPick' });
|
||||
}
|
||||
|
||||
get items() {
|
||||
return this._items;
|
||||
}
|
||||
|
||||
set items(items: T[]) {
|
||||
this._items = items.slice();
|
||||
this._handlesToItems.clear();
|
||||
this._itemsToHandles.clear();
|
||||
items.forEach((item, i) => {
|
||||
this._handlesToItems.set(i, item);
|
||||
this._itemsToHandles.set(item, i);
|
||||
});
|
||||
this.update({
|
||||
items: items.map((item, i) => ({
|
||||
label: item.label,
|
||||
description: item.description,
|
||||
handle: i,
|
||||
detail: item.detail,
|
||||
picked: item.picked
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
get canSelectMany() {
|
||||
return this._canSelectMany;
|
||||
}
|
||||
|
||||
set canSelectMany(canSelectMany: boolean) {
|
||||
this._canSelectMany = canSelectMany;
|
||||
this.update({ canSelectMany });
|
||||
}
|
||||
|
||||
get matchOnDescription() {
|
||||
return this._matchOnDescription;
|
||||
}
|
||||
|
||||
set matchOnDescription(matchOnDescription: boolean) {
|
||||
this._matchOnDescription = matchOnDescription;
|
||||
this.update({ matchOnDescription });
|
||||
}
|
||||
|
||||
get matchOnDetail() {
|
||||
return this._matchOnDetail;
|
||||
}
|
||||
|
||||
set matchOnDetail(matchOnDetail: boolean) {
|
||||
this._matchOnDetail = matchOnDetail;
|
||||
this.update({ matchOnDetail });
|
||||
}
|
||||
|
||||
get activeItems() {
|
||||
return this._activeItems;
|
||||
}
|
||||
|
||||
set activeItems(activeItems: T[]) {
|
||||
this._activeItems = activeItems.filter(item => this._itemsToHandles.has(item));
|
||||
this.update({ activeItems: this._activeItems.map(item => this._itemsToHandles.get(item)) });
|
||||
}
|
||||
|
||||
onDidChangeActive = this._onDidChangeActiveEmitter.event;
|
||||
|
||||
get selectedItems() {
|
||||
return this._selectedItems;
|
||||
}
|
||||
|
||||
set selectedItems(selectedItems: T[]) {
|
||||
this._selectedItems = selectedItems.filter(item => this._itemsToHandles.has(item));
|
||||
this.update({ selectedItems: this._selectedItems.map(item => this._itemsToHandles.get(item)) });
|
||||
}
|
||||
|
||||
onDidChangeSelection = this._onDidChangeSelectionEmitter.event;
|
||||
|
||||
_fireDidChangeActive(handles: number[]) {
|
||||
const items = handles.map(handle => this._handlesToItems.get(handle));
|
||||
this._activeItems = items;
|
||||
this._onDidChangeActiveEmitter.fire(items);
|
||||
}
|
||||
|
||||
_fireDidChangeSelection(handles: number[]) {
|
||||
const items = handles.map(handle => this._handlesToItems.get(handle));
|
||||
this._selectedItems = items;
|
||||
this._onDidChangeSelectionEmitter.fire(items);
|
||||
}
|
||||
}
|
||||
|
||||
class ExtHostInputBox extends ExtHostQuickInput implements InputBox {
|
||||
|
||||
private _password: boolean;
|
||||
private _prompt: string;
|
||||
private _validationMessage: string;
|
||||
|
||||
constructor(proxy: MainThreadQuickOpenShape, extensionId: string, onDispose: () => void) {
|
||||
super(proxy, extensionId, onDispose);
|
||||
this.update({ type: 'inputBox' });
|
||||
}
|
||||
|
||||
get password() {
|
||||
return this._password;
|
||||
}
|
||||
|
||||
set password(password: boolean) {
|
||||
this._password = password;
|
||||
this.update({ password });
|
||||
}
|
||||
|
||||
get prompt() {
|
||||
return this._prompt;
|
||||
}
|
||||
|
||||
set prompt(prompt: string) {
|
||||
this._prompt = prompt;
|
||||
this.update({ prompt });
|
||||
}
|
||||
|
||||
get validationMessage() {
|
||||
return this._validationMessage;
|
||||
}
|
||||
|
||||
set validationMessage(validationMessage: string) {
|
||||
this._validationMessage = validationMessage;
|
||||
this.update({ validationMessage });
|
||||
}
|
||||
}
|
||||
@@ -237,14 +237,14 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG
|
||||
return this._resourceStatesMap.get(handle);
|
||||
}
|
||||
|
||||
async $executeResourceCommand(handle: number): TPromise<void> {
|
||||
$executeResourceCommand(handle: number): TPromise<void> {
|
||||
const command = this._resourceStatesCommandsMap.get(handle);
|
||||
|
||||
if (!command) {
|
||||
return;
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
await this._commands.executeCommand(command.command, ...command.arguments);
|
||||
return asWinJsPromise(_ => this._commands.executeCommand(command.command, ...command.arguments));
|
||||
}
|
||||
|
||||
_takeResourceStateSnapshot(): SCMRawResourceSplice[] {
|
||||
@@ -568,25 +568,25 @@ export class ExtHostSCM implements ExtHostSCMShape {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
async $executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number): TPromise<void> {
|
||||
$executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number): TPromise<void> {
|
||||
this.logService.trace('ExtHostSCM#$executeResourceCommand', sourceControlHandle, groupHandle, handle);
|
||||
|
||||
const sourceControl = this._sourceControls.get(sourceControlHandle);
|
||||
|
||||
if (!sourceControl) {
|
||||
return;
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
const group = sourceControl.getResourceGroup(groupHandle);
|
||||
|
||||
if (!group) {
|
||||
return;
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
await group.$executeResourceCommand(handle);
|
||||
return group.$executeResourceCommand(handle);
|
||||
}
|
||||
|
||||
async $validateInput(sourceControlHandle: number, value: string, cursorPosition: number): TPromise<[string, number] | undefined> {
|
||||
$validateInput(sourceControlHandle: number, value: string, cursorPosition: number): TPromise<[string, number] | undefined> {
|
||||
this.logService.trace('ExtHostSCM#$validateInput', sourceControlHandle);
|
||||
|
||||
const sourceControl = this._sourceControls.get(sourceControlHandle);
|
||||
@@ -599,12 +599,12 @@ export class ExtHostSCM implements ExtHostSCMShape {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
const result = await sourceControl.inputBox.validateInput(value, cursorPosition);
|
||||
return asWinJsPromise(_ => Promise.resolve(sourceControl.inputBox.validateInput(value, cursorPosition))).then(result => {
|
||||
if (!result) {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
return [result.message, result.type];
|
||||
return TPromise.as<[string, number]>([result.message, result.type]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
659
src/vs/workbench/api/node/extHostSearch.fileIndex.ts
Normal file
659
src/vs/workbench/api/node/extHostSearch.fileIndex.ts
Normal file
@@ -0,0 +1,659 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as path from 'path';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import * as glob from 'vs/base/common/glob';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { compareItemsByScore, IItemAccessor, prepareQuery, ScorerCache } from 'vs/base/parts/quickopen/common/quickOpenScorer';
|
||||
import { IFileMatch, IFolderQuery, IRawSearchQuery, ISearchCompleteStats, ISearchQuery } from 'vs/platform/search/common/search';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export interface IInternalFileMatch {
|
||||
base: URI;
|
||||
original?: URI;
|
||||
relativePath?: string; // Not present for extraFiles or absolute path matches
|
||||
basename: string;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the patterns that the provider handles. Discards sibling clauses and 'false' patterns
|
||||
*/
|
||||
export function resolvePatternsForProvider(globalPattern: glob.IExpression, folderPattern: glob.IExpression): string[] {
|
||||
const merged = {
|
||||
...(globalPattern || {}),
|
||||
...(folderPattern || {})
|
||||
};
|
||||
|
||||
return Object.keys(merged)
|
||||
.filter(key => {
|
||||
const value = merged[key];
|
||||
return typeof value === 'boolean' && value;
|
||||
});
|
||||
}
|
||||
|
||||
export class QueryGlobTester {
|
||||
|
||||
private _excludeExpression: glob.IExpression;
|
||||
private _parsedExcludeExpression: glob.ParsedExpression;
|
||||
|
||||
private _parsedIncludeExpression: glob.ParsedExpression;
|
||||
|
||||
constructor(config: ISearchQuery, folderQuery: IFolderQuery) {
|
||||
this._excludeExpression = {
|
||||
...(config.excludePattern || {}),
|
||||
...(folderQuery.excludePattern || {})
|
||||
};
|
||||
this._parsedExcludeExpression = glob.parse(this._excludeExpression);
|
||||
|
||||
// Empty includeExpression means include nothing, so no {} shortcuts
|
||||
let includeExpression: glob.IExpression = config.includePattern;
|
||||
if (folderQuery.includePattern) {
|
||||
if (includeExpression) {
|
||||
includeExpression = {
|
||||
...includeExpression,
|
||||
...folderQuery.includePattern
|
||||
};
|
||||
} else {
|
||||
includeExpression = folderQuery.includePattern;
|
||||
}
|
||||
}
|
||||
|
||||
if (includeExpression) {
|
||||
this._parsedIncludeExpression = glob.parse(includeExpression);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Guaranteed sync - siblingsFn should not return a promise.
|
||||
*/
|
||||
public includedInQuerySync(testPath: string, basename?: string, hasSibling?: (name: string) => boolean): boolean {
|
||||
if (this._parsedExcludeExpression && this._parsedExcludeExpression(testPath, basename, hasSibling)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._parsedIncludeExpression && !this._parsedIncludeExpression(testPath, basename, hasSibling)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guaranteed async.
|
||||
*/
|
||||
public includedInQuery(testPath: string, basename?: string, hasSibling?: (name: string) => boolean | TPromise<boolean>): TPromise<boolean> {
|
||||
const excludeP = this._parsedExcludeExpression ?
|
||||
TPromise.as(this._parsedExcludeExpression(testPath, basename, hasSibling)).then(result => !!result) :
|
||||
TPromise.wrap(false);
|
||||
|
||||
return excludeP.then(excluded => {
|
||||
if (excluded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this._parsedIncludeExpression ?
|
||||
TPromise.as(this._parsedIncludeExpression(testPath, basename, hasSibling)).then(result => !!result) :
|
||||
TPromise.wrap(true);
|
||||
}).then(included => {
|
||||
return included;
|
||||
});
|
||||
}
|
||||
|
||||
public hasSiblingExcludeClauses(): boolean {
|
||||
return hasSiblingClauses(this._excludeExpression);
|
||||
}
|
||||
}
|
||||
|
||||
function hasSiblingClauses(pattern: glob.IExpression): boolean {
|
||||
for (let key in pattern) {
|
||||
if (typeof pattern[key] !== 'boolean') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export interface IDirectoryEntry {
|
||||
base: URI;
|
||||
relativePath: string;
|
||||
basename: string;
|
||||
}
|
||||
|
||||
export interface IDirectoryTree {
|
||||
rootEntries: IDirectoryEntry[];
|
||||
pathToEntries: { [relativePath: string]: IDirectoryEntry[] };
|
||||
}
|
||||
|
||||
// ???
|
||||
interface IInternalSearchComplete {
|
||||
limitHit: boolean;
|
||||
results: IInternalFileMatch[];
|
||||
}
|
||||
|
||||
export class FileIndexSearchEngine {
|
||||
private filePattern: string;
|
||||
private normalizedFilePatternLowercase: string;
|
||||
private includePattern: glob.ParsedExpression;
|
||||
private maxResults: number;
|
||||
private exists: boolean;
|
||||
private isLimitHit: boolean;
|
||||
private resultCount: number;
|
||||
private isCanceled: boolean;
|
||||
|
||||
private activeCancellationTokens: Set<CancellationTokenSource>;
|
||||
|
||||
private globalExcludePattern: glob.ParsedExpression;
|
||||
|
||||
constructor(private config: ISearchQuery, private provider: vscode.FileIndexProvider) {
|
||||
this.filePattern = config.filePattern;
|
||||
this.includePattern = config.includePattern && glob.parse(config.includePattern);
|
||||
this.maxResults = config.maxResults || null;
|
||||
this.exists = config.exists;
|
||||
this.resultCount = 0;
|
||||
this.isLimitHit = false;
|
||||
this.activeCancellationTokens = new Set<CancellationTokenSource>();
|
||||
|
||||
if (this.filePattern) {
|
||||
this.normalizedFilePatternLowercase = strings.stripWildcards(this.filePattern).toLowerCase();
|
||||
}
|
||||
|
||||
this.globalExcludePattern = config.excludePattern && glob.parse(config.excludePattern);
|
||||
}
|
||||
|
||||
public cancel(): void {
|
||||
this.isCanceled = true;
|
||||
this.activeCancellationTokens.forEach(t => t.cancel());
|
||||
this.activeCancellationTokens = new Set();
|
||||
}
|
||||
|
||||
public search(_onResult: (match: IInternalFileMatch) => void): TPromise<{ isLimitHit: boolean }> {
|
||||
if (this.config.folderQueries.length !== 1) {
|
||||
throw new Error('Searches just one folder');
|
||||
}
|
||||
|
||||
const folderQuery = this.config.folderQueries[0];
|
||||
|
||||
return new TPromise<{ isLimitHit: boolean }>((resolve, reject) => {
|
||||
const onResult = (match: IInternalFileMatch) => {
|
||||
this.resultCount++;
|
||||
_onResult(match);
|
||||
};
|
||||
|
||||
if (this.isCanceled) {
|
||||
return resolve({ isLimitHit: this.isLimitHit });
|
||||
}
|
||||
|
||||
// For each extra file
|
||||
if (this.config.extraFileResources) {
|
||||
this.config.extraFileResources
|
||||
.forEach(extraFile => {
|
||||
const extraFileStr = extraFile.toString(); // ?
|
||||
const basename = path.basename(extraFileStr);
|
||||
if (this.globalExcludePattern && this.globalExcludePattern(extraFileStr, basename)) {
|
||||
return; // excluded
|
||||
}
|
||||
|
||||
// File: Check for match on file pattern and include pattern
|
||||
this.matchFile(onResult, { base: extraFile, basename });
|
||||
});
|
||||
}
|
||||
|
||||
return this.searchInFolder(folderQuery, _onResult)
|
||||
.then(() => {
|
||||
resolve({ isLimitHit: this.isLimitHit });
|
||||
}, (errs: Error[]) => {
|
||||
const errMsg = errs
|
||||
.map(err => toErrorMessage(err))
|
||||
.filter(msg => !!msg)[0];
|
||||
|
||||
reject(new Error(errMsg));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private searchInFolder(fq: IFolderQuery<URI>, onResult: (match: IInternalFileMatch) => void): TPromise<void> {
|
||||
let cancellation = new CancellationTokenSource();
|
||||
return new TPromise((resolve, reject) => {
|
||||
const options = this.getSearchOptionsForFolder(fq);
|
||||
const tree = this.initDirectoryTree();
|
||||
|
||||
const queryTester = new QueryGlobTester(this.config, fq);
|
||||
const noSiblingsClauses = !queryTester.hasSiblingExcludeClauses();
|
||||
|
||||
const onProviderResult = (uri: URI) => {
|
||||
if (this.isCanceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO@rob - ???
|
||||
const relativePath = path.relative(fq.folder.path, uri.path);
|
||||
if (noSiblingsClauses) {
|
||||
const basename = path.basename(uri.path);
|
||||
this.matchFile(onResult, { base: fq.folder, relativePath, basename, original: uri });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Optimize siblings clauses with ripgrep here.
|
||||
this.addDirectoryEntries(tree, fq.folder, relativePath, onResult);
|
||||
};
|
||||
|
||||
new TPromise(resolve => process.nextTick(resolve))
|
||||
.then(() => {
|
||||
this.activeCancellationTokens.add(cancellation);
|
||||
return this.provider.provideFileIndex(options, cancellation.token);
|
||||
})
|
||||
.then(results => {
|
||||
this.activeCancellationTokens.delete(cancellation);
|
||||
if (this.isCanceled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
results.forEach(onProviderResult);
|
||||
|
||||
this.matchDirectoryTree(tree, queryTester, onResult);
|
||||
return null;
|
||||
}).then(
|
||||
() => {
|
||||
cancellation.dispose();
|
||||
resolve(undefined);
|
||||
},
|
||||
err => {
|
||||
cancellation.dispose();
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getSearchOptionsForFolder(fq: IFolderQuery<URI>): vscode.FileIndexOptions {
|
||||
const includes = resolvePatternsForProvider(this.config.includePattern, fq.includePattern);
|
||||
const excludes = resolvePatternsForProvider(this.config.excludePattern, fq.excludePattern);
|
||||
|
||||
return {
|
||||
folder: fq.folder,
|
||||
excludes,
|
||||
includes,
|
||||
useIgnoreFiles: !this.config.disregardIgnoreFiles,
|
||||
followSymlinks: !this.config.ignoreSymlinks
|
||||
};
|
||||
}
|
||||
|
||||
private initDirectoryTree(): IDirectoryTree {
|
||||
const tree: IDirectoryTree = {
|
||||
rootEntries: [],
|
||||
pathToEntries: Object.create(null)
|
||||
};
|
||||
tree.pathToEntries['.'] = tree.rootEntries;
|
||||
return tree;
|
||||
}
|
||||
|
||||
private addDirectoryEntries({ pathToEntries }: IDirectoryTree, base: URI, relativeFile: string, onResult: (result: IInternalFileMatch) => void) {
|
||||
// Support relative paths to files from a root resource (ignores excludes)
|
||||
if (relativeFile === this.filePattern) {
|
||||
const basename = path.basename(this.filePattern);
|
||||
this.matchFile(onResult, { base: base, relativePath: this.filePattern, basename });
|
||||
}
|
||||
|
||||
function add(relativePath: string) {
|
||||
const basename = path.basename(relativePath);
|
||||
const dirname = path.dirname(relativePath);
|
||||
let entries = pathToEntries[dirname];
|
||||
if (!entries) {
|
||||
entries = pathToEntries[dirname] = [];
|
||||
add(dirname);
|
||||
}
|
||||
entries.push({
|
||||
base,
|
||||
relativePath,
|
||||
basename
|
||||
});
|
||||
}
|
||||
|
||||
add(relativeFile);
|
||||
}
|
||||
|
||||
private matchDirectoryTree({ rootEntries, pathToEntries }: IDirectoryTree, queryTester: QueryGlobTester, onResult: (result: IInternalFileMatch) => void) {
|
||||
const self = this;
|
||||
const filePattern = this.filePattern;
|
||||
function matchDirectory(entries: IDirectoryEntry[]) {
|
||||
// self.directoriesWalked++;
|
||||
for (let i = 0, n = entries.length; i < n; i++) {
|
||||
const entry = entries[i];
|
||||
const { relativePath, basename } = entry;
|
||||
|
||||
// Check exclude pattern
|
||||
// If the user searches for the exact file name, we adjust the glob matching
|
||||
// to ignore filtering by siblings because the user seems to know what she
|
||||
// is searching for and we want to include the result in that case anyway
|
||||
const hasSibling = glob.hasSiblingFn(() => entries.map(entry => entry.basename));
|
||||
if (!queryTester.includedInQuerySync(relativePath, basename, filePattern !== basename ? hasSibling : undefined)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const sub = pathToEntries[relativePath];
|
||||
if (sub) {
|
||||
matchDirectory(sub);
|
||||
} else {
|
||||
// self.filesWalked++;
|
||||
if (relativePath === filePattern) {
|
||||
continue; // ignore file if its path matches with the file pattern because that is already matched above
|
||||
}
|
||||
|
||||
self.matchFile(onResult, entry);
|
||||
}
|
||||
|
||||
if (self.isLimitHit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
matchDirectory(rootEntries);
|
||||
}
|
||||
|
||||
private matchFile(onResult: (result: IInternalFileMatch) => void, candidate: IInternalFileMatch): void {
|
||||
if (this.isFilePatternMatch(candidate.relativePath) && (!this.includePattern || this.includePattern(candidate.relativePath, candidate.basename))) {
|
||||
if (this.exists || (this.maxResults && this.resultCount >= this.maxResults)) {
|
||||
this.isLimitHit = true;
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
if (!this.isLimitHit) {
|
||||
onResult(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isFilePatternMatch(path: string): boolean {
|
||||
// Check for search pattern
|
||||
if (this.filePattern) {
|
||||
if (this.filePattern === '*') {
|
||||
return true; // support the all-matching wildcard
|
||||
}
|
||||
|
||||
return strings.fuzzyContains(path, this.normalizedFilePatternLowercase);
|
||||
}
|
||||
|
||||
// No patterns means we match all
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class FileIndexSearchManager {
|
||||
|
||||
private static readonly BATCH_SIZE = 512;
|
||||
|
||||
private caches: { [cacheKey: string]: Cache; } = Object.create(null);
|
||||
|
||||
private readonly folderCacheKeys = new Map<string, Set<string>>();
|
||||
|
||||
public fileSearch(config: ISearchQuery, provider: vscode.FileIndexProvider, onBatch: (matches: IFileMatch[]) => void): TPromise<ISearchCompleteStats> {
|
||||
if (config.sortByScore) {
|
||||
let sortedSearch = this.trySortedSearchFromCache(config);
|
||||
if (!sortedSearch) {
|
||||
const engineConfig = config.maxResults ?
|
||||
{
|
||||
...config,
|
||||
...{ maxResults: null }
|
||||
} :
|
||||
config;
|
||||
|
||||
const engine = new FileIndexSearchEngine(engineConfig, provider);
|
||||
sortedSearch = this.doSortedSearch(engine, config);
|
||||
}
|
||||
|
||||
return new TPromise<ISearchCompleteStats>((c, e) => {
|
||||
sortedSearch.then(complete => {
|
||||
this.sendAsBatches(complete.results, onBatch, FileIndexSearchManager.BATCH_SIZE);
|
||||
c(complete);
|
||||
}, e, onBatch);
|
||||
}, () => {
|
||||
sortedSearch.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
const engine = new FileIndexSearchEngine(config, provider);
|
||||
return this.doSearch(engine)
|
||||
.then(complete => {
|
||||
this.sendAsBatches(complete.results, onBatch, FileIndexSearchManager.BATCH_SIZE);
|
||||
return <ISearchCompleteStats>{
|
||||
limitHit: complete.limitHit
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private getFolderCacheKey(config: ISearchQuery): string {
|
||||
const uri = config.folderQueries[0].folder.toString();
|
||||
const folderCacheKey = config.cacheKey && `${uri}_${config.cacheKey}`;
|
||||
if (!this.folderCacheKeys.get(config.cacheKey)) {
|
||||
this.folderCacheKeys.set(config.cacheKey, new Set());
|
||||
}
|
||||
|
||||
this.folderCacheKeys.get(config.cacheKey).add(folderCacheKey);
|
||||
|
||||
return folderCacheKey;
|
||||
}
|
||||
|
||||
private rawMatchToSearchItem(match: IInternalFileMatch): IFileMatch {
|
||||
return {
|
||||
resource: match.original || resources.joinPath(match.base, match.relativePath)
|
||||
};
|
||||
}
|
||||
|
||||
private doSortedSearch(engine: FileIndexSearchEngine, config: ISearchQuery): TPromise<IInternalSearchComplete> {
|
||||
let searchPromise: TPromise<void>;
|
||||
let allResultsPromise = new TPromise<IInternalSearchComplete>((c, e) => {
|
||||
searchPromise = this.doSearch(engine).then(c, e);
|
||||
}, () => {
|
||||
searchPromise.cancel();
|
||||
});
|
||||
|
||||
const folderCacheKey = this.getFolderCacheKey(config);
|
||||
let cache: Cache;
|
||||
if (folderCacheKey) {
|
||||
cache = this.getOrCreateCache(folderCacheKey);
|
||||
cache.resultsToSearchCache[config.filePattern] = allResultsPromise;
|
||||
allResultsPromise.then(null, err => {
|
||||
delete cache.resultsToSearchCache[config.filePattern];
|
||||
});
|
||||
allResultsPromise = this.preventCancellation(allResultsPromise);
|
||||
}
|
||||
|
||||
let chained: TPromise<void>;
|
||||
return new TPromise<IInternalSearchComplete>((c, e) => {
|
||||
chained = allResultsPromise.then(complete => {
|
||||
const scorerCache: ScorerCache = cache ? cache.scorerCache : Object.create(null);
|
||||
return this.sortResults(config, complete.results, scorerCache)
|
||||
.then(sortedResults => {
|
||||
|
||||
c({
|
||||
limitHit: complete.limitHit || typeof config.maxResults === 'number' && complete.results.length > config.maxResults, // ??
|
||||
results: sortedResults
|
||||
});
|
||||
});
|
||||
}, e);
|
||||
}, () => {
|
||||
chained.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
private getOrCreateCache(cacheKey: string): Cache {
|
||||
const existing = this.caches[cacheKey];
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
return this.caches[cacheKey] = new Cache();
|
||||
}
|
||||
|
||||
private trySortedSearchFromCache(config: ISearchQuery): TPromise<IInternalSearchComplete> {
|
||||
const folderCacheKey = this.getFolderCacheKey(config);
|
||||
const cache = folderCacheKey && this.caches[folderCacheKey];
|
||||
if (!cache) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const cached = this.getResultsFromCache(cache, config.filePattern);
|
||||
if (cached) {
|
||||
let chained: TPromise<void>;
|
||||
return new TPromise<IInternalSearchComplete>((c, e) => {
|
||||
chained = cached.then(complete => {
|
||||
return this.sortResults(config, complete.results, cache.scorerCache)
|
||||
.then(sortedResults => {
|
||||
c({
|
||||
limitHit: complete.limitHit || typeof config.maxResults === 'number' && complete.results.length > config.maxResults,
|
||||
results: sortedResults
|
||||
});
|
||||
});
|
||||
}, e);
|
||||
}, () => {
|
||||
chained.cancel();
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private sortResults(config: IRawSearchQuery, results: IInternalFileMatch[], scorerCache: ScorerCache): TPromise<IInternalFileMatch[]> {
|
||||
// we use the same compare function that is used later when showing the results using fuzzy scoring
|
||||
// this is very important because we are also limiting the number of results by config.maxResults
|
||||
// and as such we want the top items to be included in this result set if the number of items
|
||||
// exceeds config.maxResults.
|
||||
const query = prepareQuery(config.filePattern);
|
||||
const compare = (matchA: IInternalFileMatch, matchB: IInternalFileMatch) => compareItemsByScore(matchA, matchB, query, true, FileMatchItemAccessor, scorerCache);
|
||||
|
||||
return arrays.topAsync(results, compare, config.maxResults, 10000);
|
||||
}
|
||||
|
||||
private sendAsBatches(rawMatches: IInternalFileMatch[], onBatch: (batch: IFileMatch[]) => void, batchSize: number) {
|
||||
const serializedMatches = rawMatches.map(rawMatch => this.rawMatchToSearchItem(rawMatch));
|
||||
if (batchSize && batchSize > 0) {
|
||||
for (let i = 0; i < serializedMatches.length; i += batchSize) {
|
||||
onBatch(serializedMatches.slice(i, i + batchSize));
|
||||
}
|
||||
} else {
|
||||
onBatch(serializedMatches);
|
||||
}
|
||||
}
|
||||
|
||||
private getResultsFromCache(cache: Cache, searchValue: string): TPromise<IInternalSearchComplete> {
|
||||
if (path.isAbsolute(searchValue)) {
|
||||
return null; // bypass cache if user looks up an absolute path where matching goes directly on disk
|
||||
}
|
||||
|
||||
// Find cache entries by prefix of search value
|
||||
const hasPathSep = searchValue.indexOf(path.sep) >= 0;
|
||||
let cached: TPromise<IInternalSearchComplete>;
|
||||
for (let previousSearch in cache.resultsToSearchCache) {
|
||||
|
||||
// If we narrow down, we might be able to reuse the cached results
|
||||
if (strings.startsWith(searchValue, previousSearch)) {
|
||||
if (hasPathSep && previousSearch.indexOf(path.sep) < 0) {
|
||||
continue; // since a path character widens the search for potential more matches, require it in previous search too
|
||||
}
|
||||
|
||||
const c = cache.resultsToSearchCache[previousSearch];
|
||||
cached = this.preventCancellation(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cached) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new TPromise<IInternalSearchComplete>((c, e) => {
|
||||
cached.then(complete => {
|
||||
// Pattern match on results
|
||||
let results: IInternalFileMatch[] = [];
|
||||
const normalizedSearchValueLowercase = strings.stripWildcards(searchValue).toLowerCase();
|
||||
for (let i = 0; i < complete.results.length; i++) {
|
||||
let entry = complete.results[i];
|
||||
|
||||
// Check if this entry is a match for the search value
|
||||
if (!strings.fuzzyContains(entry.relativePath, normalizedSearchValueLowercase)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
results.push(entry);
|
||||
}
|
||||
|
||||
c({
|
||||
limitHit: complete.limitHit,
|
||||
results
|
||||
});
|
||||
}, e);
|
||||
}, () => {
|
||||
cached.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
private doSearch(engine: FileIndexSearchEngine): TPromise<IInternalSearchComplete> {
|
||||
const results: IInternalFileMatch[] = [];
|
||||
const onResult = match => results.push(match);
|
||||
return new TPromise<IInternalSearchComplete>((c, e) => {
|
||||
engine.search(onResult).then(result => {
|
||||
c({
|
||||
limitHit: result.isLimitHit,
|
||||
results
|
||||
});
|
||||
}, e);
|
||||
}, () => {
|
||||
engine.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
public clearCache(cacheKey: string): TPromise<void> {
|
||||
if (!this.folderCacheKeys.has(cacheKey)) {
|
||||
return TPromise.wrap(undefined);
|
||||
}
|
||||
|
||||
const expandedKeys = this.folderCacheKeys.get(cacheKey);
|
||||
expandedKeys.forEach(key => delete this.caches[key]);
|
||||
|
||||
this.folderCacheKeys.delete(cacheKey);
|
||||
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
private preventCancellation<C>(promise: TPromise<C>): TPromise<C> {
|
||||
return new TPromise<C>((c, e) => {
|
||||
// Allow for piled up cancellations to come through first.
|
||||
process.nextTick(() => {
|
||||
promise.then(c, e);
|
||||
});
|
||||
}, () => {
|
||||
// Do not propagate.
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Cache {
|
||||
|
||||
public resultsToSearchCache: { [searchValue: string]: TPromise<IInternalSearchComplete>; } = Object.create(null);
|
||||
|
||||
public scorerCache: ScorerCache = Object.create(null);
|
||||
}
|
||||
|
||||
const FileMatchItemAccessor = new class implements IItemAccessor<IInternalFileMatch> {
|
||||
|
||||
public getItemLabel(match: IInternalFileMatch): string {
|
||||
return match.basename; // e.g. myFile.txt
|
||||
}
|
||||
|
||||
public getItemDescription(match: IInternalFileMatch): string {
|
||||
return match.relativePath.substr(0, match.relativePath.length - match.basename.length - 1); // e.g. some/path/to/file
|
||||
}
|
||||
|
||||
public getItemPath(match: IInternalFileMatch): string {
|
||||
return match.relativePath; // e.g. some/path/to/file/myFile.txt
|
||||
}
|
||||
};
|
||||
@@ -4,61 +4,711 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { asWinJsPromise } from 'vs/base/common/async';
|
||||
import * as path from 'path';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import * as glob from 'vs/base/common/glob';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IPatternInfo } from 'vs/platform/search/common/search';
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import { IFileMatch, IFolderQuery, IPatternInfo, IRawSearchQuery, ISearchCompleteStats, ISearchQuery } from 'vs/platform/search/common/search';
|
||||
import * as vscode from 'vscode';
|
||||
import { ExtHostSearchShape, IMainContext, MainContext, MainThreadSearchShape } from './extHost.protocol';
|
||||
import { toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IInternalFileMatch, QueryGlobTester, resolvePatternsForProvider, IDirectoryTree, IDirectoryEntry, FileIndexSearchManager } from 'vs/workbench/api/node/extHostSearch.fileIndex';
|
||||
|
||||
export interface ISchemeTransformer {
|
||||
transformOutgoing(scheme: string): string;
|
||||
}
|
||||
|
||||
export class ExtHostSearch implements ExtHostSearchShape {
|
||||
|
||||
private readonly _proxy: MainThreadSearchShape;
|
||||
private readonly _searchProvider = new Map<number, vscode.SearchProvider>();
|
||||
private readonly _fileSearchProvider = new Map<number, vscode.FileSearchProvider>();
|
||||
private readonly _textSearchProvider = new Map<number, vscode.TextSearchProvider>();
|
||||
private readonly _fileIndexProvider = new Map<number, vscode.FileIndexProvider>();
|
||||
private _handlePool: number = 0;
|
||||
|
||||
constructor(mainContext: IMainContext) {
|
||||
private _fileSearchManager: FileSearchManager;
|
||||
private _fileIndexSearchManager: FileIndexSearchManager;
|
||||
|
||||
constructor(mainContext: IMainContext, private _schemeTransformer: ISchemeTransformer, private _extfs = extfs) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadSearch);
|
||||
this._fileSearchManager = new FileSearchManager();
|
||||
this._fileIndexSearchManager = new FileIndexSearchManager();
|
||||
}
|
||||
|
||||
registerSearchProvider(scheme: string, provider: vscode.SearchProvider) {
|
||||
const handle = this._handlePool++;
|
||||
this._searchProvider.set(handle, provider);
|
||||
this._proxy.$registerSearchProvider(handle, scheme);
|
||||
return {
|
||||
dispose: () => {
|
||||
this._searchProvider.delete(handle);
|
||||
this._proxy.$unregisterProvider(handle);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$provideFileSearchResults(handle: number, session: number, query: string): TPromise<void> {
|
||||
const provider = this._searchProvider.get(handle);
|
||||
if (!provider.provideFileSearchResults) {
|
||||
return TPromise.as(undefined);
|
||||
private _transformScheme(scheme: string): string {
|
||||
if (this._schemeTransformer) {
|
||||
return this._schemeTransformer.transformOutgoing(scheme);
|
||||
}
|
||||
const progress = {
|
||||
report: (uri) => {
|
||||
this._proxy.$handleFindMatch(handle, session, uri);
|
||||
}
|
||||
};
|
||||
return asWinJsPromise(token => provider.provideFileSearchResults(query, progress, token));
|
||||
return scheme;
|
||||
}
|
||||
|
||||
$provideTextSearchResults(handle: number, session: number, pattern: IPatternInfo, options: { includes: string[], excludes: string[] }): TPromise<void> {
|
||||
const provider = this._searchProvider.get(handle);
|
||||
registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProvider) {
|
||||
const handle = this._handlePool++;
|
||||
this._fileSearchProvider.set(handle, provider);
|
||||
this._proxy.$registerFileSearchProvider(handle, this._transformScheme(scheme));
|
||||
return toDisposable(() => {
|
||||
this._fileSearchProvider.delete(handle);
|
||||
this._proxy.$unregisterProvider(handle);
|
||||
});
|
||||
}
|
||||
|
||||
registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProvider) {
|
||||
const handle = this._handlePool++;
|
||||
this._textSearchProvider.set(handle, provider);
|
||||
this._proxy.$registerTextSearchProvider(handle, this._transformScheme(scheme));
|
||||
return toDisposable(() => {
|
||||
this._textSearchProvider.delete(handle);
|
||||
this._proxy.$unregisterProvider(handle);
|
||||
});
|
||||
}
|
||||
|
||||
registerFileIndexProvider(scheme: string, provider: vscode.FileIndexProvider) {
|
||||
const handle = this._handlePool++;
|
||||
this._fileIndexProvider.set(handle, provider);
|
||||
this._proxy.$registerFileIndexProvider(handle, this._transformScheme(scheme));
|
||||
return toDisposable(() => {
|
||||
this._fileSearchProvider.delete(handle);
|
||||
this._proxy.$unregisterProvider(handle); // TODO@roblou - unregisterFileIndexProvider
|
||||
});
|
||||
}
|
||||
|
||||
$provideFileSearchResults(handle: number, session: number, rawQuery: IRawSearchQuery): TPromise<ISearchCompleteStats> {
|
||||
const provider = this._fileSearchProvider.get(handle);
|
||||
const query = reviveQuery(rawQuery);
|
||||
if (provider) {
|
||||
return this._fileSearchManager.fileSearch(query, provider, batch => {
|
||||
this._proxy.$handleFileMatch(handle, session, batch.map(p => p.resource));
|
||||
});
|
||||
} else {
|
||||
const indexProvider = this._fileIndexProvider.get(handle);
|
||||
if (indexProvider) {
|
||||
return this._fileIndexSearchManager.fileSearch(query, indexProvider, batch => {
|
||||
this._proxy.$handleFileMatch(handle, session, batch.map(p => p.resource));
|
||||
});
|
||||
} else {
|
||||
throw new Error('something went wrong');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$clearCache(cacheKey: string): TPromise<void> {
|
||||
// Actually called once per provider.
|
||||
// Only relevant to file index search.
|
||||
return this._fileIndexSearchManager.clearCache(cacheKey);
|
||||
}
|
||||
|
||||
$provideTextSearchResults(handle: number, session: number, pattern: IPatternInfo, rawQuery: IRawSearchQuery): TPromise<ISearchCompleteStats> {
|
||||
const provider = this._textSearchProvider.get(handle);
|
||||
if (!provider.provideTextSearchResults) {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
const progress = {
|
||||
report: (data: vscode.TextSearchResult) => {
|
||||
this._proxy.$handleFindMatch(handle, session, [data.uri, {
|
||||
lineNumber: data.range.start.line,
|
||||
preview: data.preview.leading + data.preview.matching + data.preview.trailing,
|
||||
offsetAndLengths: [[data.preview.leading.length, data.preview.matching.length]]
|
||||
}]);
|
||||
}
|
||||
};
|
||||
return asWinJsPromise(token => provider.provideTextSearchResults(pattern, options, progress, token));
|
||||
|
||||
const query = reviveQuery(rawQuery);
|
||||
const engine = new TextSearchEngine(pattern, query, provider, this._extfs);
|
||||
return engine.search(progress => this._proxy.$handleTextMatch(handle, session, progress));
|
||||
}
|
||||
}
|
||||
|
||||
function reviveQuery(rawQuery: IRawSearchQuery): ISearchQuery {
|
||||
return {
|
||||
...rawQuery,
|
||||
...{
|
||||
folderQueries: rawQuery.folderQueries && rawQuery.folderQueries.map(reviveFolderQuery),
|
||||
extraFileResources: rawQuery.extraFileResources && rawQuery.extraFileResources.map(components => URI.revive(components))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function reviveFolderQuery(rawFolderQuery: IFolderQuery<UriComponents>): IFolderQuery<URI> {
|
||||
return {
|
||||
...rawFolderQuery,
|
||||
folder: URI.revive(rawFolderQuery.folder)
|
||||
};
|
||||
}
|
||||
|
||||
class TextSearchResultsCollector {
|
||||
private _batchedCollector: BatchedCollector<IFileMatch>;
|
||||
|
||||
private _currentFolderIdx: number;
|
||||
private _currentUri: URI;
|
||||
private _currentFileMatch: IFileMatch;
|
||||
|
||||
constructor(private _onResult: (result: IFileMatch[]) => void) {
|
||||
this._batchedCollector = new BatchedCollector<IFileMatch>(512, items => this.sendItems(items));
|
||||
}
|
||||
|
||||
add(data: vscode.TextSearchResult, folderIdx: number): void {
|
||||
// Collects TextSearchResults into IInternalFileMatches and collates using BatchedCollector.
|
||||
// This is efficient for ripgrep which sends results back one file at a time. It wouldn't be efficient for other search
|
||||
// providers that send results in random order. We could do this step afterwards instead.
|
||||
if (this._currentFileMatch && (this._currentFolderIdx !== folderIdx || resources.isEqual(this._currentUri, data.uri))) {
|
||||
this.pushToCollector();
|
||||
this._currentFileMatch = null;
|
||||
}
|
||||
|
||||
if (!this._currentFileMatch) {
|
||||
this._currentFileMatch = {
|
||||
resource: data.uri,
|
||||
lineMatches: []
|
||||
};
|
||||
}
|
||||
|
||||
// TODO@roblou - line text is sent for every match
|
||||
const matchRange = data.preview.match;
|
||||
this._currentFileMatch.lineMatches.push({
|
||||
lineNumber: data.range.start.line,
|
||||
preview: data.preview.text,
|
||||
offsetAndLengths: [[matchRange.start.character, matchRange.end.character - matchRange.start.character]]
|
||||
});
|
||||
}
|
||||
|
||||
private pushToCollector(): void {
|
||||
const size = this._currentFileMatch ?
|
||||
this._currentFileMatch.lineMatches.reduce((acc, match) => acc + match.offsetAndLengths.length, 0) :
|
||||
0;
|
||||
this._batchedCollector.addItem(this._currentFileMatch, size);
|
||||
}
|
||||
|
||||
flush(): void {
|
||||
this.pushToCollector();
|
||||
this._batchedCollector.flush();
|
||||
}
|
||||
|
||||
private sendItems(items: IFileMatch[]): void {
|
||||
this._onResult(items);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects items that have a size - before the cumulative size of collected items reaches START_BATCH_AFTER_COUNT, the callback is called for every
|
||||
* set of items collected.
|
||||
* But after that point, the callback is called with batches of maxBatchSize.
|
||||
* If the batch isn't filled within some time, the callback is also called.
|
||||
*/
|
||||
class BatchedCollector<T> {
|
||||
private static readonly TIMEOUT = 4000;
|
||||
|
||||
// After START_BATCH_AFTER_COUNT items have been collected, stop flushing on timeout
|
||||
private static readonly START_BATCH_AFTER_COUNT = 50;
|
||||
|
||||
private totalNumberCompleted = 0;
|
||||
private batch: T[] = [];
|
||||
private batchSize = 0;
|
||||
private timeoutHandle: number;
|
||||
|
||||
constructor(private maxBatchSize: number, private cb: (items: T[]) => void) {
|
||||
}
|
||||
|
||||
addItem(item: T, size: number): void {
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.addItemToBatch(item, size);
|
||||
}
|
||||
|
||||
addItems(items: T[], size: number): void {
|
||||
if (!items) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.maxBatchSize > 0) {
|
||||
this.addItemsToBatch(items, size);
|
||||
} else {
|
||||
this.cb(items);
|
||||
}
|
||||
}
|
||||
|
||||
private addItemToBatch(item: T, size: number): void {
|
||||
this.batch.push(item);
|
||||
this.batchSize += size;
|
||||
this.onUpdate();
|
||||
}
|
||||
|
||||
private addItemsToBatch(item: T[], size: number): void {
|
||||
this.batch = this.batch.concat(item);
|
||||
this.batchSize += size;
|
||||
this.onUpdate();
|
||||
}
|
||||
|
||||
private onUpdate(): void {
|
||||
if (this.totalNumberCompleted < BatchedCollector.START_BATCH_AFTER_COUNT) {
|
||||
// Flush because we aren't batching yet
|
||||
this.flush();
|
||||
} else if (this.batchSize >= this.maxBatchSize) {
|
||||
// Flush because the batch is full
|
||||
this.flush();
|
||||
} else if (!this.timeoutHandle) {
|
||||
// No timeout running, start a timeout to flush
|
||||
this.timeoutHandle = setTimeout(() => {
|
||||
this.flush();
|
||||
}, BatchedCollector.TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
flush(): void {
|
||||
if (this.batchSize) {
|
||||
this.totalNumberCompleted += this.batchSize;
|
||||
this.cb(this.batch);
|
||||
this.batch = [];
|
||||
this.batchSize = 0;
|
||||
|
||||
if (this.timeoutHandle) {
|
||||
clearTimeout(this.timeoutHandle);
|
||||
this.timeoutHandle = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TextSearchEngine {
|
||||
|
||||
private activeCancellationTokens = new Set<CancellationTokenSource>();
|
||||
private collector: TextSearchResultsCollector;
|
||||
|
||||
private isLimitHit: boolean;
|
||||
private resultCount = 0;
|
||||
private isCanceled: boolean;
|
||||
|
||||
constructor(private pattern: IPatternInfo, private config: ISearchQuery, private provider: vscode.TextSearchProvider, private _extfs: typeof extfs) {
|
||||
}
|
||||
|
||||
public cancel(): void {
|
||||
this.isCanceled = true;
|
||||
this.activeCancellationTokens.forEach(t => t.cancel());
|
||||
this.activeCancellationTokens = new Set();
|
||||
}
|
||||
|
||||
public search(onProgress: (matches: IFileMatch[]) => void): TPromise<{ limitHit: boolean }> {
|
||||
const folderQueries = this.config.folderQueries;
|
||||
|
||||
return new TPromise<{ limitHit: boolean }>((resolve, reject) => {
|
||||
this.collector = new TextSearchResultsCollector(onProgress);
|
||||
|
||||
const onResult = (match: vscode.TextSearchResult, folderIdx: number) => {
|
||||
if (this.isCanceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.resultCount >= this.config.maxResults) {
|
||||
this.isLimitHit = true;
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
if (!this.isLimitHit) {
|
||||
this.resultCount++;
|
||||
this.collector.add(match, folderIdx);
|
||||
}
|
||||
};
|
||||
|
||||
// For each root folder
|
||||
TPromise.join(folderQueries.map((fq, i) => {
|
||||
return this.searchInFolder(fq, r => onResult(r, i));
|
||||
})).then(() => {
|
||||
this.collector.flush();
|
||||
resolve({ limitHit: this.isLimitHit });
|
||||
}, (errs: Error[]) => {
|
||||
const errMsg = errs
|
||||
.map(err => toErrorMessage(err))
|
||||
.filter(msg => !!msg)[0];
|
||||
|
||||
reject(new Error(errMsg));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private searchInFolder(folderQuery: IFolderQuery<URI>, onResult: (result: vscode.TextSearchResult) => void): TPromise<void> {
|
||||
let cancellation = new CancellationTokenSource();
|
||||
return new TPromise((resolve, reject) => {
|
||||
|
||||
const queryTester = new QueryGlobTester(this.config, folderQuery);
|
||||
const testingPs = [];
|
||||
const progress = {
|
||||
report: (result: vscode.TextSearchResult) => {
|
||||
const hasSibling = folderQuery.folder.scheme === 'file' && glob.hasSiblingPromiseFn(() => {
|
||||
return this.readdir(path.dirname(result.uri.fsPath));
|
||||
});
|
||||
|
||||
const relativePath = path.relative(folderQuery.folder.fsPath, result.uri.fsPath);
|
||||
testingPs.push(
|
||||
queryTester.includedInQuery(relativePath, path.basename(relativePath), hasSibling)
|
||||
.then(included => {
|
||||
if (included) {
|
||||
onResult(result);
|
||||
}
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const searchOptions = this.getSearchOptionsForFolder(folderQuery);
|
||||
new TPromise(resolve => process.nextTick(resolve))
|
||||
.then(() => {
|
||||
this.activeCancellationTokens.add(cancellation);
|
||||
return this.provider.provideTextSearchResults(patternInfoToQuery(this.pattern), searchOptions, progress, cancellation.token);
|
||||
})
|
||||
.then(() => {
|
||||
this.activeCancellationTokens.delete(cancellation);
|
||||
return TPromise.join(testingPs);
|
||||
})
|
||||
.then(
|
||||
() => {
|
||||
cancellation.dispose();
|
||||
resolve(null);
|
||||
},
|
||||
err => {
|
||||
cancellation.dispose();
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private readdir(dirname: string): TPromise<string[]> {
|
||||
return new TPromise((resolve, reject) => {
|
||||
this._extfs.readdir(dirname, (err, files) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(files);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getSearchOptionsForFolder(fq: IFolderQuery<URI>): vscode.TextSearchOptions {
|
||||
const includes = resolvePatternsForProvider(this.config.includePattern, fq.includePattern);
|
||||
const excludes = resolvePatternsForProvider(this.config.excludePattern, fq.excludePattern);
|
||||
|
||||
return {
|
||||
folder: URI.from(fq.folder),
|
||||
excludes,
|
||||
includes,
|
||||
useIgnoreFiles: !this.config.disregardIgnoreFiles,
|
||||
followSymlinks: !this.config.ignoreSymlinks,
|
||||
encoding: this.config.fileEncoding,
|
||||
maxFileSize: this.config.maxFileSize,
|
||||
maxResults: this.config.maxResults
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function patternInfoToQuery(patternInfo: IPatternInfo): vscode.TextSearchQuery {
|
||||
return <vscode.TextSearchQuery>{
|
||||
isCaseSensitive: patternInfo.isCaseSensitive || false,
|
||||
isRegExp: patternInfo.isRegExp || false,
|
||||
isWordMatch: patternInfo.isWordMatch || false,
|
||||
pattern: patternInfo.pattern
|
||||
};
|
||||
}
|
||||
|
||||
class FileSearchEngine {
|
||||
private filePattern: string;
|
||||
private includePattern: glob.ParsedExpression;
|
||||
private maxResults: number;
|
||||
private exists: boolean;
|
||||
private isLimitHit: boolean;
|
||||
private resultCount: number;
|
||||
private isCanceled: boolean;
|
||||
|
||||
private activeCancellationTokens: Set<CancellationTokenSource>;
|
||||
|
||||
private globalExcludePattern: glob.ParsedExpression;
|
||||
|
||||
constructor(private config: ISearchQuery, private provider: vscode.FileSearchProvider) {
|
||||
this.filePattern = config.filePattern;
|
||||
this.includePattern = config.includePattern && glob.parse(config.includePattern);
|
||||
this.maxResults = config.maxResults || null;
|
||||
this.exists = config.exists;
|
||||
this.resultCount = 0;
|
||||
this.isLimitHit = false;
|
||||
this.activeCancellationTokens = new Set<CancellationTokenSource>();
|
||||
|
||||
this.globalExcludePattern = config.excludePattern && glob.parse(config.excludePattern);
|
||||
}
|
||||
|
||||
public cancel(): void {
|
||||
this.isCanceled = true;
|
||||
this.activeCancellationTokens.forEach(t => t.cancel());
|
||||
this.activeCancellationTokens = new Set();
|
||||
}
|
||||
|
||||
public search(_onResult: (match: IInternalFileMatch) => void): TPromise<IInternalSearchComplete> {
|
||||
const folderQueries = this.config.folderQueries;
|
||||
|
||||
return new TPromise((resolve, reject) => {
|
||||
const onResult = (match: IInternalFileMatch) => {
|
||||
this.resultCount++;
|
||||
_onResult(match);
|
||||
};
|
||||
|
||||
// Support that the file pattern is a full path to a file that exists
|
||||
if (this.isCanceled) {
|
||||
return resolve({ limitHit: this.isLimitHit });
|
||||
}
|
||||
|
||||
// For each extra file
|
||||
if (this.config.extraFileResources) {
|
||||
this.config.extraFileResources
|
||||
.forEach(extraFile => {
|
||||
const extraFileStr = extraFile.toString(); // ?
|
||||
const basename = path.basename(extraFileStr);
|
||||
if (this.globalExcludePattern && this.globalExcludePattern(extraFileStr, basename)) {
|
||||
return; // excluded
|
||||
}
|
||||
|
||||
// File: Check for match on file pattern and include pattern
|
||||
this.matchFile(onResult, { base: extraFile, basename });
|
||||
});
|
||||
}
|
||||
|
||||
// For each root folder
|
||||
TPromise.join(folderQueries.map(fq => {
|
||||
return this.searchInFolder(fq, onResult);
|
||||
})).then(() => {
|
||||
resolve({ limitHit: this.isLimitHit });
|
||||
}, (errs: Error[]) => {
|
||||
const errMsg = errs
|
||||
.map(err => toErrorMessage(err))
|
||||
.filter(msg => !!msg)[0];
|
||||
|
||||
reject(new Error(errMsg));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private searchInFolder(fq: IFolderQuery<URI>, onResult: (match: IInternalFileMatch) => void): TPromise<void> {
|
||||
let cancellation = new CancellationTokenSource();
|
||||
return new TPromise((resolve, reject) => {
|
||||
const options = this.getSearchOptionsForFolder(fq);
|
||||
const tree = this.initDirectoryTree();
|
||||
|
||||
const queryTester = new QueryGlobTester(this.config, fq);
|
||||
const noSiblingsClauses = !queryTester.hasSiblingExcludeClauses();
|
||||
|
||||
new TPromise(_resolve => process.nextTick(_resolve))
|
||||
.then(() => {
|
||||
this.activeCancellationTokens.add(cancellation);
|
||||
|
||||
return this.provider.provideFileSearchResults(
|
||||
{
|
||||
pattern: this.config.filePattern || ''
|
||||
},
|
||||
options,
|
||||
cancellation.token);
|
||||
})
|
||||
.then(results => {
|
||||
if (this.isCanceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (results) {
|
||||
results.forEach(result => {
|
||||
const relativePath = path.relative(fq.folder.fsPath, result.fsPath);
|
||||
|
||||
if (noSiblingsClauses) {
|
||||
const basename = path.basename(result.fsPath);
|
||||
this.matchFile(onResult, { base: fq.folder, relativePath, basename });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Optimize siblings clauses with ripgrep here.
|
||||
this.addDirectoryEntries(tree, fq.folder, relativePath, onResult);
|
||||
});
|
||||
}
|
||||
|
||||
this.activeCancellationTokens.delete(cancellation);
|
||||
if (this.isCanceled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.matchDirectoryTree(tree, queryTester, onResult);
|
||||
return null;
|
||||
}).then(
|
||||
() => {
|
||||
cancellation.dispose();
|
||||
resolve(null);
|
||||
},
|
||||
err => {
|
||||
cancellation.dispose();
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getSearchOptionsForFolder(fq: IFolderQuery<URI>): vscode.FileSearchOptions {
|
||||
const includes = resolvePatternsForProvider(this.config.includePattern, fq.includePattern);
|
||||
const excludes = resolvePatternsForProvider(this.config.excludePattern, fq.excludePattern);
|
||||
|
||||
return {
|
||||
folder: fq.folder,
|
||||
excludes,
|
||||
includes,
|
||||
useIgnoreFiles: !this.config.disregardIgnoreFiles,
|
||||
followSymlinks: !this.config.ignoreSymlinks,
|
||||
maxResults: this.config.maxResults
|
||||
};
|
||||
}
|
||||
|
||||
private initDirectoryTree(): IDirectoryTree {
|
||||
const tree: IDirectoryTree = {
|
||||
rootEntries: [],
|
||||
pathToEntries: Object.create(null)
|
||||
};
|
||||
tree.pathToEntries['.'] = tree.rootEntries;
|
||||
return tree;
|
||||
}
|
||||
|
||||
private addDirectoryEntries({ pathToEntries }: IDirectoryTree, base: URI, relativeFile: string, onResult: (result: IInternalFileMatch) => void) {
|
||||
// Support relative paths to files from a root resource (ignores excludes)
|
||||
if (relativeFile === this.filePattern) {
|
||||
const basename = path.basename(this.filePattern);
|
||||
this.matchFile(onResult, { base: base, relativePath: this.filePattern, basename });
|
||||
}
|
||||
|
||||
function add(relativePath: string) {
|
||||
const basename = path.basename(relativePath);
|
||||
const dirname = path.dirname(relativePath);
|
||||
let entries = pathToEntries[dirname];
|
||||
if (!entries) {
|
||||
entries = pathToEntries[dirname] = [];
|
||||
add(dirname);
|
||||
}
|
||||
entries.push({
|
||||
base,
|
||||
relativePath,
|
||||
basename
|
||||
});
|
||||
}
|
||||
|
||||
add(relativeFile);
|
||||
}
|
||||
|
||||
private matchDirectoryTree({ rootEntries, pathToEntries }: IDirectoryTree, queryTester: QueryGlobTester, onResult: (result: IInternalFileMatch) => void) {
|
||||
const self = this;
|
||||
const filePattern = this.filePattern;
|
||||
function matchDirectory(entries: IDirectoryEntry[]) {
|
||||
const hasSibling = glob.hasSiblingFn(() => entries.map(entry => entry.basename));
|
||||
for (let i = 0, n = entries.length; i < n; i++) {
|
||||
const entry = entries[i];
|
||||
const { relativePath, basename } = entry;
|
||||
|
||||
// Check exclude pattern
|
||||
// If the user searches for the exact file name, we adjust the glob matching
|
||||
// to ignore filtering by siblings because the user seems to know what she
|
||||
// is searching for and we want to include the result in that case anyway
|
||||
if (!queryTester.includedInQuerySync(relativePath, basename, filePattern !== basename ? hasSibling : undefined)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const sub = pathToEntries[relativePath];
|
||||
if (sub) {
|
||||
matchDirectory(sub);
|
||||
} else {
|
||||
if (relativePath === filePattern) {
|
||||
continue; // ignore file if its path matches with the file pattern because that is already matched above
|
||||
}
|
||||
|
||||
self.matchFile(onResult, entry);
|
||||
}
|
||||
|
||||
if (self.isLimitHit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
matchDirectory(rootEntries);
|
||||
}
|
||||
|
||||
private matchFile(onResult: (result: IInternalFileMatch) => void, candidate: IInternalFileMatch): void {
|
||||
if (!this.includePattern || this.includePattern(candidate.relativePath, candidate.basename)) {
|
||||
if (this.exists || (this.maxResults && this.resultCount >= this.maxResults)) {
|
||||
this.isLimitHit = true;
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
if (!this.isLimitHit) {
|
||||
onResult(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface IInternalSearchComplete {
|
||||
limitHit: boolean;
|
||||
}
|
||||
|
||||
class FileSearchManager {
|
||||
|
||||
private static readonly BATCH_SIZE = 512;
|
||||
|
||||
fileSearch(config: ISearchQuery, provider: vscode.FileSearchProvider, onBatch: (matches: IFileMatch[]) => void): TPromise<ISearchCompleteStats> {
|
||||
let searchP: TPromise;
|
||||
return new TPromise<ISearchCompleteStats>((c, e) => {
|
||||
const engine = new FileSearchEngine(config, provider);
|
||||
|
||||
const onInternalResult = (batch: IInternalFileMatch[]) => {
|
||||
onBatch(batch.map(m => this.rawMatchToSearchItem(m)));
|
||||
};
|
||||
|
||||
searchP = this.doSearch(engine, FileSearchManager.BATCH_SIZE, onInternalResult).then(
|
||||
result => {
|
||||
c({
|
||||
limitHit: result.limitHit
|
||||
});
|
||||
},
|
||||
e);
|
||||
}, () => {
|
||||
if (searchP) {
|
||||
searchP.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private rawMatchToSearchItem(match: IInternalFileMatch): IFileMatch {
|
||||
if (match.relativePath) {
|
||||
return {
|
||||
resource: resources.joinPath(match.base, match.relativePath)
|
||||
};
|
||||
} else {
|
||||
// extraFileResources
|
||||
return {
|
||||
resource: match.base
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private doSearch(engine: FileSearchEngine, batchSize: number, onResultBatch: (matches: IInternalFileMatch[]) => void): TPromise<IInternalSearchComplete> {
|
||||
return new TPromise((c, e) => {
|
||||
const _onResult = match => {
|
||||
if (match) {
|
||||
batch.push(match);
|
||||
if (batchSize > 0 && batch.length >= batchSize) {
|
||||
onResultBatch(batch);
|
||||
batch = [];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let batch: IInternalFileMatch[] = [];
|
||||
engine.search(_onResult).then(result => {
|
||||
if (batch.length) {
|
||||
onResultBatch(batch);
|
||||
}
|
||||
|
||||
c(result);
|
||||
}, error => {
|
||||
if (batch.length) {
|
||||
onResultBatch(batch);
|
||||
}
|
||||
|
||||
e(error);
|
||||
});
|
||||
}, () => {
|
||||
engine.cancel();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { asWinJsPromise } from 'vs/base/common/async';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
|
||||
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import * as TaskSystem from 'vs/workbench/parts/tasks/common/tasks';
|
||||
import * as tasks from 'vs/workbench/parts/tasks/common/tasks';
|
||||
|
||||
import { MainContext, MainThreadTaskShape, ExtHostTaskShape, IMainContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||
|
||||
@@ -21,10 +21,15 @@ import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
|
||||
import * as vscode from 'vscode';
|
||||
import {
|
||||
TaskDefinitionDTO, TaskExecutionDTO, TaskPresentationOptionsDTO, ProcessExecutionOptionsDTO, ProcessExecutionDTO,
|
||||
ShellExecutionOptionsDTO, ShellExecutionDTO, TaskDTO, TaskHandleDTO, TaskFilterDTO
|
||||
ShellExecutionOptionsDTO, ShellExecutionDTO, TaskDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO
|
||||
} from '../shared/tasks';
|
||||
|
||||
export { TaskExecutionDTO };
|
||||
// {{SQL CARBON EDIT}}
|
||||
// import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDebugService';
|
||||
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors';
|
||||
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
|
||||
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
|
||||
/*
|
||||
namespace ProblemPattern {
|
||||
@@ -197,46 +202,47 @@ namespace ProblemMatcher {
|
||||
*/
|
||||
|
||||
namespace TaskRevealKind {
|
||||
export function from(value: vscode.TaskRevealKind): TaskSystem.RevealKind {
|
||||
export function from(value: vscode.TaskRevealKind): tasks.RevealKind {
|
||||
if (value === void 0 || value === null) {
|
||||
return TaskSystem.RevealKind.Always;
|
||||
return tasks.RevealKind.Always;
|
||||
}
|
||||
switch (value) {
|
||||
case types.TaskRevealKind.Silent:
|
||||
return TaskSystem.RevealKind.Silent;
|
||||
return tasks.RevealKind.Silent;
|
||||
case types.TaskRevealKind.Never:
|
||||
return TaskSystem.RevealKind.Never;
|
||||
return tasks.RevealKind.Never;
|
||||
}
|
||||
return TaskSystem.RevealKind.Always;
|
||||
return tasks.RevealKind.Always;
|
||||
}
|
||||
}
|
||||
|
||||
namespace TaskPanelKind {
|
||||
export function from(value: vscode.TaskPanelKind): TaskSystem.PanelKind {
|
||||
export function from(value: vscode.TaskPanelKind): tasks.PanelKind {
|
||||
if (value === void 0 || value === null) {
|
||||
return TaskSystem.PanelKind.Shared;
|
||||
return tasks.PanelKind.Shared;
|
||||
}
|
||||
switch (value) {
|
||||
case types.TaskPanelKind.Dedicated:
|
||||
return TaskSystem.PanelKind.Dedicated;
|
||||
return tasks.PanelKind.Dedicated;
|
||||
case types.TaskPanelKind.New:
|
||||
return TaskSystem.PanelKind.New;
|
||||
return tasks.PanelKind.New;
|
||||
default:
|
||||
return TaskSystem.PanelKind.Shared;
|
||||
return tasks.PanelKind.Shared;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace PresentationOptions {
|
||||
export function from(value: vscode.TaskPresentationOptions): TaskSystem.PresentationOptions {
|
||||
export function from(value: vscode.TaskPresentationOptions): tasks.PresentationOptions {
|
||||
if (value === void 0 || value === null) {
|
||||
return { reveal: TaskSystem.RevealKind.Always, echo: true, focus: false, panel: TaskSystem.PanelKind.Shared };
|
||||
return { reveal: tasks.RevealKind.Always, echo: true, focus: false, panel: tasks.PanelKind.Shared, showReuseMessage: true };
|
||||
}
|
||||
return {
|
||||
reveal: TaskRevealKind.from(value.reveal),
|
||||
echo: value.echo === void 0 ? true : !!value.echo,
|
||||
focus: !!value.focus,
|
||||
panel: TaskPanelKind.from(value.panel)
|
||||
panel: TaskPanelKind.from(value.panel),
|
||||
showReuseMessage: value.showReuseMessage === void 0 ? true : !!value.showReuseMessage
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -259,11 +265,11 @@ namespace CommandOptions {
|
||||
function isShellConfiguration(value: any): value is { executable: string; shellArgs?: string[] } {
|
||||
return value && typeof value.executable === 'string';
|
||||
}
|
||||
export function from(value: vscode.ShellExecutionOptions | vscode.ProcessExecutionOptions): TaskSystem.CommandOptions {
|
||||
export function from(value: vscode.ShellExecutionOptions | vscode.ProcessExecutionOptions): tasks.CommandOptions {
|
||||
if (value === void 0 || value === null) {
|
||||
return undefined;
|
||||
}
|
||||
let result: TaskSystem.CommandOptions = {
|
||||
let result: tasks.CommandOptions = {
|
||||
};
|
||||
if (typeof value.cwd === 'string') {
|
||||
result.cwd = value.cwd;
|
||||
@@ -285,7 +291,7 @@ namespace CommandOptions {
|
||||
}
|
||||
|
||||
namespace ShellQuoteOptions {
|
||||
export function from(value: vscode.ShellQuotingOptions): TaskSystem.ShellQuotingOptions {
|
||||
export function from(value: vscode.ShellQuotingOptions): tasks.ShellQuotingOptions {
|
||||
if (value === void 0 || value === null) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -298,12 +304,12 @@ namespace ShellQuoteOptions {
|
||||
}
|
||||
|
||||
namespace ShellConfiguration {
|
||||
export function from(value: { executable?: string, shellArgs?: string[], quotes?: vscode.ShellQuotingOptions }): TaskSystem.ShellConfiguration {
|
||||
export function from(value: { executable?: string, shellArgs?: string[], quotes?: vscode.ShellQuotingOptions }): tasks.ShellConfiguration {
|
||||
if (value === void 0 || value === null || !value.executable) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let result: TaskSystem.ShellConfiguration = {
|
||||
let result: tasks.ShellConfiguration = {
|
||||
executable: value.executable,
|
||||
args: Strings.from(value.shellArgs),
|
||||
quoting: ShellQuoteOptions.from(value.quotes)
|
||||
@@ -313,7 +319,7 @@ namespace ShellConfiguration {
|
||||
}
|
||||
|
||||
namespace ShellString {
|
||||
export function from(value: (string | vscode.ShellQuotedString)[]): TaskSystem.CommandString[] {
|
||||
export function from(value: (string | vscode.ShellQuotedString)[]): tasks.CommandString[] {
|
||||
if (value === void 0 || value === null) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -323,11 +329,11 @@ namespace ShellString {
|
||||
|
||||
namespace Tasks {
|
||||
|
||||
export function from(tasks: vscode.Task[], rootFolder: vscode.WorkspaceFolder, extension: IExtensionDescription): TaskSystem.ContributedTask[] {
|
||||
export function from(tasks: vscode.Task[], rootFolder: vscode.WorkspaceFolder, extension: IExtensionDescription): tasks.ContributedTask[] {
|
||||
if (tasks === void 0 || tasks === null) {
|
||||
return [];
|
||||
}
|
||||
let result: TaskSystem.ContributedTask[] = [];
|
||||
let result: tasks.ContributedTask[] = [];
|
||||
for (let task of tasks) {
|
||||
let converted = fromSingle(task, rootFolder, extension);
|
||||
if (converted) {
|
||||
@@ -337,11 +343,11 @@ namespace Tasks {
|
||||
return result;
|
||||
}
|
||||
|
||||
function fromSingle(task: vscode.Task, rootFolder: vscode.WorkspaceFolder, extension: IExtensionDescription): TaskSystem.ContributedTask {
|
||||
function fromSingle(task: vscode.Task, rootFolder: vscode.WorkspaceFolder, extension: IExtensionDescription): tasks.ContributedTask {
|
||||
if (typeof task.name !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
let command: TaskSystem.CommandConfiguration;
|
||||
let command: tasks.CommandConfiguration;
|
||||
let execution = task.execution;
|
||||
if (execution instanceof types.ProcessExecution) {
|
||||
command = getProcessCommand(execution);
|
||||
@@ -357,21 +363,21 @@ namespace Tasks {
|
||||
|
||||
let taskScope: types.TaskScope.Global | types.TaskScope.Workspace | vscode.WorkspaceFolder | undefined = task.scope;
|
||||
let workspaceFolder: vscode.WorkspaceFolder | undefined;
|
||||
let scope: TaskSystem.TaskScope;
|
||||
let scope: tasks.TaskScope;
|
||||
// For backwards compatibility
|
||||
if (taskScope === void 0) {
|
||||
scope = TaskSystem.TaskScope.Folder;
|
||||
scope = tasks.TaskScope.Folder;
|
||||
workspaceFolder = rootFolder;
|
||||
} else if (taskScope === types.TaskScope.Global) {
|
||||
scope = TaskSystem.TaskScope.Global;
|
||||
scope = tasks.TaskScope.Global;
|
||||
} else if (taskScope === types.TaskScope.Workspace) {
|
||||
scope = TaskSystem.TaskScope.Workspace;
|
||||
scope = tasks.TaskScope.Workspace;
|
||||
} else {
|
||||
scope = TaskSystem.TaskScope.Folder;
|
||||
scope = tasks.TaskScope.Folder;
|
||||
workspaceFolder = taskScope;
|
||||
}
|
||||
let source: TaskSystem.ExtensionTaskSource = {
|
||||
kind: TaskSystem.TaskSourceKind.Extension,
|
||||
let source: tasks.ExtensionTaskSource = {
|
||||
kind: tasks.TaskSourceKind.Extension,
|
||||
label: typeof task.source === 'string' ? task.source : extension.name,
|
||||
extension: extension.id,
|
||||
scope: scope,
|
||||
@@ -380,22 +386,17 @@ namespace Tasks {
|
||||
// We can't transfer a workspace folder object from the extension host to main since they differ
|
||||
// in shape and we don't have backwards converting function. So transfer the URI and resolve the
|
||||
// workspace folder on the main side.
|
||||
(source as any as TaskSystem.ExtensionTaskSourceTransfer).__workspaceFolder = workspaceFolder ? workspaceFolder.uri as URI : undefined;
|
||||
(source as any as tasks.ExtensionTaskSourceTransfer).__workspaceFolder = workspaceFolder ? workspaceFolder.uri as URI : undefined;
|
||||
(source as any as tasks.ExtensionTaskSourceTransfer).__definition = task.definition;
|
||||
let label = nls.localize('task.label', '{0}: {1}', source.label, task.name);
|
||||
let key = (task as types.Task).definitionKey;
|
||||
let kind = (task as types.Task).definition;
|
||||
let id = `${extension.id}.${key}`;
|
||||
let taskKind: TaskSystem.TaskIdentifier = {
|
||||
_key: key,
|
||||
type: kind.type
|
||||
};
|
||||
Objects.assign(taskKind, kind);
|
||||
let result: TaskSystem.ContributedTask = {
|
||||
_id: id, // uuidMap.getUUID(identifier),
|
||||
// The definition id will be prefix on the main side since we compute it there.
|
||||
let id = `${extension.id}`;
|
||||
let result: tasks.ContributedTask = {
|
||||
_id: id,
|
||||
_source: source,
|
||||
_label: label,
|
||||
type: kind.type,
|
||||
defines: taskKind,
|
||||
type: task.definition.type,
|
||||
defines: undefined,
|
||||
name: task.name,
|
||||
identifier: label,
|
||||
group: task.group ? (task.group as types.TaskGroup).id : undefined,
|
||||
@@ -407,14 +408,14 @@ namespace Tasks {
|
||||
return result;
|
||||
}
|
||||
|
||||
function getProcessCommand(value: vscode.ProcessExecution): TaskSystem.CommandConfiguration {
|
||||
function getProcessCommand(value: vscode.ProcessExecution): tasks.CommandConfiguration {
|
||||
if (typeof value.process !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
let result: TaskSystem.CommandConfiguration = {
|
||||
let result: tasks.CommandConfiguration = {
|
||||
name: value.process,
|
||||
args: Strings.from(value.args),
|
||||
runtime: TaskSystem.RuntimeType.Process,
|
||||
runtime: tasks.RuntimeType.Process,
|
||||
suppressTaskName: true,
|
||||
presentation: undefined
|
||||
};
|
||||
@@ -424,15 +425,15 @@ namespace Tasks {
|
||||
return result;
|
||||
}
|
||||
|
||||
function getShellCommand(value: vscode.ShellExecution): TaskSystem.CommandConfiguration {
|
||||
function getShellCommand(value: vscode.ShellExecution): tasks.CommandConfiguration {
|
||||
if (value.args) {
|
||||
if (typeof value.command !== 'string' && typeof value.command.value !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
let result: TaskSystem.CommandConfiguration = {
|
||||
let result: tasks.CommandConfiguration = {
|
||||
name: value.command,
|
||||
args: ShellString.from(value.args),
|
||||
runtime: TaskSystem.RuntimeType.Shell,
|
||||
runtime: tasks.RuntimeType.Shell,
|
||||
presentation: undefined
|
||||
};
|
||||
if (value.options) {
|
||||
@@ -443,9 +444,9 @@ namespace Tasks {
|
||||
if (typeof value.commandLine !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
let result: TaskSystem.CommandConfiguration = {
|
||||
let result: tasks.CommandConfiguration = {
|
||||
name: value.commandLine,
|
||||
runtime: TaskSystem.RuntimeType.Shell,
|
||||
runtime: tasks.RuntimeType.Shell,
|
||||
presentation: undefined
|
||||
};
|
||||
if (value.options) {
|
||||
@@ -689,21 +690,28 @@ namespace TaskFilterDTO {
|
||||
}
|
||||
|
||||
class TaskExecutionImpl implements vscode.TaskExecution {
|
||||
constructor(readonly _id: string, private readonly _task: vscode.Task, private readonly _tasks: ExtHostTask) {
|
||||
|
||||
constructor(private readonly _tasks: ExtHostTask, readonly _id: string, private readonly _task: vscode.Task) {
|
||||
}
|
||||
|
||||
get task(): vscode.Task {
|
||||
public get task(): vscode.Task {
|
||||
return this._task;
|
||||
}
|
||||
|
||||
public terminate(): void {
|
||||
this._tasks.terminateTask(this);
|
||||
}
|
||||
|
||||
public fireDidStartProcess(value: TaskProcessStartedDTO): void {
|
||||
}
|
||||
|
||||
public fireDidEndProcess(value: TaskProcessEndedDTO): void {
|
||||
}
|
||||
}
|
||||
|
||||
namespace TaskExecutionDTO {
|
||||
export function to(value: TaskExecutionDTO, tasks: ExtHostTask): vscode.TaskExecution {
|
||||
return new TaskExecutionImpl(value.id, TaskDTO.to(value.task, tasks.extHostWorkspace), tasks);
|
||||
return new TaskExecutionImpl(tasks, value.id, TaskDTO.to(value.task, tasks.extHostWorkspace));
|
||||
}
|
||||
export function from(value: vscode.TaskExecution): TaskExecutionDTO {
|
||||
return {
|
||||
@@ -721,7 +729,9 @@ interface HandlerData {
|
||||
export class ExtHostTask implements ExtHostTaskShape {
|
||||
|
||||
private _proxy: MainThreadTaskShape;
|
||||
private _extHostWorkspace: ExtHostWorkspace;
|
||||
private _workspaceService: ExtHostWorkspace;
|
||||
private _editorService: ExtHostDocumentsAndEditors;
|
||||
private _configurationService: ExtHostConfiguration;
|
||||
private _handleCounter: number;
|
||||
private _handlers: Map<number, HandlerData>;
|
||||
private _taskExecutions: Map<string, TaskExecutionImpl>;
|
||||
@@ -729,16 +739,21 @@ export class ExtHostTask implements ExtHostTaskShape {
|
||||
private readonly _onDidExecuteTask: Emitter<vscode.TaskStartEvent> = new Emitter<vscode.TaskStartEvent>();
|
||||
private readonly _onDidTerminateTask: Emitter<vscode.TaskEndEvent> = new Emitter<vscode.TaskEndEvent>();
|
||||
|
||||
constructor(mainContext: IMainContext, extHostWorkspace: ExtHostWorkspace) {
|
||||
private readonly _onDidTaskProcessStarted: Emitter<vscode.TaskProcessStartEvent> = new Emitter<vscode.TaskProcessStartEvent>();
|
||||
private readonly _onDidTaskProcessEnded: Emitter<vscode.TaskProcessEndEvent> = new Emitter<vscode.TaskProcessEndEvent>();
|
||||
|
||||
constructor(mainContext: IMainContext, workspaceService: ExtHostWorkspace, editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfiguration) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadTask);
|
||||
this._extHostWorkspace = extHostWorkspace;
|
||||
this._workspaceService = workspaceService;
|
||||
this._editorService = editorService;
|
||||
this._configurationService = configurationService;
|
||||
this._handleCounter = 0;
|
||||
this._handlers = new Map<number, HandlerData>();
|
||||
this._taskExecutions = new Map<string, TaskExecutionImpl>();
|
||||
}
|
||||
|
||||
public get extHostWorkspace(): ExtHostWorkspace {
|
||||
return this._extHostWorkspace;
|
||||
return this._workspaceService;
|
||||
}
|
||||
|
||||
public registerTaskProvider(extension: IExtensionDescription, provider: vscode.TaskProvider): vscode.Disposable {
|
||||
@@ -754,11 +769,15 @@ export class ExtHostTask implements ExtHostTaskShape {
|
||||
});
|
||||
}
|
||||
|
||||
public registerTaskSystem(scheme: string, info: TaskSystemInfoDTO): void {
|
||||
this._proxy.$registerTaskSystem(scheme, info);
|
||||
}
|
||||
|
||||
public fetchTasks(filter?: vscode.TaskFilter): Thenable<vscode.Task[]> {
|
||||
return this._proxy.$fetchTasks(TaskFilterDTO.from(filter)).then((values) => {
|
||||
let result: vscode.Task[] = [];
|
||||
for (let value of values) {
|
||||
let task = TaskDTO.to(value, this._extHostWorkspace);
|
||||
let task = TaskDTO.to(value, this._workspaceService);
|
||||
if (task) {
|
||||
result.push(task);
|
||||
}
|
||||
@@ -782,22 +801,12 @@ export class ExtHostTask implements ExtHostTaskShape {
|
||||
}
|
||||
}
|
||||
|
||||
public $taskStarted(execution: TaskExecutionDTO): void {
|
||||
this._onDidExecuteTask.fire({
|
||||
execution: this.getTaskExecution(execution)
|
||||
});
|
||||
}
|
||||
|
||||
get taskExecutions(): vscode.TaskExecution[] {
|
||||
public get taskExecutions(): vscode.TaskExecution[] {
|
||||
let result: vscode.TaskExecution[] = [];
|
||||
this._taskExecutions.forEach(value => result.push(value));
|
||||
return result;
|
||||
}
|
||||
|
||||
get onDidStartTask(): Event<vscode.TaskStartEvent> {
|
||||
return this._onDidExecuteTask.event;
|
||||
}
|
||||
|
||||
public terminateTask(execution: vscode.TaskExecution): TPromise<void> {
|
||||
if (!(execution instanceof TaskExecutionImpl)) {
|
||||
throw new Error('No valid task execution provided');
|
||||
@@ -805,7 +814,21 @@ export class ExtHostTask implements ExtHostTaskShape {
|
||||
return this._proxy.$terminateTask((execution as TaskExecutionImpl)._id);
|
||||
}
|
||||
|
||||
public $taskEnded(execution: TaskExecutionDTO): void {
|
||||
public get onDidStartTask(): Event<vscode.TaskStartEvent> {
|
||||
return this._onDidExecuteTask.event;
|
||||
}
|
||||
|
||||
public $onDidStartTask(execution: TaskExecutionDTO): void {
|
||||
this._onDidExecuteTask.fire({
|
||||
execution: this.getTaskExecution(execution)
|
||||
});
|
||||
}
|
||||
|
||||
public get onDidEndTask(): Event<vscode.TaskEndEvent> {
|
||||
return this._onDidTerminateTask.event;
|
||||
}
|
||||
|
||||
public $OnDidEndTask(execution: TaskExecutionDTO): void {
|
||||
const _execution = this.getTaskExecution(execution);
|
||||
this._taskExecutions.delete(execution.id);
|
||||
this._onDidTerminateTask.fire({
|
||||
@@ -813,34 +836,92 @@ export class ExtHostTask implements ExtHostTaskShape {
|
||||
});
|
||||
}
|
||||
|
||||
get onDidEndTask(): Event<vscode.TaskEndEvent> {
|
||||
return this._onDidTerminateTask.event;
|
||||
public get onDidStartTaskProcess(): Event<vscode.TaskProcessStartEvent> {
|
||||
return this._onDidTaskProcessStarted.event;
|
||||
}
|
||||
|
||||
public $provideTasks(handle: number): TPromise<TaskSystem.TaskSet> {
|
||||
public $onDidStartTaskProcess(value: TaskProcessStartedDTO): void {
|
||||
const execution = this.getTaskExecution(value.id);
|
||||
if (execution) {
|
||||
this._onDidTaskProcessStarted.fire({
|
||||
execution: execution,
|
||||
processId: value.processId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public get onDidEndTaskProcess(): Event<vscode.TaskProcessEndEvent> {
|
||||
return this._onDidTaskProcessEnded.event;
|
||||
}
|
||||
|
||||
public $onDidEndTaskProcess(value: TaskProcessEndedDTO): void {
|
||||
const execution = this.getTaskExecution(value.id);
|
||||
if (execution) {
|
||||
this._onDidTaskProcessEnded.fire({
|
||||
execution: execution,
|
||||
exitCode: value.exitCode
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public $provideTasks(handle: number, validTypes: { [key: string]: boolean; }): TPromise<tasks.TaskSet> {
|
||||
let handler = this._handlers.get(handle);
|
||||
if (!handler) {
|
||||
return TPromise.wrapError<TaskSystem.TaskSet>(new Error('no handler found'));
|
||||
return TPromise.wrapError<tasks.TaskSet>(new Error('no handler found'));
|
||||
}
|
||||
return asWinJsPromise(token => handler.provider.provideTasks(token)).then(value => {
|
||||
let workspaceFolders = this._extHostWorkspace.getWorkspaceFolders();
|
||||
let sanitized: vscode.Task[] = [];
|
||||
for (let task of value) {
|
||||
if (task.definition && validTypes[task.definition.type] === true) {
|
||||
sanitized.push(task);
|
||||
} else {
|
||||
sanitized.push(task);
|
||||
console.warn(`The task [${task.source}, ${task.name}] uses an undefined task type. The task will be ignored in the future.`);
|
||||
}
|
||||
}
|
||||
let workspaceFolders = this._workspaceService.getWorkspaceFolders();
|
||||
return {
|
||||
tasks: Tasks.from(value, workspaceFolders && workspaceFolders.length > 0 ? workspaceFolders[0] : undefined, handler.extension),
|
||||
tasks: Tasks.from(sanitized, workspaceFolders && workspaceFolders.length > 0 ? workspaceFolders[0] : undefined, handler.extension),
|
||||
extension: handler.extension
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}} disable debug related method
|
||||
public $resolveVariables(uriComponents: UriComponents, variables: string[]): any {
|
||||
// let uri: URI = URI.revive(uriComponents);
|
||||
// let result: { [key: string]: string; } = Object.create(null);
|
||||
// let workspaceFolder = this._workspaceService.resolveWorkspaceFolder(uri);
|
||||
// let resolver = new ExtHostVariableResolverService(this._workspaceService, this._editorService, this._configurationService);
|
||||
// let ws: IWorkspaceFolder = {
|
||||
// uri: workspaceFolder.uri,
|
||||
// name: workspaceFolder.name,
|
||||
// index: workspaceFolder.index,
|
||||
// toResource: () => {
|
||||
// throw new Error('Not implemented');
|
||||
// }
|
||||
// };
|
||||
// for (let variable of variables) {
|
||||
// result[variable] = resolver.resolve(ws, variable);
|
||||
// }
|
||||
// return result;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private nextHandle(): number {
|
||||
return this._handleCounter++;
|
||||
}
|
||||
|
||||
private getTaskExecution(execution: TaskExecutionDTO, task?: vscode.Task): TaskExecutionImpl {
|
||||
private getTaskExecution(execution: TaskExecutionDTO | string, task?: vscode.Task): TaskExecutionImpl {
|
||||
if (typeof execution === 'string') {
|
||||
return this._taskExecutions.get(execution);
|
||||
}
|
||||
|
||||
let result: TaskExecutionImpl = this._taskExecutions.get(execution.id);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
result = new TaskExecutionImpl(execution.id, task ? task : TaskDTO.to(execution.task, this._extHostWorkspace), this);
|
||||
result = new TaskExecutionImpl(this, execution.id, task ? task : TaskDTO.to(execution.task, this._workspaceService));
|
||||
this._taskExecutions.set(execution.id, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -5,28 +5,77 @@
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as cp from 'child_process';
|
||||
import * as os from 'os';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as terminalEnvironment from 'vs/workbench/parts/terminal/node/terminalEnvironment';
|
||||
import Uri from 'vs/base/common/uri';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IMainContext, ShellLaunchConfigDto } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { IMessageFromTerminalProcess } from 'vs/workbench/parts/terminal/node/terminal';
|
||||
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { EXT_HOST_CREATION_DELAY } from 'vs/workbench/parts/terminal/common/terminal';
|
||||
import { TerminalProcess } from 'vs/workbench/parts/terminal/node/terminalProcess';
|
||||
|
||||
export class ExtHostTerminal implements vscode.Terminal {
|
||||
private _name: string;
|
||||
private _id: number;
|
||||
private _proxy: MainThreadTerminalServiceShape;
|
||||
private _disposed: boolean;
|
||||
private _queuedRequests: ApiRequest[];
|
||||
const RENDERER_NO_PROCESS_ID = -1;
|
||||
|
||||
export class BaseExtHostTerminal {
|
||||
public _id: number;
|
||||
protected _idPromise: Promise<number>;
|
||||
private _idPromiseComplete: (value: number) => any;
|
||||
private _disposed: boolean = false;
|
||||
private _queuedRequests: ApiRequest[] = [];
|
||||
|
||||
constructor(
|
||||
protected _proxy: MainThreadTerminalServiceShape,
|
||||
id?: number
|
||||
) {
|
||||
this._idPromise = new Promise<number>(c => {
|
||||
if (id !== undefined) {
|
||||
this._id = id;
|
||||
c(id);
|
||||
} else {
|
||||
this._idPromiseComplete = c;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (!this._disposed) {
|
||||
this._disposed = true;
|
||||
this._queueApiRequest(this._proxy.$dispose, []);
|
||||
}
|
||||
}
|
||||
|
||||
protected _checkDisposed() {
|
||||
if (this._disposed) {
|
||||
throw new Error('Terminal has already been disposed');
|
||||
}
|
||||
}
|
||||
|
||||
protected _queueApiRequest(callback: (...args: any[]) => void, args: any[]): void {
|
||||
const request: ApiRequest = new ApiRequest(callback, args);
|
||||
if (!this._id) {
|
||||
this._queuedRequests.push(request);
|
||||
return;
|
||||
}
|
||||
request.run(this._proxy, this._id);
|
||||
}
|
||||
|
||||
public _runQueuedRequests(id: number): void {
|
||||
this._id = id;
|
||||
this._idPromiseComplete(id);
|
||||
this._queuedRequests.forEach((r) => {
|
||||
r.run(this._proxy, this._id);
|
||||
});
|
||||
this._queuedRequests.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Terminal {
|
||||
private _pidPromise: Promise<number>;
|
||||
private _pidPromiseComplete: (value: number) => any;
|
||||
|
||||
private readonly _onData: Emitter<string> = new Emitter<string>();
|
||||
public get onData(): Event<string> {
|
||||
public get onDidWriteData(): Event<string> {
|
||||
// Tell the main side to start sending data if it's not already
|
||||
this._proxy.$registerOnDataListener(this._id);
|
||||
return this._onData && this._onData.event;
|
||||
@@ -34,17 +83,17 @@ export class ExtHostTerminal implements vscode.Terminal {
|
||||
|
||||
constructor(
|
||||
proxy: MainThreadTerminalServiceShape,
|
||||
name: string = '',
|
||||
id?: number
|
||||
private _name: string,
|
||||
id?: number,
|
||||
pid?: number
|
||||
) {
|
||||
this._proxy = proxy;
|
||||
this._name = name;
|
||||
if (id) {
|
||||
this._id = id;
|
||||
}
|
||||
this._queuedRequests = [];
|
||||
super(proxy, id);
|
||||
this._pidPromise = new Promise<number>(c => {
|
||||
this._pidPromiseComplete = c;
|
||||
if (pid === RENDERER_NO_PROCESS_ID) {
|
||||
c(undefined);
|
||||
} else {
|
||||
this._pidPromiseComplete = c;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -56,11 +105,7 @@ export class ExtHostTerminal implements vscode.Terminal {
|
||||
waitOnExit?: boolean
|
||||
): void {
|
||||
this._proxy.$createTerminal(this._name, shellPath, shellArgs, cwd, env, waitOnExit).then((id) => {
|
||||
this._id = id;
|
||||
this._queuedRequests.forEach((r) => {
|
||||
r.run(this._proxy, this._id);
|
||||
});
|
||||
this._queuedRequests = [];
|
||||
this._runQueuedRequests(id);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -87,13 +132,6 @@ export class ExtHostTerminal implements vscode.Terminal {
|
||||
this._queueApiRequest(this._proxy.$hide, []);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (!this._disposed) {
|
||||
this._disposed = true;
|
||||
this._queueApiRequest(this._proxy.$dispose, []);
|
||||
}
|
||||
}
|
||||
|
||||
public _setProcessId(processId: number): void {
|
||||
// The event may fire 2 times when the panel is restored
|
||||
if (this._pidPromiseComplete) {
|
||||
@@ -105,34 +143,99 @@ export class ExtHostTerminal implements vscode.Terminal {
|
||||
public _fireOnData(data: string): void {
|
||||
this._onData.fire(data);
|
||||
}
|
||||
}
|
||||
|
||||
private _queueApiRequest(callback: (...args: any[]) => void, args: any[]) {
|
||||
let request: ApiRequest = new ApiRequest(callback, args);
|
||||
if (!this._id) {
|
||||
this._queuedRequests.push(request);
|
||||
return;
|
||||
}
|
||||
request.run(this._proxy, this._id);
|
||||
export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vscode.TerminalRenderer {
|
||||
public get name(): string { return this._name; }
|
||||
public set name(newName: string) {
|
||||
this._name = newName;
|
||||
this._checkDisposed();
|
||||
this._queueApiRequest(this._proxy.$terminalRendererSetName, [this._name]);
|
||||
}
|
||||
|
||||
private _checkDisposed() {
|
||||
if (this._disposed) {
|
||||
throw new Error('Terminal has already been disposed');
|
||||
private readonly _onInput: Emitter<string> = new Emitter<string>();
|
||||
public get onDidAcceptInput(): Event<string> {
|
||||
this._checkDisposed();
|
||||
this._queueApiRequest(this._proxy.$terminalRendererRegisterOnInputListener, [this._id]);
|
||||
// Tell the main side to start sending data if it's not already
|
||||
// this._proxy.$terminalRendererRegisterOnDataListener(this._id);
|
||||
return this._onInput && this._onInput.event;
|
||||
}
|
||||
|
||||
private _dimensions: vscode.TerminalDimensions | undefined;
|
||||
public get dimensions(): vscode.TerminalDimensions { return this._dimensions; }
|
||||
public set dimensions(dimensions: vscode.TerminalDimensions) {
|
||||
this._checkDisposed();
|
||||
this._dimensions = dimensions;
|
||||
this._queueApiRequest(this._proxy.$terminalRendererSetDimensions, [dimensions]);
|
||||
}
|
||||
|
||||
private _maximumDimensions: vscode.TerminalDimensions;
|
||||
public get maximumDimensions(): vscode.TerminalDimensions {
|
||||
if (!this._maximumDimensions) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
rows: this._maximumDimensions.rows,
|
||||
columns: this._maximumDimensions.columns
|
||||
};
|
||||
}
|
||||
|
||||
private readonly _onDidChangeMaximumDimensions: Emitter<vscode.TerminalDimensions> = new Emitter<vscode.TerminalDimensions>();
|
||||
public get onDidChangeMaximumDimensions(): Event<vscode.TerminalDimensions> {
|
||||
return this._onDidChangeMaximumDimensions && this._onDidChangeMaximumDimensions.event;
|
||||
}
|
||||
|
||||
public get terminal(): ExtHostTerminal {
|
||||
return this._terminal;
|
||||
}
|
||||
|
||||
constructor(
|
||||
proxy: MainThreadTerminalServiceShape,
|
||||
private _name: string,
|
||||
private _terminal: ExtHostTerminal
|
||||
) {
|
||||
super(proxy);
|
||||
this._proxy.$createTerminalRenderer(this._name).then(id => {
|
||||
this._runQueuedRequests(id);
|
||||
(<any>this._terminal)._runQueuedRequests(id);
|
||||
});
|
||||
}
|
||||
|
||||
public write(data: string): void {
|
||||
this._checkDisposed();
|
||||
this._queueApiRequest(this._proxy.$terminalRendererWrite, [data]);
|
||||
}
|
||||
|
||||
public _fireOnInput(data: string): void {
|
||||
this._onInput.fire(data);
|
||||
}
|
||||
|
||||
public _setMaximumDimensions(columns: number, rows: number): void {
|
||||
if (this._maximumDimensions && this._maximumDimensions.columns === columns && this._maximumDimensions.rows === rows) {
|
||||
return;
|
||||
}
|
||||
this._maximumDimensions = { columns, rows };
|
||||
this._onDidChangeMaximumDimensions.fire(this.maximumDimensions);
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
|
||||
private _proxy: MainThreadTerminalServiceShape;
|
||||
private _activeTerminal: ExtHostTerminal;
|
||||
private _terminals: ExtHostTerminal[] = [];
|
||||
private _terminalProcesses: { [id: number]: cp.ChildProcess } = {};
|
||||
private _terminalProcesses: { [id: number]: TerminalProcess } = {};
|
||||
private _terminalRenderers: ExtHostTerminalRenderer[] = [];
|
||||
|
||||
public get activeTerminal(): ExtHostTerminal { return this._activeTerminal; }
|
||||
public get terminals(): ExtHostTerminal[] { return this._terminals; }
|
||||
|
||||
private readonly _onDidCloseTerminal: Emitter<vscode.Terminal> = new Emitter<vscode.Terminal>();
|
||||
public get onDidCloseTerminal(): Event<vscode.Terminal> { return this._onDidCloseTerminal && this._onDidCloseTerminal.event; }
|
||||
private readonly _onDidOpenTerminal: Emitter<vscode.Terminal> = new Emitter<vscode.Terminal>();
|
||||
public get onDidOpenTerminal(): Event<vscode.Terminal> { return this._onDidOpenTerminal && this._onDidOpenTerminal.event; }
|
||||
private readonly _onDidChangeActiveTerminal: Emitter<vscode.Terminal | undefined> = new Emitter<vscode.Terminal | undefined>();
|
||||
public get onDidChangeActiveTerminal(): Event<vscode.Terminal | undefined> { return this._onDidChangeActiveTerminal && this._onDidChangeActiveTerminal.event; }
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
@@ -143,53 +246,108 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
|
||||
}
|
||||
|
||||
public createTerminal(name?: string, shellPath?: string, shellArgs?: string[]): vscode.Terminal {
|
||||
let terminal = new ExtHostTerminal(this._proxy, name);
|
||||
const terminal = new ExtHostTerminal(this._proxy, name);
|
||||
terminal.create(shellPath, shellArgs);
|
||||
this._terminals.push(terminal);
|
||||
return terminal;
|
||||
}
|
||||
|
||||
public createTerminalFromOptions(options: vscode.TerminalOptions): vscode.Terminal {
|
||||
let terminal = new ExtHostTerminal(this._proxy, options.name);
|
||||
const terminal = new ExtHostTerminal(this._proxy, options.name);
|
||||
terminal.create(options.shellPath, options.shellArgs, options.cwd, options.env /*, options.waitOnExit*/);
|
||||
this._terminals.push(terminal);
|
||||
return terminal;
|
||||
}
|
||||
|
||||
public $acceptTerminalProcessData(id: number, data: string): void {
|
||||
let index = this._getTerminalIndexById(id);
|
||||
if (index === null) {
|
||||
return;
|
||||
public createTerminalRenderer(name: string): vscode.TerminalRenderer {
|
||||
const terminal = new ExtHostTerminal(this._proxy, name);
|
||||
terminal._setProcessId(undefined);
|
||||
this._terminals.push(terminal);
|
||||
|
||||
const renderer = new ExtHostTerminalRenderer(this._proxy, name, terminal);
|
||||
this._terminalRenderers.push(renderer);
|
||||
|
||||
return renderer;
|
||||
}
|
||||
|
||||
public $acceptActiveTerminalChanged(id: number | null): void {
|
||||
const original = this._activeTerminal;
|
||||
if (id === null) {
|
||||
this._activeTerminal = undefined;
|
||||
if (original !== this._activeTerminal) {
|
||||
this._onDidChangeActiveTerminal.fire(this._activeTerminal);
|
||||
}
|
||||
}
|
||||
this._performTerminalIdAction(id, terminal => {
|
||||
if (terminal) {
|
||||
this._activeTerminal = terminal;
|
||||
if (original !== this._activeTerminal) {
|
||||
this._onDidChangeActiveTerminal.fire(this._activeTerminal);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public $acceptTerminalProcessData(id: number, data: string): void {
|
||||
// TODO: Queue requests, currently the first 100ms of data may get missed
|
||||
const terminal = this._getTerminalById(id);
|
||||
if (terminal) {
|
||||
terminal._fireOnData(data);
|
||||
}
|
||||
}
|
||||
|
||||
public $acceptTerminalRendererDimensions(id: number, cols: number, rows: number): void {
|
||||
const renderer = this._getTerminalRendererById(id);
|
||||
if (renderer) {
|
||||
renderer._setMaximumDimensions(cols, rows);
|
||||
}
|
||||
}
|
||||
|
||||
public $acceptTerminalRendererInput(id: number, data: string): void {
|
||||
const renderer = this._getTerminalRendererById(id);
|
||||
if (renderer) {
|
||||
renderer._fireOnInput(data);
|
||||
}
|
||||
const terminal = this._terminals[index];
|
||||
terminal._fireOnData(data);
|
||||
}
|
||||
|
||||
public $acceptTerminalClosed(id: number): void {
|
||||
let index = this._getTerminalIndexById(id);
|
||||
const index = this._getTerminalObjectIndexById(this.terminals, id);
|
||||
if (index === null) {
|
||||
return;
|
||||
}
|
||||
let terminal = this._terminals.splice(index, 1)[0];
|
||||
const terminal = this._terminals.splice(index, 1)[0];
|
||||
this._onDidCloseTerminal.fire(terminal);
|
||||
}
|
||||
|
||||
public $acceptTerminalOpened(id: number, name: string): void {
|
||||
let index = this._getTerminalIndexById(id);
|
||||
const index = this._getTerminalObjectIndexById(this._terminals, id);
|
||||
if (index !== null) {
|
||||
// The terminal has already been created (via createTerminal*), only fire the event
|
||||
this._onDidOpenTerminal.fire(this.terminals[index]);
|
||||
return;
|
||||
}
|
||||
let terminal = new ExtHostTerminal(this._proxy, name, id);
|
||||
const renderer = this._getTerminalRendererById(id);
|
||||
const terminal = new ExtHostTerminal(this._proxy, name, id, renderer ? RENDERER_NO_PROCESS_ID : undefined);
|
||||
this._terminals.push(terminal);
|
||||
this._onDidOpenTerminal.fire(terminal);
|
||||
}
|
||||
|
||||
public $acceptTerminalProcessId(id: number, processId: number): void {
|
||||
this._performTerminalIdAction(id, terminal => terminal._setProcessId(processId));
|
||||
}
|
||||
|
||||
private _performTerminalIdAction(id: number, callback: (terminal: ExtHostTerminal) => void): void {
|
||||
let terminal = this._getTerminalById(id);
|
||||
if (terminal) {
|
||||
terminal._setProcessId(processId);
|
||||
callback(terminal);
|
||||
} else {
|
||||
// Retry one more time in case the terminal has not yet been initialized.
|
||||
setTimeout(() => {
|
||||
terminal = this._getTerminalById(id);
|
||||
if (terminal) {
|
||||
callback(terminal);
|
||||
}
|
||||
}, EXT_HOST_CREATION_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +357,6 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
|
||||
|
||||
const terminalConfig = this._extHostConfiguration.getConfiguration('terminal.integrated');
|
||||
|
||||
const locale = terminalConfig.get('setLocaleVariables') ? platform.locale : undefined;
|
||||
if (!shellLaunchConfig.executable) {
|
||||
// TODO: This duplicates some of TerminalConfigHelper.mergeDefaultShellPathAndArgs and should be merged
|
||||
// this._configHelper.mergeDefaultShellPathAndArgs(shellLaunchConfig);
|
||||
@@ -223,61 +380,48 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
|
||||
// const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux');
|
||||
// const envFromConfig = terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, { ...this._configHelper.config.env[platformKey] }, lastActiveWorkspaceRoot);
|
||||
// const envFromShell = terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, { ...shellLaunchConfig.env }, lastActiveWorkspaceRoot);
|
||||
// shellLaunchConfig.env = envFromShell;
|
||||
|
||||
// Merge process env with the env from config
|
||||
const parentEnv = { ...process.env };
|
||||
// terminalEnvironment.mergeEnvironments(parentEnv, envFromConfig);
|
||||
const env = { ...process.env };
|
||||
// terminalEnvironment.mergeEnvironments(env, envFromConfig);
|
||||
terminalEnvironment.mergeEnvironments(env, shellLaunchConfig.env);
|
||||
|
||||
// Continue env initialization, merging in the env from the launch
|
||||
// config and adding keys that are needed to create the process
|
||||
const env = terminalEnvironment.createTerminalEnv(parentEnv, shellLaunchConfig, initialCwd, locale, cols, rows);
|
||||
let cwd = Uri.parse(require.toUrl('../../parts/terminal/node')).fsPath;
|
||||
const options = { env, cwd, execArgv: [] };
|
||||
const locale = terminalConfig.get('setLocaleVariables') ? platform.locale : undefined;
|
||||
terminalEnvironment.addTerminalEnvironmentKeys(env, locale);
|
||||
|
||||
// Fork the process and listen for messages
|
||||
this._logService.debug(`Terminal process launching on ext host`, options);
|
||||
this._terminalProcesses[id] = cp.fork(Uri.parse(require.toUrl('bootstrap')).fsPath, ['--type=terminal'], options);
|
||||
this._terminalProcesses[id].on('message', (message: IMessageFromTerminalProcess) => {
|
||||
switch (message.type) {
|
||||
case 'pid': this._proxy.$sendProcessPid(id, <number>message.content); break;
|
||||
case 'title': this._proxy.$sendProcessTitle(id, <string>message.content); break;
|
||||
case 'data': this._proxy.$sendProcessData(id, <string>message.content); break;
|
||||
}
|
||||
});
|
||||
this._terminalProcesses[id].on('exit', (exitCode) => this._onProcessExit(id, exitCode));
|
||||
this._logService.debug(`Terminal process launching on ext host`, shellLaunchConfig, initialCwd, cols, rows, env);
|
||||
this._terminalProcesses[id] = new TerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env);
|
||||
this._terminalProcesses[id].onProcessIdReady(pid => this._proxy.$sendProcessPid(id, pid));
|
||||
this._terminalProcesses[id].onProcessTitleChanged(title => this._proxy.$sendProcessTitle(id, title));
|
||||
this._terminalProcesses[id].onProcessData(data => this._proxy.$sendProcessData(id, data));
|
||||
this._terminalProcesses[id].onProcessExit((exitCode) => this._onProcessExit(id, exitCode));
|
||||
}
|
||||
|
||||
public $acceptProcessInput(id: number, data: string): void {
|
||||
if (this._terminalProcesses[id].connected) {
|
||||
this._terminalProcesses[id].send({ event: 'input', data });
|
||||
}
|
||||
this._terminalProcesses[id].input(data);
|
||||
}
|
||||
|
||||
public $acceptProcessResize(id: number, cols: number, rows: number): void {
|
||||
if (this._terminalProcesses[id].connected) {
|
||||
try {
|
||||
this._terminalProcesses[id].send({ event: 'resize', cols, rows });
|
||||
} catch (error) {
|
||||
// We tried to write to a closed pipe / channel.
|
||||
if (error.code !== 'EPIPE' && error.code !== 'ERR_IPC_CHANNEL_CLOSED') {
|
||||
throw (error);
|
||||
}
|
||||
try {
|
||||
this._terminalProcesses[id].resize(cols, rows);
|
||||
} catch (error) {
|
||||
// We tried to write to a closed pipe / channel.
|
||||
if (error.code !== 'EPIPE' && error.code !== 'ERR_IPC_CHANNEL_CLOSED') {
|
||||
throw (error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public $acceptProcessShutdown(id: number): void {
|
||||
if (this._terminalProcesses[id].connected) {
|
||||
this._terminalProcesses[id].send({ event: 'shutdown' });
|
||||
}
|
||||
this._terminalProcesses[id].shutdown();
|
||||
}
|
||||
|
||||
private _onProcessExit(id: number, exitCode: number): void {
|
||||
// Remove listeners
|
||||
const process = this._terminalProcesses[id];
|
||||
process.removeAllListeners('message');
|
||||
process.removeAllListeners('exit');
|
||||
this._terminalProcesses[id].dispose();
|
||||
|
||||
// Remove process reference
|
||||
delete this._terminalProcesses[id];
|
||||
@@ -286,16 +430,43 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
|
||||
this._proxy.$sendProcessExit(id, exitCode);
|
||||
}
|
||||
|
||||
private _getTerminalById(id: number): ExtHostTerminal {
|
||||
let index = this._getTerminalIndexById(id);
|
||||
return index !== null ? this._terminals[index] : null;
|
||||
private _getTerminalByIdEventually(id: number, retries: number = 5): Promise<ExtHostTerminal> {
|
||||
return new Promise(c => {
|
||||
if (retries === 0) {
|
||||
c(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const terminal = this._getTerminalById(id);
|
||||
if (terminal) {
|
||||
c(terminal);
|
||||
} else {
|
||||
// This should only be needed immediately after createTerminalRenderer is called as
|
||||
// the ExtHostTerminal has not yet been iniitalized
|
||||
setTimeout(() => {
|
||||
c(this._getTerminalByIdEventually(id, retries - 1));
|
||||
}, 200);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _getTerminalIndexById(id: number): number {
|
||||
private _getTerminalById(id: number): ExtHostTerminal {
|
||||
return this._getTerminalObjectById(this._terminals, id);
|
||||
}
|
||||
|
||||
private _getTerminalRendererById(id: number): ExtHostTerminalRenderer {
|
||||
return this._getTerminalObjectById(this._terminalRenderers, id);
|
||||
}
|
||||
|
||||
private _getTerminalObjectById<T extends ExtHostTerminal | ExtHostTerminalRenderer>(array: T[], id: number): T {
|
||||
const index = this._getTerminalObjectIndexById(array, id);
|
||||
return index !== null ? array[index] : null;
|
||||
}
|
||||
|
||||
private _getTerminalObjectIndexById<T extends ExtHostTerminal | ExtHostTerminalRenderer>(array: T[], id: number): number {
|
||||
let index: number = null;
|
||||
this._terminals.some((terminal, i) => {
|
||||
// TODO: This shouldn't be cas
|
||||
let thisId = (<any>terminal)._id;
|
||||
array.some((item, i) => {
|
||||
const thisId = item._id;
|
||||
if (thisId === id) {
|
||||
index = i;
|
||||
return true;
|
||||
|
||||
@@ -345,7 +345,7 @@ export class ExtHostTextEditor implements vscode.TextEditor {
|
||||
}
|
||||
|
||||
@deprecated('TextEditor.show') show(column: vscode.ViewColumn) {
|
||||
this._proxy.$tryShowEditor(this._id, TypeConverters.fromViewColumn(column));
|
||||
this._proxy.$tryShowEditor(this._id, TypeConverters.ViewColumn.from(column));
|
||||
}
|
||||
|
||||
@deprecated('TextEditor.hide') hide() {
|
||||
@@ -467,14 +467,14 @@ export class ExtHostTextEditor implements vscode.TextEditor {
|
||||
this._runOnProxy(
|
||||
() => this._proxy.$tryRevealRange(
|
||||
this._id,
|
||||
TypeConverters.fromRange(range),
|
||||
TypeConverters.Range.from(range),
|
||||
(revealType || TextEditorRevealType.Default)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private _trySetSelection(): TPromise<vscode.TextEditor> {
|
||||
let selection = this._selections.map(TypeConverters.fromSelection);
|
||||
let selection = this._selections.map(TypeConverters.Selection.from);
|
||||
return this._runOnProxy(() => this._proxy.$trySetSelections(this._id, selection));
|
||||
}
|
||||
|
||||
@@ -497,6 +497,11 @@ export class ExtHostTextEditor implements vscode.TextEditor {
|
||||
private _applyEdit(editBuilder: TextEditorEdit): TPromise<boolean> {
|
||||
let editData = editBuilder.finalize();
|
||||
|
||||
// return when there is nothing to do
|
||||
if (editData.edits.length === 0 && !editData.setEndOfLine) {
|
||||
return TPromise.wrap(true);
|
||||
}
|
||||
|
||||
// check that the edits are not overlapping (i.e. illegal)
|
||||
let editRanges = editData.edits.map(edit => edit.range);
|
||||
|
||||
@@ -530,7 +535,7 @@ export class ExtHostTextEditor implements vscode.TextEditor {
|
||||
// prepare data for serialization
|
||||
let edits: ISingleEditOperation[] = editData.edits.map((edit) => {
|
||||
return {
|
||||
range: TypeConverters.fromRange(edit.range),
|
||||
range: TypeConverters.Range.from(edit.range),
|
||||
text: edit.text,
|
||||
forceMoveMarkers: edit.forceMoveMarkers
|
||||
};
|
||||
@@ -550,21 +555,21 @@ export class ExtHostTextEditor implements vscode.TextEditor {
|
||||
let ranges: IRange[];
|
||||
|
||||
if (!where || (Array.isArray(where) && where.length === 0)) {
|
||||
ranges = this._selections.map(TypeConverters.fromRange);
|
||||
ranges = this._selections.map(TypeConverters.Range.from);
|
||||
|
||||
} else if (where instanceof Position) {
|
||||
const { lineNumber, column } = TypeConverters.fromPosition(where);
|
||||
const { lineNumber, column } = TypeConverters.Position.from(where);
|
||||
ranges = [{ startLineNumber: lineNumber, startColumn: column, endLineNumber: lineNumber, endColumn: column }];
|
||||
|
||||
} else if (where instanceof Range) {
|
||||
ranges = [TypeConverters.fromRange(where)];
|
||||
ranges = [TypeConverters.Range.from(where)];
|
||||
} else {
|
||||
ranges = [];
|
||||
for (const posOrRange of where) {
|
||||
if (posOrRange instanceof Range) {
|
||||
ranges.push(TypeConverters.fromRange(posOrRange));
|
||||
ranges.push(TypeConverters.Range.from(posOrRange));
|
||||
} else {
|
||||
const { lineNumber, column } = TypeConverters.fromPosition(posOrRange);
|
||||
const { lineNumber, column } = TypeConverters.Position.from(posOrRange);
|
||||
ranges.push({ startLineNumber: lineNumber, startColumn: column, endLineNumber: lineNumber, endColumn: column });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,7 @@ import { TextEditorSelectionChangeKind } from './extHostTypes';
|
||||
import * as TypeConverters from './extHostTypeConverters';
|
||||
import { TextEditorDecorationType, ExtHostTextEditor } from './extHostTextEditor';
|
||||
import { ExtHostDocumentsAndEditors } from './extHostDocumentsAndEditors';
|
||||
import { Position as EditorPosition } from 'vs/platform/editor/common/editor';
|
||||
import { MainContext, MainThreadTextEditorsShape, ExtHostEditorsShape, ITextDocumentShowOptions, ITextEditorPositionData, IMainContext, WorkspaceEditDto, IEditorPropertiesChangeData } from './extHost.protocol';
|
||||
import { MainContext, MainThreadTextEditorsShape, ExtHostEditorsShape, ITextDocumentShowOptions, ITextEditorPositionData, IMainContext, IEditorPropertiesChangeData } from './extHost.protocol';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export class ExtHostEditors implements ExtHostEditorsShape {
|
||||
@@ -61,19 +60,18 @@ export class ExtHostEditors implements ExtHostEditorsShape {
|
||||
let options: ITextDocumentShowOptions;
|
||||
if (typeof columnOrOptions === 'number') {
|
||||
options = {
|
||||
position: TypeConverters.fromViewColumn(columnOrOptions),
|
||||
position: TypeConverters.ViewColumn.from(columnOrOptions),
|
||||
preserveFocus
|
||||
};
|
||||
} else if (typeof columnOrOptions === 'object') {
|
||||
options = {
|
||||
position: TypeConverters.fromViewColumn(columnOrOptions.viewColumn),
|
||||
position: TypeConverters.ViewColumn.from(columnOrOptions.viewColumn),
|
||||
preserveFocus: columnOrOptions.preserveFocus,
|
||||
selection: typeof columnOrOptions.selection === 'object' ? TypeConverters.fromRange(columnOrOptions.selection) : undefined,
|
||||
selection: typeof columnOrOptions.selection === 'object' ? TypeConverters.Range.from(columnOrOptions.selection) : undefined,
|
||||
pinned: typeof columnOrOptions.preview === 'boolean' ? !columnOrOptions.preview : undefined
|
||||
};
|
||||
} else {
|
||||
options = {
|
||||
position: EditorPosition.ONE,
|
||||
preserveFocus: false
|
||||
};
|
||||
}
|
||||
@@ -93,23 +91,7 @@ export class ExtHostEditors implements ExtHostEditorsShape {
|
||||
}
|
||||
|
||||
applyWorkspaceEdit(edit: vscode.WorkspaceEdit): TPromise<boolean> {
|
||||
|
||||
const dto: WorkspaceEditDto = { edits: [] };
|
||||
|
||||
for (let entry of edit.entries()) {
|
||||
let [uri, uriOrEdits] = entry;
|
||||
if (Array.isArray(uriOrEdits)) {
|
||||
let doc = this._extHostDocumentsAndEditors.getDocument(uri.toString());
|
||||
dto.edits.push({
|
||||
resource: uri,
|
||||
modelVersionId: doc && doc.version,
|
||||
edits: uriOrEdits.map(TypeConverters.TextEdit.from)
|
||||
});
|
||||
// } else {
|
||||
// dto.edits.push({ oldUri: uri, newUri: uriOrEdits });
|
||||
}
|
||||
}
|
||||
|
||||
const dto = TypeConverters.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors);
|
||||
return this._proxy.$tryApplyWorkspaceEdit(dto);
|
||||
}
|
||||
|
||||
@@ -123,11 +105,11 @@ export class ExtHostEditors implements ExtHostEditorsShape {
|
||||
textEditor._acceptOptions(data.options);
|
||||
}
|
||||
if (data.selections) {
|
||||
const selections = data.selections.selections.map(TypeConverters.toSelection);
|
||||
const selections = data.selections.selections.map(TypeConverters.Selection.to);
|
||||
textEditor._acceptSelections(selections);
|
||||
}
|
||||
if (data.visibleRanges) {
|
||||
const visibleRanges = data.visibleRanges.map(TypeConverters.toRange);
|
||||
const visibleRanges = data.visibleRanges.map(TypeConverters.Range.to);
|
||||
textEditor._acceptVisibleRanges(visibleRanges);
|
||||
}
|
||||
|
||||
@@ -140,7 +122,7 @@ export class ExtHostEditors implements ExtHostEditorsShape {
|
||||
}
|
||||
if (data.selections) {
|
||||
const kind = TextEditorSelectionChangeKind.fromValue(data.selections.source);
|
||||
const selections = data.selections.selections.map(TypeConverters.toSelection);
|
||||
const selections = data.selections.selections.map(TypeConverters.Selection.to);
|
||||
this._onDidChangeTextEditorSelection.fire({
|
||||
textEditor,
|
||||
selections,
|
||||
@@ -148,7 +130,7 @@ export class ExtHostEditors implements ExtHostEditorsShape {
|
||||
});
|
||||
}
|
||||
if (data.visibleRanges) {
|
||||
const visibleRanges = data.visibleRanges.map(TypeConverters.toRange);
|
||||
const visibleRanges = data.visibleRanges.map(TypeConverters.Range.to);
|
||||
this._onDidChangeTextEditorVisibleRanges.fire({
|
||||
textEditor,
|
||||
visibleRanges
|
||||
@@ -159,7 +141,7 @@ export class ExtHostEditors implements ExtHostEditorsShape {
|
||||
$acceptEditorPositionData(data: ITextEditorPositionData): void {
|
||||
for (let id in data) {
|
||||
let textEditor = this._extHostDocumentsAndEditors.getEditor(id);
|
||||
let viewColumn = TypeConverters.toViewColumn(data[id]);
|
||||
let viewColumn = TypeConverters.ViewColumn.to(data[id]);
|
||||
if (textEditor.viewColumn !== viewColumn) {
|
||||
textEditor._acceptViewColumn(viewColumn);
|
||||
this._onDidChangeTextEditorViewColumn.fire({ textEditor, viewColumn });
|
||||
|
||||
@@ -8,7 +8,7 @@ import { localize } from 'vs/nls';
|
||||
import * as vscode from 'vscode';
|
||||
import { basename } from 'vs/base/common/paths';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { debounceEvent } from 'vs/base/common/event';
|
||||
import { debounceEvent, Emitter, Event } from 'vs/base/common/event';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.protocol';
|
||||
@@ -17,6 +17,8 @@ import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHos
|
||||
import { asWinJsPromise } from 'vs/base/common/async';
|
||||
import { TreeItemCollapsibleState, ThemeIcon } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { equals } from 'vs/base/common/arrays';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
export type TreeItemHandle = string;
|
||||
@@ -27,7 +29,8 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
|
||||
|
||||
constructor(
|
||||
private _proxy: MainThreadTreeViewsShape,
|
||||
private commands: ExtHostCommands
|
||||
private commands: ExtHostCommands,
|
||||
private logService: ILogService
|
||||
) {
|
||||
commands.registerArgumentProcessor({
|
||||
processArgument: arg => {
|
||||
@@ -50,7 +53,13 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
|
||||
}
|
||||
const treeView = this.createExtHostTreeViewer(viewId, options.treeDataProvider);
|
||||
return {
|
||||
reveal: (element: T, options?: { select?: boolean }): Thenable<void> => {
|
||||
get onDidCollapseElement() { return treeView.onDidCollapseElement; },
|
||||
get onDidExpandElement() { return treeView.onDidExpandElement; },
|
||||
get selection() { return treeView.selectedElements; },
|
||||
get onDidChangeSelection() { return treeView.onDidChangeSelection; },
|
||||
get visible() { return treeView.visible; },
|
||||
get onDidChangeVisibility() { return treeView.onDidChangeVisibility; },
|
||||
reveal: (element: T, options?: { select?: boolean, focus?: boolean }): Thenable<void> => {
|
||||
return treeView.reveal(element, options);
|
||||
},
|
||||
dispose: () => {
|
||||
@@ -68,8 +77,32 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
|
||||
return treeView.getChildren(treeItemHandle);
|
||||
}
|
||||
|
||||
$setExpanded(treeViewId: string, treeItemHandle: string, expanded: boolean): void {
|
||||
const treeView = this.treeViews.get(treeViewId);
|
||||
if (!treeView) {
|
||||
throw new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId));
|
||||
}
|
||||
treeView.setExpanded(treeItemHandle, expanded);
|
||||
}
|
||||
|
||||
$setSelection(treeViewId: string, treeItemHandles: string[]): void {
|
||||
const treeView = this.treeViews.get(treeViewId);
|
||||
if (!treeView) {
|
||||
throw new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId));
|
||||
}
|
||||
treeView.setSelection(treeItemHandles);
|
||||
}
|
||||
|
||||
$setVisible(treeViewId: string, isVisible: boolean): void {
|
||||
const treeView = this.treeViews.get(treeViewId);
|
||||
if (!treeView) {
|
||||
throw new Error(localize('treeView.notRegistered', 'No tree view with id \'{0}\' registered.', treeViewId));
|
||||
}
|
||||
treeView.setVisible(isVisible);
|
||||
}
|
||||
|
||||
private createExtHostTreeViewer<T>(id: string, dataProvider: vscode.TreeDataProvider<T>): ExtHostTreeView<T> {
|
||||
const treeView = new ExtHostTreeView<T>(id, dataProvider, this._proxy, this.commands.converter);
|
||||
const treeView = new ExtHostTreeView<T>(id, dataProvider, this._proxy, this.commands.converter, this.logService);
|
||||
this.treeViews.set(id, treeView);
|
||||
return treeView;
|
||||
}
|
||||
@@ -98,14 +131,46 @@ export class ExtHostTreeView<T> extends Disposable {
|
||||
// {{SQL CARBON EDIT}}
|
||||
protected nodes: Map<T, TreeNode> = new Map<T, TreeNode>();
|
||||
|
||||
constructor(private viewId: string, private dataProvider: vscode.TreeDataProvider<T>, private proxy: MainThreadTreeViewsShape, private commands: CommandsConverter) {
|
||||
private _visible: boolean = false;
|
||||
get visible(): boolean { return this._visible; }
|
||||
|
||||
private _selectedHandles: TreeItemHandle[] = [];
|
||||
get selectedElements(): T[] { return this._selectedHandles.map(handle => this.getExtensionElement(handle)).filter(element => !isUndefinedOrNull(element)); }
|
||||
|
||||
private _onDidExpandElement: Emitter<vscode.TreeViewExpansionEvent<T>> = this._register(new Emitter<vscode.TreeViewExpansionEvent<T>>());
|
||||
readonly onDidExpandElement: Event<vscode.TreeViewExpansionEvent<T>> = this._onDidExpandElement.event;
|
||||
|
||||
private _onDidCollapseElement: Emitter<vscode.TreeViewExpansionEvent<T>> = this._register(new Emitter<vscode.TreeViewExpansionEvent<T>>());
|
||||
readonly onDidCollapseElement: Event<vscode.TreeViewExpansionEvent<T>> = this._onDidCollapseElement.event;
|
||||
|
||||
private _onDidChangeSelection: Emitter<vscode.TreeViewSelectionChangeEvent<T>> = this._register(new Emitter<vscode.TreeViewSelectionChangeEvent<T>>());
|
||||
readonly onDidChangeSelection: Event<vscode.TreeViewSelectionChangeEvent<T>> = this._onDidChangeSelection.event;
|
||||
|
||||
private _onDidChangeVisibility: Emitter<vscode.TreeViewVisibilityChangeEvent> = this._register(new Emitter<vscode.TreeViewVisibilityChangeEvent>());
|
||||
readonly onDidChangeVisibility: Event<vscode.TreeViewVisibilityChangeEvent> = this._onDidChangeVisibility.event;
|
||||
|
||||
private refreshPromise: TPromise<void> = TPromise.as(null);
|
||||
|
||||
constructor(private viewId: string, private dataProvider: vscode.TreeDataProvider<T>, private proxy: MainThreadTreeViewsShape, private commands: CommandsConverter, private logService: ILogService) {
|
||||
super();
|
||||
// {{SQL CARBON EDIT}}
|
||||
if (this.proxy) {
|
||||
this.proxy.$registerTreeViewDataProvider(viewId);
|
||||
}
|
||||
if (this.dataProvider.onDidChangeTreeData) {
|
||||
this._register(debounceEvent<T, T[]>(this.dataProvider.onDidChangeTreeData, (last, current) => last ? [...last, current] : [current], 200)(elements => this.refresh(elements)));
|
||||
let refreshingPromise, promiseCallback;
|
||||
this._register(debounceEvent<T, T[]>(this.dataProvider.onDidChangeTreeData, (last, current) => {
|
||||
if (!refreshingPromise) {
|
||||
// New refresh has started
|
||||
refreshingPromise = new TPromise((c, e) => promiseCallback = c);
|
||||
this.refreshPromise = this.refreshPromise.then(() => refreshingPromise);
|
||||
}
|
||||
return last ? [...last, current] : [current];
|
||||
}, 200)(elements => {
|
||||
const _promiseCallback = promiseCallback;
|
||||
refreshingPromise = null;
|
||||
this.refresh(elements);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,13 +190,43 @@ export class ExtHostTreeView<T> extends Disposable {
|
||||
return this.elements.get(treeItemHandle);
|
||||
}
|
||||
|
||||
reveal(element: T, options?: { select?: boolean }): TPromise<void> {
|
||||
reveal(element: T, options?: { select?: boolean, focus?: boolean }): TPromise<void> {
|
||||
options = options ? options : { select: true, focus: false };
|
||||
const select = isUndefinedOrNull(options.select) ? true : options.select;
|
||||
const focus = isUndefinedOrNull(options.focus) ? false : options.focus;
|
||||
|
||||
if (typeof this.dataProvider.getParent !== 'function') {
|
||||
return TPromise.wrapError(new Error(`Required registered TreeDataProvider to implement 'getParent' method to access 'reveal' mehtod`));
|
||||
return TPromise.wrapError(new Error(`Required registered TreeDataProvider to implement 'getParent' method to access 'reveal' method`));
|
||||
}
|
||||
return this.resolveUnknownParentChain(element)
|
||||
return this.refreshPromise
|
||||
.then(() => this.resolveUnknownParentChain(element))
|
||||
.then(parentChain => this.resolveTreeNode(element, parentChain[parentChain.length - 1])
|
||||
.then(treeNode => this.proxy.$reveal(this.viewId, treeNode.item, parentChain.map(p => p.item), options)));
|
||||
.then(treeNode => this.proxy.$reveal(this.viewId, treeNode.item, parentChain.map(p => p.item), { select, focus })), error => this.logService.error(error));
|
||||
}
|
||||
|
||||
setExpanded(treeItemHandle: TreeItemHandle, expanded: boolean): void {
|
||||
const element = this.getExtensionElement(treeItemHandle);
|
||||
if (element) {
|
||||
if (expanded) {
|
||||
this._onDidExpandElement.fire(Object.freeze({ element }));
|
||||
} else {
|
||||
this._onDidCollapseElement.fire(Object.freeze({ element }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setSelection(treeItemHandles: TreeItemHandle[]): void {
|
||||
if (!equals(this._selectedHandles, treeItemHandles)) {
|
||||
this._selectedHandles = treeItemHandles;
|
||||
this._onDidChangeSelection.fire(Object.freeze({ selection: this.selectedElements }));
|
||||
}
|
||||
}
|
||||
|
||||
setVisible(visible: boolean): void {
|
||||
if (visible !== this._visible) {
|
||||
this._visible = visible;
|
||||
this._onDidChangeVisibility.fire(Object.freeze({ visible: this._visible }));
|
||||
}
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
@@ -204,7 +299,7 @@ export class ExtHostTreeView<T> extends Disposable {
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
protected refresh(elements: T[]): void {
|
||||
private refresh(elements: T[]): void {
|
||||
const hasRoot = elements.some(element => !element);
|
||||
if (hasRoot) {
|
||||
this.clearAll(); // clear cache
|
||||
@@ -318,7 +413,7 @@ export class ExtHostTreeView<T> extends Disposable {
|
||||
return item;
|
||||
}
|
||||
|
||||
private createHandle(element: T, { id, label, resourceUri }: vscode.TreeItem, parent: TreeNode, first?: boolean): TreeItemHandle {
|
||||
private createHandle(element: T, { id, label, resourceUri }: vscode.TreeItem, parent: TreeNode, returnFirst?: boolean): TreeItemHandle {
|
||||
if (id) {
|
||||
return `${ExtHostTreeView.ID_HANDLE_PREFIX}/${id}`;
|
||||
}
|
||||
@@ -329,14 +424,20 @@ export class ExtHostTreeView<T> extends Disposable {
|
||||
const existingHandle = this.nodes.has(element) ? this.nodes.get(element).item.handle : void 0;
|
||||
const childrenNodes = (this.getChildrenNodes(parent) || []);
|
||||
|
||||
for (let counter = 0; counter <= childrenNodes.length; counter++) {
|
||||
const handle = `${prefix}/${counter}:${elementId}`;
|
||||
if (first || !this.elements.has(handle) || existingHandle === handle) {
|
||||
return handle;
|
||||
let handle: TreeItemHandle;
|
||||
let counter = 0;
|
||||
do {
|
||||
handle = `${prefix}/${counter}:${elementId}`;
|
||||
if (returnFirst || !this.elements.has(handle) || existingHandle === handle) {
|
||||
// Return first if asked for or
|
||||
// Return if handle does not exist or
|
||||
// Return if handle is being reused
|
||||
break;
|
||||
}
|
||||
}
|
||||
counter++;
|
||||
} while (counter <= childrenNodes.length);
|
||||
|
||||
throw new Error('This should not be reached');
|
||||
return handle;
|
||||
}
|
||||
|
||||
private getLightIconPath(extensionTreeItem: vscode.TreeItem): string {
|
||||
@@ -414,7 +515,7 @@ export class ExtHostTreeView<T> extends Disposable {
|
||||
}
|
||||
}
|
||||
}
|
||||
node.children = [];
|
||||
node.children = void 0;
|
||||
} else {
|
||||
this.clearAll();
|
||||
}
|
||||
|
||||
@@ -6,21 +6,25 @@
|
||||
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import * as types from './extHostTypes';
|
||||
import { Position as EditorPosition, ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import * as search from 'vs/workbench/parts/search/common/search';
|
||||
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { EditorViewColumn } from 'vs/workbench/api/shared/editor';
|
||||
import { IDecorationOptions } from 'vs/editor/common/editorCommon';
|
||||
import { EndOfLineSequence } from 'vs/editor/common/model';
|
||||
import * as vscode from 'vscode';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { ProgressLocation as MainProgressLocation } from 'vs/platform/progress/common/progress';
|
||||
import { ProgressLocation as MainProgressLocation } from 'vs/workbench/services/progress/common/progress';
|
||||
import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { ISelection } from 'vs/editor/common/core/selection';
|
||||
import * as htmlContent from 'vs/base/common/htmlContent';
|
||||
import { IRelativePattern } from 'vs/base/common/glob';
|
||||
import { LanguageSelector, LanguageFilter } from 'vs/editor/common/modes/languageSelector';
|
||||
import { WorkspaceEditDto, ResourceTextEditDto } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { MarkerSeverity, IRelatedInformation, IMarkerData } from 'vs/platform/markers/common/markers';
|
||||
import * as languageSelector from 'vs/editor/common/modes/languageSelector';
|
||||
import { WorkspaceEditDto, ResourceTextEditDto, ResourceFileEditDto } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { MarkerSeverity, IRelatedInformation, IMarkerData, MarkerTag } from 'vs/platform/markers/common/markers';
|
||||
import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors';
|
||||
|
||||
export interface PositionLike {
|
||||
line: number;
|
||||
@@ -36,131 +40,145 @@ export interface SelectionLike extends RangeLike {
|
||||
anchor: PositionLike;
|
||||
active: PositionLike;
|
||||
}
|
||||
export namespace Selection {
|
||||
|
||||
export function toSelection(selection: ISelection): types.Selection {
|
||||
let { selectionStartLineNumber, selectionStartColumn, positionLineNumber, positionColumn } = selection;
|
||||
let start = new types.Position(selectionStartLineNumber - 1, selectionStartColumn - 1);
|
||||
let end = new types.Position(positionLineNumber - 1, positionColumn - 1);
|
||||
return new types.Selection(start, end);
|
||||
export function to(selection: ISelection): types.Selection {
|
||||
let { selectionStartLineNumber, selectionStartColumn, positionLineNumber, positionColumn } = selection;
|
||||
let start = new types.Position(selectionStartLineNumber - 1, selectionStartColumn - 1);
|
||||
let end = new types.Position(positionLineNumber - 1, positionColumn - 1);
|
||||
return new types.Selection(start, end);
|
||||
}
|
||||
|
||||
export function from(selection: SelectionLike): ISelection {
|
||||
let { anchor, active } = selection;
|
||||
return {
|
||||
selectionStartLineNumber: anchor.line + 1,
|
||||
selectionStartColumn: anchor.character + 1,
|
||||
positionLineNumber: active.line + 1,
|
||||
positionColumn: active.character + 1
|
||||
};
|
||||
}
|
||||
}
|
||||
export namespace Range {
|
||||
|
||||
export function from(range: RangeLike): IRange {
|
||||
if (!range) {
|
||||
return undefined;
|
||||
}
|
||||
let { start, end } = range;
|
||||
return {
|
||||
startLineNumber: start.line + 1,
|
||||
startColumn: start.character + 1,
|
||||
endLineNumber: end.line + 1,
|
||||
endColumn: end.character + 1
|
||||
};
|
||||
}
|
||||
|
||||
export function to(range: IRange): types.Range {
|
||||
if (!range) {
|
||||
return undefined;
|
||||
}
|
||||
let { startLineNumber, startColumn, endLineNumber, endColumn } = range;
|
||||
return new types.Range(startLineNumber - 1, startColumn - 1, endLineNumber - 1, endColumn - 1);
|
||||
}
|
||||
}
|
||||
|
||||
export function fromSelection(selection: SelectionLike): ISelection {
|
||||
let { anchor, active } = selection;
|
||||
return {
|
||||
selectionStartLineNumber: anchor.line + 1,
|
||||
selectionStartColumn: anchor.character + 1,
|
||||
positionLineNumber: active.line + 1,
|
||||
positionColumn: active.character + 1
|
||||
};
|
||||
export namespace Position {
|
||||
export function to(position: IPosition): types.Position {
|
||||
return new types.Position(position.lineNumber - 1, position.column - 1);
|
||||
}
|
||||
export function from(position: types.Position): IPosition {
|
||||
return { lineNumber: position.line + 1, column: position.character + 1 };
|
||||
}
|
||||
}
|
||||
|
||||
export function fromRange(range: RangeLike): IRange {
|
||||
if (!range) {
|
||||
export namespace DiagnosticTag {
|
||||
export function from(value: vscode.DiagnosticTag): MarkerTag {
|
||||
switch (value) {
|
||||
case types.DiagnosticTag.Unnecessary:
|
||||
return MarkerTag.Unnecessary;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
let { start, end } = range;
|
||||
return {
|
||||
startLineNumber: start.line + 1,
|
||||
startColumn: start.character + 1,
|
||||
endLineNumber: end.line + 1,
|
||||
endColumn: end.character + 1
|
||||
};
|
||||
}
|
||||
|
||||
export function toRange(range: IRange): types.Range {
|
||||
if (!range) {
|
||||
export namespace Diagnostic {
|
||||
export function from(value: vscode.Diagnostic): IMarkerData {
|
||||
return {
|
||||
...Range.from(value.range),
|
||||
message: value.message,
|
||||
source: value.source,
|
||||
code: String(value.code),
|
||||
severity: DiagnosticSeverity.from(value.severity),
|
||||
relatedInformation: value.relatedInformation && value.relatedInformation.map(DiagnosticRelatedInformation.from),
|
||||
tags: Array.isArray(value.tags) ? value.tags.map(DiagnosticTag.from) : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export namespace DiagnosticRelatedInformation {
|
||||
export function from(value: types.DiagnosticRelatedInformation): IRelatedInformation {
|
||||
return {
|
||||
...Range.from(value.location.range),
|
||||
message: value.message,
|
||||
resource: value.location.uri
|
||||
};
|
||||
}
|
||||
export function to(value: IRelatedInformation): types.DiagnosticRelatedInformation {
|
||||
return new types.DiagnosticRelatedInformation(new types.Location(value.resource, Range.to(value)), value.message);
|
||||
}
|
||||
}
|
||||
export namespace DiagnosticSeverity {
|
||||
|
||||
export function from(value: number): MarkerSeverity {
|
||||
switch (value) {
|
||||
case types.DiagnosticSeverity.Error:
|
||||
return MarkerSeverity.Error;
|
||||
case types.DiagnosticSeverity.Warning:
|
||||
return MarkerSeverity.Warning;
|
||||
case types.DiagnosticSeverity.Information:
|
||||
return MarkerSeverity.Info;
|
||||
case types.DiagnosticSeverity.Hint:
|
||||
return MarkerSeverity.Hint;
|
||||
}
|
||||
return MarkerSeverity.Error;
|
||||
}
|
||||
|
||||
export function to(value: MarkerSeverity): types.DiagnosticSeverity {
|
||||
switch (value) {
|
||||
case MarkerSeverity.Info:
|
||||
return types.DiagnosticSeverity.Information;
|
||||
case MarkerSeverity.Warning:
|
||||
return types.DiagnosticSeverity.Warning;
|
||||
case MarkerSeverity.Error:
|
||||
return types.DiagnosticSeverity.Error;
|
||||
case MarkerSeverity.Hint:
|
||||
return types.DiagnosticSeverity.Hint;
|
||||
}
|
||||
return types.DiagnosticSeverity.Error;
|
||||
}
|
||||
}
|
||||
|
||||
export namespace ViewColumn {
|
||||
export function from(column?: vscode.ViewColumn): EditorViewColumn {
|
||||
if (typeof column === 'number' && column >= types.ViewColumn.One) {
|
||||
return column - 1; // adjust zero index (ViewColumn.ONE => 0)
|
||||
}
|
||||
|
||||
if (column === types.ViewColumn.Beside) {
|
||||
return SIDE_GROUP;
|
||||
}
|
||||
|
||||
return ACTIVE_GROUP; // default is always the active group
|
||||
}
|
||||
|
||||
export function to(position?: EditorViewColumn): vscode.ViewColumn {
|
||||
if (typeof position === 'number' && position >= 0) {
|
||||
return position + 1; // adjust to index (ViewColumn.ONE => 1)
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
let { startLineNumber, startColumn, endLineNumber, endColumn } = range;
|
||||
return new types.Range(startLineNumber - 1, startColumn - 1, endLineNumber - 1, endColumn - 1);
|
||||
}
|
||||
|
||||
export function toPosition(position: IPosition): types.Position {
|
||||
return new types.Position(position.lineNumber - 1, position.column - 1);
|
||||
}
|
||||
|
||||
export function fromPosition(position: types.Position): IPosition {
|
||||
return { lineNumber: position.line + 1, column: position.character + 1 };
|
||||
}
|
||||
|
||||
export function fromDiagnostic(value: vscode.Diagnostic): IMarkerData {
|
||||
return {
|
||||
...fromRange(value.range),
|
||||
message: value.message,
|
||||
source: value.source,
|
||||
code: String(value.code),
|
||||
severity: fromDiagnosticSeverity(value.severity),
|
||||
relatedInformation: value.relatedInformation && value.relatedInformation.map(fromDiagnosticRelatedInformation)
|
||||
};
|
||||
}
|
||||
|
||||
export function fromDiagnosticRelatedInformation(value: types.DiagnosticRelatedInformation): IRelatedInformation {
|
||||
return {
|
||||
...fromRange(value.location.range),
|
||||
message: value.message,
|
||||
resource: value.location.uri
|
||||
};
|
||||
}
|
||||
|
||||
export function toDiagnosticRelatedInformation(value: IRelatedInformation): types.DiagnosticRelatedInformation {
|
||||
return new types.DiagnosticRelatedInformation(new types.Location(value.resource, toRange(value)), value.message);
|
||||
}
|
||||
|
||||
export function fromDiagnosticSeverity(value: number): MarkerSeverity {
|
||||
switch (value) {
|
||||
case types.DiagnosticSeverity.Error:
|
||||
return MarkerSeverity.Error;
|
||||
case types.DiagnosticSeverity.Warning:
|
||||
return MarkerSeverity.Warning;
|
||||
case types.DiagnosticSeverity.Information:
|
||||
return MarkerSeverity.Info;
|
||||
case types.DiagnosticSeverity.Hint:
|
||||
return MarkerSeverity.Hint;
|
||||
}
|
||||
return MarkerSeverity.Error;
|
||||
}
|
||||
|
||||
export function toDiagnosticSeverty(value: MarkerSeverity): types.DiagnosticSeverity {
|
||||
switch (value) {
|
||||
case MarkerSeverity.Info:
|
||||
return types.DiagnosticSeverity.Information;
|
||||
case MarkerSeverity.Warning:
|
||||
return types.DiagnosticSeverity.Warning;
|
||||
case MarkerSeverity.Error:
|
||||
return types.DiagnosticSeverity.Error;
|
||||
case MarkerSeverity.Hint:
|
||||
return types.DiagnosticSeverity.Hint;
|
||||
}
|
||||
return types.DiagnosticSeverity.Error;
|
||||
}
|
||||
|
||||
|
||||
export function fromViewColumn(column?: vscode.ViewColumn): EditorPosition {
|
||||
let editorColumn = EditorPosition.ONE;
|
||||
if (typeof column !== 'number') {
|
||||
// stick with ONE
|
||||
} else if (column === <number>types.ViewColumn.Two) {
|
||||
editorColumn = EditorPosition.TWO;
|
||||
} else if (column === <number>types.ViewColumn.Three) {
|
||||
editorColumn = EditorPosition.THREE;
|
||||
} else if (column === <number>types.ViewColumn.Active) {
|
||||
editorColumn = undefined;
|
||||
}
|
||||
return editorColumn;
|
||||
}
|
||||
|
||||
export function toViewColumn(position?: EditorPosition): vscode.ViewColumn {
|
||||
if (typeof position !== 'number') {
|
||||
return undefined;
|
||||
}
|
||||
if (position === EditorPosition.ONE) {
|
||||
return <number>types.ViewColumn.One;
|
||||
} else if (position === EditorPosition.TWO) {
|
||||
return <number>types.ViewColumn.Two;
|
||||
} else if (position === EditorPosition.THREE) {
|
||||
return <number>types.ViewColumn.Three;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isDecorationOptions(something: any): something is vscode.DecorationOptions {
|
||||
@@ -221,7 +239,7 @@ export function fromRangeOrRangeWithMessage(ranges: vscode.Range[] | vscode.Deco
|
||||
if (isDecorationOptionsArr(ranges)) {
|
||||
return ranges.map(r => {
|
||||
return {
|
||||
range: fromRange(r.range),
|
||||
range: Range.from(r.range),
|
||||
hoverMessage: Array.isArray(r.hoverMessage) ? MarkdownString.fromMany(r.hoverMessage) : r.hoverMessage && MarkdownString.from(r.hoverMessage),
|
||||
renderOptions: <any> /* URI vs Uri */r.renderOptions
|
||||
};
|
||||
@@ -229,7 +247,7 @@ export function fromRangeOrRangeWithMessage(ranges: vscode.Range[] | vscode.Deco
|
||||
} else {
|
||||
return ranges.map((r): IDecorationOptions => {
|
||||
return {
|
||||
range: fromRange(r)
|
||||
range: Range.from(r)
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -241,29 +259,30 @@ export const TextEdit = {
|
||||
return <modes.TextEdit>{
|
||||
text: edit.newText,
|
||||
eol: EndOfLine.from(edit.newEol),
|
||||
range: fromRange(edit.range)
|
||||
range: Range.from(edit.range)
|
||||
};
|
||||
},
|
||||
to(edit: modes.TextEdit): types.TextEdit {
|
||||
let result = new types.TextEdit(toRange(edit.range), edit.text);
|
||||
let result = new types.TextEdit(Range.to(edit.range), edit.text);
|
||||
result.newEol = EndOfLine.to(edit.eol);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
export namespace WorkspaceEdit {
|
||||
export function from(value: vscode.WorkspaceEdit): modes.WorkspaceEdit {
|
||||
const result: modes.WorkspaceEdit = {
|
||||
export function from(value: vscode.WorkspaceEdit, documents?: ExtHostDocumentsAndEditors): WorkspaceEditDto {
|
||||
const result: WorkspaceEditDto = {
|
||||
edits: []
|
||||
};
|
||||
for (const entry of value.entries()) {
|
||||
for (const entry of (value as types.WorkspaceEdit)._allEntries()) {
|
||||
const [uri, uriOrEdits] = entry;
|
||||
if (Array.isArray(uriOrEdits)) {
|
||||
// text edits
|
||||
result.edits.push({ resource: uri, edits: uriOrEdits.map(TextEdit.from) });
|
||||
let doc = documents ? documents.getDocument(uri.toString()) : undefined;
|
||||
result.edits.push(<ResourceTextEditDto>{ resource: uri, modelVersionId: doc && doc.version, edits: uriOrEdits.map(TextEdit.from) });
|
||||
} else {
|
||||
// resource edits
|
||||
result.edits.push({ oldUri: uri, newUri: uriOrEdits });
|
||||
result.edits.push(<ResourceFileEditDto>{ oldUri: uri, newUri: uriOrEdits, options: entry[2] });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -277,11 +296,12 @@ export namespace WorkspaceEdit {
|
||||
URI.revive((<ResourceTextEditDto>edit).resource),
|
||||
<types.TextEdit[]>(<ResourceTextEditDto>edit).edits.map(TextEdit.to)
|
||||
);
|
||||
// } else {
|
||||
// result.renameResource(
|
||||
// URI.revive((<ResourceFileEditDto>edit).oldUri),
|
||||
// URI.revive((<ResourceFileEditDto>edit).newUri)
|
||||
// );
|
||||
} else {
|
||||
result.renameFile(
|
||||
URI.revive((<ResourceFileEditDto>edit).oldUri),
|
||||
URI.revive((<ResourceFileEditDto>edit).newUri),
|
||||
(<ResourceFileEditDto>edit).options
|
||||
);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -333,50 +353,105 @@ export namespace SymbolKind {
|
||||
}
|
||||
}
|
||||
|
||||
export function fromSymbolInformation(info: vscode.SymbolInformation): modes.SymbolInformation {
|
||||
return <modes.SymbolInformation>{
|
||||
name: info.name,
|
||||
kind: SymbolKind.from(info.kind),
|
||||
containerName: info.containerName,
|
||||
location: location.from(info.location)
|
||||
};
|
||||
export namespace WorkspaceSymbol {
|
||||
export function from(info: vscode.SymbolInformation): search.IWorkspaceSymbol {
|
||||
return <search.IWorkspaceSymbol>{
|
||||
name: info.name,
|
||||
kind: SymbolKind.from(info.kind),
|
||||
containerName: info.containerName,
|
||||
location: location.from(info.location)
|
||||
};
|
||||
}
|
||||
export function to(info: search.IWorkspaceSymbol): types.SymbolInformation {
|
||||
return new types.SymbolInformation(
|
||||
info.name,
|
||||
SymbolKind.to(info.kind),
|
||||
info.containerName,
|
||||
location.to(info.location)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function toSymbolInformation(bearing: modes.SymbolInformation): types.SymbolInformation {
|
||||
return new types.SymbolInformation(
|
||||
bearing.name,
|
||||
SymbolKind.to(bearing.kind),
|
||||
bearing.containerName,
|
||||
location.to(bearing.location)
|
||||
);
|
||||
export namespace DocumentSymbol {
|
||||
export function from(info: vscode.DocumentSymbol): modes.DocumentSymbol {
|
||||
let result: modes.DocumentSymbol = {
|
||||
name: info.name,
|
||||
detail: info.detail,
|
||||
range: Range.from(info.range),
|
||||
selectionRange: Range.from(info.selectionRange),
|
||||
kind: SymbolKind.from(info.kind)
|
||||
};
|
||||
if (info.children) {
|
||||
result.children = info.children.map(from);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
export function to(info: modes.DocumentSymbol): vscode.DocumentSymbol {
|
||||
let result = new types.DocumentSymbol(
|
||||
info.name,
|
||||
info.detail,
|
||||
SymbolKind.to(info.kind),
|
||||
Range.to(info.range),
|
||||
Range.to(info.selectionRange),
|
||||
);
|
||||
if (info.children) {
|
||||
result.children = info.children.map(to) as any;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const location = {
|
||||
from(value: vscode.Location): modes.Location {
|
||||
return {
|
||||
range: value.range && fromRange(value.range),
|
||||
range: value.range && Range.from(value.range),
|
||||
uri: value.uri
|
||||
};
|
||||
},
|
||||
to(value: modes.Location): types.Location {
|
||||
return new types.Location(value.uri, toRange(value.range));
|
||||
return new types.Location(value.uri, Range.to(value.range));
|
||||
}
|
||||
};
|
||||
|
||||
export function fromHover(hover: vscode.Hover): modes.Hover {
|
||||
return <modes.Hover>{
|
||||
range: fromRange(hover.range),
|
||||
contents: MarkdownString.fromMany(hover.contents)
|
||||
};
|
||||
export namespace DefinitionLink {
|
||||
export function from(value: vscode.Location | vscode.DefinitionLink): modes.DefinitionLink {
|
||||
const definitionLink = <vscode.DefinitionLink>value;
|
||||
const location = <vscode.Location>value;
|
||||
return {
|
||||
origin: definitionLink.originSelectionRange
|
||||
? Range.from(definitionLink.originSelectionRange)
|
||||
: undefined,
|
||||
uri: definitionLink.targetUri ? definitionLink.targetUri : location.uri,
|
||||
range: Range.from(definitionLink.targetRange ? definitionLink.targetRange : location.range),
|
||||
selectionRange: definitionLink.targetSelectionRange
|
||||
? Range.from(definitionLink.targetSelectionRange)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function toHover(info: modes.Hover): types.Hover {
|
||||
return new types.Hover(info.contents.map(MarkdownString.to), toRange(info.range));
|
||||
}
|
||||
export namespace Hover {
|
||||
export function from(hover: vscode.Hover): modes.Hover {
|
||||
return <modes.Hover>{
|
||||
range: Range.from(hover.range),
|
||||
contents: MarkdownString.fromMany(hover.contents)
|
||||
};
|
||||
}
|
||||
|
||||
export function toDocumentHighlight(occurrence: modes.DocumentHighlight): types.DocumentHighlight {
|
||||
return new types.DocumentHighlight(toRange(occurrence.range), occurrence.kind);
|
||||
export function to(info: modes.Hover): types.Hover {
|
||||
return new types.Hover(info.contents.map(MarkdownString.to), Range.to(info.range));
|
||||
}
|
||||
}
|
||||
export namespace DocumentHighlight {
|
||||
export function from(documentHighlight: vscode.DocumentHighlight): modes.DocumentHighlight {
|
||||
return {
|
||||
range: Range.from(documentHighlight.range),
|
||||
kind: documentHighlight.kind
|
||||
};
|
||||
}
|
||||
export function to(occurrence: modes.DocumentHighlight): types.DocumentHighlight {
|
||||
return new types.DocumentHighlight(Range.to(occurrence.range), occurrence.kind);
|
||||
}
|
||||
}
|
||||
|
||||
export namespace CompletionTriggerKind {
|
||||
@@ -454,6 +529,7 @@ export namespace Suggest {
|
||||
result.documentation = htmlContent.isMarkdownString(suggestion.documentation) ? MarkdownString.to(suggestion.documentation) : suggestion.documentation;
|
||||
result.sortText = suggestion.sortText;
|
||||
result.filterText = suggestion.filterText;
|
||||
result.preselect = suggestion.preselect;
|
||||
|
||||
// 'overwrite[Before|After]'-logic
|
||||
let overwriteBefore = (typeof suggestion.overwriteBefore === 'number') ? suggestion.overwriteBefore : 0;
|
||||
@@ -535,13 +611,13 @@ export namespace DocumentLink {
|
||||
|
||||
export function from(link: vscode.DocumentLink): modes.ILink {
|
||||
return {
|
||||
range: fromRange(link.range),
|
||||
range: Range.from(link.range),
|
||||
url: link.target && link.target.toString()
|
||||
};
|
||||
}
|
||||
|
||||
export function to(link: modes.ILink): vscode.DocumentLink {
|
||||
return new types.DocumentLink(toRange(link.range), link.url && URI.parse(link.url));
|
||||
return new types.DocumentLink(Range.to(link.range), link.url && URI.parse(link.url));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -649,57 +725,58 @@ export namespace FoldingRangeKind {
|
||||
}
|
||||
}
|
||||
|
||||
export function toTextEditorOptions(options?: vscode.TextDocumentShowOptions): ITextEditorOptions {
|
||||
if (options) {
|
||||
return {
|
||||
pinned: typeof options.preview === 'boolean' ? !options.preview : undefined,
|
||||
preserveFocus: options.preserveFocus,
|
||||
selection: typeof options.selection === 'object' ? fromRange(options.selection) : undefined
|
||||
} as ITextEditorOptions;
|
||||
export namespace TextEditorOptions {
|
||||
|
||||
export function from(options?: vscode.TextDocumentShowOptions): ITextEditorOptions {
|
||||
if (options) {
|
||||
return {
|
||||
pinned: typeof options.preview === 'boolean' ? !options.preview : undefined,
|
||||
preserveFocus: options.preserveFocus,
|
||||
selection: typeof options.selection === 'object' ? Range.from(options.selection) : undefined
|
||||
} as ITextEditorOptions;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function toGlobPattern(pattern: vscode.GlobPattern): string | IRelativePattern {
|
||||
if (typeof pattern === 'string') {
|
||||
return pattern;
|
||||
export namespace GlobPattern {
|
||||
|
||||
export function from(pattern: vscode.GlobPattern): string | IRelativePattern {
|
||||
if (typeof pattern === 'string') {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
if (isRelativePattern(pattern)) {
|
||||
return new types.RelativePattern(pattern.base, pattern.pattern);
|
||||
}
|
||||
|
||||
return pattern; // preserve `undefined` and `null`
|
||||
}
|
||||
|
||||
if (isRelativePattern(pattern)) {
|
||||
return new types.RelativePattern(pattern.base, pattern.pattern);
|
||||
function isRelativePattern(obj: any): obj is vscode.RelativePattern {
|
||||
const rp = obj as vscode.RelativePattern;
|
||||
return rp && typeof rp.base === 'string' && typeof rp.pattern === 'string';
|
||||
}
|
||||
|
||||
return pattern; // preserve `undefined` and `null`
|
||||
}
|
||||
|
||||
function isRelativePattern(obj: any): obj is vscode.RelativePattern {
|
||||
const rp = obj as vscode.RelativePattern;
|
||||
export namespace LanguageSelector {
|
||||
|
||||
return rp && typeof rp.base === 'string' && typeof rp.pattern === 'string';
|
||||
}
|
||||
|
||||
export function toLanguageSelector(selector: vscode.DocumentSelector): LanguageSelector {
|
||||
if (Array.isArray(selector)) {
|
||||
return selector.map(sel => doToLanguageSelector(sel));
|
||||
export function from(selector: vscode.DocumentSelector): languageSelector.LanguageSelector {
|
||||
if (!selector) {
|
||||
return undefined;
|
||||
} else if (Array.isArray(selector)) {
|
||||
return <languageSelector.LanguageSelector>selector.map(from);
|
||||
} else if (typeof selector === 'string') {
|
||||
return selector;
|
||||
} else {
|
||||
return <languageSelector.LanguageFilter>{
|
||||
language: selector.language,
|
||||
scheme: selector.scheme,
|
||||
pattern: GlobPattern.from(selector.pattern),
|
||||
exclusive: selector.exclusive
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return doToLanguageSelector(selector);
|
||||
}
|
||||
|
||||
function doToLanguageSelector(selector: string | vscode.DocumentFilter): string | LanguageFilter {
|
||||
if (typeof selector === 'string') {
|
||||
return selector;
|
||||
}
|
||||
|
||||
if (selector) {
|
||||
return {
|
||||
language: selector.language,
|
||||
scheme: selector.scheme,
|
||||
pattern: toGlobPattern(selector.pattern),
|
||||
exclusive: selector.exclusive
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ import { isMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { IRelativePattern } from 'vs/base/common/glob';
|
||||
import { relative } from 'path';
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
import { values } from 'vs/base/common/map';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
|
||||
export class Disposable {
|
||||
|
||||
@@ -492,38 +494,45 @@ export class TextEdit {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export interface IFileOperationOptions {
|
||||
overwrite?: boolean;
|
||||
ignoreIfExists?: boolean;
|
||||
ignoreIfNotExists?: boolean;
|
||||
recursive?: boolean;
|
||||
}
|
||||
|
||||
export interface IFileOperation {
|
||||
_type: 1;
|
||||
from: URI;
|
||||
to: URI;
|
||||
options?: IFileOperationOptions;
|
||||
}
|
||||
|
||||
export interface IFileTextEdit {
|
||||
_type: 2;
|
||||
uri: URI;
|
||||
edit: TextEdit;
|
||||
}
|
||||
|
||||
export class WorkspaceEdit implements vscode.WorkspaceEdit {
|
||||
|
||||
private _seqPool: number = 0;
|
||||
private _edits = new Array<IFileOperation | IFileTextEdit>();
|
||||
|
||||
private _resourceEdits: { seq: number, from: URI, to: URI }[] = [];
|
||||
private _textEdits = new Map<string, { seq: number, uri: URI, edits: TextEdit[] }>();
|
||||
renameFile(from: vscode.Uri, to: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean }): void {
|
||||
this._edits.push({ _type: 1, from, to, options });
|
||||
}
|
||||
|
||||
// createResource(uri: vscode.Uri): void {
|
||||
// this.renameResource(undefined, uri);
|
||||
// }
|
||||
createFile(uri: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean }): void {
|
||||
this._edits.push({ _type: 1, from: undefined, to: uri, options });
|
||||
}
|
||||
|
||||
// deleteResource(uri: vscode.Uri): void {
|
||||
// this.renameResource(uri, undefined);
|
||||
// }
|
||||
|
||||
// renameResource(from: vscode.Uri, to: vscode.Uri): void {
|
||||
// this._resourceEdits.push({ seq: this._seqPool++, from, to });
|
||||
// }
|
||||
|
||||
// resourceEdits(): [vscode.Uri, vscode.Uri][] {
|
||||
// return this._resourceEdits.map(({ from, to }) => (<[vscode.Uri, vscode.Uri]>[from, to]));
|
||||
// }
|
||||
deleteFile(uri: vscode.Uri, options?: { recursive?: boolean, ignoreIfNotExists?: boolean }): void {
|
||||
this._edits.push({ _type: 1, from: uri, to: undefined, options });
|
||||
}
|
||||
|
||||
replace(uri: URI, range: Range, newText: string): void {
|
||||
let edit = new TextEdit(range, newText);
|
||||
let array = this.get(uri);
|
||||
if (array) {
|
||||
array.push(edit);
|
||||
} else {
|
||||
array = [edit];
|
||||
}
|
||||
this.set(uri, array);
|
||||
this._edits.push({ _type: 2, uri, edit: new TextEdit(range, newText) });
|
||||
}
|
||||
|
||||
insert(resource: URI, position: Position, newText: string): void {
|
||||
@@ -535,55 +544,76 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
|
||||
}
|
||||
|
||||
has(uri: URI): boolean {
|
||||
return this._textEdits.has(uri.toString());
|
||||
for (const edit of this._edits) {
|
||||
if (edit._type === 2 && edit.uri.toString() === uri.toString()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
set(uri: URI, edits: TextEdit[]): void {
|
||||
let data = this._textEdits.get(uri.toString());
|
||||
if (!data) {
|
||||
data = { seq: this._seqPool++, uri, edits: [] };
|
||||
this._textEdits.set(uri.toString(), data);
|
||||
}
|
||||
if (!edits) {
|
||||
data.edits = undefined;
|
||||
// remove all text edits for `uri`
|
||||
for (let i = 0; i < this._edits.length; i++) {
|
||||
const element = this._edits[i];
|
||||
if (element._type === 2 && element.uri.toString() === uri.toString()) {
|
||||
this._edits[i] = undefined;
|
||||
}
|
||||
}
|
||||
this._edits = coalesce(this._edits);
|
||||
} else {
|
||||
data.edits = edits.slice(0);
|
||||
// append edit to the end
|
||||
for (const edit of edits) {
|
||||
if (edit) {
|
||||
this._edits.push({ _type: 2, uri, edit });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get(uri: URI): TextEdit[] {
|
||||
if (!this._textEdits.has(uri.toString())) {
|
||||
let res: TextEdit[] = [];
|
||||
for (let candidate of this._edits) {
|
||||
if (candidate._type === 2 && candidate.uri.toString() === uri.toString()) {
|
||||
res.push(candidate.edit);
|
||||
}
|
||||
}
|
||||
if (res.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const { edits } = this._textEdits.get(uri.toString());
|
||||
return edits ? edits.slice() : undefined;
|
||||
return res;
|
||||
}
|
||||
|
||||
entries(): [URI, TextEdit[]][] {
|
||||
const res: [URI, TextEdit[]][] = [];
|
||||
this._textEdits.forEach(value => res.push([value.uri, value.edits]));
|
||||
return res.slice();
|
||||
let textEdits = new Map<string, [URI, TextEdit[]]>();
|
||||
for (let candidate of this._edits) {
|
||||
if (candidate._type === 2) {
|
||||
let textEdit = textEdits.get(candidate.uri.toString());
|
||||
if (!textEdit) {
|
||||
textEdit = [candidate.uri, []];
|
||||
textEdits.set(candidate.uri.toString(), textEdit);
|
||||
}
|
||||
textEdit[1].push(candidate.edit);
|
||||
}
|
||||
}
|
||||
return values(textEdits);
|
||||
}
|
||||
|
||||
allEntries(): ([URI, TextEdit[]] | [URI, URI])[] {
|
||||
return this.entries();
|
||||
// // use the 'seq' the we have assigned when inserting
|
||||
// // the operation and use that order in the resulting
|
||||
// // array
|
||||
// const res: ([URI, TextEdit[]] | [URI, URI])[] = [];
|
||||
// this._textEdits.forEach(value => {
|
||||
// const { seq, uri, edits } = value;
|
||||
// res[seq] = [uri, edits];
|
||||
// });
|
||||
// this._resourceEdits.forEach(value => {
|
||||
// const { seq, from, to } = value;
|
||||
// res[seq] = [from, to];
|
||||
// });
|
||||
// return res;
|
||||
_allEntries(): ([URI, TextEdit[]] | [URI, URI, IFileOperationOptions])[] {
|
||||
let res: ([URI, TextEdit[]] | [URI, URI, IFileOperationOptions])[] = [];
|
||||
for (let edit of this._edits) {
|
||||
if (edit._type === 1) {
|
||||
res.push([edit.from, edit.to, edit.options]);
|
||||
} else {
|
||||
res.push([edit.uri, [edit.edit]]);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this._textEdits.size + this._resourceEdits.length;
|
||||
return this.entries().length;
|
||||
}
|
||||
|
||||
toJSON(): any {
|
||||
@@ -673,6 +703,10 @@ export class SnippetString {
|
||||
}
|
||||
}
|
||||
|
||||
export enum DiagnosticTag {
|
||||
Unnecessary = 1,
|
||||
}
|
||||
|
||||
export enum DiagnosticSeverity {
|
||||
Hint = 3,
|
||||
Information = 2,
|
||||
@@ -747,6 +781,7 @@ export class Diagnostic {
|
||||
code: string | number;
|
||||
severity: DiagnosticSeverity;
|
||||
relatedInformation: DiagnosticRelatedInformation[];
|
||||
tags?: DiagnosticTag[];
|
||||
|
||||
constructor(range: Range, message: string, severity: DiagnosticSeverity = DiagnosticSeverity.Error) {
|
||||
this.range = range;
|
||||
@@ -876,36 +911,32 @@ export class SymbolInformation {
|
||||
}
|
||||
}
|
||||
|
||||
export class HierarchicalSymbolInformation {
|
||||
export class DocumentSymbol {
|
||||
name: string;
|
||||
location: Location;
|
||||
detail: string;
|
||||
kind: SymbolKind;
|
||||
range: Range;
|
||||
children: HierarchicalSymbolInformation[];
|
||||
selectionRange: Range;
|
||||
children: DocumentSymbol[];
|
||||
|
||||
constructor(name: string, kind: SymbolKind, location: Location, range: Range) {
|
||||
constructor(name: string, detail: string, kind: SymbolKind, range: Range, selectionRange: Range) {
|
||||
this.name = name;
|
||||
this.detail = detail;
|
||||
this.kind = kind;
|
||||
this.location = location;
|
||||
this.range = range;
|
||||
this.selectionRange = selectionRange;
|
||||
this.children = [];
|
||||
}
|
||||
|
||||
static toFlatSymbolInformation(info: HierarchicalSymbolInformation): SymbolInformation[] {
|
||||
let result: SymbolInformation[] = [];
|
||||
HierarchicalSymbolInformation._toFlatSymbolInformation(info, undefined, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static _toFlatSymbolInformation(info: HierarchicalSymbolInformation, containerName: string, bucket: SymbolInformation[]): void {
|
||||
bucket.push(new SymbolInformation(info.name, info.kind, containerName, new Location(info.location.uri, info.range)));
|
||||
if (Array.isArray(info.children)) {
|
||||
for (const child of info.children) {
|
||||
HierarchicalSymbolInformation._toFlatSymbolInformation(child, info.name, bucket);
|
||||
}
|
||||
if (!this.range.contains(this.selectionRange)) {
|
||||
throw new Error('selectionRange must be contained in fullRange');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export enum CodeActionTrigger {
|
||||
Automatic = 1,
|
||||
Manual = 2,
|
||||
}
|
||||
|
||||
export class CodeAction {
|
||||
@@ -1072,7 +1103,7 @@ export enum CompletionItemKind {
|
||||
TypeParameter = 24
|
||||
}
|
||||
|
||||
export class CompletionItem {
|
||||
export class CompletionItem implements vscode.CompletionItem {
|
||||
|
||||
label: string;
|
||||
kind: CompletionItemKind;
|
||||
@@ -1080,6 +1111,7 @@ export class CompletionItem {
|
||||
documentation: string | MarkdownString;
|
||||
sortText: string;
|
||||
filterText: string;
|
||||
preselect: boolean;
|
||||
insertText: string | SnippetString;
|
||||
range: Range;
|
||||
textEdit: TextEdit;
|
||||
@@ -1099,6 +1131,7 @@ export class CompletionItem {
|
||||
documentation: this.documentation,
|
||||
sortText: this.sortText,
|
||||
filterText: this.filterText,
|
||||
preselect: this.preselect,
|
||||
insertText: this.insertText,
|
||||
textEdit: this.textEdit
|
||||
};
|
||||
@@ -1119,9 +1152,16 @@ export class CompletionList {
|
||||
|
||||
export enum ViewColumn {
|
||||
Active = -1,
|
||||
Beside = -2,
|
||||
One = 1,
|
||||
Two = 2,
|
||||
Three = 3
|
||||
Three = 3,
|
||||
Four = 4,
|
||||
Five = 5,
|
||||
Six = 6,
|
||||
Seven = 7,
|
||||
Eight = 8,
|
||||
Nine = 9
|
||||
}
|
||||
|
||||
export enum StatusBarAlignment {
|
||||
@@ -1494,7 +1534,6 @@ export class Task implements vscode.Task {
|
||||
private __id: string;
|
||||
|
||||
private _definition: vscode.TaskDefinition;
|
||||
private _definitionKey: string;
|
||||
private _scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder;
|
||||
private _name: string;
|
||||
private _execution: ProcessExecution | ShellExecution;
|
||||
@@ -1555,7 +1594,6 @@ export class Task implements vscode.Task {
|
||||
}
|
||||
this.__id = undefined;
|
||||
this._scope = undefined;
|
||||
this._definitionKey = undefined;
|
||||
this._definition = undefined;
|
||||
if (this._execution instanceof ProcessExecution) {
|
||||
this._definition = {
|
||||
@@ -1579,19 +1617,9 @@ export class Task implements vscode.Task {
|
||||
throw illegalArgument('Kind can\'t be undefined or null');
|
||||
}
|
||||
this.clear();
|
||||
this._definitionKey = undefined;
|
||||
this._definition = value;
|
||||
}
|
||||
|
||||
get definitionKey(): string {
|
||||
if (!this._definitionKey) {
|
||||
const hash = crypto.createHash('md5');
|
||||
hash.update(JSON.stringify(this._definition));
|
||||
this._definitionKey = hash.digest('hex');
|
||||
}
|
||||
return this._definitionKey;
|
||||
}
|
||||
|
||||
get scope(): vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder {
|
||||
return this._scope;
|
||||
}
|
||||
@@ -1848,12 +1876,6 @@ export enum LogLevel {
|
||||
}
|
||||
|
||||
//#region file api
|
||||
// todo@remote
|
||||
export enum DeprecatedFileChangeType {
|
||||
Updated = 0,
|
||||
Added = 1,
|
||||
Deleted = 2
|
||||
}
|
||||
|
||||
export enum FileChangeType {
|
||||
Changed = 1,
|
||||
@@ -1861,12 +1883,6 @@ export enum FileChangeType {
|
||||
Deleted = 3,
|
||||
}
|
||||
|
||||
export enum DeprecatedFileType {
|
||||
File = 0,
|
||||
Dir = 1,
|
||||
Symlink = 2
|
||||
}
|
||||
|
||||
export class FileSystemError extends Error {
|
||||
|
||||
static FileExists(messageOrUri?: string | URI): FileSystemError {
|
||||
@@ -1931,3 +1947,22 @@ export enum FoldingRangeKind {
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
export enum CommentThreadCollapsibleState {
|
||||
/**
|
||||
* Determines an item is collapsed
|
||||
*/
|
||||
Collapsed = 0,
|
||||
/**
|
||||
* Determines an item is expanded
|
||||
*/
|
||||
Expanded = 1
|
||||
}
|
||||
|
||||
export class QuickInputButtons {
|
||||
|
||||
static readonly Back: vscode.QuickInputButton = { iconPath: 'back.svg' };
|
||||
|
||||
private constructor() { }
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import { MainContext, IMainContext, ExtHostUrlsShape, MainThreadUrlsShape } from
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { asWinJsPromise } from 'vs/base/common/async';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
|
||||
export class ExtHostUrls implements ExtHostUrlsShape {
|
||||
|
||||
@@ -15,7 +17,7 @@ export class ExtHostUrls implements ExtHostUrlsShape {
|
||||
private readonly _proxy: MainThreadUrlsShape;
|
||||
|
||||
private handles = new Set<string>();
|
||||
private handlers = new Map<number, vscode.ProtocolHandler>();
|
||||
private handlers = new Map<number, vscode.UriHandler>();
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext
|
||||
@@ -23,7 +25,7 @@ export class ExtHostUrls implements ExtHostUrlsShape {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadUrls);
|
||||
}
|
||||
|
||||
registerProtocolHandler(extensionId: string, handler: vscode.ProtocolHandler): vscode.Disposable {
|
||||
registerUriHandler(extensionId: string, handler: vscode.UriHandler): vscode.Disposable {
|
||||
if (this.handles.has(extensionId)) {
|
||||
throw new Error(`Protocol handler already registered for extension ${extensionId}`);
|
||||
}
|
||||
@@ -31,12 +33,12 @@ export class ExtHostUrls implements ExtHostUrlsShape {
|
||||
const handle = ExtHostUrls.HandlePool++;
|
||||
this.handles.add(extensionId);
|
||||
this.handlers.set(handle, handler);
|
||||
this._proxy.$registerProtocolHandler(handle, extensionId);
|
||||
this._proxy.$registerUriHandler(handle, extensionId);
|
||||
|
||||
return toDisposable(() => {
|
||||
this.handles.delete(extensionId);
|
||||
this.handlers.delete(handle);
|
||||
this._proxy.$unregisterProtocolHandler(handle);
|
||||
this._proxy.$unregisterUriHandler(handle);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -47,7 +49,9 @@ export class ExtHostUrls implements ExtHostUrlsShape {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
handler.handleUri(URI.revive(uri));
|
||||
asWinJsPromise(_ => handler.handleUri(URI.revive(uri)))
|
||||
.done(null, onUnexpectedError);
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,17 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MainContext, MainThreadWebviewsShape, IMainContext, ExtHostWebviewsShape, WebviewPanelHandle } from './extHost.protocol';
|
||||
import * as vscode from 'vscode';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { Position } from 'vs/platform/editor/common/editor';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { EditorViewColumn } from 'vs/workbench/api/shared/editor';
|
||||
import * as vscode from 'vscode';
|
||||
import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewState } from './extHost.protocol';
|
||||
import { Disposable } from './extHostTypes';
|
||||
|
||||
type IconPath = URI | { light: URI, dark: URI };
|
||||
|
||||
export class ExtHostWebview implements vscode.Webview {
|
||||
private readonly _handle: WebviewPanelHandle;
|
||||
private readonly _proxy: MainThreadWebviewsShape;
|
||||
@@ -18,7 +21,7 @@ export class ExtHostWebview implements vscode.Webview {
|
||||
private _options: vscode.WebviewOptions;
|
||||
private _isDisposed: boolean = false;
|
||||
|
||||
readonly _onMessageEmitter = new Emitter<any>();
|
||||
public readonly _onMessageEmitter = new Emitter<any>();
|
||||
public readonly onDidReceiveMessage: Event<any> = this._onMessageEmitter.event;
|
||||
|
||||
constructor(
|
||||
@@ -31,16 +34,16 @@ export class ExtHostWebview implements vscode.Webview {
|
||||
this._options = options;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
public dispose() {
|
||||
this._onMessageEmitter.dispose();
|
||||
}
|
||||
|
||||
get html(): string {
|
||||
public get html(): string {
|
||||
this.assertNotDisposed();
|
||||
return this._html;
|
||||
}
|
||||
|
||||
set html(value: string) {
|
||||
public set html(value: string) {
|
||||
this.assertNotDisposed();
|
||||
if (this._html !== value) {
|
||||
this._html = value;
|
||||
@@ -48,11 +51,17 @@ export class ExtHostWebview implements vscode.Webview {
|
||||
}
|
||||
}
|
||||
|
||||
get options(): vscode.WebviewOptions {
|
||||
public get options(): vscode.WebviewOptions {
|
||||
this.assertNotDisposed();
|
||||
return this._options;
|
||||
}
|
||||
|
||||
public set options(newOptions: vscode.WebviewOptions) {
|
||||
this.assertNotDisposed();
|
||||
this._proxy.$setOptions(this._handle, newOptions);
|
||||
this._options = newOptions;
|
||||
}
|
||||
|
||||
public postMessage(message: any): Thenable<boolean> {
|
||||
this.assertNotDisposed();
|
||||
return this._proxy.$postMessage(this._handle, message);
|
||||
@@ -71,12 +80,14 @@ export class ExtHostWebviewPanel implements vscode.WebviewPanel {
|
||||
private readonly _proxy: MainThreadWebviewsShape;
|
||||
private readonly _viewType: string;
|
||||
private _title: string;
|
||||
private _iconPath: IconPath;
|
||||
|
||||
private readonly _options: vscode.WebviewPanelOptions;
|
||||
private readonly _webview: ExtHostWebview;
|
||||
private _isDisposed: boolean = false;
|
||||
private _viewColumn: vscode.ViewColumn;
|
||||
private _visible: boolean = true;
|
||||
private _active: boolean = true;
|
||||
|
||||
readonly _onDisposeEmitter = new Emitter<void>();
|
||||
public readonly onDidDispose: Event<void> = this._onDisposeEmitter.event;
|
||||
@@ -142,6 +153,20 @@ export class ExtHostWebviewPanel implements vscode.WebviewPanel {
|
||||
}
|
||||
}
|
||||
|
||||
get iconPath(): IconPath | undefined {
|
||||
this.assertNotDisposed();
|
||||
return this._iconPath;
|
||||
}
|
||||
|
||||
set iconPath(value: IconPath | undefined) {
|
||||
this.assertNotDisposed();
|
||||
if (this._iconPath !== value) {
|
||||
this._iconPath = value;
|
||||
|
||||
this._proxy.$setIconPath(this._handle, URI.isUri(value) ? { light: value, dark: value } : value);
|
||||
}
|
||||
}
|
||||
|
||||
get options() {
|
||||
return this._options;
|
||||
}
|
||||
@@ -156,7 +181,17 @@ export class ExtHostWebviewPanel implements vscode.WebviewPanel {
|
||||
this._viewColumn = value;
|
||||
}
|
||||
|
||||
get visible(): boolean {
|
||||
public get active(): boolean {
|
||||
this.assertNotDisposed();
|
||||
return this._active;
|
||||
}
|
||||
|
||||
_setActive(value: boolean) {
|
||||
this.assertNotDisposed();
|
||||
this._active = value;
|
||||
}
|
||||
|
||||
public get visible(): boolean {
|
||||
this.assertNotDisposed();
|
||||
return this._visible;
|
||||
}
|
||||
@@ -171,9 +206,12 @@ export class ExtHostWebviewPanel implements vscode.WebviewPanel {
|
||||
return this._proxy.$postMessage(this._handle, message);
|
||||
}
|
||||
|
||||
public reveal(viewColumn?: vscode.ViewColumn): void {
|
||||
public reveal(viewColumn?: vscode.ViewColumn, preserveFocus?: boolean): void {
|
||||
this.assertNotDisposed();
|
||||
this._proxy.$reveal(this._handle, viewColumn ? typeConverters.fromViewColumn(viewColumn) : undefined);
|
||||
this._proxy.$reveal(this._handle, {
|
||||
viewColumn: viewColumn ? typeConverters.ViewColumn.from(viewColumn) : undefined,
|
||||
preserveFocus: !!preserveFocus
|
||||
});
|
||||
}
|
||||
|
||||
private assertNotDisposed() {
|
||||
@@ -186,8 +224,11 @@ export class ExtHostWebviewPanel implements vscode.WebviewPanel {
|
||||
export class ExtHostWebviews implements ExtHostWebviewsShape {
|
||||
private static webviewHandlePool = 1;
|
||||
|
||||
private readonly _proxy: MainThreadWebviewsShape;
|
||||
private static newHandle(): WebviewPanelHandle {
|
||||
return ExtHostWebviews.webviewHandlePool++ + '';
|
||||
}
|
||||
|
||||
private readonly _proxy: MainThreadWebviewsShape;
|
||||
private readonly _webviewPanels = new Map<WebviewPanelHandle, ExtHostWebviewPanel>();
|
||||
private readonly _serializers = new Map<string, vscode.WebviewPanelSerializer>();
|
||||
|
||||
@@ -197,16 +238,21 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadWebviews);
|
||||
}
|
||||
|
||||
createWebview(
|
||||
public createWebview(
|
||||
extensionLocation: URI,
|
||||
viewType: string,
|
||||
title: string,
|
||||
viewColumn: vscode.ViewColumn,
|
||||
options: (vscode.WebviewPanelOptions & vscode.WebviewOptions) | undefined,
|
||||
extensionFolderPath: string
|
||||
showOptions: vscode.ViewColumn | { viewColumn: vscode.ViewColumn, preserveFocus?: boolean },
|
||||
options: (vscode.WebviewPanelOptions & vscode.WebviewOptions) = {},
|
||||
): vscode.WebviewPanel {
|
||||
options = options || {};
|
||||
const handle = ExtHostWebviews.webviewHandlePool++ + '';
|
||||
this._proxy.$createWebviewPanel(handle, viewType, title, typeConverters.fromViewColumn(viewColumn), options, extensionFolderPath);
|
||||
const viewColumn = typeof showOptions === 'object' ? showOptions.viewColumn : showOptions;
|
||||
const webviewShowOptions = {
|
||||
viewColumn: typeConverters.ViewColumn.from(viewColumn),
|
||||
preserveFocus: typeof showOptions === 'object' && !!showOptions.preserveFocus
|
||||
};
|
||||
|
||||
const handle = ExtHostWebviews.newHandle();
|
||||
this._proxy.$createWebviewPanel(handle, viewType, title, webviewShowOptions, options, extensionLocation);
|
||||
|
||||
const webview = new ExtHostWebview(handle, this._proxy, options);
|
||||
const panel = new ExtHostWebviewPanel(handle, this._proxy, viewType, title, viewColumn, options, webview);
|
||||
@@ -214,7 +260,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
|
||||
return panel;
|
||||
}
|
||||
|
||||
registerWebviewPanelSerializer(
|
||||
public registerWebviewPanelSerializer(
|
||||
viewType: string,
|
||||
serializer: vscode.WebviewPanelSerializer
|
||||
): vscode.Disposable {
|
||||
@@ -231,19 +277,26 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
|
||||
});
|
||||
}
|
||||
|
||||
$onMessage(handle: WebviewPanelHandle, message: any): void {
|
||||
public $onMessage(
|
||||
handle: WebviewPanelHandle,
|
||||
message: any
|
||||
): void {
|
||||
const panel = this.getWebviewPanel(handle);
|
||||
if (panel) {
|
||||
panel.webview._onMessageEmitter.fire(message);
|
||||
}
|
||||
}
|
||||
|
||||
$onDidChangeWebviewPanelViewState(handle: WebviewPanelHandle, visible: boolean, position: Position): void {
|
||||
public $onDidChangeWebviewPanelViewState(
|
||||
handle: WebviewPanelHandle,
|
||||
newState: WebviewPanelViewState
|
||||
): void {
|
||||
const panel = this.getWebviewPanel(handle);
|
||||
if (panel) {
|
||||
const viewColumn = typeConverters.toViewColumn(position);
|
||||
if (panel.visible !== visible || panel.viewColumn !== viewColumn) {
|
||||
panel._setVisible(visible);
|
||||
const viewColumn = typeConverters.ViewColumn.to(newState.position);
|
||||
if (panel.active !== newState.active || panel.visible !== newState.visible || panel.viewColumn !== viewColumn) {
|
||||
panel._setActive(newState.active);
|
||||
panel._setVisible(newState.visible);
|
||||
panel._setViewColumn(viewColumn);
|
||||
panel._onDidChangeViewStateEmitter.fire({ webviewPanel: panel });
|
||||
}
|
||||
@@ -264,7 +317,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
|
||||
viewType: string,
|
||||
title: string,
|
||||
state: any,
|
||||
position: Position,
|
||||
position: EditorViewColumn,
|
||||
options: vscode.WebviewOptions & vscode.WebviewPanelOptions
|
||||
): Thenable<void> {
|
||||
const serializer = this._serializers.get(viewType);
|
||||
@@ -273,28 +326,12 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
|
||||
}
|
||||
|
||||
const webview = new ExtHostWebview(webviewHandle, this._proxy, options);
|
||||
const revivedPanel = new ExtHostWebviewPanel(webviewHandle, this._proxy, viewType, title, typeConverters.toViewColumn(position), options, webview);
|
||||
const revivedPanel = new ExtHostWebviewPanel(webviewHandle, this._proxy, viewType, title, typeConverters.ViewColumn.to(position), options, webview);
|
||||
this._webviewPanels.set(webviewHandle, revivedPanel);
|
||||
return serializer.deserializeWebviewPanel(revivedPanel, state);
|
||||
}
|
||||
|
||||
$serializeWebviewPanel(
|
||||
webviewHandle: WebviewPanelHandle
|
||||
): Thenable<any> {
|
||||
const panel = this.getWebviewPanel(webviewHandle);
|
||||
if (!panel) {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
const serialzer = this._serializers.get(panel.viewType);
|
||||
if (!serialzer) {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
return serialzer.serializeWebviewPanel(panel);
|
||||
}
|
||||
|
||||
private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewPanel | undefined {
|
||||
return this._webviewPanels.get(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,22 +4,25 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { normalize } from 'vs/base/common/paths';
|
||||
import { posix, relative, join } from 'path';
|
||||
import { delta as arrayDelta } from 'vs/base/common/arrays';
|
||||
import { relative, posix } from 'path';
|
||||
import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkspaceData, ExtHostWorkspaceShape, MainContext, MainThreadWorkspaceShape, IMainContext, MainThreadMessageServiceShape } from './extHost.protocol';
|
||||
import * as vscode from 'vscode';
|
||||
import { compare } from 'vs/base/common/strings';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { basenameOrAuthority, isEqual } from 'vs/base/common/resources';
|
||||
import { normalize } from 'vs/base/common/paths';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { basenameOrAuthority, isEqual } from 'vs/base/common/resources';
|
||||
import { compare } from 'vs/base/common/strings';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Severity } from 'vs/platform/notification/common/notification';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IQueryOptions, IRawFileMatch2 } from 'vs/platform/search/common/search';
|
||||
import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { Range } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import * as vscode from 'vscode';
|
||||
import { ExtHostWorkspaceShape, IMainContext, IWorkspaceData, MainContext, MainThreadMessageServiceShape, MainThreadWorkspaceShape } from './extHost.protocol';
|
||||
|
||||
function isFolderEqual(folderA: URI, folderB: URI): boolean {
|
||||
return isEqual(folderA, folderB, !isLinux);
|
||||
@@ -145,6 +148,8 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
|
||||
|
||||
readonly onDidChangeWorkspace: Event<vscode.WorkspaceFoldersChangeEvent> = this._onDidChangeWorkspace.event;
|
||||
|
||||
private readonly _activeSearchCallbacks = [];
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
data: IWorkspaceData,
|
||||
@@ -263,6 +268,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
|
||||
if (folders.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
// #54483 @Joh Why are we still using fsPath?
|
||||
return folders[0].uri.fsPath;
|
||||
}
|
||||
|
||||
@@ -359,13 +365,79 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
|
||||
}
|
||||
}
|
||||
|
||||
const result = this._proxy.$startSearch(includePattern, includeFolder, excludePatternOrDisregardExcludes, maxResults, requestId);
|
||||
const result = this._proxy.$startFileSearch(includePattern, includeFolder, excludePatternOrDisregardExcludes, maxResults, requestId);
|
||||
if (token) {
|
||||
token.onCancellationRequested(() => this._proxy.$cancelSearch(requestId));
|
||||
}
|
||||
return result.then(data => Array.isArray(data) ? data.map(URI.revive) : []);
|
||||
}
|
||||
|
||||
findTextInFiles(query: vscode.TextSearchQuery, options: vscode.FindTextInFilesOptions, callback: (result: vscode.TextSearchResult) => void, extensionId: string, token?: vscode.CancellationToken) {
|
||||
this._logService.trace(`extHostWorkspace#findTextInFiles: textSearch, extension: ${extensionId}, entryPoint: findTextInFiles`);
|
||||
|
||||
const requestId = ExtHostWorkspace._requestIdPool++;
|
||||
|
||||
const globPatternToString = (pattern: vscode.GlobPattern | string) => {
|
||||
if (typeof pattern === 'string') {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
return join(pattern.base, pattern.pattern);
|
||||
};
|
||||
|
||||
const queryOptions: IQueryOptions = {
|
||||
ignoreSymlinks: typeof options.followSymlinks === 'boolean' ? !options.followSymlinks : undefined,
|
||||
disregardIgnoreFiles: typeof options.useIgnoreFiles === 'boolean' ? !options.useIgnoreFiles : undefined,
|
||||
disregardExcludeSettings: options.exclude === null,
|
||||
fileEncoding: options.encoding,
|
||||
maxResults: options.maxResults,
|
||||
|
||||
includePattern: options.include && globPatternToString(options.include),
|
||||
excludePattern: options.exclude && globPatternToString(options.exclude)
|
||||
};
|
||||
|
||||
let isCanceled = false;
|
||||
|
||||
this._activeSearchCallbacks[requestId] = p => {
|
||||
if (isCanceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
p.lineMatches.forEach(lineMatch => {
|
||||
lineMatch.offsetAndLengths.forEach(offsetAndLength => {
|
||||
const range = new Range(lineMatch.lineNumber, offsetAndLength[0], lineMatch.lineNumber, offsetAndLength[0] + offsetAndLength[1]);
|
||||
callback({
|
||||
uri: URI.revive(p.resource),
|
||||
preview: { text: lineMatch.preview, match: range },
|
||||
range
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
if (token) {
|
||||
token.onCancellationRequested(() => {
|
||||
isCanceled = true;
|
||||
this._proxy.$cancelSearch(requestId);
|
||||
});
|
||||
}
|
||||
|
||||
return this._proxy.$startTextSearch(query, queryOptions, requestId).then(
|
||||
() => {
|
||||
delete this._activeSearchCallbacks[requestId];
|
||||
},
|
||||
err => {
|
||||
delete this._activeSearchCallbacks[requestId];
|
||||
return TPromise.wrapError(err);
|
||||
});
|
||||
}
|
||||
|
||||
$handleTextSearchResult(result: IRawFileMatch2, requestId: number): void {
|
||||
if (this._activeSearchCallbacks[requestId]) {
|
||||
this._activeSearchCallbacks[requestId](result);
|
||||
}
|
||||
}
|
||||
|
||||
saveAll(includeUntitled?: boolean): Thenable<boolean> {
|
||||
return this._proxy.$saveAll(includeUntitled);
|
||||
}
|
||||
|
||||
38
src/vs/workbench/api/shared/editor.ts
Normal file
38
src/vs/workbench/api/shared/editor.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { IEditorGroupsService, IEditorGroup, GroupsOrder } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { GroupIdentifier } from 'vs/workbench/common/editor';
|
||||
import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
export type EditorViewColumn = number;
|
||||
|
||||
export function viewColumnToEditorGroup(editorGroupService: IEditorGroupsService, position?: EditorViewColumn): GroupIdentifier {
|
||||
if (typeof position !== 'number' || position === ACTIVE_GROUP) {
|
||||
return ACTIVE_GROUP; // prefer active group when position is undefined or passed in as such
|
||||
}
|
||||
|
||||
const groups = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE);
|
||||
|
||||
let candidate = groups[position];
|
||||
if (candidate) {
|
||||
return candidate.id; // found direct match
|
||||
}
|
||||
|
||||
let firstGroup = groups[0];
|
||||
if (groups.length === 1 && firstGroup.count === 0) {
|
||||
return firstGroup.id; // first editor should always open in first group independent from position provided
|
||||
}
|
||||
|
||||
return SIDE_GROUP; // open to the side if group not found or we are instructed to
|
||||
}
|
||||
|
||||
export function editorGroupToViewColumn(editorGroupService: IEditorGroupsService, editorGroup: IEditorGroup | GroupIdentifier): EditorViewColumn {
|
||||
const group = (typeof editorGroup === 'number') ? editorGroupService.getGroup(editorGroup) : editorGroup;
|
||||
|
||||
return editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE).indexOf(group);
|
||||
}
|
||||
@@ -16,6 +16,7 @@ export interface TaskPresentationOptionsDTO {
|
||||
echo?: boolean;
|
||||
focus?: boolean;
|
||||
panel?: number;
|
||||
showReuseMessage?: boolean;
|
||||
}
|
||||
|
||||
export interface ExecutionOptionsDTO {
|
||||
@@ -88,7 +89,25 @@ export interface TaskExecutionDTO {
|
||||
task: TaskDTO;
|
||||
}
|
||||
|
||||
export interface TaskProcessStartedDTO {
|
||||
id: string;
|
||||
processId: number;
|
||||
}
|
||||
|
||||
export interface TaskProcessEndedDTO {
|
||||
id: string;
|
||||
exitCode: number;
|
||||
}
|
||||
|
||||
|
||||
export interface TaskFilterDTO {
|
||||
version?: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export interface TaskSystemInfoDTO {
|
||||
scheme: string;
|
||||
host: string;
|
||||
port: number;
|
||||
platform: string;
|
||||
}
|
||||
@@ -20,35 +20,35 @@ export class ActionBarContributor {
|
||||
/**
|
||||
* Returns true if this contributor has actions for the given context.
|
||||
*/
|
||||
public hasActions(context: any): boolean {
|
||||
hasActions(context: any): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of primary actions in the given context.
|
||||
*/
|
||||
public getActions(context: any): IAction[] {
|
||||
getActions(context: any): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this contributor has secondary actions for the given context.
|
||||
*/
|
||||
public hasSecondaryActions(context: any): boolean {
|
||||
hasSecondaryActions(context: any): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of secondary actions in the given context.
|
||||
*/
|
||||
public getSecondaryActions(context: any): IAction[] {
|
||||
getSecondaryActions(context: any): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Can return a specific IActionItem to render the given action.
|
||||
*/
|
||||
public getActionItem(context: any, action: Action): BaseActionItem {
|
||||
getActionItem(context: any, action: Action): BaseActionItem {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ export class ContributableActionProvider implements IActionProvider {
|
||||
};
|
||||
}
|
||||
|
||||
public hasActions(tree: ITree, element: any): boolean {
|
||||
hasActions(tree: ITree, element: any): boolean {
|
||||
const context = this.toContext(tree, element);
|
||||
|
||||
const contributors = this.registry.getActionBarContributors(Scope.VIEWER);
|
||||
@@ -94,7 +94,7 @@ export class ContributableActionProvider implements IActionProvider {
|
||||
return false;
|
||||
}
|
||||
|
||||
public getActions(tree: ITree, element: any): TPromise<IAction[]> {
|
||||
getActions(tree: ITree, element: any): TPromise<IAction[]> {
|
||||
const actions: IAction[] = [];
|
||||
const context = this.toContext(tree, element);
|
||||
|
||||
@@ -110,7 +110,7 @@ export class ContributableActionProvider implements IActionProvider {
|
||||
return TPromise.as(prepareActions(actions));
|
||||
}
|
||||
|
||||
public hasSecondaryActions(tree: ITree, element: any): boolean {
|
||||
hasSecondaryActions(tree: ITree, element: any): boolean {
|
||||
const context = this.toContext(tree, element);
|
||||
|
||||
const contributors = this.registry.getActionBarContributors(Scope.VIEWER);
|
||||
@@ -124,7 +124,7 @@ export class ContributableActionProvider implements IActionProvider {
|
||||
return false;
|
||||
}
|
||||
|
||||
public getSecondaryActions(tree: ITree, element: any): TPromise<IAction[]> {
|
||||
getSecondaryActions(tree: ITree, element: any): TPromise<IAction[]> {
|
||||
const actions: IAction[] = [];
|
||||
const context = this.toContext(tree, element);
|
||||
|
||||
@@ -140,7 +140,7 @@ export class ContributableActionProvider implements IActionProvider {
|
||||
return TPromise.as(prepareActions(actions));
|
||||
}
|
||||
|
||||
public getActionItem(tree: ITree, element: any, action: Action): BaseActionItem {
|
||||
getActionItem(tree: ITree, element: any, action: Action): BaseActionItem {
|
||||
const contributors = this.registry.getActionBarContributors(Scope.VIEWER);
|
||||
const context = this.toContext(tree, element);
|
||||
|
||||
@@ -279,7 +279,7 @@ class ActionBarRegistry implements IActionBarRegistry {
|
||||
private actionBarContributorInstances: { [scope: string]: ActionBarContributor[] } = Object.create(null);
|
||||
private instantiationService: IInstantiationService;
|
||||
|
||||
public setInstantiationService(service: IInstantiationService): void {
|
||||
setInstantiationService(service: IInstantiationService): void {
|
||||
this.instantiationService = service;
|
||||
|
||||
while (this.actionBarContributorConstructors.length > 0) {
|
||||
@@ -301,7 +301,7 @@ class ActionBarRegistry implements IActionBarRegistry {
|
||||
return this.actionBarContributorInstances[scope] || [];
|
||||
}
|
||||
|
||||
public getActionBarActionsForContext(scope: string, context: any): IAction[] {
|
||||
getActionBarActionsForContext(scope: string, context: any): IAction[] {
|
||||
const actions: IAction[] = [];
|
||||
|
||||
// Go through contributors for scope
|
||||
@@ -316,7 +316,7 @@ class ActionBarRegistry implements IActionBarRegistry {
|
||||
return actions;
|
||||
}
|
||||
|
||||
public getSecondaryActionBarActionsForContext(scope: string, context: any): IAction[] {
|
||||
getSecondaryActionBarActionsForContext(scope: string, context: any): IAction[] {
|
||||
const actions: IAction[] = [];
|
||||
|
||||
// Go through contributors
|
||||
@@ -331,7 +331,7 @@ class ActionBarRegistry implements IActionBarRegistry {
|
||||
return actions;
|
||||
}
|
||||
|
||||
public getActionItemForContext(scope: string, context: any, action: Action): BaseActionItem {
|
||||
getActionItemForContext(scope: string, context: any, action: Action): BaseActionItem {
|
||||
const contributors = this.getContributors(scope);
|
||||
for (let i = 0; i < contributors.length; i++) {
|
||||
const contributor = contributors[i];
|
||||
@@ -344,7 +344,7 @@ class ActionBarRegistry implements IActionBarRegistry {
|
||||
return null;
|
||||
}
|
||||
|
||||
public registerActionBarContributor(scope: string, ctor: IConstructorSignature0<ActionBarContributor>): void {
|
||||
registerActionBarContributor(scope: string, ctor: IConstructorSignature0<ActionBarContributor>): void {
|
||||
if (!this.instantiationService) {
|
||||
this.actionBarContributorConstructors.push({
|
||||
scope: scope,
|
||||
@@ -355,7 +355,7 @@ class ActionBarRegistry implements IActionBarRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
public getActionBarContributors(scope: string): ActionBarContributor[] {
|
||||
getActionBarContributors(scope: string): ActionBarContributor[] {
|
||||
return this.getContributors(scope).slice(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.vs .monaco-workbench .toggle-editor-layout {
|
||||
.vs .monaco-workbench .flip-editor-layout {
|
||||
background-image: url('editor-layout.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .toggle-editor-layout,
|
||||
.hc-black .monaco-workbench .toggle-editor-layout {
|
||||
.vs-dark .monaco-workbench .flip-editor-layout,
|
||||
.hc-black .monaco-workbench .flip-editor-layout {
|
||||
background-image: url('editor-layout-inverse.svg') !important;
|
||||
}
|
||||
|
||||
@@ -8,15 +8,15 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
|
||||
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
|
||||
export class ToggleActivityBarVisibilityAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.toggleActivityBarVisibility';
|
||||
public static readonly LABEL = nls.localize('toggleActivityBar', "Toggle Activity Bar Visibility");
|
||||
static readonly ID = 'workbench.action.toggleActivityBarVisibility';
|
||||
static readonly LABEL = nls.localize('toggleActivityBar', "Toggle Activity Bar Visibility");
|
||||
|
||||
private static readonly activityBarVisibleKey = 'workbench.activityBar.visible';
|
||||
|
||||
@@ -31,7 +31,7 @@ export class ToggleActivityBarVisibilityAction extends Action {
|
||||
this.enabled = !!this.partService;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
run(): TPromise<any> {
|
||||
const visibility = this.partService.isVisible(Parts.ACTIVITYBAR_PART);
|
||||
const newVisibilityValue = !visibility;
|
||||
|
||||
@@ -40,4 +40,13 @@ export class ToggleActivityBarVisibilityAction extends Action {
|
||||
}
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, ToggleActivityBarVisibilityAction.LABEL), 'View: Toggle Activity Bar Visibility', nls.localize('view', "View"));
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, ToggleActivityBarVisibilityAction.LABEL), 'View: Toggle Activity Bar Visibility', nls.localize('view', "View"));
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
|
||||
group: '2_workbench_layout',
|
||||
command: {
|
||||
id: ToggleActivityBarVisibilityAction.ID,
|
||||
title: nls.localize({ key: 'miToggleActivityBar', comment: ['&& denotes a mnemonic'] }, "Toggle &&Activity Bar")
|
||||
},
|
||||
order: 4
|
||||
});
|
||||
|
||||
@@ -7,14 +7,14 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
|
||||
class ToggleCenteredLayout extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.toggleCenteredLayout';
|
||||
public static readonly LABEL = nls.localize('toggleCenteredLayout', "Toggle Centered Layout");
|
||||
static readonly ID = 'workbench.action.toggleCenteredLayout';
|
||||
static readonly LABEL = nls.localize('toggleCenteredLayout', "Toggle Centered Layout");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@@ -25,7 +25,7 @@ class ToggleCenteredLayout extends Action {
|
||||
this.enabled = !!this.partService;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
run(): TPromise<any> {
|
||||
this.partService.centerEditorLayout(!this.partService.isEditorLayoutCentered());
|
||||
|
||||
return TPromise.as(null);
|
||||
@@ -34,3 +34,21 @@ class ToggleCenteredLayout extends Action {
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleCenteredLayout, ToggleCenteredLayout.ID, ToggleCenteredLayout.LABEL), 'View: Toggle Centered Layout', nls.localize('view', "View"));
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
|
||||
group: '1_toggle_view',
|
||||
command: {
|
||||
id: ToggleCenteredLayout.ID,
|
||||
title: nls.localize('miToggleCenteredLayout', "Toggle Centered Layout")
|
||||
},
|
||||
order: 3
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
|
||||
group: '2_layouts',
|
||||
command: {
|
||||
id: 'workbench.action.editorLayoutCentered',
|
||||
title: nls.localize({ key: 'miCenteredEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Centered")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
@@ -9,61 +9,53 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { IEditorGroupService, GroupOrientation } from 'vs/workbench/services/group/common/groupService';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IEditorGroupsService, GroupOrientation } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
|
||||
export class ToggleEditorLayoutAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.toggleEditorGroupLayout';
|
||||
public static readonly LABEL = nls.localize('toggleEditorGroupLayout', "Toggle Editor Group Vertical/Horizontal Layout");
|
||||
static readonly ID = 'workbench.action.toggleEditorGroupLayout';
|
||||
static readonly LABEL = nls.localize('flipLayout', "Toggle Vertical/Horizontal Editor Layout");
|
||||
|
||||
private toDispose: IDisposable[];
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorGroupService private editorGroupService: IEditorGroupService
|
||||
@IEditorGroupsService private editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(id, label);
|
||||
|
||||
this.toDispose = [];
|
||||
|
||||
this.class = 'toggle-editor-layout';
|
||||
this.class = 'flip-editor-layout';
|
||||
this.updateEnablement();
|
||||
this.updateLabel();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.toDispose.push(this.editorGroupService.onEditorsChanged(() => this.updateEnablement()));
|
||||
this.toDispose.push(this.editorGroupService.onGroupOrientationChanged(() => this.updateLabel()));
|
||||
}
|
||||
|
||||
private updateLabel(): void {
|
||||
const editorGroupLayoutVertical = (this.editorGroupService.getGroupOrientation() !== 'horizontal');
|
||||
this.label = editorGroupLayoutVertical ? nls.localize('horizontalLayout', "Horizontal Editor Group Layout") : nls.localize('verticalLayout', "Vertical Editor Group Layout");
|
||||
this.toDispose.push(this.editorGroupService.onDidAddGroup(() => this.updateEnablement()));
|
||||
this.toDispose.push(this.editorGroupService.onDidRemoveGroup(() => this.updateEnablement()));
|
||||
}
|
||||
|
||||
private updateEnablement(): void {
|
||||
this.enabled = this.editorGroupService.getStacksModel().groups.length > 0;
|
||||
this.enabled = this.editorGroupService.count > 1;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
const groupOrientiation = this.editorGroupService.getGroupOrientation();
|
||||
const newGroupOrientation: GroupOrientation = (groupOrientiation === 'vertical') ? 'horizontal' : 'vertical';
|
||||
|
||||
this.editorGroupService.setGroupOrientation(newGroupOrientation);
|
||||
run(): TPromise<any> {
|
||||
const newOrientation = (this.editorGroupService.orientation === GroupOrientation.VERTICAL) ? GroupOrientation.HORIZONTAL : GroupOrientation.VERTICAL;
|
||||
this.editorGroupService.setGroupOrientation(newOrientation);
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
dispose(): void {
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
|
||||
super.dispose();
|
||||
@@ -71,7 +63,7 @@ export class ToggleEditorLayoutAction extends Action {
|
||||
}
|
||||
|
||||
CommandsRegistry.registerCommand('_workbench.editor.setGroupOrientation', function (accessor: ServicesAccessor, args: [GroupOrientation]) {
|
||||
const editorGroupService = accessor.get(IEditorGroupService);
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
const [orientation] = args;
|
||||
|
||||
editorGroupService.setGroupOrientation(orientation);
|
||||
@@ -81,4 +73,13 @@ CommandsRegistry.registerCommand('_workbench.editor.setGroupOrientation', functi
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
const group = nls.localize('view', "View");
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleEditorLayoutAction, ToggleEditorLayoutAction.ID, ToggleEditorLayoutAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_0, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_0 } }), 'View: Toggle Editor Group Vertical/Horizontal Layout', group);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleEditorLayoutAction, ToggleEditorLayoutAction.ID, ToggleEditorLayoutAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_0, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_0 } }), 'View: Flip Editor Group Layout', group);
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
|
||||
group: 'z_flip',
|
||||
command: {
|
||||
id: ToggleEditorLayoutAction.ID,
|
||||
title: nls.localize({ key: 'miToggleEditorLayout', comment: ['&& denotes a mnemonic'] }, "Flip &&Layout")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
@@ -8,15 +8,15 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
|
||||
import { IPartService, Position } from 'vs/workbench/services/part/common/partService';
|
||||
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
export class ToggleSidebarPositionAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.toggleSidebarPosition';
|
||||
public static readonly LABEL = nls.localize('toggleSidebarPosition', "Toggle Side Bar Position");
|
||||
static readonly ID = 'workbench.action.toggleSidebarPosition';
|
||||
static readonly LABEL = nls.localize('toggleSidebarPosition', "Toggle Side Bar Position");
|
||||
|
||||
private static readonly sidebarPositionConfigurationKey = 'workbench.sideBar.location';
|
||||
|
||||
@@ -31,7 +31,7 @@ export class ToggleSidebarPositionAction extends Action {
|
||||
this.enabled = !!this.partService && !!this.configurationService;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
run(): TPromise<any> {
|
||||
const position = this.partService.getSideBarPosition();
|
||||
const newPositionValue = (position === Position.LEFT) ? 'right' : 'left';
|
||||
|
||||
@@ -41,3 +41,12 @@ export class ToggleSidebarPositionAction extends Action {
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSidebarPositionAction, ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.LABEL), 'View: Toggle Side Bar Position', nls.localize('view', "View"));
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
|
||||
group: '2_workbench_layout',
|
||||
command: {
|
||||
id: ToggleSidebarPositionAction.ID,
|
||||
title: nls.localize({ key: 'miMoveSidebarLeftRight', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar Left/Right")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
@@ -8,15 +8,15 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
|
||||
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
|
||||
export class ToggleSidebarVisibilityAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.toggleSidebarVisibility';
|
||||
public static readonly LABEL = nls.localize('toggleSidebar', "Toggle Side Bar Visibility");
|
||||
static readonly ID = 'workbench.action.toggleSidebarVisibility';
|
||||
static readonly LABEL = nls.localize('toggleSidebar', "Toggle Side Bar Visibility");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@@ -28,11 +28,20 @@ export class ToggleSidebarVisibilityAction extends Action {
|
||||
this.enabled = !!this.partService;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
run(): TPromise<any> {
|
||||
const hideSidebar = this.partService.isVisible(Parts.SIDEBAR_PART);
|
||||
return this.partService.setSideBarHidden(hideSidebar);
|
||||
}
|
||||
}
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSidebarVisibilityAction, ToggleSidebarVisibilityAction.ID, ToggleSidebarVisibilityAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_B }), 'View: Toggle Side Bar Visibility', nls.localize('view', "View"));
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSidebarVisibilityAction, ToggleSidebarVisibilityAction.ID, ToggleSidebarVisibilityAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_B }), 'View: Toggle Side Bar Visibility', nls.localize('view', "View"));
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
|
||||
group: '2_workbench_layout',
|
||||
command: {
|
||||
id: ToggleSidebarVisibilityAction.ID,
|
||||
title: nls.localize({ key: 'miToggleSidebar', comment: ['&& denotes a mnemonic'] }, "&&Toggle Side Bar")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
@@ -8,15 +8,15 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
|
||||
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
|
||||
export class ToggleStatusbarVisibilityAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.toggleStatusbarVisibility';
|
||||
public static readonly LABEL = nls.localize('toggleStatusbar', "Toggle Status Bar Visibility");
|
||||
static readonly ID = 'workbench.action.toggleStatusbarVisibility';
|
||||
static readonly LABEL = nls.localize('toggleStatusbar', "Toggle Status Bar Visibility");
|
||||
|
||||
private static readonly statusbarVisibleKey = 'workbench.statusBar.visible';
|
||||
|
||||
@@ -31,7 +31,7 @@ export class ToggleStatusbarVisibilityAction extends Action {
|
||||
this.enabled = !!this.partService;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
run(): TPromise<any> {
|
||||
const visibility = this.partService.isVisible(Parts.STATUSBAR_PART);
|
||||
const newVisibilityValue = !visibility;
|
||||
|
||||
@@ -40,4 +40,13 @@ export class ToggleStatusbarVisibilityAction extends Action {
|
||||
}
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleStatusbarVisibilityAction, ToggleStatusbarVisibilityAction.ID, ToggleStatusbarVisibilityAction.LABEL), 'View: Toggle Status Bar Visibility', nls.localize('view', "View"));
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleStatusbarVisibilityAction, ToggleStatusbarVisibilityAction.ID, ToggleStatusbarVisibilityAction.LABEL), 'View: Toggle Status Bar Visibility', nls.localize('view', "View"));
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
|
||||
group: '2_workbench_layout',
|
||||
command: {
|
||||
id: ToggleStatusbarVisibilityAction.ID,
|
||||
title: nls.localize({ key: 'miToggleStatusbar', comment: ['&& denotes a mnemonic'] }, "&&Toggle Status Bar")
|
||||
},
|
||||
order: 3
|
||||
});
|
||||
|
||||
@@ -15,8 +15,8 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
|
||||
export class ToggleTabsVisibilityAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.toggleTabsVisibility';
|
||||
public static readonly LABEL = nls.localize('toggleTabs', "Toggle Tab Visibility");
|
||||
static readonly ID = 'workbench.action.toggleTabsVisibility';
|
||||
static readonly LABEL = nls.localize('toggleTabs', "Toggle Tab Visibility");
|
||||
|
||||
private static readonly tabsVisibleKey = 'workbench.editor.showTabs';
|
||||
|
||||
@@ -28,7 +28,7 @@ export class ToggleTabsVisibilityAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
run(): TPromise<any> {
|
||||
const visibility = this.configurationService.getValue<string>(ToggleTabsVisibilityAction.tabsVisibleKey);
|
||||
const newVisibilityValue = !visibility;
|
||||
|
||||
|
||||
@@ -8,14 +8,14 @@ import * as nls from 'vs/nls';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
|
||||
class ToggleZenMode extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.toggleZenMode';
|
||||
public static readonly LABEL = nls.localize('toggleZenMode', "Toggle Zen Mode");
|
||||
static readonly ID = 'workbench.action.toggleZenMode';
|
||||
static readonly LABEL = nls.localize('toggleZenMode', "Toggle Zen Mode");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@@ -26,11 +26,20 @@ class ToggleZenMode extends Action {
|
||||
this.enabled = !!this.partService;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
run(): TPromise<any> {
|
||||
this.partService.toggleZenMode();
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleZenMode, ToggleZenMode.ID, ToggleZenMode.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_Z) }), 'View: Toggle Zen Mode', nls.localize('view', "View"));
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleZenMode, ToggleZenMode.ID, ToggleZenMode.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_Z) }), 'View: Toggle Zen Mode', nls.localize('view', "View"));
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
|
||||
group: '1_toggle_view',
|
||||
command: {
|
||||
id: ToggleZenMode.ID,
|
||||
title: nls.localize('miToggleZenMode', "Toggle Zen Mode")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
@@ -8,17 +8,18 @@
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
|
||||
import { WORKSPACE_FILTER, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IHistoryService } from 'vs/workbench/services/history/common/history';
|
||||
import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL, PICK_WORKSPACE_FOLDER_COMMAND_ID, defaultWorkspacePath, defaultFilePath, defaultFolderPath } from 'vs/workbench/browser/actions/workspaceCommands';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
export class OpenFileAction extends Action {
|
||||
|
||||
@@ -93,7 +94,7 @@ export class AddRootFolderAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
run(): TPromise<any> {
|
||||
return this.commandService.executeCommand(ADD_ROOT_FOLDER_COMMAND_ID);
|
||||
}
|
||||
}
|
||||
@@ -113,7 +114,7 @@ export class GlobalRemoveRootFolderAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
run(): TPromise<any> {
|
||||
const state = this.contextService.getWorkbenchState();
|
||||
|
||||
// Workspace / Folder
|
||||
@@ -148,7 +149,7 @@ export class SaveWorkspaceAsAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
run(): TPromise<any> {
|
||||
return this.getNewWorkspaceConfigPath().then(configPath => {
|
||||
if (configPath) {
|
||||
switch (this.contextService.getWorkbenchState()) {
|
||||
@@ -192,71 +193,55 @@ export class OpenWorkspaceAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(event?: any, data?: ITelemetryData): TPromise<any> {
|
||||
run(event?: any, data?: ITelemetryData): TPromise<any> {
|
||||
return this.windowService.pickWorkspaceAndOpen({ telemetryExtraData: data, dialogOptions: { defaultPath: defaultWorkspacePath(this.contextService, this.historyService, this.environmentService) } });
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenWorkspaceConfigFileAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.openWorkspaceConfigFile';
|
||||
public static readonly LABEL = nls.localize('openWorkspaceConfigFile', "Open Workspace Configuration File");
|
||||
static readonly ID = 'workbench.action.openWorkspaceConfigFile';
|
||||
static readonly LABEL = nls.localize('openWorkspaceConfigFile', "Open Workspace Configuration File");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWorkspaceContextService private workspaceContextService: IWorkspaceContextService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService
|
||||
@IEditorService private editorService: IEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
|
||||
this.enabled = !!this.workspaceContextService.getWorkspace().configuration;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
run(): TPromise<any> {
|
||||
return this.editorService.openEditor({ resource: this.workspaceContextService.getWorkspace().configuration });
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenFolderAsWorkspaceInNewWindowAction extends Action {
|
||||
export class DuplicateWorkspaceInNewWindowAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.openFolderAsWorkspaceInNewWindow';
|
||||
public static readonly LABEL = nls.localize('openFolderAsWorkspaceInNewWindow', "Open Folder as Workspace in New Window");
|
||||
static readonly ID = 'workbench.action.duplicateWorkspaceInNewWindow';
|
||||
static readonly LABEL = nls.localize('duplicateWorkspaceInNewWindow', "Duplicate Workspace in New Window");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWorkspaceContextService private workspaceContextService: IWorkspaceContextService,
|
||||
@IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService,
|
||||
@IWindowsService private windowsService: IWindowsService,
|
||||
@ICommandService private commandService: ICommandService,
|
||||
@IWindowService private windowService: IWindowService,
|
||||
@IWorkspacesService private workspacesService: IWorkspacesService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
run(): TPromise<any> {
|
||||
const folders = this.workspaceContextService.getWorkspace().folders;
|
||||
|
||||
let folderPromise: TPromise<IWorkspaceFolder>;
|
||||
if (folders.length === 0) {
|
||||
folderPromise = TPromise.as(null);
|
||||
} else if (folders.length === 1) {
|
||||
folderPromise = TPromise.as(folders[0]);
|
||||
} else {
|
||||
folderPromise = this.commandService.executeCommand<IWorkspaceFolder>(PICK_WORKSPACE_FOLDER_COMMAND_ID);
|
||||
}
|
||||
|
||||
return folderPromise.then(folder => {
|
||||
if (!folder) {
|
||||
return void 0; // need at least one folder
|
||||
}
|
||||
|
||||
return this.workspacesService.createWorkspace([{ uri: folder.uri }]).then(newWorkspace => {
|
||||
return this.workspaceEditingService.copyWorkspaceSettings(newWorkspace).then(() => {
|
||||
return this.windowsService.openWindow([newWorkspace.configPath], { forceNewWindow: true });
|
||||
});
|
||||
return this.workspacesService.createWorkspace(folders).then(newWorkspace => {
|
||||
return this.workspaceEditingService.copyWorkspaceSettings(newWorkspace).then(() => {
|
||||
return this.windowService.openWindow([URI.file(newWorkspace.configPath)], { forceNewWindow: true });
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,13 +16,14 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { dirname } from 'vs/base/common/paths';
|
||||
import { IQuickOpenService, IFilePickOpenEntry, IPickOptions } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { mnemonicButtonLabel, getPathLabel } from 'vs/base/common/labels';
|
||||
import { mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { IHistoryService } from 'vs/workbench/services/history/common/history';
|
||||
import { FileKind, isParent } from 'vs/platform/files/common/files';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { IUriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay';
|
||||
|
||||
export const ADD_ROOT_FOLDER_COMMAND_ID = 'addRootFolder';
|
||||
export const ADD_ROOT_FOLDER_LABEL = nls.localize('addFolderToWorkspace', "Add Folder to Workspace...");
|
||||
@@ -158,9 +159,9 @@ CommandsRegistry.registerCommand({
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, function (accessor, args?: [IPickOptions, CancellationToken]) {
|
||||
const contextService = accessor.get(IWorkspaceContextService);
|
||||
const quickOpenService = accessor.get(IQuickOpenService);
|
||||
const environmentService = accessor.get(IEnvironmentService);
|
||||
const uriDisplayService = accessor.get(IUriDisplayService);
|
||||
const contextService = accessor.get(IWorkspaceContextService);
|
||||
|
||||
const folders = contextService.getWorkspace().folders;
|
||||
if (!folders.length) {
|
||||
@@ -170,7 +171,7 @@ CommandsRegistry.registerCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, function (acc
|
||||
const folderPicks = folders.map(folder => {
|
||||
return {
|
||||
label: folder.name,
|
||||
description: getPathLabel(resources.dirname(folder.uri), void 0, environmentService),
|
||||
description: uriDisplayService.getLabel(resources.dirname(folder.uri), true),
|
||||
folder,
|
||||
resource: folder.uri,
|
||||
fileKind: FileKind.ROOT_FOLDER
|
||||
|
||||
@@ -8,13 +8,11 @@ import { IAction, IActionRunner, ActionRunner } from 'vs/base/common/actions';
|
||||
import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Component } from 'vs/workbench/common/component';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IComposite } from 'vs/workbench/common/composite';
|
||||
import { IEditorControl } from 'vs/platform/editor/common/editor';
|
||||
import { IComposite, ICompositeControl } from 'vs/workbench/common/composite';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IConstructorSignature0, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IFocusTracker, trackFocus, Dimension } from 'vs/base/browser/dom';
|
||||
import { trackFocus, Dimension } from 'vs/base/browser/dom';
|
||||
|
||||
/**
|
||||
* Composites are layed out in the sidebar and panel part of the workbench. At a time only one composite
|
||||
@@ -27,17 +25,27 @@ import { IFocusTracker, trackFocus, Dimension } from 'vs/base/browser/dom';
|
||||
* layout and focus call, but only one create and dispose call.
|
||||
*/
|
||||
export abstract class Composite extends Component implements IComposite {
|
||||
private readonly _onTitleAreaUpdate: Emitter<void>;
|
||||
private readonly _onDidFocus: Emitter<void>;
|
||||
|
||||
private _focusTracker?: IFocusTracker;
|
||||
private _focusListenerDisposable?: IDisposable;
|
||||
private readonly _onTitleAreaUpdate: Emitter<void> = this._register(new Emitter<void>());
|
||||
get onTitleAreaUpdate(): Event<void> { return this._onTitleAreaUpdate.event; }
|
||||
|
||||
private _onDidFocus: Emitter<void>;
|
||||
get onDidFocus(): Event<void> {
|
||||
if (!this._onDidFocus) {
|
||||
this._onDidFocus = this._register(new Emitter<void>());
|
||||
|
||||
const focusTracker = this._register(trackFocus(this.getContainer()));
|
||||
this._register(focusTracker.onDidFocus(() => this._onDidFocus.fire()));
|
||||
}
|
||||
|
||||
return this._onDidFocus.event;
|
||||
}
|
||||
|
||||
protected actionRunner: IActionRunner;
|
||||
|
||||
private visible: boolean;
|
||||
private parent: HTMLElement;
|
||||
|
||||
protected actionRunner: IActionRunner;
|
||||
|
||||
/**
|
||||
* Create a new composite with the given ID and context.
|
||||
*/
|
||||
@@ -49,11 +57,9 @@ export abstract class Composite extends Component implements IComposite {
|
||||
super(id, themeService);
|
||||
|
||||
this.visible = false;
|
||||
this._onTitleAreaUpdate = new Emitter<void>();
|
||||
this._onDidFocus = new Emitter<void>();
|
||||
}
|
||||
|
||||
public getTitle(): string {
|
||||
getTitle(): string {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -61,10 +67,6 @@ export abstract class Composite extends Component implements IComposite {
|
||||
return this._telemetryService;
|
||||
}
|
||||
|
||||
public get onTitleAreaUpdate(): Event<void> {
|
||||
return this._onTitleAreaUpdate.event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Clients should not call this method, the workbench calls this
|
||||
* method. Calling it otherwise may result in unexpected behavior.
|
||||
@@ -74,31 +76,23 @@ export abstract class Composite extends Component implements IComposite {
|
||||
* Note that DOM-dependent calculations should be performed from the setVisible()
|
||||
* call. Only then the composite will be part of the DOM.
|
||||
*/
|
||||
public create(parent: HTMLElement): TPromise<void> {
|
||||
create(parent: HTMLElement): TPromise<void> {
|
||||
this.parent = parent;
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public updateStyles(): void {
|
||||
updateStyles(): void {
|
||||
super.updateStyles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the container this composite is being build in.
|
||||
*/
|
||||
public getContainer(): HTMLElement {
|
||||
getContainer(): HTMLElement {
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
public get onDidFocus(): Event<any> {
|
||||
this._focusTracker = trackFocus(this.getContainer());
|
||||
this._focusListenerDisposable = this._focusTracker.onDidFocus(() => {
|
||||
this._onDidFocus.fire();
|
||||
});
|
||||
return this._onDidFocus.event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Clients should not call this method, the workbench calls this
|
||||
* method. Calling it otherwise may result in unexpected behavior.
|
||||
@@ -111,7 +105,7 @@ export abstract class Composite extends Component implements IComposite {
|
||||
* to do a long running operation from this call. Typically this operation should be
|
||||
* fast though because setVisible might be called many times during a session.
|
||||
*/
|
||||
public setVisible(visible: boolean): TPromise<void> {
|
||||
setVisible(visible: boolean): TPromise<void> {
|
||||
this.visible = visible;
|
||||
|
||||
return TPromise.as(null);
|
||||
@@ -120,19 +114,19 @@ export abstract class Composite extends Component implements IComposite {
|
||||
/**
|
||||
* Called when this composite should receive keyboard focus.
|
||||
*/
|
||||
public focus(): void {
|
||||
focus(): void {
|
||||
// Subclasses can implement
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout the contents of this composite using the provided dimensions.
|
||||
*/
|
||||
public abstract layout(dimension: Dimension): void;
|
||||
abstract layout(dimension: Dimension): void;
|
||||
|
||||
/**
|
||||
* Returns an array of actions to show in the action bar of the composite.
|
||||
*/
|
||||
public getActions(): IAction[] {
|
||||
getActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -140,14 +134,14 @@ export abstract class Composite extends Component implements IComposite {
|
||||
* Returns an array of actions to show in the action bar of the composite
|
||||
* in a less prominent way then action from getActions.
|
||||
*/
|
||||
public getSecondaryActions(): IAction[] {
|
||||
getSecondaryActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of actions to show in the context menu of the composite
|
||||
*/
|
||||
public getContextMenuActions(): IAction[] {
|
||||
getContextMenuActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -157,7 +151,7 @@ export abstract class Composite extends Component implements IComposite {
|
||||
* of an action. Returns null to indicate that the action is not rendered through
|
||||
* an action item.
|
||||
*/
|
||||
public getActionItem(action: IAction): IActionItem {
|
||||
getActionItem(action: IAction): IActionItem {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -165,7 +159,7 @@ export abstract class Composite extends Component implements IComposite {
|
||||
* Returns the instance of IActionRunner to use with this composite for the
|
||||
* composite tool bar.
|
||||
*/
|
||||
public getActionRunner(): IActionRunner {
|
||||
getActionRunner(): IActionRunner {
|
||||
if (!this.actionRunner) {
|
||||
this.actionRunner = new ActionRunner();
|
||||
}
|
||||
@@ -186,43 +180,28 @@ export abstract class Composite extends Component implements IComposite {
|
||||
/**
|
||||
* Returns true if this composite is currently visible and false otherwise.
|
||||
*/
|
||||
public isVisible(): boolean {
|
||||
isVisible(): boolean {
|
||||
return this.visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying composite control or null if it is not accessible.
|
||||
*/
|
||||
public getControl(): IEditorControl {
|
||||
getControl(): ICompositeControl {
|
||||
return null;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._onTitleAreaUpdate.dispose();
|
||||
this._onDidFocus.dispose();
|
||||
|
||||
if (this._focusTracker) {
|
||||
this._focusTracker.dispose();
|
||||
}
|
||||
|
||||
if (this._focusListenerDisposable) {
|
||||
this._focusListenerDisposable.dispose();
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A composite descriptor is a leightweight descriptor of a composite in the workbench.
|
||||
*/
|
||||
export abstract class CompositeDescriptor<T extends Composite> {
|
||||
public id: string;
|
||||
public name: string;
|
||||
public cssClass: string;
|
||||
public order: number;
|
||||
public keybindingId: string;
|
||||
public enabled: boolean;
|
||||
id: string;
|
||||
name: string;
|
||||
cssClass: string;
|
||||
order: number;
|
||||
keybindingId: string;
|
||||
enabled: boolean;
|
||||
|
||||
private ctor: IConstructorSignature0<T>;
|
||||
|
||||
@@ -236,7 +215,7 @@ export abstract class CompositeDescriptor<T extends Composite> {
|
||||
this.keybindingId = keybindingId;
|
||||
}
|
||||
|
||||
public instantiate(instantiationService: IInstantiationService): T {
|
||||
instantiate(instantiationService: IInstantiationService): T {
|
||||
return instantiationService.createInstance(this.ctor);
|
||||
}
|
||||
}
|
||||
@@ -244,13 +223,9 @@ export abstract class CompositeDescriptor<T extends Composite> {
|
||||
export abstract class CompositeRegistry<T extends Composite> {
|
||||
|
||||
private readonly _onDidRegister: Emitter<CompositeDescriptor<T>> = new Emitter<CompositeDescriptor<T>>();
|
||||
readonly onDidRegister: Event<CompositeDescriptor<T>> = this._onDidRegister.event;
|
||||
get onDidRegister(): Event<CompositeDescriptor<T>> { return this._onDidRegister.event; }
|
||||
|
||||
private composites: CompositeDescriptor<T>[];
|
||||
|
||||
constructor() {
|
||||
this.composites = [];
|
||||
}
|
||||
private composites: CompositeDescriptor<T>[] = [];
|
||||
|
||||
protected registerComposite(descriptor: CompositeDescriptor<T>): void {
|
||||
if (this.compositeById(descriptor.id) !== null) {
|
||||
@@ -261,7 +236,7 @@ export abstract class CompositeRegistry<T extends Composite> {
|
||||
this._onDidRegister.fire(descriptor);
|
||||
}
|
||||
|
||||
public getComposite(id: string): CompositeDescriptor<T> {
|
||||
getComposite(id: string): CompositeDescriptor<T> {
|
||||
return this.compositeById(id);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,18 +6,15 @@
|
||||
'use strict';
|
||||
|
||||
import { WORKSPACE_EXTENSION, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { extname, basename } from 'vs/base/common/paths';
|
||||
import { extname, basename, normalize } from 'vs/base/common/paths';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { Position } from 'vs/platform/editor/common/editor';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { DefaultEndOfLine } from 'vs/editor/common/model';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
@@ -25,15 +22,20 @@ import { IEditorViewState } from 'vs/editor/common/editorCommon';
|
||||
import { DataTransfers } from 'vs/base/browser/dnd';
|
||||
import { DefaultDragAndDrop } from 'vs/base/parts/tree/browser/treeDefaults';
|
||||
import { DragMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { getPathLabel } from 'vs/base/common/labels';
|
||||
import { normalizeDriveLetter } from 'vs/base/common/labels';
|
||||
import { MIME_BINARY } from 'vs/base/common/mime';
|
||||
import { ITree, IDragAndDropData } from 'vs/base/parts/tree/browser/tree';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { getCodeEditor } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { IEditorIdentifier } from 'vs/workbench/common/editor';
|
||||
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IEditorIdentifier, GroupIdentifier } from 'vs/workbench/common/editor';
|
||||
import { basenameOrAuthority } from 'vs/base/common/resources';
|
||||
import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { addDisposableListener, EventType } from 'vs/base/browser/dom';
|
||||
import { IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { IUriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay';
|
||||
|
||||
export interface IDraggedResource {
|
||||
resource: URI;
|
||||
@@ -41,10 +43,17 @@ export interface IDraggedResource {
|
||||
}
|
||||
|
||||
export class DraggedEditorIdentifier {
|
||||
constructor(private _identifier: IEditorIdentifier) {
|
||||
}
|
||||
constructor(private _identifier: IEditorIdentifier) { }
|
||||
|
||||
public get identifier(): IEditorIdentifier {
|
||||
get identifier(): IEditorIdentifier {
|
||||
return this._identifier;
|
||||
}
|
||||
}
|
||||
|
||||
export class DraggedEditorGroupIdentifier {
|
||||
constructor(private _identifier: GroupIdentifier) { }
|
||||
|
||||
get identifier(): GroupIdentifier {
|
||||
return this._identifier;
|
||||
}
|
||||
}
|
||||
@@ -155,14 +164,14 @@ export class ResourcesDropHandler {
|
||||
@IWorkspacesService private workspacesService: IWorkspacesService,
|
||||
@ITextFileService private textFileService: ITextFileService,
|
||||
@IBackupFileService private backupFileService: IBackupFileService,
|
||||
@IEditorGroupService private groupService: IEditorGroupService,
|
||||
@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IConfigurationService private configurationService: IConfigurationService
|
||||
@IEditorService private editorService: IEditorService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IUriDisplayService private uriDisplayService: IUriDisplayService
|
||||
) {
|
||||
}
|
||||
|
||||
public handleDrop(event: DragEvent, afterDrop: () => void, targetPosition: Position, targetIndex?: number): void {
|
||||
handleDrop(event: DragEvent, resolveTargetGroup: () => IEditorGroup, afterDrop: (targetGroup: IEditorGroup) => void, targetIndex?: number): void {
|
||||
const untitledOrFileResources = extractResources(event).filter(r => this.fileService.canHandleResource(r.resource) || r.resource.scheme === Schemas.untitled);
|
||||
if (!untitledOrFileResources.length) {
|
||||
return;
|
||||
@@ -180,26 +189,24 @@ export class ResourcesDropHandler {
|
||||
// Add external ones to recently open list unless dropped resource is a workspace
|
||||
const filesToAddToHistory = untitledOrFileResources.filter(d => d.isExternal && d.resource.scheme === Schemas.file).map(d => d.resource);
|
||||
if (filesToAddToHistory.length) {
|
||||
this.windowsService.addRecentlyOpened(filesToAddToHistory.map(resource => resource.fsPath));
|
||||
this.windowsService.addRecentlyOpened(filesToAddToHistory.map(resource => this.uriDisplayService.getLabel(resource)));
|
||||
}
|
||||
|
||||
const editors: IResourceEditor[] = untitledOrFileResources.map(untitledOrFileResource => ({
|
||||
resource: untitledOrFileResource.resource,
|
||||
options: {
|
||||
pinned: true,
|
||||
index: targetIndex,
|
||||
viewState: (untitledOrFileResource as IDraggedEditor).viewState
|
||||
}
|
||||
}));
|
||||
|
||||
// Open in Editor
|
||||
return this.editorService.openEditors(untitledOrFileResources.map(untitledOrFileResource => {
|
||||
return {
|
||||
input: {
|
||||
resource: untitledOrFileResource.resource,
|
||||
options: {
|
||||
pinned: true,
|
||||
index: targetIndex,
|
||||
viewState: (untitledOrFileResource as IDraggedEditor).viewState
|
||||
}
|
||||
},
|
||||
position: targetPosition
|
||||
};
|
||||
})).then(() => {
|
||||
const targetGroup = resolveTargetGroup();
|
||||
return this.editorService.openEditors(editors, targetGroup).then(() => {
|
||||
|
||||
// Finish with provided function
|
||||
afterDrop();
|
||||
afterDrop(targetGroup);
|
||||
});
|
||||
});
|
||||
}).done(null, onUnexpectedError);
|
||||
@@ -232,7 +239,7 @@ export class ResourcesDropHandler {
|
||||
}
|
||||
|
||||
// Return early if the resource is already dirty in target or opened already
|
||||
if (this.textFileService.isDirty(droppedDirtyEditor.resource) || this.groupService.getStacksModel().isOpen(droppedDirtyEditor.resource)) {
|
||||
if (this.textFileService.isDirty(droppedDirtyEditor.resource) || this.editorService.isOpen({ resource: droppedDirtyEditor.resource })) {
|
||||
return TPromise.as(false);
|
||||
}
|
||||
|
||||
@@ -285,21 +292,21 @@ export class ResourcesDropHandler {
|
||||
// Pass focus to window
|
||||
this.windowService.focusWindow();
|
||||
|
||||
let workspacesToOpen: TPromise<string[]>;
|
||||
let workspacesToOpen: TPromise<URI[]>;
|
||||
|
||||
// Open in separate windows if we drop workspaces or just one folder
|
||||
if (workspaces.length > 0 || folders.length === 1) {
|
||||
workspacesToOpen = TPromise.as([...workspaces, ...folders].map(resources => resources.fsPath));
|
||||
workspacesToOpen = TPromise.as([...workspaces, ...folders].map(resources => resources));
|
||||
}
|
||||
|
||||
// Multiple folders: Create new workspace with folders and open
|
||||
else if (folders.length > 1) {
|
||||
workspacesToOpen = this.workspacesService.createWorkspace(folders.map(folder => ({ uri: folder }))).then(workspace => [workspace.configPath]);
|
||||
workspacesToOpen = this.workspacesService.createWorkspace(folders.map(folder => ({ uri: folder }))).then(workspace => [URI.file(workspace.configPath)]);
|
||||
}
|
||||
|
||||
// Open
|
||||
workspacesToOpen.then(workspaces => {
|
||||
this.windowsService.openWindow(workspaces, { forceReuseWindow: true });
|
||||
this.windowService.openWindow(workspaces, { forceReuseWindow: true });
|
||||
});
|
||||
|
||||
return true;
|
||||
@@ -316,7 +323,7 @@ export class SimpleFileResourceDragAndDrop extends DefaultDragAndDrop {
|
||||
super();
|
||||
}
|
||||
|
||||
public getDragURI(tree: ITree, obj: any): string {
|
||||
getDragURI(tree: ITree, obj: any): string {
|
||||
const resource = this.toResource(obj);
|
||||
if (resource) {
|
||||
return resource.toString();
|
||||
@@ -325,7 +332,7 @@ export class SimpleFileResourceDragAndDrop extends DefaultDragAndDrop {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
public getDragLabel(tree: ITree, elements: any[]): string {
|
||||
getDragLabel(tree: ITree, elements: any[]): string {
|
||||
if (elements.length > 1) {
|
||||
return String(elements.length);
|
||||
}
|
||||
@@ -338,7 +345,7 @@ export class SimpleFileResourceDragAndDrop extends DefaultDragAndDrop {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
public onDragStart(tree: ITree, data: IDragAndDropData, originalEvent: DragMouseEvent): void {
|
||||
onDragStart(tree: ITree, data: IDragAndDropData, originalEvent: DragMouseEvent): void {
|
||||
|
||||
// Apply some datatransfer types to allow for dragging the element outside of the application
|
||||
const resources: URI[] = data.getData().map(source => this.toResource(source));
|
||||
@@ -365,7 +372,7 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources:
|
||||
|
||||
// Text: allows to paste into text-capable areas
|
||||
const lineDelimiter = isWindows ? '\r\n' : '\n';
|
||||
event.dataTransfer.setData(DataTransfers.TEXT, sources.map(source => source.resource.scheme === Schemas.file ? getPathLabel(source.resource) : source.resource.toString()).join(lineDelimiter));
|
||||
event.dataTransfer.setData(DataTransfers.TEXT, sources.map(source => source.resource.scheme === Schemas.file ? normalize(normalizeDriveLetter(source.resource.fsPath), true) : source.resource.toString()).join(lineDelimiter));
|
||||
|
||||
// Download URL: enables support to drag a tab as file to desktop (only single file supported)
|
||||
if (firstSource.resource.scheme === Schemas.file) {
|
||||
@@ -381,21 +388,20 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources:
|
||||
// Editors: enables cross window DND of tabs into the editor area
|
||||
const textFileService = accessor.get(ITextFileService);
|
||||
const backupFileService = accessor.get(IBackupFileService);
|
||||
const editorService = accessor.get(IWorkbenchEditorService);
|
||||
const editorService = accessor.get(IEditorService);
|
||||
|
||||
const draggedEditors: ISerializedDraggedEditor[] = [];
|
||||
files.forEach(file => {
|
||||
|
||||
// Try to find editor view state from the visible editors that match given resource
|
||||
let viewState: IEditorViewState;
|
||||
const editors = editorService.getVisibleEditors();
|
||||
for (let i = 0; i < editors.length; i++) {
|
||||
const editor = editors[i];
|
||||
const codeEditor = getCodeEditor(editor);
|
||||
if (codeEditor) {
|
||||
const model = codeEditor.getModel();
|
||||
const textEditorWidgets = editorService.visibleTextEditorWidgets;
|
||||
for (let i = 0; i < textEditorWidgets.length; i++) {
|
||||
const textEditorWidget = textEditorWidgets[i];
|
||||
if (isCodeEditor(textEditorWidget)) {
|
||||
const model = textEditorWidget.getModel();
|
||||
if (model && model.uri && model.uri.toString() === file.resource.toString()) {
|
||||
viewState = codeEditor.saveViewState();
|
||||
viewState = textEditorWidget.saveViewState();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -428,20 +434,22 @@ export class LocalSelectionTransfer<T> {
|
||||
// protect against external instantiation
|
||||
}
|
||||
|
||||
public static getInstance<T>(): LocalSelectionTransfer<T> {
|
||||
static getInstance<T>(): LocalSelectionTransfer<T> {
|
||||
return LocalSelectionTransfer.INSTANCE as LocalSelectionTransfer<T>;
|
||||
}
|
||||
|
||||
public hasData(proto: T): boolean {
|
||||
hasData(proto: T): boolean {
|
||||
return proto && proto === this.proto;
|
||||
}
|
||||
|
||||
public clearData(): void {
|
||||
this.proto = void 0;
|
||||
this.data = void 0;
|
||||
clearData(proto: T): void {
|
||||
if (this.hasData(proto)) {
|
||||
this.proto = void 0;
|
||||
this.data = void 0;
|
||||
}
|
||||
}
|
||||
|
||||
public getData(proto: T): T[] {
|
||||
getData(proto: T): T[] {
|
||||
if (this.hasData(proto)) {
|
||||
return this.data;
|
||||
}
|
||||
@@ -449,10 +457,66 @@ export class LocalSelectionTransfer<T> {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
public setData(data: T[], proto: T): void {
|
||||
setData(data: T[], proto: T): void {
|
||||
if (proto) {
|
||||
this.data = data;
|
||||
this.proto = proto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface IDragAndDropObserverCallbacks {
|
||||
onDragEnter: (e: DragEvent) => void;
|
||||
onDragLeave: (e: DragEvent) => void;
|
||||
onDrop: (e: DragEvent) => void;
|
||||
onDragEnd: (e: DragEvent) => void;
|
||||
|
||||
onDragOver?: (e: DragEvent) => void;
|
||||
}
|
||||
|
||||
export class DragAndDropObserver extends Disposable {
|
||||
|
||||
// A helper to fix issues with repeated DRAG_ENTER / DRAG_LEAVE
|
||||
// calls see https://github.com/Microsoft/vscode/issues/14470
|
||||
// when the element has child elements where the events are fired
|
||||
// repeadedly.
|
||||
private counter: number = 0;
|
||||
|
||||
constructor(private element: HTMLElement, private callbacks: IDragAndDropObserverCallbacks) {
|
||||
super();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(addDisposableListener(this.element, EventType.DRAG_ENTER, (e: DragEvent) => {
|
||||
this.counter++;
|
||||
|
||||
this.callbacks.onDragEnter(e);
|
||||
}));
|
||||
|
||||
this._register(addDisposableListener(this.element, EventType.DRAG_OVER, (e: DragEvent) => {
|
||||
if (this.callbacks.onDragOver) {
|
||||
this.callbacks.onDragOver(e);
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(addDisposableListener(this.element, EventType.DRAG_LEAVE, (e: DragEvent) => {
|
||||
this.counter--;
|
||||
|
||||
if (this.counter === 0) {
|
||||
this.callbacks.onDragLeave(e);
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(addDisposableListener(this.element, EventType.DRAG_END, (e: DragEvent) => {
|
||||
this.counter = 0;
|
||||
this.callbacks.onDragEnd(e);
|
||||
}));
|
||||
|
||||
this._register(addDisposableListener(this.element, EventType.DROP, (e: DragEvent) => {
|
||||
this.counter = 0;
|
||||
this.callbacks.onDrop(e);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,19 +66,19 @@ export class EditorDescriptor implements IEditorDescriptor {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public instantiate(instantiationService: IInstantiationService): BaseEditor {
|
||||
instantiate(instantiationService: IInstantiationService): BaseEditor {
|
||||
return instantiationService.createInstance(this.ctor);
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
getId(): string {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public getName(): string {
|
||||
getName(): string {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public describes(obj: any): boolean {
|
||||
describes(obj: any): boolean {
|
||||
return obj instanceof BaseEditor && (<BaseEditor>obj).getId() === this.id;
|
||||
}
|
||||
}
|
||||
@@ -86,15 +86,11 @@ export class EditorDescriptor implements IEditorDescriptor {
|
||||
const INPUT_DESCRIPTORS_PROPERTY = '__$inputDescriptors';
|
||||
|
||||
class EditorRegistry implements IEditorRegistry {
|
||||
private editors: EditorDescriptor[];
|
||||
private editors: EditorDescriptor[] = [];
|
||||
|
||||
constructor() {
|
||||
this.editors = [];
|
||||
}
|
||||
|
||||
public registerEditor(descriptor: EditorDescriptor, editorInputDescriptor: SyncDescriptor<EditorInput>): void;
|
||||
public registerEditor(descriptor: EditorDescriptor, editorInputDescriptor: SyncDescriptor<EditorInput>[]): void;
|
||||
public registerEditor(descriptor: EditorDescriptor, editorInputDescriptor: any): void {
|
||||
registerEditor(descriptor: EditorDescriptor, editorInputDescriptor: SyncDescriptor<EditorInput>): void;
|
||||
registerEditor(descriptor: EditorDescriptor, editorInputDescriptor: SyncDescriptor<EditorInput>[]): void;
|
||||
registerEditor(descriptor: EditorDescriptor, editorInputDescriptor: any): void {
|
||||
|
||||
// Support both non-array and array parameter
|
||||
let inputDescriptors: SyncDescriptor<EditorInput>[] = [];
|
||||
@@ -109,7 +105,7 @@ class EditorRegistry implements IEditorRegistry {
|
||||
this.editors.push(descriptor);
|
||||
}
|
||||
|
||||
public getEditor(input: EditorInput): EditorDescriptor {
|
||||
getEditor(input: EditorInput): EditorDescriptor {
|
||||
const findEditorDescriptors = (input: EditorInput, byInstanceOf?: boolean): EditorDescriptor[] => {
|
||||
const matchingDescriptors: EditorDescriptor[] = [];
|
||||
|
||||
@@ -161,7 +157,7 @@ class EditorRegistry implements IEditorRegistry {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getEditorById(editorId: string): EditorDescriptor {
|
||||
getEditorById(editorId: string): EditorDescriptor {
|
||||
for (let i = 0; i < this.editors.length; i++) {
|
||||
const editor = this.editors[i];
|
||||
if (editor.getId() === editorId) {
|
||||
@@ -172,15 +168,15 @@ class EditorRegistry implements IEditorRegistry {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getEditors(): EditorDescriptor[] {
|
||||
getEditors(): EditorDescriptor[] {
|
||||
return this.editors.slice(0);
|
||||
}
|
||||
|
||||
public setEditors(editorsToSet: EditorDescriptor[]): void {
|
||||
setEditors(editorsToSet: EditorDescriptor[]): void {
|
||||
this.editors = editorsToSet;
|
||||
}
|
||||
|
||||
public getEditorInputs(): any[] {
|
||||
getEditorInputs(): any[] {
|
||||
const inputClasses: any[] = [];
|
||||
for (let i = 0; i < this.editors.length; i++) {
|
||||
const editor = this.editors[i];
|
||||
|
||||
@@ -10,15 +10,11 @@ import * as resources from 'vs/base/common/resources';
|
||||
import { IconLabel, IIconLabelValueOptions, IIconLabelCreationOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IEditorInput } from 'vs/platform/editor/common/editor';
|
||||
import { toResource } from 'vs/workbench/common/editor';
|
||||
import { getPathLabel, IWorkspaceFolderProvider } from 'vs/base/common/labels';
|
||||
import { toResource, IEditorInput } from 'vs/workbench/common/editor';
|
||||
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import { IDecorationsService, IResourceDecorationChangeEvent, IDecorationData } from 'vs/workbench/services/decorations/browser/decorations';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
@@ -26,6 +22,8 @@ import { FileKind, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/fi
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { DataUri } from 'vs/workbench/common/resources';
|
||||
import { IUriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay';
|
||||
|
||||
export interface IResourceLabel {
|
||||
name: string;
|
||||
@@ -40,51 +38,47 @@ export interface IResourceLabelOptions extends IIconLabelValueOptions {
|
||||
|
||||
export class ResourceLabel extends IconLabel {
|
||||
|
||||
private toDispose: IDisposable[];
|
||||
private _onDidRender = this._register(new Emitter<void>());
|
||||
get onDidRender(): Event<void> { return this._onDidRender.event; }
|
||||
|
||||
private label: IResourceLabel;
|
||||
private options: IResourceLabelOptions;
|
||||
private computedIconClasses: string[];
|
||||
private lastKnownConfiguredLangId: string;
|
||||
private computedPathLabel: string;
|
||||
|
||||
private _onDidRender = new Emitter<void>();
|
||||
readonly onDidRender: Event<void> = this._onDidRender.event;
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
options: IIconLabelCreationOptions,
|
||||
@IExtensionService private extensionService: IExtensionService,
|
||||
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IModeService private modeService: IModeService,
|
||||
@IModelService private modelService: IModelService,
|
||||
@IEnvironmentService protected environmentService: IEnvironmentService,
|
||||
@IDecorationsService protected decorationsService: IDecorationsService,
|
||||
@IThemeService private themeService: IThemeService
|
||||
@IThemeService private themeService: IThemeService,
|
||||
@IUriDisplayService protected uriDisplayService: IUriDisplayService
|
||||
) {
|
||||
super(container, options);
|
||||
|
||||
this.toDispose = [];
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// update when extensions are registered with potentially new languages
|
||||
this.toDispose.push(this.extensionService.onDidRegisterExtensions(() => this.render(true /* clear cache */)));
|
||||
this._register(this.extensionService.onDidRegisterExtensions(() => this.render(true /* clear cache */)));
|
||||
|
||||
// react to model mode changes
|
||||
this.toDispose.push(this.modelService.onModelModeChanged(e => this.onModelModeChanged(e)));
|
||||
this._register(this.modelService.onModelModeChanged(e => this.onModelModeChanged(e)));
|
||||
|
||||
// react to file decoration changes
|
||||
this.toDispose.push(this.decorationsService.onDidChangeDecorations(this.onFileDecorationsChanges, this));
|
||||
this._register(this.decorationsService.onDidChangeDecorations(this.onFileDecorationsChanges, this));
|
||||
|
||||
// react to theme changes
|
||||
this.toDispose.push(this.themeService.onThemeChange(() => this.render(false)));
|
||||
this._register(this.themeService.onThemeChange(() => this.render(false)));
|
||||
|
||||
// react to files.associations changes
|
||||
this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => {
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(FILES_ASSOCIATIONS_CONFIG)) {
|
||||
this.render(true /* clear cache */);
|
||||
}
|
||||
@@ -121,7 +115,7 @@ export class ResourceLabel extends IconLabel {
|
||||
}
|
||||
}
|
||||
|
||||
public setLabel(label: IResourceLabel, options?: IResourceLabelOptions): void {
|
||||
setLabel(label: IResourceLabel, options?: IResourceLabelOptions): void {
|
||||
const hasResourceChanged = this.hasResourceChanged(label, options);
|
||||
|
||||
this.label = label;
|
||||
@@ -156,7 +150,7 @@ export class ResourceLabel extends IconLabel {
|
||||
return true;
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
clear(): void {
|
||||
this.label = void 0;
|
||||
this.options = void 0;
|
||||
this.lastKnownConfiguredLangId = void 0;
|
||||
@@ -187,6 +181,7 @@ export class ResourceLabel extends IconLabel {
|
||||
title: '',
|
||||
italic: this.options && this.options.italic,
|
||||
matches: this.options && this.options.matches,
|
||||
extraClasses: []
|
||||
};
|
||||
|
||||
const resource = this.label.resource;
|
||||
@@ -196,17 +191,18 @@ export class ResourceLabel extends IconLabel {
|
||||
iconLabelOptions.title = this.options.title;
|
||||
} else if (resource && resource.scheme !== Schemas.data /* do not accidentally inline Data URIs */) {
|
||||
if (!this.computedPathLabel) {
|
||||
this.computedPathLabel = getPathLabel(resource, void 0, this.environmentService);
|
||||
this.computedPathLabel = this.uriDisplayService.getLabel(resource);
|
||||
}
|
||||
|
||||
iconLabelOptions.title = this.computedPathLabel;
|
||||
}
|
||||
|
||||
if (!this.computedIconClasses) {
|
||||
this.computedIconClasses = getIconClasses(this.modelService, this.modeService, resource, this.options && this.options.fileKind);
|
||||
if (this.options && !this.options.hideIcon) {
|
||||
if (!this.computedIconClasses) {
|
||||
this.computedIconClasses = getIconClasses(this.modelService, this.modeService, resource, this.options && this.options.fileKind);
|
||||
}
|
||||
iconLabelOptions.extraClasses = this.computedIconClasses.slice(0);
|
||||
}
|
||||
|
||||
iconLabelOptions.extraClasses = this.computedIconClasses.slice(0);
|
||||
if (this.options && this.options.extraClasses) {
|
||||
iconLabelOptions.extraClasses.push(...this.options.extraClasses);
|
||||
}
|
||||
@@ -238,10 +234,9 @@ export class ResourceLabel extends IconLabel {
|
||||
this._onDidRender.fire();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
this.label = void 0;
|
||||
this.options = void 0;
|
||||
this.lastKnownConfiguredLangId = void 0;
|
||||
@@ -252,7 +247,7 @@ export class ResourceLabel extends IconLabel {
|
||||
|
||||
export class EditorLabel extends ResourceLabel {
|
||||
|
||||
public setEditor(editor: IEditorInput, options?: IResourceLabelOptions): void {
|
||||
setEditor(editor: IEditorInput, options?: IResourceLabelOptions): void {
|
||||
this.setLabel({
|
||||
resource: toResource(editor, { supportSideBySide: true }),
|
||||
name: editor.getName(),
|
||||
@@ -264,7 +259,6 @@ export class EditorLabel extends ResourceLabel {
|
||||
export interface IFileLabelOptions extends IResourceLabelOptions {
|
||||
hideLabel?: boolean;
|
||||
hidePath?: boolean;
|
||||
root?: uri;
|
||||
}
|
||||
|
||||
export class FileLabel extends ResourceLabel {
|
||||
@@ -273,19 +267,19 @@ export class FileLabel extends ResourceLabel {
|
||||
container: HTMLElement,
|
||||
options: IIconLabelCreationOptions,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IWorkspaceContextService contextService: IWorkspaceContextService,
|
||||
@IWorkspaceContextService private contextService: IWorkspaceContextService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IModeService modeService: IModeService,
|
||||
@IModelService modelService: IModelService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IDecorationsService decorationsService: IDecorationsService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
|
||||
@IUriDisplayService uriDisplayService: IUriDisplayService
|
||||
) {
|
||||
super(container, options, extensionService, contextService, configurationService, modeService, modelService, environmentService, decorationsService, themeService);
|
||||
super(container, options, extensionService, configurationService, modeService, modelService, decorationsService, themeService, uriDisplayService);
|
||||
}
|
||||
|
||||
public setFile(resource: uri, options?: IFileLabelOptions): void {
|
||||
setFile(resource: uri, options?: IFileLabelOptions): void {
|
||||
const hideLabel = options && options.hideLabel;
|
||||
let name: string;
|
||||
if (!hideLabel) {
|
||||
@@ -304,17 +298,7 @@ export class FileLabel extends ResourceLabel {
|
||||
let description: string;
|
||||
const hidePath = (options && options.hidePath) || (resource.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(resource));
|
||||
if (!hidePath) {
|
||||
let rootProvider: IWorkspaceFolderProvider;
|
||||
if (options && options.root) {
|
||||
rootProvider = {
|
||||
getWorkspaceFolder(): { uri } { return { uri: options.root }; },
|
||||
getWorkspace(): { folders: { uri: uri }[]; } { return { folders: [{ uri: options.root }] }; },
|
||||
};
|
||||
} else {
|
||||
rootProvider = this.contextService;
|
||||
}
|
||||
|
||||
description = getPathLabel(resources.dirname(resource), rootProvider, this.environmentService);
|
||||
description = this.uriDisplayService.getLabel(resources.dirname(resource), true);
|
||||
}
|
||||
|
||||
this.setLabel({ resource, name, description }, options);
|
||||
@@ -327,7 +311,15 @@ export function getIconClasses(modelService: IModelService, modeService: IModeSe
|
||||
const classes = fileKind === FileKind.ROOT_FOLDER ? ['rootfolder-icon'] : fileKind === FileKind.FOLDER ? ['folder-icon'] : ['file-icon'];
|
||||
|
||||
if (resource) {
|
||||
const name = cssEscape(resources.basenameOrAuthority(resource).toLowerCase());
|
||||
|
||||
// Get the name of the resource. For data-URIs, we need to parse specially
|
||||
let name: string;
|
||||
if (resource.scheme === Schemas.data) {
|
||||
const metadata = DataUri.parseMetaData(resource);
|
||||
name = metadata.get(DataUri.META_DATA_LABEL);
|
||||
} else {
|
||||
name = cssEscape(resources.basenameOrAuthority(resource).toLowerCase());
|
||||
}
|
||||
|
||||
// Folders
|
||||
if (fileKind === FileKind.FOLDER) {
|
||||
@@ -337,15 +329,17 @@ export function getIconClasses(modelService: IModelService, modeService: IModeSe
|
||||
// Files
|
||||
else {
|
||||
|
||||
// Name
|
||||
classes.push(`${name}-name-file-icon`);
|
||||
// Name & Extension(s)
|
||||
if (name) {
|
||||
classes.push(`${name}-name-file-icon`);
|
||||
|
||||
// Extension(s)
|
||||
const dotSegments = name.split('.');
|
||||
for (let i = 1; i < dotSegments.length; i++) {
|
||||
classes.push(`${dotSegments.slice(i).join('.')}-ext-file-icon`); // add each combination of all found extensions if more than one
|
||||
const dotSegments = name.split('.');
|
||||
for (let i = 1; i < dotSegments.length; i++) {
|
||||
classes.push(`${dotSegments.slice(i).join('.')}-ext-file-icon`); // add each combination of all found extensions if more than one
|
||||
}
|
||||
|
||||
classes.push(`ext-file-icon`); // extra segment to increase file-ext score
|
||||
}
|
||||
classes.push(`ext-file-icon`); // extra segment to increase file-ext score
|
||||
|
||||
// Configured Language
|
||||
let configuredLangId = getConfiguredLangId(modelService, resource);
|
||||
|
||||
@@ -6,76 +6,61 @@
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { Part } from 'vs/workbench/browser/part';
|
||||
import { QuickOpenController } from 'vs/workbench/browser/parts/quickopen/quickOpenController';
|
||||
import { QuickInputService } from 'vs/workbench/browser/parts/quickinput/quickInput';
|
||||
import { Sash, ISashEvent, IVerticalSashLayoutProvider, IHorizontalSashLayoutProvider, Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IPartService, Position, ILayoutOptions, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { getZoomFactor } from 'vs/base/browser/browser';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { NotificationsCenter } from 'vs/workbench/browser/parts/notifications/notificationsCenter';
|
||||
import { NotificationsToasts } from 'vs/workbench/browser/parts/notifications/notificationsToasts';
|
||||
import { Dimension, getClientArea, size, position, hide, show } from 'vs/base/browser/dom';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart';
|
||||
import { TitlebarPart } from 'vs/workbench/browser/parts/titlebar/titlebarPart';
|
||||
import { ActivitybarPart } from 'vs/workbench/browser/parts/activitybar/activitybarPart';
|
||||
import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart';
|
||||
import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart';
|
||||
import { StatusbarPart } from 'vs/workbench/browser/parts/statusbar/statusbarPart';
|
||||
import { getZoomFactor } from 'vs/base/browser/browser';
|
||||
|
||||
const MIN_SIDEBAR_PART_WIDTH = 170;
|
||||
const DEFAULT_SIDEBAR_PART_WIDTH = 300;
|
||||
const MIN_EDITOR_PART_HEIGHT = 70;
|
||||
const MIN_EDITOR_PART_WIDTH = 220;
|
||||
const MIN_PANEL_PART_HEIGHT = 77;
|
||||
const DEFAULT_PANEL_PART_SIZE = 350;
|
||||
const MIN_PANEL_PART_WIDTH = 300;
|
||||
const DEFAULT_PANEL_SIZE_COEFFICIENT = 0.4;
|
||||
const PANEL_SIZE_BEFORE_MAXIMIZED_BOUNDARY = 0.7;
|
||||
const HIDE_SIDEBAR_WIDTH_THRESHOLD = 50;
|
||||
const HIDE_PANEL_HEIGHT_THRESHOLD = 50;
|
||||
const HIDE_PANEL_WIDTH_THRESHOLD = 100;
|
||||
const TITLE_BAR_HEIGHT = 22;
|
||||
const TITLE_BAR_HEIGHT = isMacintosh ? 22 : 30;
|
||||
const STATUS_BAR_HEIGHT = 22;
|
||||
const ACTIVITY_BAR_WIDTH = 50;
|
||||
|
||||
interface PartLayoutInfo {
|
||||
titlebar: { height: number; };
|
||||
activitybar: { width: number; };
|
||||
sidebar: { minWidth: number; };
|
||||
panel: { minHeight: number; minWidth: number; };
|
||||
editor: { minWidth: number; minHeight: number; };
|
||||
statusbar: { height: number; };
|
||||
}
|
||||
const MIN_SIDEBAR_PART_WIDTH = 170;
|
||||
const DEFAULT_SIDEBAR_PART_WIDTH = 300;
|
||||
const HIDE_SIDEBAR_WIDTH_THRESHOLD = 50;
|
||||
|
||||
const MIN_PANEL_PART_HEIGHT = 77;
|
||||
const MIN_PANEL_PART_WIDTH = 300;
|
||||
const DEFAULT_PANEL_PART_SIZE = 350;
|
||||
const DEFAULT_PANEL_SIZE_COEFFICIENT = 0.4;
|
||||
const PANEL_SIZE_BEFORE_MAXIMIZED_BOUNDARY = 0.7;
|
||||
const HIDE_PANEL_HEIGHT_THRESHOLD = 50;
|
||||
const HIDE_PANEL_WIDTH_THRESHOLD = 100;
|
||||
|
||||
/**
|
||||
* The workbench layout is responsible to lay out all parts that make the Workbench.
|
||||
*/
|
||||
export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontalSashLayoutProvider {
|
||||
export class WorkbenchLayout extends Disposable implements IVerticalSashLayoutProvider, IHorizontalSashLayoutProvider {
|
||||
|
||||
private static readonly sashXOneWidthSettingsKey = 'workbench.sidebar.width';
|
||||
private static readonly sashXTwoWidthSettingsKey = 'workbench.panel.width';
|
||||
private static readonly sashYHeightSettingsKey = 'workbench.panel.height';
|
||||
private static readonly panelSizeBeforeMaximizedKey = 'workbench.panel.sizeBeforeMaximized';
|
||||
|
||||
private parent: HTMLElement;
|
||||
private workbenchContainer: HTMLElement;
|
||||
private titlebar: Part;
|
||||
private activitybar: Part;
|
||||
private editor: Part;
|
||||
private sidebar: Part;
|
||||
private panel: Part;
|
||||
private statusbar: Part;
|
||||
private quickopen: QuickOpenController;
|
||||
private quickInput: QuickInputService;
|
||||
private notificationsCenter: NotificationsCenter;
|
||||
private notificationsToasts: NotificationsToasts;
|
||||
private toUnbind: IDisposable[];
|
||||
private workbenchSize: Dimension;
|
||||
|
||||
private sashXOne: Sash;
|
||||
private sashXTwo: Sash;
|
||||
private sashY: Sash;
|
||||
|
||||
private _sidebarWidth: number;
|
||||
private sidebarHeight: number;
|
||||
private titlebarHeight: number;
|
||||
@@ -84,80 +69,81 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
private panelMaximized: boolean;
|
||||
private _panelHeight: number;
|
||||
private _panelWidth: number;
|
||||
private layoutEditorGroupsVertically: boolean;
|
||||
|
||||
// Take parts as an object bag since instatation service does not have typings for constructors with 9+ arguments
|
||||
constructor(
|
||||
parent: HTMLElement,
|
||||
workbenchContainer: HTMLElement,
|
||||
parts: {
|
||||
titlebar: Part,
|
||||
activitybar: Part,
|
||||
editor: Part,
|
||||
sidebar: Part,
|
||||
panel: Part,
|
||||
statusbar: Part
|
||||
private parent: HTMLElement,
|
||||
private workbenchContainer: HTMLElement,
|
||||
private parts: {
|
||||
titlebar: TitlebarPart,
|
||||
activitybar: ActivitybarPart,
|
||||
editor: EditorPart,
|
||||
sidebar: SidebarPart,
|
||||
panel: PanelPart,
|
||||
statusbar: StatusbarPart
|
||||
},
|
||||
quickopen: QuickOpenController,
|
||||
quickInput: QuickInputService,
|
||||
notificationsCenter: NotificationsCenter,
|
||||
notificationsToasts: NotificationsToasts,
|
||||
private quickopen: QuickOpenController,
|
||||
private quickInput: QuickInputService,
|
||||
private notificationsCenter: NotificationsCenter,
|
||||
private notificationsToasts: NotificationsToasts,
|
||||
@IStorageService private storageService: IStorageService,
|
||||
@IContextViewService private contextViewService: IContextViewService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IEditorGroupService private editorGroupService: IEditorGroupService,
|
||||
@IPartService private partService: IPartService,
|
||||
@IViewletService private viewletService: IViewletService,
|
||||
@IThemeService themeService: IThemeService
|
||||
@IThemeService private themeService: IThemeService,
|
||||
@IEditorGroupsService private editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
this.parent = parent;
|
||||
this.workbenchContainer = workbenchContainer;
|
||||
this.titlebar = parts.titlebar;
|
||||
this.activitybar = parts.activitybar;
|
||||
this.editor = parts.editor;
|
||||
this.sidebar = parts.sidebar;
|
||||
this.panel = parts.panel;
|
||||
this.statusbar = parts.statusbar;
|
||||
this.quickopen = quickopen;
|
||||
this.quickInput = quickInput;
|
||||
this.notificationsCenter = notificationsCenter;
|
||||
this.notificationsToasts = notificationsToasts;
|
||||
this.toUnbind = [];
|
||||
this.panelSizeBeforeMaximized = this.storageService.getInteger(WorkbenchLayout.panelSizeBeforeMaximizedKey, StorageScope.GLOBAL, 0);
|
||||
this.panelMaximized = false;
|
||||
super();
|
||||
|
||||
this.sashXOne = new Sash(this.workbenchContainer, this, {
|
||||
baseSize: 5
|
||||
});
|
||||
// Restore state
|
||||
this.restorePreviousState();
|
||||
|
||||
this.sashXTwo = new Sash(this.workbenchContainer, this, {
|
||||
baseSize: 5
|
||||
});
|
||||
// Create layout sashes
|
||||
this.sashXOne = new Sash(this.workbenchContainer, this);
|
||||
this.sashXTwo = new Sash(this.workbenchContainer, this);
|
||||
this.sashY = new Sash(this.workbenchContainer, this, { orientation: Orientation.HORIZONTAL });
|
||||
|
||||
this.sashY = new Sash(this.workbenchContainer, this, {
|
||||
baseSize: 4,
|
||||
orientation: Orientation.HORIZONTAL
|
||||
});
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private restorePreviousState(): void {
|
||||
this._sidebarWidth = Math.max(this.partLayoutInfo.sidebar.minWidth, this.storageService.getInteger(WorkbenchLayout.sashXOneWidthSettingsKey, StorageScope.GLOBAL, DEFAULT_SIDEBAR_PART_WIDTH));
|
||||
this._panelHeight = Math.max(this.partLayoutInfo.panel.minHeight, this.storageService.getInteger(WorkbenchLayout.sashYHeightSettingsKey, StorageScope.GLOBAL, DEFAULT_PANEL_PART_SIZE));
|
||||
|
||||
this._panelWidth = Math.max(this.partLayoutInfo.panel.minWidth, this.storageService.getInteger(WorkbenchLayout.sashXTwoWidthSettingsKey, StorageScope.GLOBAL, DEFAULT_PANEL_PART_SIZE));
|
||||
this._panelHeight = Math.max(this.partLayoutInfo.panel.minHeight, this.storageService.getInteger(WorkbenchLayout.sashYHeightSettingsKey, StorageScope.GLOBAL, DEFAULT_PANEL_PART_SIZE));
|
||||
|
||||
this.layoutEditorGroupsVertically = (this.editorGroupService.getGroupOrientation() !== 'horizontal');
|
||||
this.panelMaximized = false;
|
||||
this.panelSizeBeforeMaximized = this.storageService.getInteger(WorkbenchLayout.panelSizeBeforeMaximizedKey, StorageScope.GLOBAL, 0);
|
||||
}
|
||||
|
||||
this.toUnbind.push(themeService.onThemeChange(_ => this.layout()));
|
||||
this.toUnbind.push(editorGroupService.onEditorsChanged(() => this.onEditorsChanged()));
|
||||
this.toUnbind.push(editorGroupService.onGroupOrientationChanged(e => this.onGroupOrientationChanged()));
|
||||
private registerListeners(): void {
|
||||
this._register(this.themeService.onThemeChange(_ => this.layout()));
|
||||
this._register(this.parts.editor.onDidSizeConstraintsChange(() => this.onDidEditorSizeConstraintsChange()));
|
||||
|
||||
this.registerSashListeners();
|
||||
}
|
||||
|
||||
private get editorCountForHeight(): number {
|
||||
return Math.max(1, this.editorGroupService.getGroupOrientation() === 'horizontal' ? this.editorGroupService.getStacksModel().groups.length : 1);
|
||||
}
|
||||
private onDidEditorSizeConstraintsChange(): void {
|
||||
if (this.workbenchSize && (this.sidebarWidth || this.panelHeight)) {
|
||||
if (this.editorGroupService.count > 1) {
|
||||
const minimumEditorPartSize = new Dimension(this.parts.editor.minimumWidth, this.parts.editor.minimumHeight);
|
||||
|
||||
private get editorCountForWidth(): number {
|
||||
return Math.max(1, this.editorGroupService.getGroupOrientation() === 'vertical' ? this.editorGroupService.getStacksModel().groups.length : 1);
|
||||
const sidebarOverflow = this.workbenchSize.width - this.sidebarWidth < minimumEditorPartSize.width;
|
||||
|
||||
let panelOverflow = false;
|
||||
if (this.partService.getPanelPosition() === Position.RIGHT) {
|
||||
panelOverflow = this.workbenchSize.width - this.panelWidth - this.sidebarWidth < minimumEditorPartSize.width;
|
||||
} else {
|
||||
panelOverflow = this.workbenchSize.height - this.panelHeight < minimumEditorPartSize.height;
|
||||
}
|
||||
|
||||
// Trigger a layout if we detect that either sidebar or panel overflow
|
||||
// as a matter of a new editor group being added to the editor part
|
||||
if (sidebarOverflow || panelOverflow) {
|
||||
this.layout();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private get activitybarWidth(): number {
|
||||
@@ -195,12 +181,22 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
}
|
||||
|
||||
private computeMaxPanelWidth(): number {
|
||||
const minSidebarSize = this.partService.isVisible(Parts.SIDEBAR_PART) ? (this.partService.getSideBarPosition() === Position.LEFT ? this.partLayoutInfo.sidebar.minWidth : this.sidebarWidth) : 0;
|
||||
return Math.max(this.partLayoutInfo.panel.minWidth, this.workbenchSize.width - this.editorCountForWidth * this.partLayoutInfo.editor.minWidth - minSidebarSize - this.activitybarWidth);
|
||||
let minSidebarWidth: number;
|
||||
if (this.partService.isVisible(Parts.SIDEBAR_PART)) {
|
||||
if (this.partService.getSideBarPosition() === Position.LEFT) {
|
||||
minSidebarWidth = this.partLayoutInfo.sidebar.minWidth;
|
||||
} else {
|
||||
minSidebarWidth = this.sidebarWidth;
|
||||
}
|
||||
} else {
|
||||
minSidebarWidth = 0;
|
||||
}
|
||||
|
||||
return Math.max(this.partLayoutInfo.panel.minWidth, this.workbenchSize.width - this.parts.editor.minimumWidth - minSidebarWidth - this.activitybarWidth);
|
||||
}
|
||||
|
||||
private computeMaxPanelHeight(): number {
|
||||
return Math.max(this.partLayoutInfo.panel.minHeight, this.sidebarHeight - this.editorCountForHeight * this.partLayoutInfo.editor.minHeight);
|
||||
return Math.max(this.partLayoutInfo.panel.minHeight, this.sidebarHeight /* simplification for: window.height - status.height - title-height */ - this.parts.editor.minimumHeight);
|
||||
}
|
||||
|
||||
private get sidebarWidth(): number {
|
||||
@@ -213,12 +209,13 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
|
||||
private set sidebarWidth(value: number) {
|
||||
const panelMinWidth = this.partService.getPanelPosition() === Position.RIGHT && this.partService.isVisible(Parts.PANEL_PART) ? this.partLayoutInfo.panel.minWidth : 0;
|
||||
const maxSidebarWidth = this.workbenchSize.width - this.activitybarWidth - this.editorCountForWidth * this.partLayoutInfo.editor.minWidth - panelMinWidth;
|
||||
const maxSidebarWidth = this.workbenchSize.width - this.activitybarWidth - this.parts.editor.minimumWidth - panelMinWidth;
|
||||
|
||||
this._sidebarWidth = Math.max(this.partLayoutInfo.sidebar.minWidth, Math.min(maxSidebarWidth, value));
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get partLayoutInfo(): PartLayoutInfo {
|
||||
public get partLayoutInfo() {
|
||||
return {
|
||||
titlebar: {
|
||||
height: TITLE_BAR_HEIGHT
|
||||
@@ -233,10 +230,6 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
minHeight: MIN_PANEL_PART_HEIGHT,
|
||||
minWidth: MIN_PANEL_PART_WIDTH
|
||||
},
|
||||
editor: {
|
||||
minWidth: MIN_EDITOR_PART_WIDTH,
|
||||
minHeight: MIN_EDITOR_PART_HEIGHT
|
||||
},
|
||||
statusbar: {
|
||||
height: STATUS_BAR_HEIGHT
|
||||
}
|
||||
@@ -251,22 +244,22 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
let startPanelHeight: number;
|
||||
let startPanelWidth: number;
|
||||
|
||||
this.toUnbind.push(this.sashXOne.onDidStart((e: ISashEvent) => {
|
||||
this._register(this.sashXOne.onDidStart((e: ISashEvent) => {
|
||||
startSidebarWidth = this.sidebarWidth;
|
||||
startX = e.startX;
|
||||
}));
|
||||
|
||||
this.toUnbind.push(this.sashY.onDidStart((e: ISashEvent) => {
|
||||
this._register(this.sashY.onDidStart((e: ISashEvent) => {
|
||||
startPanelHeight = this.panelHeight;
|
||||
startY = e.startY;
|
||||
}));
|
||||
|
||||
this.toUnbind.push(this.sashXTwo.onDidStart((e: ISashEvent) => {
|
||||
this._register(this.sashXTwo.onDidStart((e: ISashEvent) => {
|
||||
startPanelWidth = this.panelWidth;
|
||||
startXTwo = e.startX;
|
||||
}));
|
||||
|
||||
this.toUnbind.push(this.sashXOne.onDidChange((e: ISashEvent) => {
|
||||
this._register(this.sashXOne.onDidChange((e: ISashEvent) => {
|
||||
let doLayout = false;
|
||||
let sidebarPosition = this.partService.getSideBarPosition();
|
||||
let isSidebarVisible = this.partService.isVisible(Parts.SIDEBAR_PART);
|
||||
@@ -306,7 +299,7 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
}
|
||||
}));
|
||||
|
||||
this.toUnbind.push(this.sashY.onDidChange((e: ISashEvent) => {
|
||||
this._register(this.sashY.onDidChange((e: ISashEvent) => {
|
||||
let doLayout = false;
|
||||
let isPanelVisible = this.partService.isVisible(Parts.PANEL_PART);
|
||||
let newSashHeight = startPanelHeight - (e.currentY - startY);
|
||||
@@ -344,7 +337,7 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
}
|
||||
}));
|
||||
|
||||
this.toUnbind.push(this.sashXTwo.onDidChange((e: ISashEvent) => {
|
||||
this._register(this.sashXTwo.onDidChange((e: ISashEvent) => {
|
||||
let doLayout = false;
|
||||
let isPanelVisible = this.partService.isVisible(Parts.PANEL_PART);
|
||||
let newSashWidth = startPanelWidth - (e.currentX - startXTwo);
|
||||
@@ -382,25 +375,25 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
}
|
||||
}));
|
||||
|
||||
this.toUnbind.push(this.sashXOne.onDidEnd(() => {
|
||||
this._register(this.sashXOne.onDidEnd(() => {
|
||||
this.storageService.store(WorkbenchLayout.sashXOneWidthSettingsKey, this.sidebarWidth, StorageScope.GLOBAL);
|
||||
}));
|
||||
|
||||
this.toUnbind.push(this.sashY.onDidEnd(() => {
|
||||
this._register(this.sashY.onDidEnd(() => {
|
||||
this.storageService.store(WorkbenchLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL);
|
||||
}));
|
||||
|
||||
this.toUnbind.push(this.sashXTwo.onDidEnd(() => {
|
||||
this._register(this.sashXTwo.onDidEnd(() => {
|
||||
this.storageService.store(WorkbenchLayout.sashXTwoWidthSettingsKey, this.panelWidth, StorageScope.GLOBAL);
|
||||
}));
|
||||
|
||||
this.toUnbind.push(this.sashY.onDidReset(() => {
|
||||
this._register(this.sashY.onDidReset(() => {
|
||||
this.panelHeight = this.sidebarHeight * DEFAULT_PANEL_SIZE_COEFFICIENT;
|
||||
this.storageService.store(WorkbenchLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL);
|
||||
this.layout();
|
||||
}));
|
||||
|
||||
this.toUnbind.push(this.sashXOne.onDidReset(() => {
|
||||
this._register(this.sashXOne.onDidReset(() => {
|
||||
let activeViewlet = this.viewletService.getActiveViewlet();
|
||||
let optimalWidth = activeViewlet && activeViewlet.getOptimalWidth();
|
||||
this.sidebarWidth = Math.max(optimalWidth, DEFAULT_SIDEBAR_PART_WIDTH);
|
||||
@@ -408,45 +401,14 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
this.partService.setSideBarHidden(false).done(() => this.layout(), errors.onUnexpectedError);
|
||||
}));
|
||||
|
||||
this.toUnbind.push(this.sashXTwo.onDidReset(() => {
|
||||
this._register(this.sashXTwo.onDidReset(() => {
|
||||
this.panelWidth = (this.workbenchSize.width - this.sidebarWidth - this.activitybarWidth) * DEFAULT_PANEL_SIZE_COEFFICIENT;
|
||||
this.storageService.store(WorkbenchLayout.sashXTwoWidthSettingsKey, this.panelWidth, StorageScope.GLOBAL);
|
||||
this.layout();
|
||||
}));
|
||||
}
|
||||
|
||||
private onEditorsChanged(): void {
|
||||
|
||||
// Make sure that we layout properly in case we detect that the sidebar or panel is large enought to cause
|
||||
// multiple opened editors to go below minimal size. The fix is to trigger a layout for any editor
|
||||
// input change that falls into this category.
|
||||
if (this.workbenchSize && (this.sidebarWidth || this.panelHeight)) {
|
||||
let visibleEditors = this.editorService.getVisibleEditors().length;
|
||||
const panelVertical = this.partService.getPanelPosition() === Position.RIGHT;
|
||||
if (visibleEditors > 1) {
|
||||
const sidebarOverflow = this.layoutEditorGroupsVertically && (this.workbenchSize.width - this.sidebarWidth < visibleEditors * this.partLayoutInfo.editor.minWidth);
|
||||
const panelOverflow = !this.layoutEditorGroupsVertically && !panelVertical && (this.workbenchSize.height - this.panelHeight < visibleEditors * this.partLayoutInfo.editor.minHeight) ||
|
||||
panelVertical && this.layoutEditorGroupsVertically && (this.workbenchSize.width - this.panelWidth - this.sidebarWidth < visibleEditors * this.partLayoutInfo.editor.minWidth);
|
||||
|
||||
if (sidebarOverflow || panelOverflow) {
|
||||
this.layout();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onGroupOrientationChanged(): void {
|
||||
const newLayoutEditorGroupsVertically = (this.editorGroupService.getGroupOrientation() !== 'horizontal');
|
||||
|
||||
const doLayout = this.layoutEditorGroupsVertically !== newLayoutEditorGroupsVertically;
|
||||
this.layoutEditorGroupsVertically = newLayoutEditorGroupsVertically;
|
||||
|
||||
if (doLayout) {
|
||||
this.layout();
|
||||
}
|
||||
}
|
||||
|
||||
public layout(options?: ILayoutOptions): void {
|
||||
layout(options?: ILayoutOptions): void {
|
||||
this.workbenchSize = getClientArea(this.parent);
|
||||
|
||||
const isActivityBarHidden = !this.partService.isVisible(Parts.ACTIVITYBAR_PART);
|
||||
@@ -456,6 +418,7 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
const isSidebarHidden = !this.partService.isVisible(Parts.SIDEBAR_PART);
|
||||
const sidebarPosition = this.partService.getSideBarPosition();
|
||||
const panelPosition = this.partService.getPanelPosition();
|
||||
const menubarVisibility = this.partService.getMenubarVisibility();
|
||||
|
||||
// Sidebar
|
||||
if (this.sidebarWidth === -1) {
|
||||
@@ -463,7 +426,7 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
}
|
||||
|
||||
this.statusbarHeight = isStatusbarHidden ? 0 : this.partLayoutInfo.statusbar.height;
|
||||
this.titlebarHeight = isTitlebarHidden ? 0 : this.partLayoutInfo.titlebar.height / getZoomFactor(); // adjust for zoom prevention
|
||||
this.titlebarHeight = isTitlebarHidden ? 0 : this.partLayoutInfo.titlebar.height / (!menubarVisibility || menubarVisibility === 'hidden' ? getZoomFactor() : 1); // adjust for zoom prevention
|
||||
|
||||
this.sidebarHeight = this.workbenchSize.height - this.statusbarHeight - this.titlebarHeight;
|
||||
let sidebarSize = new Dimension(this.sidebarWidth, this.sidebarHeight);
|
||||
@@ -525,41 +488,45 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
editorSize.width = this.workbenchSize.width - sidebarSize.width - activityBarSize.width - (panelPosition === Position.RIGHT ? panelDimension.width : 0);
|
||||
editorSize.height = sidebarSize.height - (panelPosition === Position.BOTTOM ? panelDimension.height : 0);
|
||||
|
||||
// Assert Sidebar and Editor Size to not overflow
|
||||
let editorMinWidth = this.partLayoutInfo.editor.minWidth;
|
||||
let editorMinHeight = this.partLayoutInfo.editor.minHeight;
|
||||
let visibleEditorCount = this.editorService.getVisibleEditors().length;
|
||||
if (visibleEditorCount > 1) {
|
||||
if (this.layoutEditorGroupsVertically) {
|
||||
editorMinWidth *= visibleEditorCount; // when editors layout vertically, multiply the min editor width by number of visible editors
|
||||
} else {
|
||||
editorMinHeight *= visibleEditorCount; // when editors layout horizontally, multiply the min editor height by number of visible editors
|
||||
// Adjust for Editor Part minimum width
|
||||
const minimumEditorPartSize = new Dimension(this.parts.editor.minimumWidth, this.parts.editor.minimumHeight);
|
||||
if (editorSize.width < minimumEditorPartSize.width) {
|
||||
const missingPreferredEditorWidth = minimumEditorPartSize.width - editorSize.width;
|
||||
let outstandingMissingPreferredEditorWidth = missingPreferredEditorWidth;
|
||||
|
||||
// Take from Panel if Panel Position on the Right and Visible
|
||||
if (!isPanelHidden && panelPosition === Position.RIGHT && (!options || options.source !== Parts.PANEL_PART)) {
|
||||
const oldPanelWidth = panelDimension.width;
|
||||
panelDimension.width = Math.max(this.partLayoutInfo.panel.minWidth, panelDimension.width - outstandingMissingPreferredEditorWidth);
|
||||
outstandingMissingPreferredEditorWidth -= oldPanelWidth - panelDimension.width;
|
||||
}
|
||||
|
||||
// Take from Sidebar if Visible
|
||||
if (!isSidebarHidden && outstandingMissingPreferredEditorWidth > 0) {
|
||||
const oldSidebarWidth = sidebarSize.width;
|
||||
sidebarSize.width = Math.max(this.partLayoutInfo.sidebar.minWidth, sidebarSize.width - outstandingMissingPreferredEditorWidth);
|
||||
outstandingMissingPreferredEditorWidth -= oldSidebarWidth - sidebarSize.width;
|
||||
}
|
||||
|
||||
editorSize.width += missingPreferredEditorWidth - outstandingMissingPreferredEditorWidth;
|
||||
if (!isPanelHidden && panelPosition === Position.BOTTOM) {
|
||||
panelDimension.width = editorSize.width; // ensure panel width is always following editor width
|
||||
}
|
||||
}
|
||||
|
||||
if (editorSize.width < editorMinWidth) {
|
||||
let diff = editorMinWidth - editorSize.width;
|
||||
editorSize.width = editorMinWidth;
|
||||
if (panelPosition === Position.BOTTOM) {
|
||||
panelDimension.width = editorMinWidth;
|
||||
} else if (panelDimension.width >= diff && (!options || options.source !== Parts.PANEL_PART)) {
|
||||
const oldWidth = panelDimension.width;
|
||||
panelDimension.width = Math.max(this.partLayoutInfo.panel.minWidth, panelDimension.width - diff);
|
||||
diff = diff - (oldWidth - panelDimension.width);
|
||||
// Adjust for Editor Part minimum height
|
||||
if (editorSize.height < minimumEditorPartSize.height) {
|
||||
const missingPreferredEditorHeight = minimumEditorPartSize.height - editorSize.height;
|
||||
let outstandingMissingPreferredEditorHeight = missingPreferredEditorHeight;
|
||||
|
||||
// Take from Panel if Panel Position on the Bottom and Visible
|
||||
if (!isPanelHidden && panelPosition === Position.BOTTOM) {
|
||||
const oldPanelHeight = panelDimension.height;
|
||||
panelDimension.height = Math.max(this.partLayoutInfo.panel.minHeight, panelDimension.height - outstandingMissingPreferredEditorHeight);
|
||||
outstandingMissingPreferredEditorHeight -= oldPanelHeight - panelDimension.height;
|
||||
}
|
||||
|
||||
if (sidebarSize.width >= diff) {
|
||||
sidebarSize.width -= diff;
|
||||
sidebarSize.width = Math.max(this.partLayoutInfo.sidebar.minWidth, sidebarSize.width);
|
||||
}
|
||||
}
|
||||
|
||||
if (editorSize.height < editorMinHeight && panelPosition === Position.BOTTOM) {
|
||||
let diff = editorMinHeight - editorSize.height;
|
||||
editorSize.height = editorMinHeight;
|
||||
|
||||
panelDimension.height -= diff;
|
||||
panelDimension.height = Math.max(this.partLayoutInfo.panel.minHeight, panelDimension.height);
|
||||
editorSize.height += missingPreferredEditorHeight - outstandingMissingPreferredEditorHeight;
|
||||
}
|
||||
|
||||
if (!isSidebarHidden) {
|
||||
@@ -591,7 +558,7 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
}
|
||||
|
||||
// Title Part
|
||||
const titleContainer = this.titlebar.getContainer();
|
||||
const titleContainer = this.parts.titlebar.getContainer();
|
||||
if (isTitlebarHidden) {
|
||||
hide(titleContainer);
|
||||
} else {
|
||||
@@ -599,8 +566,8 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
}
|
||||
|
||||
// Editor Part and Panel part
|
||||
const editorContainer = this.editor.getContainer();
|
||||
const panelContainer = this.panel.getContainer();
|
||||
const editorContainer = this.parts.editor.getContainer();
|
||||
const panelContainer = this.parts.panel.getContainer();
|
||||
size(editorContainer, editorSize.width, editorSize.height);
|
||||
size(panelContainer, panelDimension.width, panelDimension.height);
|
||||
|
||||
@@ -623,13 +590,13 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
}
|
||||
|
||||
// Activity Bar Part
|
||||
const activitybarContainer = this.activitybar.getContainer();
|
||||
const activitybarContainer = this.parts.activitybar.getContainer();
|
||||
size(activitybarContainer, null, activityBarSize.height);
|
||||
if (sidebarPosition === Position.LEFT) {
|
||||
this.activitybar.getContainer().style.right = '';
|
||||
this.parts.activitybar.getContainer().style.right = '';
|
||||
position(activitybarContainer, this.titlebarHeight, null, 0, 0);
|
||||
} else {
|
||||
this.activitybar.getContainer().style.left = '';
|
||||
this.parts.activitybar.getContainer().style.left = '';
|
||||
position(activitybarContainer, this.titlebarHeight, 0, 0, null);
|
||||
}
|
||||
if (isActivityBarHidden) {
|
||||
@@ -639,7 +606,7 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
}
|
||||
|
||||
// Sidebar Part
|
||||
const sidebarContainer = this.sidebar.getContainer();
|
||||
const sidebarContainer = this.parts.sidebar.getContainer();
|
||||
size(sidebarContainer, sidebarSize.width, sidebarSize.height);
|
||||
const editorAndPanelWidth = editorSize.width + (panelPosition === Position.RIGHT ? panelWidth : 0);
|
||||
if (sidebarPosition === Position.LEFT) {
|
||||
@@ -649,7 +616,7 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
}
|
||||
|
||||
// Statusbar Part
|
||||
const statusbarContainer = this.statusbar.getContainer();
|
||||
const statusbarContainer = this.parts.statusbar.getContainer();
|
||||
position(statusbarContainer, this.workbenchSize.height - this.statusbarHeight);
|
||||
if (isStatusbarHidden) {
|
||||
hide(statusbarContainer);
|
||||
@@ -680,21 +647,21 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
}
|
||||
|
||||
// Propagate to Part Layouts
|
||||
this.titlebar.layout(new Dimension(this.workbenchSize.width, this.titlebarHeight));
|
||||
this.editor.layout(new Dimension(editorSize.width, editorSize.height));
|
||||
this.sidebar.layout(sidebarSize);
|
||||
this.panel.layout(panelDimension);
|
||||
this.activitybar.layout(activityBarSize);
|
||||
this.parts.titlebar.layout(new Dimension(this.workbenchSize.width, this.titlebarHeight));
|
||||
this.parts.editor.layout(new Dimension(editorSize.width, editorSize.height));
|
||||
this.parts.sidebar.layout(sidebarSize);
|
||||
this.parts.panel.layout(panelDimension);
|
||||
this.parts.activitybar.layout(activityBarSize);
|
||||
|
||||
// Propagate to Context View
|
||||
this.contextViewService.layout();
|
||||
}
|
||||
|
||||
public getVerticalSashTop(sash: Sash): number {
|
||||
getVerticalSashTop(sash: Sash): number {
|
||||
return this.titlebarHeight;
|
||||
}
|
||||
|
||||
public getVerticalSashLeft(sash: Sash): number {
|
||||
getVerticalSashLeft(sash: Sash): number {
|
||||
let sidebarPosition = this.partService.getSideBarPosition();
|
||||
if (sash === this.sashXOne) {
|
||||
|
||||
@@ -708,7 +675,7 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
return this.workbenchSize.width - this.panelWidth - (sidebarPosition === Position.RIGHT ? this.sidebarWidth + this.activitybarWidth : 0);
|
||||
}
|
||||
|
||||
public getVerticalSashHeight(sash: Sash): number {
|
||||
getVerticalSashHeight(sash: Sash): number {
|
||||
if (sash === this.sashXTwo && !this.partService.isVisible(Parts.PANEL_PART)) {
|
||||
return 0;
|
||||
}
|
||||
@@ -716,12 +683,12 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
return this.sidebarHeight;
|
||||
}
|
||||
|
||||
public getHorizontalSashTop(sash: Sash): number {
|
||||
// Horizontal sash should be a bit lower than the editor area, thus add 2px #5524
|
||||
return 2 + (this.partService.isVisible(Parts.PANEL_PART) ? this.sidebarHeight - this.panelHeight + this.titlebarHeight : this.sidebarHeight + this.titlebarHeight);
|
||||
getHorizontalSashTop(sash: Sash): number {
|
||||
const offset = 2; // Horizontal sash should be a bit lower than the editor area, thus add 2px #5524
|
||||
return offset + (this.partService.isVisible(Parts.PANEL_PART) ? this.sidebarHeight - this.panelHeight + this.titlebarHeight : this.sidebarHeight + this.titlebarHeight);
|
||||
}
|
||||
|
||||
public getHorizontalSashLeft(sash: Sash): number {
|
||||
getHorizontalSashLeft(sash: Sash): number {
|
||||
if (this.partService.getSideBarPosition() === Position.RIGHT) {
|
||||
return 0;
|
||||
}
|
||||
@@ -729,28 +696,27 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
return this.sidebarWidth + this.activitybarWidth;
|
||||
}
|
||||
|
||||
public getHorizontalSashWidth(sash: Sash): number {
|
||||
getHorizontalSashWidth(sash: Sash): number {
|
||||
return this.panelWidth;
|
||||
}
|
||||
|
||||
public isPanelMaximized(): boolean {
|
||||
isPanelMaximized(): boolean {
|
||||
return this.panelMaximized;
|
||||
}
|
||||
|
||||
// change part size along the main axis
|
||||
public resizePart(part: Parts, sizeChange: number): void {
|
||||
resizePart(part: Parts, sizeChange: number): void {
|
||||
const panelPosition = this.partService.getPanelPosition();
|
||||
const sizeChangePxWidth = this.workbenchSize.width * (sizeChange / 100);
|
||||
const sizeChangePxHeight = this.workbenchSize.height * (sizeChange / 100);
|
||||
|
||||
let doLayout = false;
|
||||
|
||||
switch (part) {
|
||||
case Parts.SIDEBAR_PART:
|
||||
this.sidebarWidth = this.sidebarWidth + sizeChangePxWidth; // Sidebar can not become smaller than MIN_PART_WIDTH
|
||||
|
||||
if (this.layoutEditorGroupsVertically && (this.workbenchSize.width - this.sidebarWidth < this.editorCountForWidth * MIN_EDITOR_PART_WIDTH)) {
|
||||
this.sidebarWidth = (this.workbenchSize.width - this.editorCountForWidth * MIN_EDITOR_PART_WIDTH);
|
||||
if (this.workbenchSize.width - this.sidebarWidth < this.parts.editor.minimumWidth) {
|
||||
this.sidebarWidth = this.workbenchSize.width - this.parts.editor.minimumWidth;
|
||||
}
|
||||
|
||||
doLayout = true;
|
||||
@@ -767,7 +733,7 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
case Parts.EDITOR_PART:
|
||||
// If we have one editor we can cheat and resize sidebar with the negative delta
|
||||
// If the sidebar is not visible and panel is, resize panel main axis with negative Delta
|
||||
if (this.editorCountForWidth === 1) {
|
||||
if (this.editorGroupService.count === 1) {
|
||||
if (this.partService.isVisible(Parts.SIDEBAR_PART)) {
|
||||
this.sidebarWidth = this.sidebarWidth - sizeChangePxWidth;
|
||||
doLayout = true;
|
||||
@@ -779,13 +745,11 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
}
|
||||
doLayout = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
const stacks = this.editorGroupService.getStacksModel();
|
||||
const activeGroup = stacks.positionOfGroup(stacks.activeGroup);
|
||||
const activeGroup = this.editorGroupService.activeGroup;
|
||||
|
||||
this.editorGroupService.resizeGroup(activeGroup, sizeChangePxWidth);
|
||||
doLayout = false;
|
||||
const activeGroupSize = this.editorGroupService.getSize(activeGroup);
|
||||
this.editorGroupService.setSize(activeGroup, activeGroupSize + sizeChangePxWidth);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -793,11 +757,4 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal
|
||||
this.layout();
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this.toUnbind) {
|
||||
dispose(this.toUnbind);
|
||||
this.toUnbind = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,7 @@
|
||||
display: none; /* Parts have to opt in to show title area */
|
||||
}
|
||||
|
||||
/* title styles are defined for two classes because the editor puts the title into the content */
|
||||
|
||||
.monaco-workbench > .part > .title,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title {
|
||||
.monaco-workbench > .part > .title {
|
||||
height: 35px;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
@@ -33,9 +30,15 @@
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .title > .title-label span {
|
||||
.monaco-workbench > .part > .title > .title-label h2 {
|
||||
font-size: 11px;
|
||||
cursor: default;
|
||||
font-weight: normal;
|
||||
-webkit-margin-before: 0;
|
||||
-webkit-margin-after: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .title > .title-label a {
|
||||
@@ -68,7 +71,8 @@
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .content .monaco-progress-container {
|
||||
.monaco-workbench > .part > .content > .monaco-progress-container,
|
||||
.monaco-workbench > .part.editor > .content .monaco-progress-container {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 33px; /* at the bottom of the 35px height title container */
|
||||
@@ -76,6 +80,7 @@
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part > .content .monaco-progress-container .progress-bit {
|
||||
.monaco-workbench > .part > .content > .monaco-progress-container .progress-bit,
|
||||
.monaco-workbench > .part.editor > .content .monaco-progress-container .progress-bit {
|
||||
height: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,28 +31,28 @@ export class PanelRegistry extends CompositeRegistry<Panel> {
|
||||
/**
|
||||
* Registers a panel to the platform.
|
||||
*/
|
||||
public registerPanel(descriptor: PanelDescriptor): void {
|
||||
registerPanel(descriptor: PanelDescriptor): void {
|
||||
super.registerComposite(descriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of registered panels known to the platform.
|
||||
*/
|
||||
public getPanels(): PanelDescriptor[] {
|
||||
getPanels(): PanelDescriptor[] {
|
||||
return this.getComposites() as PanelDescriptor[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the id of the panel that should open on startup by default.
|
||||
*/
|
||||
public setDefaultPanelId(id: string): void {
|
||||
setDefaultPanelId(id: string): void {
|
||||
this.defaultPanelId = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the id of the panel that should open on startup by default.
|
||||
*/
|
||||
public getDefaultPanelId(): string {
|
||||
getDefaultPanelId(): string {
|
||||
return this.defaultPanelId;
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ export abstract class TogglePanelAction extends Action {
|
||||
this.panelId = panelId;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
run(): TPromise<any> {
|
||||
|
||||
if (this.isPanelShowing()) {
|
||||
return this.partService.setPanelHidden(true);
|
||||
|
||||
@@ -47,7 +47,7 @@ export abstract class Part extends Component {
|
||||
*
|
||||
* Called to create title and content area of the part.
|
||||
*/
|
||||
public create(parent: HTMLElement): void {
|
||||
create(parent: HTMLElement): void {
|
||||
this.parent = parent;
|
||||
this.titleArea = this.createTitleArea(parent);
|
||||
this.contentArea = this.createContentArea(parent);
|
||||
@@ -60,7 +60,7 @@ export abstract class Part extends Component {
|
||||
/**
|
||||
* Returns the overall part container.
|
||||
*/
|
||||
public getContainer(): HTMLElement {
|
||||
getContainer(): HTMLElement {
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ export abstract class Part extends Component {
|
||||
/**
|
||||
* Layout title and content area in the given dimension.
|
||||
*/
|
||||
public layout(dimension: Dimension): Dimension[] {
|
||||
layout(dimension: Dimension): Dimension[] {
|
||||
return this.partLayout.layout(dimension);
|
||||
}
|
||||
}
|
||||
@@ -106,7 +106,7 @@ export class PartLayout {
|
||||
|
||||
constructor(container: HTMLElement, private options: IPartOptions, titleArea: HTMLElement, private contentArea: HTMLElement) { }
|
||||
|
||||
public layout(dimension: Dimension): Dimension[] {
|
||||
layout(dimension: Dimension): Dimension[] {
|
||||
const { width, height } = dimension;
|
||||
|
||||
// Return the applied sizes to title and content
|
||||
|
||||
@@ -21,8 +21,9 @@ import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colo
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { ActivityAction, ActivityActionItem, ICompositeBarColors } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
|
||||
import { ActivityAction, ActivityActionItem, ICompositeBarColors, ToggleCompositePinnedAction, ICompositeBar } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
export class ViewletActivityAction extends ActivityAction {
|
||||
|
||||
@@ -39,7 +40,7 @@ export class ViewletActivityAction extends ActivityAction {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
public run(event: any): TPromise<any> {
|
||||
run(event: any): TPromise<any> {
|
||||
if (event instanceof MouseEvent && event.button === 2) {
|
||||
return TPromise.as(false); // do not run on right click
|
||||
}
|
||||
@@ -85,7 +86,7 @@ export class ToggleViewletAction extends Action {
|
||||
super(_viewlet.id, _viewlet.name);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
run(): TPromise<any> {
|
||||
const sideBarVisible = this.partService.isVisible(Parts.SIDEBAR_PART);
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
|
||||
@@ -116,7 +117,7 @@ export class GlobalActivityActionItem extends ActivityActionItem {
|
||||
super(action, { draggable: false, colors, icon: true }, themeService);
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
// Context menus are triggered on mouse down so that an item can be picked
|
||||
@@ -158,6 +159,41 @@ export class GlobalActivityActionItem extends ActivityActionItem {
|
||||
}
|
||||
}
|
||||
|
||||
export class PlaceHolderViewletActivityAction extends ViewletActivityAction {
|
||||
|
||||
constructor(
|
||||
id: string, iconUrl: URI,
|
||||
@IViewletService viewletService: IViewletService,
|
||||
@IPartService partService: IPartService,
|
||||
@ITelemetryService telemetryService: ITelemetryService
|
||||
) {
|
||||
super({ id, name: id, cssClass: `extensionViewlet-placeholder-${id.replace(/\./g, '-')}` }, viewletService, partService, telemetryService);
|
||||
|
||||
const iconClass = `.monaco-workbench > .activitybar .monaco-action-bar .action-label.${this.class}`; // Generate Placeholder CSS to show the icon in the activity bar
|
||||
DOM.createCSSRule(iconClass, `-webkit-mask: url('${iconUrl || ''}') no-repeat 50% 50%`);
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
setActivity(activity: IActivity): void {
|
||||
this.activity = activity;
|
||||
this.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
export class PlaceHolderToggleCompositePinnedAction extends ToggleCompositePinnedAction {
|
||||
|
||||
constructor(id: string, compositeBar: ICompositeBar) {
|
||||
super({ id, name: id, cssClass: void 0 }, compositeBar);
|
||||
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
setActivity(activity: IActivity): void {
|
||||
this.label = activity.name;
|
||||
this.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
|
||||
// Styling with Outline color (e.g. high contrast theme)
|
||||
|
||||
@@ -7,85 +7,105 @@
|
||||
|
||||
import 'vs/css!./media/activitybarpart';
|
||||
import * as nls from 'vs/nls';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { illegalArgument } from 'vs/base/common/errors';
|
||||
import { $ } from 'vs/base/browser/builder';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { ActionsOrientation, ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { GlobalActivityExtensions, IGlobalActivityRegistry } from 'vs/workbench/common/activity';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Part } from 'vs/workbench/browser/part';
|
||||
import { GlobalActivityActionItem, GlobalActivityAction, ViewletActivityAction, ToggleViewletAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions';
|
||||
import { GlobalActivityActionItem, GlobalActivityAction, ViewletActivityAction, ToggleViewletAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewletActivityAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
import { IPartService, Parts, Position as SideBarPosition } from 'vs/workbench/services/part/common/partService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ToggleActivityBarVisibilityAction } from 'vs/workbench/browser/actions/toggleActivityBarVisibility';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { CompositeBar } from 'vs/workbench/browser/parts/compositebar/compositeBar';
|
||||
import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
|
||||
// {{SQL CARBON EDIT}}
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { scheduleAtNextAnimationFrame, Dimension } from 'vs/base/browser/dom';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { ViewLocation, ViewsRegistry } from 'vs/workbench/common/views';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
|
||||
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
|
||||
import { Dimension } from 'vs/base/browser/dom';
|
||||
|
||||
interface IPlaceholderComposite {
|
||||
id: string;
|
||||
iconUrl: URI;
|
||||
}
|
||||
|
||||
export class ActivitybarPart extends Part {
|
||||
|
||||
private static readonly ACTION_HEIGHT = 50;
|
||||
private static readonly PINNED_VIEWLETS = 'workbench.activity.pinnedViewlets';
|
||||
private static readonly PLACEHOLDER_VIEWLETS = 'workbench.activity.placeholderViewlets';
|
||||
private static readonly COLORS = {
|
||||
backgroundColor: ACTIVITY_BAR_FOREGROUND,
|
||||
badgeBackground: ACTIVITY_BAR_BADGE_BACKGROUND,
|
||||
badgeForeground: ACTIVITY_BAR_BADGE_FOREGROUND,
|
||||
dragAndDropBackground: ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND
|
||||
};
|
||||
private static readonly ACTION_HEIGHT = 50;
|
||||
|
||||
public _serviceBrand: any;
|
||||
|
||||
private dimension: Dimension;
|
||||
|
||||
private globalActionBar: ActionBar;
|
||||
private globalActivityIdToActions: { [globalActivityId: string]: GlobalActivityAction; };
|
||||
private globalActivityIdToActions: { [globalActivityId: string]: GlobalActivityAction; } = Object.create(null);
|
||||
|
||||
private placeholderComposites: IPlaceholderComposite[] = [];
|
||||
private compositeBar: CompositeBar;
|
||||
private compositeActions: { [compositeId: string]: { activityAction: ViewletActivityAction, pinnedAction: ToggleCompositePinnedAction } } = Object.create(null);
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@IViewletService private viewletService: IViewletService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IPartService private partService: IPartService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
// {{SQL CARBON EDIT}}
|
||||
@IStorageService private storageService: IStorageService
|
||||
@ILifecycleService private lifecycleService: ILifecycleService,
|
||||
@IStorageService private storageService: IStorageService,
|
||||
@IExtensionService private extensionService: IExtensionService
|
||||
) {
|
||||
super(id, { hasTitle: false }, themeService);
|
||||
|
||||
this.globalActivityIdToActions = Object.create(null);
|
||||
|
||||
this.compositeBar = this.instantiationService.createInstance(CompositeBar, {
|
||||
this.compositeBar = this._register(this.instantiationService.createInstance(CompositeBar, {
|
||||
icon: true,
|
||||
storageId: ActivitybarPart.PINNED_VIEWLETS,
|
||||
orientation: ActionsOrientation.VERTICAL,
|
||||
composites: this.viewletService.getViewlets(),
|
||||
openComposite: (compositeId: string) => this.viewletService.openViewlet(compositeId, true),
|
||||
getActivityAction: (compositeId: string) => this.instantiationService.createInstance(ViewletActivityAction, this.viewletService.getViewlet(compositeId)),
|
||||
getCompositePinnedAction: (compositeId: string) => new ToggleCompositePinnedAction(this.viewletService.getViewlet(compositeId), this.compositeBar),
|
||||
getActivityAction: (compositeId: string) => this.getCompositeActions(compositeId).activityAction,
|
||||
getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction,
|
||||
getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(ToggleViewletAction, this.viewletService.getViewlet(compositeId)),
|
||||
getContextMenuActions: () => [this.instantiationService.createInstance(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, nls.localize('hideActivitBar', "Hide Activity Bar"))],
|
||||
getDefaultCompositeId: () => this.viewletService.getDefaultViewletId(),
|
||||
hidePart: () => this.partService.setSideBarHidden(true),
|
||||
compositeSize: 50,
|
||||
colors: ActivitybarPart.COLORS,
|
||||
overflowActionSize: ActivitybarPart.ACTION_HEIGHT
|
||||
});
|
||||
}));
|
||||
|
||||
const previousState = this.storageService.get(ActivitybarPart.PLACEHOLDER_VIEWLETS, StorageScope.GLOBAL, void 0);
|
||||
if (previousState) {
|
||||
let parsedPreviousState = <IPlaceholderComposite[]>JSON.parse(previousState);
|
||||
parsedPreviousState.forEach((s) => {
|
||||
if (typeof s.iconUrl === 'object') {
|
||||
s.iconUrl = URI.revive(s.iconUrl);
|
||||
} else {
|
||||
s.iconUrl = void 0;
|
||||
}
|
||||
});
|
||||
this.placeholderComposites = parsedPreviousState;
|
||||
} else {
|
||||
this.placeholderComposites = this.compositeBar.getCompositesFromStorage().map(id => (<IPlaceholderComposite>{ id, iconUrl: void 0 }));
|
||||
}
|
||||
|
||||
this.registerListeners();
|
||||
this.updateCompositebar();
|
||||
this.updatePlaceholderComposites();
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
@@ -101,27 +121,30 @@ export class ActivitybarPart extends Part {
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
this.toUnbind.push(this.viewletService.onDidViewletRegister(() => this.updateCompositebar()));
|
||||
this.toUnbind.push(ViewsRegistry.onViewsRegistered(() => this.updateCompositebar()));
|
||||
this.toUnbind.push(ViewsRegistry.onViewsDeregistered(() => this.updateCompositebar()));
|
||||
this._register(this.viewletService.onDidViewletRegister(() => this.updateCompositebar()));
|
||||
|
||||
// Activate viewlet action on opening of a viewlet
|
||||
this.toUnbind.push(this.viewletService.onDidViewletOpen(viewlet => this.compositeBar.activateComposite(viewlet.getId())));
|
||||
this._register(this.viewletService.onDidViewletOpen(viewlet => this.compositeBar.activateComposite(viewlet.getId())));
|
||||
|
||||
// Deactivate viewlet action on close
|
||||
this.toUnbind.push(this.viewletService.onDidViewletClose(viewlet => this.compositeBar.deactivateComposite(viewlet.getId())));
|
||||
this.toUnbind.push(this.compositeBar.onDidContextMenu(e => this.showContextMenu(e)));
|
||||
this.toUnbind.push(this.viewletService.onDidViewletEnablementChange(({ id, enabled }) => {
|
||||
this._register(this.viewletService.onDidViewletClose(viewlet => this.compositeBar.deactivateComposite(viewlet.getId())));
|
||||
this._register(this.viewletService.onDidViewletEnablementChange(({ id, enabled }) => {
|
||||
if (enabled) {
|
||||
this.compositeBar.addComposite(this.viewletService.getViewlet(id), true);
|
||||
this.compositeBar.addComposite(this.viewletService.getViewlet(id));
|
||||
} else {
|
||||
this.compositeBar.removeComposite(id);
|
||||
this.removeComposite(id);
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this.extensionService.onDidRegisterExtensions(() => this.onDidRegisterExtensions()));
|
||||
}
|
||||
|
||||
public showActivity(viewletOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable {
|
||||
private onDidRegisterExtensions(): void {
|
||||
this.removeNotExistingPlaceholderComposites();
|
||||
this.updateCompositebar();
|
||||
}
|
||||
|
||||
showActivity(viewletOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable {
|
||||
if (this.viewletService.getViewlet(viewletOrActionId)) {
|
||||
return this.compositeBar.showActivity(viewletOrActionId, badge, clazz, priority);
|
||||
}
|
||||
@@ -144,7 +167,7 @@ export class ActivitybarPart extends Part {
|
||||
return toDisposable(() => action.setBadge(undefined));
|
||||
}
|
||||
|
||||
public createContentArea(parent: HTMLElement): HTMLElement {
|
||||
createContentArea(parent: HTMLElement): HTMLElement {
|
||||
const $el = $(parent);
|
||||
const $result = $('.content').appendTo($el);
|
||||
|
||||
@@ -154,10 +177,31 @@ export class ActivitybarPart extends Part {
|
||||
// Top Actionbar with action items for each viewlet action
|
||||
this.createGlobalActivityActionBar($('.global-activity').appendTo($result).getHTMLElement());
|
||||
|
||||
// TODO@Ben: workaround for https://github.com/Microsoft/vscode/issues/45700
|
||||
// It looks like there are rendering glitches on macOS with Chrome 61 when
|
||||
// using --webkit-mask with a background color that is different from the image
|
||||
// The workaround is to promote the element onto its own drawing layer. We do
|
||||
// this only after the workbench has loaded because otherwise there is ugly flicker.
|
||||
if (isMacintosh) {
|
||||
this.lifecycleService.when(LifecyclePhase.Running).then(() => {
|
||||
scheduleAtNextAnimationFrame(() => { // another delay...
|
||||
scheduleAtNextAnimationFrame(() => { // ...to prevent more flickering on startup
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const activityBarForeground = theme.getColor(ACTIVITY_BAR_FOREGROUND);
|
||||
if (activityBarForeground && !activityBarForeground.equals(Color.white)) {
|
||||
// only apply this workaround if the color is different from the image one (white)
|
||||
collector.addRule('.monaco-workbench .activitybar > .content .monaco-action-bar .action-label { will-change: transform; }');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return $result.getHTMLElement();
|
||||
}
|
||||
|
||||
public updateStyles(): void {
|
||||
updateStyles(): void {
|
||||
super.updateStyles();
|
||||
|
||||
// Part container
|
||||
@@ -176,22 +220,6 @@ export class ActivitybarPart extends Part {
|
||||
container.style('border-left-color', !isPositionLeft ? borderColor : null);
|
||||
}
|
||||
|
||||
private showContextMenu(e: MouseEvent): void {
|
||||
const event = new StandardMouseEvent(e);
|
||||
|
||||
const actions: Action[] = this.viewletService.getViewlets()
|
||||
.filter(viewlet => this.canShow(viewlet))
|
||||
.map(viewlet => this.instantiationService.createInstance(ToggleCompositePinnedAction, viewlet, this.compositeBar));
|
||||
actions.push(new Separator());
|
||||
actions.push(this.instantiationService.createInstance(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, nls.localize('hideActivitBar', "Hide Activity Bar")));
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => { return { x: event.posx, y: event.posy }; },
|
||||
getActions: () => TPromise.as(actions),
|
||||
onHide: () => dispose(actions)
|
||||
});
|
||||
}
|
||||
|
||||
private createGlobalActivityActionBar(container: HTMLElement): void {
|
||||
const activityRegistry = Registry.as<IGlobalActivityRegistry>(GlobalActivityExtensions);
|
||||
const descriptors = activityRegistry.getActivities();
|
||||
@@ -199,13 +227,12 @@ export class ActivitybarPart extends Part {
|
||||
.map(d => this.instantiationService.createInstance(d))
|
||||
.map(a => new GlobalActivityAction(a));
|
||||
|
||||
this.globalActionBar = new ActionBar(container, {
|
||||
this.globalActionBar = this._register(new ActionBar(container, {
|
||||
actionItemProvider: a => this.instantiationService.createInstance(GlobalActivityActionItem, a, ActivitybarPart.COLORS),
|
||||
orientation: ActionsOrientation.VERTICAL,
|
||||
ariaLabel: nls.localize('globalActions', "Global Actions"),
|
||||
animated: false
|
||||
});
|
||||
this.toUnbind.push(this.globalActionBar);
|
||||
}));
|
||||
|
||||
actions.forEach(a => {
|
||||
this.globalActivityIdToActions[a.id] = a;
|
||||
@@ -213,34 +240,91 @@ export class ActivitybarPart extends Part {
|
||||
});
|
||||
}
|
||||
|
||||
private getCompositeActions(compositeId: string): { activityAction: ViewletActivityAction, pinnedAction: ToggleCompositePinnedAction } {
|
||||
let compositeActions = this.compositeActions[compositeId];
|
||||
if (!compositeActions) {
|
||||
const viewlet = this.viewletService.getViewlet(compositeId);
|
||||
if (viewlet) {
|
||||
compositeActions = {
|
||||
activityAction: this.instantiationService.createInstance(ViewletActivityAction, viewlet),
|
||||
pinnedAction: new ToggleCompositePinnedAction(viewlet, this.compositeBar)
|
||||
};
|
||||
} else {
|
||||
const placeHolderComposite = this.placeholderComposites.filter(c => c.id === compositeId)[0];
|
||||
compositeActions = {
|
||||
activityAction: this.instantiationService.createInstance(PlaceHolderViewletActivityAction, compositeId, placeHolderComposite.iconUrl),
|
||||
pinnedAction: new PlaceHolderToggleCompositePinnedAction(compositeId, this.compositeBar)
|
||||
};
|
||||
}
|
||||
|
||||
this.compositeActions[compositeId] = compositeActions;
|
||||
}
|
||||
|
||||
return compositeActions;
|
||||
}
|
||||
|
||||
private updateCompositebar(): void {
|
||||
const viewlets = this.viewletService.getViewlets();
|
||||
for (const viewlet of viewlets) {
|
||||
const canShow = this.canShow(viewlet);
|
||||
if (canShow) {
|
||||
this.compositeBar.addComposite(viewlet, false);
|
||||
} else {
|
||||
this.compositeBar.removeComposite(viewlet.id);
|
||||
this.compositeBar.addComposite(viewlet);
|
||||
|
||||
// Pin it by default if it is new => it does not has a placeholder
|
||||
if (this.placeholderComposites.every(c => c.id !== viewlet.id)) {
|
||||
this.compositeBar.pin(viewlet.id);
|
||||
}
|
||||
|
||||
this.enableCompositeActions(viewlet);
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
if (activeViewlet && activeViewlet.getId() === viewlet.id) {
|
||||
this.compositeBar.pin(viewlet.id);
|
||||
this.compositeBar.activateComposite(viewlet.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private canShow(viewlet: ViewletDescriptor): boolean {
|
||||
const viewLocation = ViewLocation.get(viewlet.id);
|
||||
if (viewLocation) {
|
||||
return ViewsRegistry.getViews(viewLocation).length > 0;
|
||||
private updatePlaceholderComposites(): void {
|
||||
const viewlets = this.viewletService.getViewlets();
|
||||
for (const { id } of this.placeholderComposites) {
|
||||
if (viewlets.every(viewlet => viewlet.id !== id)) {
|
||||
this.compositeBar.addComposite({ id, name: id, order: void 0 });
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public getPinned(): string[] {
|
||||
private removeNotExistingPlaceholderComposites(): void {
|
||||
const viewlets = this.viewletService.getViewlets();
|
||||
for (const { id } of this.placeholderComposites) {
|
||||
if (viewlets.every(viewlet => viewlet.id !== id)) {
|
||||
this.removeComposite(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private removeComposite(compositeId: string): void {
|
||||
this.compositeBar.removeComposite(compositeId);
|
||||
const compositeActions = this.compositeActions[compositeId];
|
||||
if (compositeActions) {
|
||||
compositeActions.activityAction.dispose();
|
||||
compositeActions.pinnedAction.dispose();
|
||||
delete this.compositeActions[compositeId];
|
||||
}
|
||||
}
|
||||
|
||||
private enableCompositeActions(viewlet: ViewletDescriptor): void {
|
||||
const { activityAction, pinnedAction } = this.getCompositeActions(viewlet.id);
|
||||
if (activityAction instanceof PlaceHolderViewletActivityAction) {
|
||||
activityAction.setActivity(viewlet);
|
||||
}
|
||||
if (pinnedAction instanceof PlaceHolderToggleCompositePinnedAction) {
|
||||
pinnedAction.setActivity(viewlet);
|
||||
}
|
||||
}
|
||||
|
||||
getPinned(): string[] {
|
||||
return this.viewletService.getViewlets().map(v => v.id).filter(id => this.compositeBar.isPinned(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout title, content and status area in the given dimension.
|
||||
*/
|
||||
public layout(dimension: Dimension): Dimension[] {
|
||||
layout(dimension: Dimension): Dimension[] {
|
||||
if (!this.partService.isVisible(Parts.ACTIVITYBAR_PART)) {
|
||||
return [dimension];
|
||||
}
|
||||
@@ -260,22 +344,10 @@ export class ActivitybarPart extends Part {
|
||||
return sizes;
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
this.compositeBar.shutdown();
|
||||
shutdown(): void {
|
||||
const state = this.viewletService.getViewlets().map(viewlet => ({ id: viewlet.id, iconUrl: viewlet.iconUrl }));
|
||||
this.storageService.store(ActivitybarPart.PLACEHOLDER_VIEWLETS, JSON.stringify(state), StorageScope.GLOBAL);
|
||||
|
||||
super.shutdown();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this.compositeBar) {
|
||||
this.compositeBar.dispose();
|
||||
this.compositeBar = null;
|
||||
}
|
||||
|
||||
if (this.globalActionBar) {
|
||||
this.globalActionBar.dispose();
|
||||
this.globalActionBar = null;
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { Part, IPartOptions } from 'vs/workbench/browser/part';
|
||||
import { Composite, CompositeRegistry } from 'vs/workbench/browser/composite';
|
||||
import { IComposite } from 'vs/workbench/common/composite';
|
||||
import { WorkbenchProgressService } from 'vs/workbench/services/progress/browser/progressService';
|
||||
import { ScopedProgressService } from 'vs/workbench/services/progress/browser/progressService';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
@@ -51,6 +51,12 @@ export interface ICompositeTitleLabel {
|
||||
}
|
||||
|
||||
export abstract class CompositePart<T extends Composite> extends Part {
|
||||
|
||||
protected _onDidCompositeOpen = this._register(new Emitter<IComposite>());
|
||||
protected _onDidCompositeClose = this._register(new Emitter<IComposite>());
|
||||
|
||||
protected toolBar: ToolBar;
|
||||
|
||||
private instantiatedCompositeListeners: IDisposable[];
|
||||
private mapCompositeToCompositeContainer: { [compositeId: string]: Builder; };
|
||||
private mapActionsBindingToComposite: { [compositeId: string]: () => void; };
|
||||
@@ -59,13 +65,10 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
private lastActiveCompositeId: string;
|
||||
private instantiatedComposites: Composite[];
|
||||
private titleLabel: ICompositeTitleLabel;
|
||||
protected toolBar: ToolBar;
|
||||
private progressBar: ProgressBar;
|
||||
private contentAreaSize: Dimension;
|
||||
private telemetryActionsListener: IDisposable;
|
||||
private currentCompositeOpenToken: string;
|
||||
protected _onDidCompositeOpen = new Emitter<IComposite>();
|
||||
protected _onDidCompositeClose = new Emitter<IComposite>();
|
||||
|
||||
constructor(
|
||||
private notificationService: INotificationService,
|
||||
@@ -178,7 +181,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
// Instantiate composite from registry otherwise
|
||||
const compositeDescriptor = this.registry.getComposite(id);
|
||||
if (compositeDescriptor) {
|
||||
const progressService = this.instantiationService.createInstance(WorkbenchProgressService, this.progressBar, compositeDescriptor.id, isActive);
|
||||
const progressService = this.instantiationService.createInstance(ScopedProgressService, this.progressBar, compositeDescriptor.id, isActive);
|
||||
const compositeInstantiationService = this.instantiationService.createChild(new ServiceCollection([IProgressService, progressService]));
|
||||
|
||||
const composite = compositeDescriptor.instantiate(compositeInstantiationService);
|
||||
@@ -400,7 +403,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
});
|
||||
}
|
||||
|
||||
public createTitleArea(parent: HTMLElement): HTMLElement {
|
||||
createTitleArea(parent: HTMLElement): HTMLElement {
|
||||
|
||||
// Title Area Container
|
||||
const titleArea = $(parent).div({
|
||||
@@ -416,11 +419,11 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
}, div => {
|
||||
|
||||
// Toolbar
|
||||
this.toolBar = new ToolBar(div.getHTMLElement(), this.contextMenuService, {
|
||||
this.toolBar = this._register(new ToolBar(div.getHTMLElement(), this.contextMenuService, {
|
||||
actionItemProvider: action => this.actionItemProvider(action as Action),
|
||||
orientation: ActionsOrientation.HORIZONTAL,
|
||||
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id)
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
return titleArea.getHTMLElement();
|
||||
@@ -431,7 +434,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
$(parent).div({
|
||||
'class': 'title-label'
|
||||
}, div => {
|
||||
titleLabel = div.span();
|
||||
titleLabel = div.element('h2');
|
||||
});
|
||||
|
||||
const $this = this;
|
||||
@@ -463,12 +466,12 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public createContentArea(parent: HTMLElement): HTMLElement {
|
||||
createContentArea(parent: HTMLElement): HTMLElement {
|
||||
return $(parent).div({
|
||||
'class': 'content'
|
||||
}, div => {
|
||||
this.progressBar = new ProgressBar(div.getHTMLElement());
|
||||
this.toUnbind.push(attachProgressBarStyler(this.progressBar, this.themeService));
|
||||
this.progressBar = this._register(new ProgressBar(div.getHTMLElement()));
|
||||
this._register(attachProgressBarStyler(this.progressBar, this.themeService));
|
||||
this.progressBar.hide();
|
||||
}).getHTMLElement();
|
||||
}
|
||||
@@ -477,7 +480,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
this.notificationService.error(types.isString(error) ? new Error(error) : error);
|
||||
}
|
||||
|
||||
public getProgressIndicator(id: string): IProgressService {
|
||||
getProgressIndicator(id: string): IProgressService {
|
||||
return this.mapProgressServiceToComposite[id];
|
||||
}
|
||||
|
||||
@@ -489,7 +492,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
return [];
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): Dimension[] {
|
||||
layout(dimension: Dimension): Dimension[] {
|
||||
|
||||
// Pass to super
|
||||
const sizes = super.layout(dimension);
|
||||
@@ -503,13 +506,13 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
return sizes;
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
shutdown(): void {
|
||||
this.instantiatedComposites.forEach(i => i.shutdown());
|
||||
|
||||
super.shutdown();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
dispose(): void {
|
||||
this.mapCompositeToCompositeContainer = null;
|
||||
this.mapProgressServiceToComposite = null;
|
||||
this.mapActionsBindingToComposite = null;
|
||||
@@ -519,13 +522,8 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
}
|
||||
|
||||
this.instantiatedComposites = [];
|
||||
|
||||
this.instantiatedCompositeListeners = dispose(this.instantiatedCompositeListeners);
|
||||
|
||||
this.progressBar.dispose();
|
||||
this.toolBar.dispose();
|
||||
|
||||
// Super Dispose
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,146 +6,182 @@
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { illegalArgument } from 'vs/base/common/errors';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ActionBar, IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ActionBar, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { CompositeActionItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionItem, ActivityAction, ICompositeBar, ICompositeBarColors } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Dimension, $, addDisposableListener, EventType, EventHelper } from 'vs/base/browser/dom';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
|
||||
export interface ICompositeBarOptions {
|
||||
icon: boolean;
|
||||
storageId: string;
|
||||
orientation: ActionsOrientation;
|
||||
composites: { id: string, name: string, order: number }[];
|
||||
colors: ICompositeBarColors;
|
||||
compositeSize: number;
|
||||
overflowActionSize: number;
|
||||
getActivityAction: (compositeId: string) => ActivityAction;
|
||||
getCompositePinnedAction: (compositeId: string) => Action;
|
||||
getOnCompositeClickAction: (compositeId: string) => Action;
|
||||
getContextMenuActions: () => Action[];
|
||||
openComposite: (compositeId: string) => TPromise<any>;
|
||||
getDefaultCompositeId: () => string;
|
||||
hidePart: () => TPromise<any>;
|
||||
}
|
||||
|
||||
interface CompositeState {
|
||||
id: string;
|
||||
pinned: boolean;
|
||||
}
|
||||
|
||||
export class CompositeBar implements ICompositeBar {
|
||||
|
||||
private readonly _onDidContextMenu: Emitter<MouseEvent>;
|
||||
export class CompositeBar extends Widget implements ICompositeBar {
|
||||
|
||||
private dimension: Dimension;
|
||||
private toDispose: IDisposable[];
|
||||
|
||||
private compositeSwitcherBar: ActionBar;
|
||||
private compositeOverflowAction: CompositeOverflowActivityAction;
|
||||
private compositeOverflowActionItem: CompositeOverflowActivityActionItem;
|
||||
|
||||
private compositeIdToActions: { [compositeId: string]: ActivityAction; };
|
||||
private compositeIdToActionItems: { [compositeId: string]: IActionItem; };
|
||||
private compositeIdToActivityStack: { [compositeId: string]: ICompositeActivity[]; };
|
||||
private model: CompositeBarModel;
|
||||
private storedState: ISerializedCompositeBarItem[];
|
||||
private visibleComposites: string[];
|
||||
private compositeSizeInBar: Map<string, number>;
|
||||
|
||||
private initialCompositesStates: CompositeState[];
|
||||
private pinnedComposites: string[];
|
||||
private activeCompositeId: string;
|
||||
private activeUnpinnedCompositeId: string;
|
||||
|
||||
constructor(
|
||||
private options: ICompositeBarOptions,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IStorageService private storageService: IStorageService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService
|
||||
) {
|
||||
this.toDispose = [];
|
||||
this.compositeIdToActionItems = Object.create(null);
|
||||
this.compositeIdToActions = Object.create(null);
|
||||
this.compositeIdToActivityStack = Object.create(null);
|
||||
super();
|
||||
|
||||
this.model = new CompositeBarModel(options);
|
||||
this.storedState = this.loadCompositeItemsFromStorage();
|
||||
this.visibleComposites = [];
|
||||
this.compositeSizeInBar = new Map<string, number>();
|
||||
|
||||
this._onDidContextMenu = new Emitter<MouseEvent>();
|
||||
this.initialCompositesStates = this.loadCompositesStates();
|
||||
this.pinnedComposites = this.initialCompositesStates
|
||||
.filter(c => c.pinned)
|
||||
.map(c => c.id)
|
||||
.filter(id => this.options.composites.some(c => c.id === id));
|
||||
}
|
||||
|
||||
public get onDidContextMenu(): Event<MouseEvent> {
|
||||
return this._onDidContextMenu.event;
|
||||
getCompositesFromStorage(): string[] {
|
||||
return this.storedState.map(s => s.id);
|
||||
}
|
||||
|
||||
public addComposite(compositeData: { id: string; name: string, order: number }, activate: boolean): void {
|
||||
if (this.options.composites.filter(c => c.id === compositeData.id).length) {
|
||||
return;
|
||||
}
|
||||
this.options.composites.push(compositeData);
|
||||
create(parent: HTMLElement): HTMLElement {
|
||||
const actionBarDiv = parent.appendChild($('.composite-bar'));
|
||||
this.compositeSwitcherBar = this._register(new ActionBar(actionBarDiv, {
|
||||
actionItemProvider: (action: Action) => {
|
||||
if (action instanceof CompositeOverflowActivityAction) {
|
||||
return this.compositeOverflowActionItem;
|
||||
}
|
||||
const item = this.model.findItem(action.id);
|
||||
return item && this.instantiationService.createInstance(CompositeActionItem, action, item.pinnedAction, this.options.colors, this.options.icon, this);
|
||||
},
|
||||
orientation: this.options.orientation,
|
||||
ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"),
|
||||
animated: false,
|
||||
}));
|
||||
|
||||
const compositeState = this.initialCompositesStates.filter(c => c.id === compositeData.id)[0];
|
||||
if (!compositeState /* new composites are pinned by default */ || compositeState.pinned) {
|
||||
let index;
|
||||
if (compositeState) {
|
||||
index = this.initialCompositesStates.indexOf(compositeState);
|
||||
} else {
|
||||
index = 0;
|
||||
while (index < this.options.composites.length && this.options.composites[index].order < compositeData.order) {
|
||||
index++;
|
||||
// Contextmenu for composites
|
||||
this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, e => this.showContextMenu(e)));
|
||||
|
||||
// Allow to drop at the end to move composites to the end
|
||||
this._register(addDisposableListener(parent, EventType.DROP, (e: DragEvent) => {
|
||||
const draggedCompositeId = CompositeActionItem.getDraggedCompositeId();
|
||||
if (draggedCompositeId) {
|
||||
EventHelper.stop(e, true);
|
||||
CompositeActionItem.clearDraggedComposite();
|
||||
|
||||
const targetItem = this.model.items[this.model.items.length - 1];
|
||||
if (targetItem && targetItem.id !== draggedCompositeId) {
|
||||
this.move(draggedCompositeId, targetItem.id);
|
||||
}
|
||||
}
|
||||
this.pin(compositeData.id, true, index, activate);
|
||||
}
|
||||
}));
|
||||
|
||||
return actionBarDiv;
|
||||
}
|
||||
|
||||
public removeComposite(id: string): void {
|
||||
if (this.options.composites.filter(c => c.id === id).length === 0) {
|
||||
layout(dimension: Dimension): void {
|
||||
this.dimension = dimension;
|
||||
if (dimension.height === 0 || dimension.width === 0) {
|
||||
// Do not layout if not visible. Otherwise the size measurment would be computed wrongly
|
||||
return;
|
||||
}
|
||||
|
||||
this.options.composites = this.options.composites.filter(c => c.id !== id);
|
||||
this.unpin(id);
|
||||
this.pullComposite(id);
|
||||
// Only at the end deactivate composite so the unpin and pull properly finish
|
||||
this.deactivateComposite(id);
|
||||
if (this.compositeSizeInBar.size === 0) {
|
||||
// Compute size of each composite by getting the size from the css renderer
|
||||
// Size is later used for overflow computation
|
||||
this.computeSizes(this.model.items);
|
||||
}
|
||||
|
||||
this.updateCompositeSwitcher();
|
||||
}
|
||||
|
||||
public activateComposite(id: string): void {
|
||||
if (this.compositeIdToActions[id]) {
|
||||
if (this.compositeIdToActions[this.activeCompositeId]) {
|
||||
this.compositeIdToActions[this.activeCompositeId].deactivate();
|
||||
addComposite({ id, name, order }: { id: string; name: string, order: number }): void {
|
||||
const state = this.storedState.filter(s => s.id === id)[0];
|
||||
const pinned = state ? state.pinned : true;
|
||||
let index = order >= 0 ? order : this.model.items.length;
|
||||
|
||||
if (state) {
|
||||
// Find the index by looking its previous item
|
||||
index = 0;
|
||||
for (let i = this.storedState.indexOf(state) - 1; i >= 0; i--) {
|
||||
const previousItemId = this.storedState[i].id;
|
||||
const previousItemIndex = this.model.findIndex(previousItemId);
|
||||
if (previousItemIndex !== -1) {
|
||||
index = previousItemIndex + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.compositeIdToActions[id].activate();
|
||||
}
|
||||
this.activeCompositeId = id;
|
||||
|
||||
const activeUnpinnedCompositeShouldClose = this.activeUnpinnedCompositeId && this.activeUnpinnedCompositeId !== id;
|
||||
const activeUnpinnedCompositeShouldShow = !this.pinnedComposites.some(pid => pid === id);
|
||||
if (activeUnpinnedCompositeShouldShow || activeUnpinnedCompositeShouldClose) {
|
||||
// Add to the model
|
||||
if (this.model.add(id, name, order, index)) {
|
||||
this.computeSizes([this.model.findItem(id)]);
|
||||
if (pinned) {
|
||||
this.pin(id);
|
||||
} else {
|
||||
this.updateCompositeSwitcher();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeComposite(id: string): void {
|
||||
|
||||
// If it pinned, unpin it first
|
||||
if (this.isPinned(id)) {
|
||||
this.unpin(id);
|
||||
}
|
||||
|
||||
// Remove from the model
|
||||
if (this.model.remove(id)) {
|
||||
this.updateCompositeSwitcher();
|
||||
}
|
||||
}
|
||||
|
||||
public deactivateComposite(id: string): void {
|
||||
if (this.compositeIdToActions[id]) {
|
||||
this.compositeIdToActions[id].deactivate();
|
||||
}
|
||||
if (this.activeCompositeId === id) {
|
||||
this.activeCompositeId = undefined;
|
||||
}
|
||||
if (this.activeUnpinnedCompositeId === id) {
|
||||
this.updateCompositeSwitcher();
|
||||
this.activeUnpinnedCompositeId = undefined;
|
||||
activateComposite(id: string): void {
|
||||
const previousActiveItem = this.model.activeItem;
|
||||
if (this.model.activate(id)) {
|
||||
// Update if current composite is neither visible nor pinned
|
||||
// or previous active composite is not pinned
|
||||
if (this.visibleComposites.indexOf(id) === - 1 || !this.model.activeItem.pinned || (previousActiveItem && !previousActiveItem.pinned)) {
|
||||
this.updateCompositeSwitcher();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public showActivity(compositeId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable {
|
||||
deactivateComposite(id: string): void {
|
||||
const previousActiveItem = this.model.activeItem;
|
||||
if (this.model.deactivate()) {
|
||||
if (previousActiveItem && !previousActiveItem.pinned) {
|
||||
this.updateCompositeSwitcher();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showActivity(compositeId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable {
|
||||
if (!badge) {
|
||||
throw illegalArgument('badge');
|
||||
}
|
||||
@@ -155,100 +191,90 @@ export class CompositeBar implements ICompositeBar {
|
||||
}
|
||||
|
||||
const activity: ICompositeActivity = { badge, clazz, priority };
|
||||
const stack = this.compositeIdToActivityStack[compositeId] || (this.compositeIdToActivityStack[compositeId] = []);
|
||||
|
||||
for (let i = 0; i <= stack.length; i++) {
|
||||
if (i === stack.length) {
|
||||
stack.push(activity);
|
||||
break;
|
||||
} else if (stack[i].priority <= priority) {
|
||||
stack.splice(i, 0, activity);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.updateActivity(compositeId);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
const stack = this.compositeIdToActivityStack[compositeId];
|
||||
if (!stack) {
|
||||
return;
|
||||
}
|
||||
|
||||
const idx = stack.indexOf(activity);
|
||||
if (idx < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
stack.splice(idx, 1);
|
||||
if (stack.length === 0) {
|
||||
delete this.compositeIdToActivityStack[compositeId];
|
||||
}
|
||||
|
||||
this.updateActivity(compositeId);
|
||||
}
|
||||
};
|
||||
this.model.addActivity(compositeId, activity);
|
||||
return toDisposable(() => this.model.removeActivity(compositeId, activity));
|
||||
}
|
||||
|
||||
private updateActivity(compositeId: string) {
|
||||
const action = this.compositeIdToActions[compositeId];
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
pin(compositeId: string, open?: boolean): void {
|
||||
if (this.model.setPinned(compositeId, true)) {
|
||||
this.updateCompositeSwitcher();
|
||||
|
||||
const stack = this.compositeIdToActivityStack[compositeId];
|
||||
|
||||
// reset
|
||||
if (!stack || !stack.length) {
|
||||
action.setBadge(undefined);
|
||||
}
|
||||
|
||||
// update
|
||||
else {
|
||||
const [{ badge, clazz }] = stack;
|
||||
action.setBadge(badge);
|
||||
if (clazz) {
|
||||
action.class = clazz;
|
||||
if (open) {
|
||||
this.options.openComposite(compositeId)
|
||||
.done(() => this.activateComposite(compositeId)); // Activate after opening
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public create(parent: HTMLElement): HTMLElement {
|
||||
const actionBarDiv = parent.appendChild($('.composite-bar'));
|
||||
this.compositeSwitcherBar = new ActionBar(actionBarDiv, {
|
||||
actionItemProvider: (action: Action) => action instanceof CompositeOverflowActivityAction ? this.compositeOverflowActionItem : this.compositeIdToActionItems[action.id],
|
||||
orientation: this.options.orientation,
|
||||
ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"),
|
||||
animated: false,
|
||||
});
|
||||
this.toDispose.push(this.compositeSwitcherBar);
|
||||
unpin(compositeId: string): void {
|
||||
if (this.model.setPinned(compositeId, false)) {
|
||||
|
||||
// Contextmenu for composites
|
||||
this.toDispose.push(addDisposableListener(parent, EventType.CONTEXT_MENU, (e: MouseEvent) => {
|
||||
EventHelper.stop(e, true);
|
||||
this._onDidContextMenu.fire(e);
|
||||
}));
|
||||
this.updateCompositeSwitcher();
|
||||
|
||||
// Allow to drop at the end to move composites to the end
|
||||
this.toDispose.push(addDisposableListener(parent, EventType.DROP, (e: DragEvent) => {
|
||||
const draggedCompositeId = CompositeActionItem.getDraggedCompositeId();
|
||||
if (draggedCompositeId) {
|
||||
EventHelper.stop(e, true);
|
||||
CompositeActionItem.clearDraggedComposite();
|
||||
const defaultCompositeId = this.options.getDefaultCompositeId();
|
||||
|
||||
const targetId = this.pinnedComposites[this.pinnedComposites.length - 1];
|
||||
if (targetId !== draggedCompositeId) {
|
||||
this.move(draggedCompositeId, this.pinnedComposites[this.pinnedComposites.length - 1]);
|
||||
}
|
||||
// Case: composite is not the active one or the active one is a different one
|
||||
// Solv: we do nothing
|
||||
if (!this.model.activeItem || this.model.activeItem.id !== compositeId) {
|
||||
return;
|
||||
}
|
||||
}));
|
||||
|
||||
return actionBarDiv;
|
||||
// Deactivate itself
|
||||
this.deactivateComposite(compositeId);
|
||||
|
||||
// Case: composite is not the default composite and default composite is still showing
|
||||
// Solv: we open the default composite
|
||||
if (defaultCompositeId !== compositeId && this.isPinned(defaultCompositeId)) {
|
||||
this.options.openComposite(defaultCompositeId);
|
||||
}
|
||||
|
||||
// Case: we closed the last visible composite
|
||||
// Solv: we hide the part
|
||||
else if (this.visibleComposites.length === 1) {
|
||||
this.options.hidePart();
|
||||
}
|
||||
|
||||
// Case: we closed the default composite
|
||||
// Solv: we open the next visible composite from top
|
||||
else {
|
||||
this.options.openComposite(this.visibleComposites.filter(cid => cid !== compositeId)[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getAction(compositeId): ActivityAction {
|
||||
return this.compositeIdToActions[compositeId];
|
||||
isPinned(compositeId: string): boolean {
|
||||
const item = this.model.findItem(compositeId);
|
||||
return item && item.pinned;
|
||||
}
|
||||
|
||||
move(compositeId: string, toCompositeId: string): void {
|
||||
if (this.model.move(compositeId, toCompositeId)) {
|
||||
// timeout helps to prevent artifacts from showing up
|
||||
setTimeout(() => this.updateCompositeSwitcher(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
getAction(compositeId): ActivityAction {
|
||||
const item = this.model.findItem(compositeId);
|
||||
return item && item.activityAction;
|
||||
}
|
||||
|
||||
private computeSizes(items: ICompositeBarItem[]): void {
|
||||
const size = this.options.compositeSize;
|
||||
if (size) {
|
||||
items.forEach(composite => this.compositeSizeInBar.set(composite.id, size));
|
||||
} else {
|
||||
if (this.dimension && this.dimension.height !== 0 && this.dimension.width !== 0) {
|
||||
// Compute sizes only if visible. Otherwise the size measurment would be computed wrongly.
|
||||
const currentItemsLength = this.compositeSwitcherBar.items.length;
|
||||
this.compositeSwitcherBar.push(items.map(composite => composite.activityAction));
|
||||
items.map((composite, index) => this.compositeSizeInBar.set(composite.id, this.options.orientation === ActionsOrientation.VERTICAL
|
||||
? this.compositeSwitcherBar.getHeight(currentItemsLength + index)
|
||||
: this.compositeSwitcherBar.getWidth(currentItemsLength + index)
|
||||
));
|
||||
items.forEach(() => this.compositeSwitcherBar.pull(this.compositeSwitcherBar.items.length - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateCompositeSwitcher(): void {
|
||||
@@ -256,15 +282,10 @@ export class CompositeBar implements ICompositeBar {
|
||||
return; // We have not been rendered yet so there is nothing to update.
|
||||
}
|
||||
|
||||
let compositesToShow = this.pinnedComposites.slice(0); // never modify original array
|
||||
|
||||
// Always show the active composite even if it is marked to be hidden
|
||||
if (this.activeCompositeId && !compositesToShow.some(id => id === this.activeCompositeId)) {
|
||||
this.activeUnpinnedCompositeId = this.activeCompositeId;
|
||||
compositesToShow = compositesToShow.concat(this.activeUnpinnedCompositeId);
|
||||
} else {
|
||||
this.activeUnpinnedCompositeId = void 0;
|
||||
}
|
||||
let compositesToShow = this.model.items.filter(item =>
|
||||
item.pinned
|
||||
|| (this.model.activeItem && this.model.activeItem.id === item.id) /* Show the active composite even if it is not pinned */
|
||||
).map(item => item.id);
|
||||
|
||||
// Ensure we are not showing more composites than we have height for
|
||||
let overflows = false;
|
||||
@@ -288,19 +309,20 @@ export class CompositeBar implements ICompositeBar {
|
||||
if (size > limit) {
|
||||
size -= this.compositeSizeInBar.get(compositesToShow.pop());
|
||||
}
|
||||
|
||||
// We always try show the active composite
|
||||
if (this.activeCompositeId && compositesToShow.length && compositesToShow.indexOf(this.activeCompositeId) === -1) {
|
||||
if (this.model.activeItem && compositesToShow.every(compositeId => compositeId !== this.model.activeItem.id)) {
|
||||
const removedComposite = compositesToShow.pop();
|
||||
size = size - this.compositeSizeInBar.get(removedComposite) + this.compositeSizeInBar.get(this.activeCompositeId);
|
||||
compositesToShow.push(this.activeCompositeId);
|
||||
size = size - this.compositeSizeInBar.get(removedComposite) + this.compositeSizeInBar.get(this.model.activeItem.id);
|
||||
compositesToShow.push(this.model.activeItem.id);
|
||||
}
|
||||
|
||||
// The active composite might have bigger size than the removed composite, check for overflow again
|
||||
if (size > limit) {
|
||||
compositesToShow.length ? compositesToShow.splice(compositesToShow.length - 2, 1) : compositesToShow.pop();
|
||||
}
|
||||
|
||||
const visibleComposites = Object.keys(this.compositeIdToActions);
|
||||
const visibleCompositesChange = !arrays.equals(compositesToShow, visibleComposites);
|
||||
const visibleCompositesChange = !arrays.equals(compositesToShow, this.visibleComposites);
|
||||
|
||||
// Pull out overflow action if there is a composite change so that we can add it to the end later
|
||||
if (this.compositeOverflowAction && visibleCompositesChange) {
|
||||
@@ -313,37 +335,35 @@ export class CompositeBar implements ICompositeBar {
|
||||
this.compositeOverflowActionItem = null;
|
||||
}
|
||||
|
||||
// Pull out composites that overflow, got hidden or changed position
|
||||
visibleComposites.forEach((compositeId, index) => {
|
||||
if (compositesToShow.indexOf(compositeId) !== index) {
|
||||
this.pullComposite(compositeId);
|
||||
// Pull out composites that overflow or got hidden
|
||||
const compositesToRemove: number[] = [];
|
||||
this.visibleComposites.forEach((compositeId, index) => {
|
||||
if (compositesToShow.indexOf(compositeId) === -1) {
|
||||
compositesToRemove.push(index);
|
||||
}
|
||||
});
|
||||
compositesToRemove.reverse().forEach(index => {
|
||||
const actionItem = this.compositeSwitcherBar.items[index];
|
||||
this.compositeSwitcherBar.pull(index);
|
||||
actionItem.dispose();
|
||||
this.visibleComposites.splice(index, 1);
|
||||
});
|
||||
|
||||
// Built actions for composites to show
|
||||
const newCompositesToShow = compositesToShow
|
||||
.filter(compositeId => !this.compositeIdToActions[compositeId])
|
||||
.map(compositeId => this.toAction(compositeId));
|
||||
|
||||
// Update when we have new composites to show
|
||||
if (newCompositesToShow.length) {
|
||||
|
||||
// Add to composite switcher
|
||||
this.compositeSwitcherBar.push(newCompositesToShow, { label: true, icon: this.options.icon });
|
||||
|
||||
// Make sure to activate the active one
|
||||
if (this.activeCompositeId) {
|
||||
const activeCompositeEntry = this.compositeIdToActions[this.activeCompositeId];
|
||||
if (activeCompositeEntry) {
|
||||
activeCompositeEntry.activate();
|
||||
// Update the positions of the composites
|
||||
compositesToShow.forEach((compositeId, newIndex) => {
|
||||
const currentIndex = this.visibleComposites.indexOf(compositeId);
|
||||
if (newIndex !== currentIndex) {
|
||||
if (currentIndex !== -1) {
|
||||
const actionItem = this.compositeSwitcherBar.items[currentIndex];
|
||||
this.compositeSwitcherBar.pull(currentIndex);
|
||||
actionItem.dispose();
|
||||
this.visibleComposites.splice(currentIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure to restore activity
|
||||
Object.keys(this.compositeIdToActions).forEach(compositeId => {
|
||||
this.updateActivity(compositeId);
|
||||
});
|
||||
}
|
||||
this.compositeSwitcherBar.push(this.model.findItem(compositeId).activityAction, { label: true, icon: this.options.icon, index: newIndex });
|
||||
this.visibleComposites.splice(newIndex, 0, compositeId);
|
||||
}
|
||||
});
|
||||
|
||||
// Add overflow action as needed
|
||||
if ((visibleCompositesChange && overflows) || this.compositeSwitcherBar.length() === 0) {
|
||||
@@ -352,210 +372,254 @@ export class CompositeBar implements ICompositeBar {
|
||||
CompositeOverflowActivityActionItem,
|
||||
this.compositeOverflowAction,
|
||||
() => this.getOverflowingComposites(),
|
||||
() => this.activeCompositeId,
|
||||
(compositeId: string) => this.compositeIdToActivityStack[compositeId] && this.compositeIdToActivityStack[compositeId][0].badge,
|
||||
() => this.model.activeItem ? this.model.activeItem.id : void 0,
|
||||
(compositeId: string) => {
|
||||
const item = this.model.findItem(compositeId);
|
||||
return item && item.activity[0] && item.activity[0].badge;
|
||||
},
|
||||
this.options.getOnCompositeClickAction,
|
||||
this.options.colors
|
||||
);
|
||||
|
||||
this.compositeSwitcherBar.push(this.compositeOverflowAction, { label: false, icon: true });
|
||||
}
|
||||
|
||||
// Persist
|
||||
this.saveCompositeItems();
|
||||
}
|
||||
|
||||
private getOverflowingComposites(): { id: string, name: string }[] {
|
||||
let overflowingIds = this.pinnedComposites;
|
||||
if (this.activeUnpinnedCompositeId) {
|
||||
overflowingIds = overflowingIds.concat(this.activeUnpinnedCompositeId);
|
||||
}
|
||||
const visibleComposites = Object.keys(this.compositeIdToActions);
|
||||
let overflowingIds = this.model.items.filter(item => item.pinned).map(item => item.id);
|
||||
|
||||
overflowingIds = overflowingIds.filter(compositeId => visibleComposites.indexOf(compositeId) === -1);
|
||||
return this.options.composites.filter(c => overflowingIds.indexOf(c.id) !== -1);
|
||||
// Show the active composite even if it is not pinned
|
||||
if (this.model.activeItem && !this.model.activeItem.pinned) {
|
||||
overflowingIds.push(this.model.activeItem.id);
|
||||
}
|
||||
|
||||
overflowingIds = overflowingIds.filter(compositeId => this.visibleComposites.indexOf(compositeId) === -1);
|
||||
return this.model.items.filter(c => overflowingIds.indexOf(c.id) !== -1);
|
||||
}
|
||||
|
||||
private getVisibleComposites(): string[] {
|
||||
return Object.keys(this.compositeIdToActions);
|
||||
}
|
||||
|
||||
private pullComposite(compositeId: string): void {
|
||||
const index = Object.keys(this.compositeIdToActions).indexOf(compositeId);
|
||||
if (index >= 0) {
|
||||
this.compositeSwitcherBar.pull(index);
|
||||
|
||||
const action = this.compositeIdToActions[compositeId];
|
||||
action.dispose();
|
||||
delete this.compositeIdToActions[compositeId];
|
||||
|
||||
const actionItem = this.compositeIdToActionItems[action.id];
|
||||
actionItem.dispose();
|
||||
delete this.compositeIdToActionItems[action.id];
|
||||
private showContextMenu(e: MouseEvent): void {
|
||||
EventHelper.stop(e, true);
|
||||
const event = new StandardMouseEvent(e);
|
||||
const actions: IAction[] = this.model.items
|
||||
.map(({ id, name, activityAction }) => (<IAction>{
|
||||
id,
|
||||
label: name,
|
||||
checked: this.isPinned(id),
|
||||
enabled: activityAction.enabled,
|
||||
run: () => {
|
||||
if (this.isPinned(id)) {
|
||||
this.unpin(id);
|
||||
} else {
|
||||
this.pin(id, true);
|
||||
}
|
||||
}
|
||||
}));
|
||||
const otherActions = this.options.getContextMenuActions();
|
||||
if (otherActions.length) {
|
||||
actions.push(new Separator());
|
||||
actions.push(...otherActions);
|
||||
}
|
||||
}
|
||||
|
||||
private toAction(compositeId: string): ActivityAction {
|
||||
if (this.compositeIdToActions[compositeId]) {
|
||||
return this.compositeIdToActions[compositeId];
|
||||
}
|
||||
|
||||
const compositeActivityAction = this.options.getActivityAction(compositeId);
|
||||
const pinnedAction = this.options.getCompositePinnedAction(compositeId);
|
||||
this.compositeIdToActionItems[compositeId] = this.instantiationService.createInstance(CompositeActionItem, compositeActivityAction, pinnedAction, this.options.colors, this.options.icon, this);
|
||||
this.compositeIdToActions[compositeId] = compositeActivityAction;
|
||||
|
||||
return compositeActivityAction;
|
||||
}
|
||||
|
||||
public unpin(compositeId: string): void {
|
||||
if (!this.isPinned(compositeId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultCompositeId = this.options.getDefaultCompositeId();
|
||||
const visibleComposites = this.getVisibleComposites();
|
||||
|
||||
let unpinPromise: TPromise<any>;
|
||||
|
||||
// remove from pinned
|
||||
const index = this.pinnedComposites.indexOf(compositeId);
|
||||
this.pinnedComposites.splice(index, 1);
|
||||
|
||||
// Case: composite is not the active one or the active one is a different one
|
||||
// Solv: we do nothing
|
||||
if (!this.activeCompositeId || this.activeCompositeId !== compositeId) {
|
||||
unpinPromise = TPromise.as(null);
|
||||
}
|
||||
|
||||
// Case: composite is not the default composite and default composite is still showing
|
||||
// Solv: we open the default composite
|
||||
else if (defaultCompositeId !== compositeId && this.isPinned(defaultCompositeId)) {
|
||||
unpinPromise = this.options.openComposite(defaultCompositeId);
|
||||
}
|
||||
|
||||
// Case: we closed the last visible composite
|
||||
// Solv: we hide the part
|
||||
else if (visibleComposites.length === 1) {
|
||||
unpinPromise = this.options.hidePart();
|
||||
}
|
||||
|
||||
// Case: we closed the default composite
|
||||
// Solv: we open the next visible composite from top
|
||||
else {
|
||||
unpinPromise = this.options.openComposite(visibleComposites.filter(cid => cid !== compositeId)[0]);
|
||||
}
|
||||
|
||||
unpinPromise.then(() => {
|
||||
this.updateCompositeSwitcher();
|
||||
});
|
||||
|
||||
// Persist
|
||||
this.saveCompositesStates();
|
||||
}
|
||||
|
||||
public isPinned(compositeId: string): boolean {
|
||||
return this.pinnedComposites.indexOf(compositeId) >= 0;
|
||||
}
|
||||
|
||||
public pin(compositeId: string, update = true, index = this.pinnedComposites.length, activate: boolean = true): void {
|
||||
if (this.isPinned(compositeId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activatePromise = activate ? this.options.openComposite(compositeId) : TPromise.as(null);
|
||||
activatePromise.then(() => {
|
||||
this.pinnedComposites.splice(index, 0, compositeId);
|
||||
this.pinnedComposites = arrays.distinct(this.pinnedComposites);
|
||||
|
||||
if (update) {
|
||||
this.updateCompositeSwitcher();
|
||||
}
|
||||
|
||||
// Persist
|
||||
this.saveCompositesStates();
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => { return { x: event.posx, y: event.posy }; },
|
||||
getActions: () => TPromise.as(actions),
|
||||
});
|
||||
}
|
||||
|
||||
public move(compositeId: string, toCompositeId: string): void {
|
||||
// Make sure both composites are known to this composite bar
|
||||
if (this.options.composites.filter(c => c.id === compositeId || c.id === toCompositeId).length !== 2) {
|
||||
return;
|
||||
}
|
||||
// Make sure a moved composite gets pinned
|
||||
if (!this.isPinned(compositeId)) {
|
||||
this.pin(compositeId, false /* defer update, we take care of it */);
|
||||
}
|
||||
|
||||
const fromIndex = this.pinnedComposites.indexOf(compositeId);
|
||||
const toIndex = this.pinnedComposites.indexOf(toCompositeId);
|
||||
|
||||
this.pinnedComposites.splice(fromIndex, 1);
|
||||
this.pinnedComposites.splice(toIndex, 0, compositeId);
|
||||
|
||||
// Clear composites that are impacted by the move
|
||||
const visibleComposites = Object.keys(this.compositeIdToActions);
|
||||
for (let i = Math.min(fromIndex, toIndex); i < visibleComposites.length; i++) {
|
||||
this.pullComposite(visibleComposites[i]);
|
||||
}
|
||||
|
||||
// timeout helps to prevent artifacts from showing up
|
||||
setTimeout(() => {
|
||||
this.updateCompositeSwitcher();
|
||||
}, 0);
|
||||
|
||||
// Persist
|
||||
this.saveCompositesStates();
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
this.dimension = dimension;
|
||||
if (dimension.height === 0 || dimension.width === 0) {
|
||||
// Do not layout if not visible. Otherwise the size measurment would be computed wrongly
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.compositeSizeInBar.size === 0) {
|
||||
// Compute size of each composite by getting the size from the css renderer
|
||||
// Size is later used for overflow computation
|
||||
this.compositeSwitcherBar.clear();
|
||||
this.compositeSwitcherBar.push(this.options.composites.map(c => this.options.getActivityAction(c.id)));
|
||||
this.options.composites.map((c, index) => this.compositeSizeInBar.set(c.id, this.options.orientation === ActionsOrientation.VERTICAL
|
||||
? this.compositeSwitcherBar.getHeight(index)
|
||||
: this.compositeSwitcherBar.getWidth(index)
|
||||
));
|
||||
this.compositeSwitcherBar.clear();
|
||||
}
|
||||
this.updateCompositeSwitcher();
|
||||
}
|
||||
|
||||
private loadCompositesStates(): CompositeState[] {
|
||||
const storedStates = <Array<string | CompositeState>>JSON.parse(this.storageService.get(this.options.storageId, StorageScope.GLOBAL, '[]'));
|
||||
const isOldData = storedStates && storedStates.length && typeof storedStates[0] === 'string';
|
||||
const compositeStates = <CompositeState[]>storedStates.map(c =>
|
||||
private loadCompositeItemsFromStorage(): ISerializedCompositeBarItem[] {
|
||||
const storedStates = <Array<string | ISerializedCompositeBarItem>>JSON.parse(this.storageService.get(this.options.storageId, StorageScope.GLOBAL, '[]'));
|
||||
const compositeStates = <ISerializedCompositeBarItem[]>storedStates.map(c =>
|
||||
typeof c === 'string' /* migration from pinned states to composites states */ ? { id: c, pinned: true } : c);
|
||||
|
||||
if (!isOldData) { /* Add new composites only if it is new data */
|
||||
const newComposites = this.options.composites.filter(c => compositeStates.every(s => s.id !== c.id));
|
||||
newComposites.sort((c1, c2) => c1.order < c2.order ? -1 : 1);
|
||||
newComposites.forEach(c => compositeStates.push({ id: c.id, pinned: true /* new composites are pinned by default */ }));
|
||||
}
|
||||
|
||||
return compositeStates;
|
||||
}
|
||||
|
||||
private saveCompositesStates(): void {
|
||||
const toSave = this.pinnedComposites.map(id => (<CompositeState>{ id, pinned: true }));
|
||||
for (const composite of this.options.composites) {
|
||||
if (this.pinnedComposites.indexOf(composite.id) === -1) { // Unpinned composites
|
||||
toSave.push({ id: composite.id, pinned: false });
|
||||
}
|
||||
}
|
||||
this.storageService.store(this.options.storageId, JSON.stringify(toSave), StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
this.saveCompositesStates();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
private saveCompositeItems(): void {
|
||||
this.storedState = this.model.toJSON();
|
||||
this.storageService.store(this.options.storageId, JSON.stringify(this.storedState), StorageScope.GLOBAL);
|
||||
}
|
||||
}
|
||||
|
||||
interface ISerializedCompositeBarItem {
|
||||
id: string;
|
||||
pinned: boolean;
|
||||
order: number;
|
||||
}
|
||||
|
||||
interface ICompositeBarItem extends ISerializedCompositeBarItem {
|
||||
name: string;
|
||||
activityAction: ActivityAction;
|
||||
pinnedAction: Action;
|
||||
activity: ICompositeActivity[];
|
||||
}
|
||||
|
||||
class CompositeBarModel {
|
||||
|
||||
readonly items: ICompositeBarItem[] = [];
|
||||
activeItem: ICompositeBarItem;
|
||||
|
||||
constructor(private options: ICompositeBarOptions) { }
|
||||
|
||||
private createCompositeBarItem(id: string, name: string, order: number, pinned: boolean): ICompositeBarItem {
|
||||
const options = this.options;
|
||||
return {
|
||||
id, name, pinned, order, activity: [],
|
||||
get activityAction() {
|
||||
return options.getActivityAction(id);
|
||||
},
|
||||
get pinnedAction() {
|
||||
return options.getCompositePinnedAction(id);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
add(id: string, name: string, order: number, index: number): boolean {
|
||||
const item = this.findItem(id);
|
||||
if (item) {
|
||||
item.order = order;
|
||||
item.name = name;
|
||||
return false;
|
||||
} else {
|
||||
if (index === void 0) {
|
||||
index = 0;
|
||||
while (index < this.items.length && this.items[index].order < order) {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
this.items.splice(index, 0, this.createCompositeBarItem(id, name, order, false));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
remove(id: string): boolean {
|
||||
for (let index = 0; index < this.items.length; index++) {
|
||||
if (this.items[index].id === id) {
|
||||
this.items.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
move(compositeId: string, toCompositeId: string): boolean {
|
||||
|
||||
const fromIndex = this.findIndex(compositeId);
|
||||
const toIndex = this.findIndex(toCompositeId);
|
||||
|
||||
// Make sure both items are known to the model
|
||||
if (fromIndex === -1 || toIndex === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const sourceItem = this.items.splice(fromIndex, 1)[0];
|
||||
this.items.splice(toIndex, 0, sourceItem);
|
||||
|
||||
// Make sure a moved composite gets pinned
|
||||
sourceItem.pinned = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
setPinned(id: string, pinned: boolean): boolean {
|
||||
for (let index = 0; index < this.items.length; index++) {
|
||||
const item = this.items[index];
|
||||
if (item.id === id) {
|
||||
if (item.pinned !== pinned) {
|
||||
item.pinned = pinned;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
addActivity(id: string, activity: ICompositeActivity): boolean {
|
||||
const item = this.findItem(id);
|
||||
if (item) {
|
||||
const stack = item.activity;
|
||||
for (let i = 0; i <= stack.length; i++) {
|
||||
if (i === stack.length) {
|
||||
stack.push(activity);
|
||||
break;
|
||||
} else if (stack[i].priority <= activity.priority) {
|
||||
stack.splice(i, 0, activity);
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.updateActivity(id);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
removeActivity(id: string, activity: ICompositeActivity): boolean {
|
||||
const item = this.findItem(id);
|
||||
if (item) {
|
||||
const index = item.activity.indexOf(activity);
|
||||
if (index !== -1) {
|
||||
item.activity.splice(index, 1);
|
||||
this.updateActivity(id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
updateActivity(id: string): void {
|
||||
const item = this.findItem(id);
|
||||
if (item) {
|
||||
if (item.activity.length) {
|
||||
const [{ badge, clazz }] = item.activity;
|
||||
item.activityAction.setBadge(badge, clazz);
|
||||
}
|
||||
else {
|
||||
item.activityAction.setBadge(undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activate(id: string): boolean {
|
||||
if (!this.activeItem || this.activeItem.id !== id) {
|
||||
if (this.activeItem) {
|
||||
this.deactivate();
|
||||
}
|
||||
for (let index = 0; index < this.items.length; index++) {
|
||||
const item = this.items[index];
|
||||
if (item.id === id) {
|
||||
this.activeItem = item;
|
||||
this.activeItem.activityAction.activate();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
deactivate(): boolean {
|
||||
if (this.activeItem) {
|
||||
this.activeItem.activityAction.deactivate();
|
||||
this.activeItem = void 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
findItem(id: string): ICompositeBarItem {
|
||||
return this.items.filter(item => item.id === id)[0];
|
||||
}
|
||||
|
||||
findIndex(id: string): number {
|
||||
for (let index = 0; index < this.items.length; index++) {
|
||||
if (this.items[index].id === id) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
toJSON(): ISerializedCompositeBarItem[] {
|
||||
return this.items.map(({ id, pinned, order }) => ({ id, pinned, order }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import * as dom from 'vs/base/browser/dom';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { BaseActionItem, IBaseActionItemOptions, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { dispose, IDisposable, empty, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { dispose, IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
|
||||
import { TextBadge, NumberBadge, IBadge, IconBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
@@ -52,9 +52,15 @@ export interface ICompositeBar {
|
||||
}
|
||||
|
||||
export class ActivityAction extends Action {
|
||||
|
||||
private _onDidChangeActivity = new Emitter<this>();
|
||||
get onDidChangeActivity(): Event<this> { return this._onDidChangeActivity.event; }
|
||||
|
||||
private _onDidChangeBadge = new Emitter<this>();
|
||||
get onDidChangeBadge(): Event<this> { return this._onDidChangeBadge.event; }
|
||||
|
||||
private badge: IBadge;
|
||||
private clazz: string | undefined;
|
||||
private _onDidChangeBadge = new Emitter<this>();
|
||||
|
||||
constructor(private _activity: IActivity) {
|
||||
super(_activity.id, _activity.name, _activity.cssClass);
|
||||
@@ -62,39 +68,47 @@ export class ActivityAction extends Action {
|
||||
this.badge = null;
|
||||
}
|
||||
|
||||
public get activity(): IActivity {
|
||||
get activity(): IActivity {
|
||||
return this._activity;
|
||||
}
|
||||
|
||||
public get onDidChangeBadge(): Event<this> {
|
||||
return this._onDidChangeBadge.event;
|
||||
set activity(activity: IActivity) {
|
||||
this._activity = activity;
|
||||
this._onDidChangeActivity.fire(this);
|
||||
}
|
||||
|
||||
public activate(): void {
|
||||
activate(): void {
|
||||
if (!this.checked) {
|
||||
this._setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
public deactivate(): void {
|
||||
deactivate(): void {
|
||||
if (this.checked) {
|
||||
this._setChecked(false);
|
||||
}
|
||||
}
|
||||
|
||||
public getBadge(): IBadge {
|
||||
getBadge(): IBadge {
|
||||
return this.badge;
|
||||
}
|
||||
|
||||
public getClass(): string | undefined {
|
||||
getClass(): string | undefined {
|
||||
return this.clazz;
|
||||
}
|
||||
|
||||
public setBadge(badge: IBadge, clazz?: string): void {
|
||||
setBadge(badge: IBadge, clazz?: string): void {
|
||||
this.badge = badge;
|
||||
this.clazz = clazz;
|
||||
this._onDidChangeBadge.fire(this);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._onDidChangeActivity.dispose();
|
||||
this._onDidChangeBadge.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export interface ICompositeBarColors {
|
||||
@@ -116,7 +130,7 @@ export class ActivityActionItem extends BaseActionItem {
|
||||
protected options: IActivityActionItemOptions;
|
||||
|
||||
private $badgeContent: Builder;
|
||||
private badgeDisposable: IDisposable = empty;
|
||||
private badgeDisposable: IDisposable = Disposable.None;
|
||||
private mouseUpTimeout: number;
|
||||
|
||||
constructor(
|
||||
@@ -127,7 +141,8 @@ export class ActivityActionItem extends BaseActionItem {
|
||||
super(null, action, options);
|
||||
|
||||
this.themeService.onThemeChange(this.onThemeChange, this, this._callOnDispose);
|
||||
action.onDidChangeBadge(this.handleBadgeChangeEvenet, this, this._callOnDispose);
|
||||
action.onDidChangeActivity(this.updateActivity, this, this._callOnDispose);
|
||||
action.onDidChangeBadge(this.updateBadge, this, this._callOnDispose);
|
||||
}
|
||||
|
||||
protected get activity(): IActivity {
|
||||
@@ -159,14 +174,13 @@ export class ActivityActionItem extends BaseActionItem {
|
||||
}
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
// Make the container tab-able for keyboard navigation
|
||||
this.$container = $(container).attr({
|
||||
tabIndex: '0',
|
||||
role: 'button',
|
||||
title: this.activity.name
|
||||
role: 'button'
|
||||
});
|
||||
|
||||
// Try hard to prevent keyboard only focus feedback when using mouse
|
||||
@@ -186,19 +200,13 @@ export class ActivityActionItem extends BaseActionItem {
|
||||
|
||||
// Label
|
||||
this.$label = $('a.action-label').appendTo(this.builder);
|
||||
if (this.activity.cssClass) {
|
||||
this.$label.addClass(this.activity.cssClass);
|
||||
}
|
||||
if (!this.options.icon) {
|
||||
this.$label.text(this.getAction().label);
|
||||
}
|
||||
|
||||
this.$badge = this.builder.clone().div({ 'class': 'badge' }, badge => {
|
||||
this.$badgeContent = badge.div({ 'class': 'badge-content' });
|
||||
});
|
||||
|
||||
this.$badge.hide();
|
||||
|
||||
this.updateActivity();
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
@@ -206,13 +214,23 @@ export class ActivityActionItem extends BaseActionItem {
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
protected updateBadge(badge: IBadge, clazz?: string): void {
|
||||
if (!this.$badge || !this.$badgeContent) {
|
||||
protected updateActivity(): void {
|
||||
this.updateLabel();
|
||||
this.updateTitle(this.activity.name);
|
||||
this.updateBadge();
|
||||
}
|
||||
|
||||
protected updateBadge(): void {
|
||||
const action = this.getAction();
|
||||
if (!this.$badge || !this.$badgeContent || !(action instanceof ActivityAction)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const badge = action.getBadge();
|
||||
const clazz = action.getClass();
|
||||
|
||||
this.badgeDisposable.dispose();
|
||||
this.badgeDisposable = empty;
|
||||
this.badgeDisposable = Disposable.None;
|
||||
|
||||
this.$badgeContent.empty();
|
||||
this.$badge.hide();
|
||||
@@ -266,7 +284,19 @@ export class ActivityActionItem extends BaseActionItem {
|
||||
} else {
|
||||
title = this.activity.name;
|
||||
}
|
||||
this.updateTitle(title);
|
||||
}
|
||||
|
||||
private updateLabel(): void {
|
||||
if (this.activity.cssClass) {
|
||||
this.$label.addClass(this.activity.cssClass);
|
||||
}
|
||||
if (!this.options.icon) {
|
||||
this.$label.text(this.getAction().label);
|
||||
}
|
||||
}
|
||||
|
||||
private updateTitle(title: string): void {
|
||||
[this.$label, this.$badge, this.$container].forEach(b => {
|
||||
if (b) {
|
||||
b.attr('aria-label', title);
|
||||
@@ -275,14 +305,7 @@ export class ActivityActionItem extends BaseActionItem {
|
||||
});
|
||||
}
|
||||
|
||||
private handleBadgeChangeEvenet(): void {
|
||||
const action = this.getAction();
|
||||
if (action instanceof ActivityAction) {
|
||||
this.updateBadge(action.getBadge(), action.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
if (this.mouseUpTimeout) {
|
||||
@@ -305,7 +328,7 @@ export class CompositeOverflowActivityAction extends ActivityAction {
|
||||
});
|
||||
}
|
||||
|
||||
public run(event: any): TPromise<any> {
|
||||
run(event: any): TPromise<any> {
|
||||
this.showMenu();
|
||||
|
||||
return TPromise.as(true);
|
||||
@@ -328,7 +351,7 @@ export class CompositeOverflowActivityActionItem extends ActivityActionItem {
|
||||
super(action, { icon: true, colors }, themeService);
|
||||
}
|
||||
|
||||
public showMenu(): void {
|
||||
showMenu(): void {
|
||||
if (this.actions) {
|
||||
dispose(this.actions);
|
||||
}
|
||||
@@ -365,7 +388,7 @@ export class CompositeOverflowActivityActionItem extends ActivityActionItem {
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this.actions = dispose(this.actions);
|
||||
@@ -380,7 +403,7 @@ class ManageExtensionAction extends Action {
|
||||
super('activitybar.manage.extension', nls.localize('manageExtension', "Manage Extension"));
|
||||
}
|
||||
|
||||
public run(id: string): TPromise<any> {
|
||||
run(id: string): TPromise<any> {
|
||||
return this.commandService.executeCommand('_extensions.manage', id);
|
||||
}
|
||||
}
|
||||
@@ -411,6 +434,8 @@ export class CompositeActionItem extends ActivityActionItem {
|
||||
if (!CompositeActionItem.manageExtensionAction) {
|
||||
CompositeActionItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction);
|
||||
}
|
||||
|
||||
compositeActivityAction.onDidChangeActivity(() => { this.compositeActivity = null; this.updateActivity(); }, this, this._callOnDispose);
|
||||
}
|
||||
|
||||
protected get activity(): IActivity {
|
||||
@@ -442,9 +467,12 @@ export class CompositeActionItem extends ActivityActionItem {
|
||||
return null;
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
this._updateChecked();
|
||||
this._updateEnabled();
|
||||
|
||||
this.$container.on('contextmenu', e => {
|
||||
dom.EventHelper.stop(e, true);
|
||||
|
||||
@@ -524,7 +552,7 @@ export class CompositeActionItem extends ActivityActionItem {
|
||||
element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : null;
|
||||
}
|
||||
|
||||
public static getDraggedCompositeId(): string {
|
||||
static getDraggedCompositeId(): string {
|
||||
return CompositeActionItem.draggedCompositeId;
|
||||
}
|
||||
|
||||
@@ -532,7 +560,7 @@ export class CompositeActionItem extends ActivityActionItem {
|
||||
CompositeActionItem.draggedCompositeId = compositeId;
|
||||
}
|
||||
|
||||
public static clearDraggedComposite(): void {
|
||||
static clearDraggedComposite(): void {
|
||||
CompositeActionItem.draggedCompositeId = void 0;
|
||||
}
|
||||
|
||||
@@ -558,24 +586,28 @@ export class CompositeActionItem extends ActivityActionItem {
|
||||
});
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
focus(): void {
|
||||
this.$container.domFocus();
|
||||
}
|
||||
|
||||
protected _updateClass(): void {
|
||||
if (this.cssClass) {
|
||||
this.$badge.removeClass(this.cssClass);
|
||||
this.$label.removeClass(this.cssClass);
|
||||
}
|
||||
|
||||
this.cssClass = this.getAction().class;
|
||||
this.$badge.addClass(this.cssClass);
|
||||
if (this.cssClass) {
|
||||
this.$label.addClass(this.cssClass);
|
||||
}
|
||||
}
|
||||
|
||||
protected _updateChecked(): void {
|
||||
if (this.getAction().checked) {
|
||||
this.$container.addClass('checked');
|
||||
this.$container.attr('aria-label', nls.localize('compositeActive', "{0} active", this.$container.getHTMLElement().title));
|
||||
} else {
|
||||
this.$container.removeClass('checked');
|
||||
this.$container.attr('aria-label', this.$container.getHTMLElement().title);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -587,7 +619,7 @@ export class CompositeActionItem extends ActivityActionItem {
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
CompositeActionItem.clearDraggedComposite();
|
||||
@@ -607,7 +639,7 @@ export class ToggleCompositePinnedAction extends Action {
|
||||
this.checked = this.activity && this.compositeBar.isPinned(this.activity.id);
|
||||
}
|
||||
|
||||
public run(context: string): TPromise<any> {
|
||||
run(context: string): TPromise<any> {
|
||||
const id = this.activity ? this.activity.id : context;
|
||||
|
||||
if (this.compositeBar.isPinned(id)) {
|
||||
|
||||
@@ -6,16 +6,24 @@
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Panel } from 'vs/workbench/browser/panel';
|
||||
import { EditorInput, EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { IEditor, Position } from 'vs/platform/editor/common/editor';
|
||||
import { EditorInput, EditorOptions, IEditor, GroupIdentifier, IEditorMemento } from 'vs/workbench/common/editor';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { LRUCache } from 'vs/base/common/map';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { once, Event } from 'vs/base/common/event';
|
||||
import { isEmptyObject } from 'vs/base/common/types';
|
||||
import { DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor';
|
||||
|
||||
/**
|
||||
* The base class of editors in the workbench. Editors register themselves for specific editor inputs.
|
||||
* Editors are layed out in the editor part of the workbench. Only one editor can be open at a time.
|
||||
* Each editor has a minimized representation that is good enough to provide some information about the
|
||||
* state of the editor data.
|
||||
* Editors are layed out in the editor part of the workbench in editor groups. Multiple editors can be
|
||||
* open at the same time. Each editor has a minimized representation that is good enough to provide some
|
||||
* information about the state of the editor data.
|
||||
*
|
||||
* The workbench will keep an editor alive after it has been created and show/hide it based on
|
||||
* user interaction. The lifecycle of a editor goes in the order create(), setVisible(true|false),
|
||||
* layout(), setInput(), focus(), dispose(). During use of the workbench, a editor will often receive a
|
||||
@@ -24,30 +32,53 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
* This class is only intended to be subclassed and not instantiated.
|
||||
*/
|
||||
export abstract class BaseEditor extends Panel implements IEditor {
|
||||
protected _input: EditorInput;
|
||||
private _options: EditorOptions;
|
||||
private _position: Position;
|
||||
|
||||
constructor(id: string, telemetryService: ITelemetryService, themeService: IThemeService) {
|
||||
private static readonly EDITOR_MEMENTOS: Map<string, EditorMemento<any>> = new Map<string, EditorMemento<any>>();
|
||||
|
||||
readonly minimumWidth = DEFAULT_EDITOR_MIN_DIMENSIONS.width;
|
||||
readonly maximumWidth = DEFAULT_EDITOR_MAX_DIMENSIONS.width;
|
||||
readonly minimumHeight = DEFAULT_EDITOR_MIN_DIMENSIONS.height;
|
||||
readonly maximumHeight = DEFAULT_EDITOR_MAX_DIMENSIONS.height;
|
||||
|
||||
readonly onDidSizeConstraintsChange: Event<{ width: number; height: number; }> = Event.None;
|
||||
|
||||
protected _input: EditorInput;
|
||||
|
||||
private _options: EditorOptions;
|
||||
private _group: IEditorGroup;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
telemetryService: ITelemetryService,
|
||||
themeService: IThemeService
|
||||
) {
|
||||
super(id, telemetryService, themeService);
|
||||
}
|
||||
|
||||
public get input(): EditorInput {
|
||||
get input(): EditorInput {
|
||||
return this._input;
|
||||
}
|
||||
|
||||
public get options(): EditorOptions {
|
||||
get options(): EditorOptions {
|
||||
return this._options;
|
||||
}
|
||||
|
||||
get group(): IEditorGroup {
|
||||
return this._group;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Clients should not call this method, the workbench calls this
|
||||
* method. Calling it otherwise may result in unexpected behavior.
|
||||
*
|
||||
* Sets the given input with the options to the part. An editor has to deal with the
|
||||
* situation that the same input is being set with different options.
|
||||
* Sets the given input with the options to the editor. The input is guaranteed
|
||||
* to be different from the previous input that was set using the input.matches()
|
||||
* method.
|
||||
*
|
||||
* The provided cancellation token should be used to test if the operation
|
||||
* was cancelled.
|
||||
*/
|
||||
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
|
||||
setInput(input: EditorInput, options: EditorOptions, token: CancellationToken): Thenable<void> {
|
||||
this._input = input;
|
||||
this._options = options;
|
||||
|
||||
@@ -55,17 +86,28 @@ export abstract class BaseEditor extends Panel implements IEditor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to indicate to the editor that the input should be cleared and resources associated with the
|
||||
* input should be freed.
|
||||
* Called to indicate to the editor that the input should be cleared and
|
||||
* resources associated with the input should be freed.
|
||||
*/
|
||||
public clearInput(): void {
|
||||
clearInput(): void {
|
||||
this._input = null;
|
||||
this._options = null;
|
||||
}
|
||||
|
||||
public create(parent: HTMLElement): void; // create is sync for editors
|
||||
public create(parent: HTMLElement): TPromise<void>;
|
||||
public create(parent: HTMLElement): TPromise<void> {
|
||||
/**
|
||||
* Note: Clients should not call this method, the workbench calls this
|
||||
* method. Calling it otherwise may result in unexpected behavior.
|
||||
*
|
||||
* Sets the given options to the editor. Clients should apply the options
|
||||
* to the current input.
|
||||
*/
|
||||
setOptions(options: EditorOptions): void {
|
||||
this._options = options;
|
||||
}
|
||||
|
||||
create(parent: HTMLElement): void; // create is sync for editors
|
||||
create(parent: HTMLElement): TPromise<void>;
|
||||
create(parent: HTMLElement): TPromise<void> {
|
||||
const res = super.create(parent);
|
||||
|
||||
// Create Editor
|
||||
@@ -79,50 +121,183 @@ export abstract class BaseEditor extends Panel implements IEditor {
|
||||
*/
|
||||
protected abstract createEditor(parent: HTMLElement): void;
|
||||
|
||||
/**
|
||||
* Subclasses can set this to false if it does not make sense to center editor input.
|
||||
*/
|
||||
public supportsCenteredLayout(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload this function to allow for passing in a position argument.
|
||||
*/
|
||||
public setVisible(visible: boolean, position?: Position): void; // setVisible is sync for editors
|
||||
public setVisible(visible: boolean, position?: Position): TPromise<void>;
|
||||
public setVisible(visible: boolean, position: Position = null): TPromise<void> {
|
||||
setVisible(visible: boolean, group?: IEditorGroup): void; // setVisible is sync for editors
|
||||
setVisible(visible: boolean, group?: IEditorGroup): TPromise<void>;
|
||||
setVisible(visible: boolean, group?: IEditorGroup): TPromise<void> {
|
||||
const promise = super.setVisible(visible);
|
||||
|
||||
// Propagate to Editor
|
||||
this.setEditorVisible(visible, position);
|
||||
this.setEditorVisible(visible, group);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
protected setEditorVisible(visible: boolean, position: Position = null): void {
|
||||
this._position = position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the position of the editor changes while it is visible.
|
||||
* Indicates that the editor control got visible or hidden in a specific group. A
|
||||
* editor instance will only ever be visible in one editor group.
|
||||
*
|
||||
* @param visible the state of visibility of this editor
|
||||
* @param group the editor group this editor is in.
|
||||
*/
|
||||
public changePosition(position: Position): void {
|
||||
this._position = position;
|
||||
protected setEditorVisible(visible: boolean, group: IEditorGroup): void {
|
||||
this._group = group;
|
||||
}
|
||||
|
||||
/**
|
||||
* The position this editor is showing in or null if none.
|
||||
*/
|
||||
public get position(): Position {
|
||||
return this._position;
|
||||
protected getEditorMemento<T>(storageService: IStorageService, editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento<T> {
|
||||
const mementoKey = `${this.getId()}${key}`;
|
||||
|
||||
let editorMemento = BaseEditor.EDITOR_MEMENTOS.get(mementoKey);
|
||||
if (!editorMemento) {
|
||||
editorMemento = new EditorMemento(this.getId(), key, this.getMemento(storageService), limit, editorGroupService);
|
||||
BaseEditor.EDITOR_MEMENTOS.set(mementoKey, editorMemento);
|
||||
}
|
||||
|
||||
return editorMemento;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
shutdown(): void {
|
||||
|
||||
// Shutdown all editor memento for this editor type
|
||||
BaseEditor.EDITOR_MEMENTOS.forEach(editorMemento => {
|
||||
if (editorMemento.id === this.getId()) {
|
||||
editorMemento.shutdown();
|
||||
}
|
||||
});
|
||||
|
||||
super.shutdown();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._input = null;
|
||||
this._options = null;
|
||||
|
||||
// Super Dispose
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
interface MapGroupToMemento<T> {
|
||||
[group: number]: T;
|
||||
}
|
||||
|
||||
export class EditorMemento<T> implements IEditorMemento<T> {
|
||||
private cache: LRUCache<string, MapGroupToMemento<T>>;
|
||||
private cleanedUp = false;
|
||||
|
||||
constructor(
|
||||
private _id: string,
|
||||
private key: string,
|
||||
private memento: object,
|
||||
private limit: number,
|
||||
private editorGroupService: IEditorGroupsService
|
||||
) { }
|
||||
|
||||
get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
saveState(group: IEditorGroup, resource: URI, state: T): void;
|
||||
saveState(group: IEditorGroup, editor: EditorInput, state: T): void;
|
||||
saveState(group: IEditorGroup, resourceOrEditor: URI | EditorInput, state: T): void {
|
||||
const resource = this.doGetResource(resourceOrEditor);
|
||||
if (!resource || !group) {
|
||||
return; // we are not in a good state to save any state for a resource
|
||||
}
|
||||
|
||||
const cache = this.doLoad();
|
||||
|
||||
let mementoForResource = cache.get(resource.toString());
|
||||
if (!mementoForResource) {
|
||||
mementoForResource = Object.create(null) as MapGroupToMemento<T>;
|
||||
cache.set(resource.toString(), mementoForResource);
|
||||
}
|
||||
|
||||
mementoForResource[group.id] = state;
|
||||
|
||||
// Automatically clear when editor input gets disposed if any
|
||||
if (resourceOrEditor instanceof EditorInput) {
|
||||
once(resourceOrEditor.onDispose)(() => {
|
||||
this.clearState(resource);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
loadState(group: IEditorGroup, resource: URI): T;
|
||||
loadState(group: IEditorGroup, editor: EditorInput): T;
|
||||
loadState(group: IEditorGroup, resourceOrEditor: URI | EditorInput): T {
|
||||
const resource = this.doGetResource(resourceOrEditor);
|
||||
if (!resource || !group) {
|
||||
return void 0; // we are not in a good state to load any state for a resource
|
||||
}
|
||||
|
||||
const cache = this.doLoad();
|
||||
|
||||
const mementoForResource = cache.get(resource.toString());
|
||||
if (mementoForResource) {
|
||||
return mementoForResource[group.id];
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
|
||||
clearState(resource: URI): void;
|
||||
clearState(editor: EditorInput): void;
|
||||
clearState(resourceOrEditor: URI | EditorInput): void {
|
||||
const resource = this.doGetResource(resourceOrEditor);
|
||||
if (resource) {
|
||||
const cache = this.doLoad();
|
||||
cache.delete(resource.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private doGetResource(resourceOrEditor: URI | EditorInput): URI {
|
||||
if (resourceOrEditor instanceof EditorInput) {
|
||||
return resourceOrEditor.getResource();
|
||||
}
|
||||
|
||||
return resourceOrEditor;
|
||||
}
|
||||
|
||||
private doLoad(): LRUCache<string, MapGroupToMemento<T>> {
|
||||
if (!this.cache) {
|
||||
this.cache = new LRUCache<string, MapGroupToMemento<T>>(this.limit);
|
||||
|
||||
// Restore from serialized map state
|
||||
const rawEditorMemento = this.memento[this.key];
|
||||
if (Array.isArray(rawEditorMemento)) {
|
||||
this.cache.fromJSON(rawEditorMemento);
|
||||
}
|
||||
}
|
||||
|
||||
return this.cache;
|
||||
}
|
||||
|
||||
shutdown(): void {
|
||||
const cache = this.doLoad();
|
||||
|
||||
// Cleanup once during shutdown
|
||||
if (!this.cleanedUp) {
|
||||
this.cleanUp();
|
||||
this.cleanedUp = true;
|
||||
}
|
||||
|
||||
this.memento[this.key] = cache.toJSON();
|
||||
}
|
||||
|
||||
private cleanUp(): void {
|
||||
const cache = this.doLoad();
|
||||
|
||||
// Remove groups from states that no longer exist
|
||||
cache.forEach((mapGroupToMemento, resource) => {
|
||||
Object.keys(mapGroupToMemento).forEach(group => {
|
||||
const groupId: GroupIdentifier = Number(group);
|
||||
if (!this.editorGroupService.getGroup(groupId)) {
|
||||
delete mapGroupToMemento[groupId];
|
||||
|
||||
if (isEmptyObject(mapGroupToMemento)) {
|
||||
cache.delete(resource);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/bina
|
||||
*/
|
||||
export class BinaryResourceDiffEditor extends SideBySideEditor {
|
||||
|
||||
public static readonly ID = BINARY_DIFF_EDITOR_ID;
|
||||
static readonly ID = BINARY_DIFF_EDITOR_ID;
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@@ -28,7 +28,7 @@ export class BinaryResourceDiffEditor extends SideBySideEditor {
|
||||
super(telemetryService, instantiationService, themeService);
|
||||
}
|
||||
|
||||
public getMetadata(): string {
|
||||
getMetadata(): string {
|
||||
const master = this.masterEditor;
|
||||
const details = this.detailsEditor;
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ResourceViewerContext, ResourceViewer } from 'vs/workbench/browser/parts/editor/resourceViewer';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { Dimension } from 'vs/base/browser/dom';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
export interface IOpenCallbacks {
|
||||
openInternal: (input: EditorInput, options: EditorOptions) => void;
|
||||
@@ -30,7 +32,8 @@ export interface IOpenCallbacks {
|
||||
*/
|
||||
export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
|
||||
private readonly _onMetadataChanged: Emitter<void>;
|
||||
private readonly _onMetadataChanged: Emitter<void> = this._register(new Emitter<void>());
|
||||
get onMetadataChanged(): Event<void> { return this._onMetadataChanged.event; }
|
||||
|
||||
private callbacks: IOpenCallbacks;
|
||||
private metadata: string;
|
||||
@@ -42,21 +45,15 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
id: string,
|
||||
callbacks: IOpenCallbacks,
|
||||
telemetryService: ITelemetryService,
|
||||
themeService: IThemeService
|
||||
themeService: IThemeService,
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
) {
|
||||
super(id, telemetryService, themeService);
|
||||
|
||||
this._onMetadataChanged = new Emitter<void>();
|
||||
this.toUnbind.push(this._onMetadataChanged);
|
||||
|
||||
this.callbacks = callbacks;
|
||||
}
|
||||
|
||||
public get onMetadataChanged(): Event<void> {
|
||||
return this._onMetadataChanged.event;
|
||||
}
|
||||
|
||||
public getTitle(): string {
|
||||
getTitle(): string {
|
||||
return this.input ? this.input.getName() : nls.localize('binaryEditor', "Binary Viewer");
|
||||
}
|
||||
|
||||
@@ -70,35 +67,28 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
this.binaryContainer.tabindex(0); // enable focus support from the editor part (do not remove)
|
||||
|
||||
// Custom Scrollbars
|
||||
this.scrollbar = new DomScrollableElement(binaryContainerElement, { horizontal: ScrollbarVisibility.Auto, vertical: ScrollbarVisibility.Auto });
|
||||
this.scrollbar = this._register(new DomScrollableElement(binaryContainerElement, { horizontal: ScrollbarVisibility.Auto, vertical: ScrollbarVisibility.Auto }));
|
||||
parent.appendChild(this.scrollbar.getDomNode());
|
||||
}
|
||||
|
||||
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
|
||||
setInput(input: EditorInput, options: EditorOptions, token: CancellationToken): Thenable<void> {
|
||||
return super.setInput(input, options, token).then(() => {
|
||||
return input.resolve().then(model => {
|
||||
|
||||
// Return early for same input unless we force to open
|
||||
const forceOpen = options && options.forceOpen;
|
||||
if (!forceOpen && input.matches(this.input)) {
|
||||
return TPromise.wrap<void>(null);
|
||||
}
|
||||
|
||||
// Otherwise set input and resolve
|
||||
return super.setInput(input, options).then(() => {
|
||||
return input.resolve(true).then(model => {
|
||||
// Check for cancellation
|
||||
if (token.isCancellationRequested) {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
// Assert Model instance
|
||||
if (!(model instanceof BinaryEditorModel)) {
|
||||
return TPromise.wrapError<void>(new Error('Unable to open file as binary'));
|
||||
}
|
||||
|
||||
// Assert that the current input is still the one we expect. This prevents a race condition when loading takes long and another input was set meanwhile
|
||||
if (!this.input || this.input !== input) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Render Input
|
||||
this.resourceViewerContext = ResourceViewer.show(
|
||||
{ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() },
|
||||
this._fileService,
|
||||
this.binaryContainer.getHTMLElement(),
|
||||
this.scrollbar,
|
||||
resource => this.callbacks.openInternal(input, options),
|
||||
@@ -106,25 +96,22 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
meta => this.handleMetadataChanged(meta)
|
||||
);
|
||||
|
||||
return TPromise.as<void>(null);
|
||||
return void 0;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private handleMetadataChanged(meta: string): void {
|
||||
this.metadata = meta;
|
||||
|
||||
this._onMetadataChanged.fire();
|
||||
}
|
||||
|
||||
public getMetadata(): string {
|
||||
getMetadata(): string {
|
||||
return this.metadata;
|
||||
}
|
||||
|
||||
public supportsCenteredLayout(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public clearInput(): void {
|
||||
clearInput(): void {
|
||||
|
||||
// Clear Meta
|
||||
this.handleMetadataChanged(null);
|
||||
@@ -135,7 +122,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
super.clearInput();
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
layout(dimension: Dimension): void {
|
||||
|
||||
// Pass on to Binary Container
|
||||
this.binaryContainer.size(dimension.width, dimension.height);
|
||||
@@ -145,15 +132,14 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
}
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
focus(): void {
|
||||
this.binaryContainer.domFocus();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
dispose(): void {
|
||||
|
||||
// Destroy Container
|
||||
this.binaryContainer.destroy();
|
||||
this.scrollbar.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
147
src/vs/workbench/browser/parts/editor/breadcrumbs.ts
Normal file
147
src/vs/workbench/browser/parts/editor/breadcrumbs.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { BreadcrumbsWidget } from 'vs/base/browser/ui/breadcrumbs/breadcrumbsWidget';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { GroupIdentifier } from 'vs/workbench/common/editor';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export const IBreadcrumbsService = createDecorator<IBreadcrumbsService>('IEditorBreadcrumbsService');
|
||||
|
||||
export interface IBreadcrumbsService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
register(group: GroupIdentifier, widget: BreadcrumbsWidget): IDisposable;
|
||||
|
||||
getWidget(group: GroupIdentifier): BreadcrumbsWidget;
|
||||
}
|
||||
|
||||
|
||||
export class BreadcrumbsService implements IBreadcrumbsService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private readonly _map = new Map<number, BreadcrumbsWidget>();
|
||||
|
||||
register(group: number, widget: BreadcrumbsWidget): IDisposable {
|
||||
if (this._map.has(group)) {
|
||||
throw new Error(`group (${group}) has already a widget`);
|
||||
}
|
||||
this._map.set(group, widget);
|
||||
return {
|
||||
dispose: () => this._map.delete(group)
|
||||
};
|
||||
}
|
||||
|
||||
getWidget(group: number): BreadcrumbsWidget {
|
||||
return this._map.get(group);
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IBreadcrumbsService, BreadcrumbsService);
|
||||
|
||||
|
||||
//#region config
|
||||
|
||||
export abstract class BreadcrumbsConfig<T> {
|
||||
|
||||
name: string;
|
||||
value: T;
|
||||
onDidChange: Event<T>;
|
||||
abstract dispose(): void;
|
||||
|
||||
private constructor() {
|
||||
// internal
|
||||
}
|
||||
|
||||
static IsEnabled = BreadcrumbsConfig._stub<boolean>('breadcrumbs.enabled');
|
||||
static UseQuickPick = BreadcrumbsConfig._stub<boolean>('breadcrumbs.useQuickPick');
|
||||
static FilePath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.filePath');
|
||||
static SymbolPath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.symbolPath');
|
||||
|
||||
private static _stub<T>(name: string): { bindTo(service: IConfigurationService): BreadcrumbsConfig<T> } {
|
||||
return {
|
||||
bindTo(service) {
|
||||
let value: T = service.getValue(name);
|
||||
let onDidChange = new Emitter<T>();
|
||||
|
||||
let listener = service.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(name)) {
|
||||
value = service.getValue(name);
|
||||
onDidChange.fire(value);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
name,
|
||||
get value() {
|
||||
return value;
|
||||
},
|
||||
set value(newValue: T) {
|
||||
service.updateValue(name, newValue);
|
||||
value = newValue;
|
||||
},
|
||||
onDidChange: onDidChange.event,
|
||||
dispose(): void {
|
||||
listener.dispose();
|
||||
onDidChange.dispose();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
|
||||
id: 'breadcrumbs',
|
||||
title: localize('title', "Breadcrumb Navigation"),
|
||||
order: 101,
|
||||
type: 'object',
|
||||
properties: {
|
||||
'breadcrumbs.enabled': {
|
||||
description: localize('enabled', "Enable/disable navigation breadcrumbs"),
|
||||
type: 'boolean',
|
||||
default: false
|
||||
},
|
||||
// 'breadcrumbs.useQuickPick': {
|
||||
// description: localize('useQuickPick', "Use quick pick instead of breadcrumb-pickers."),
|
||||
// type: 'boolean',
|
||||
// default: false
|
||||
// },
|
||||
'breadcrumbs.filePath': {
|
||||
description: localize('filepath', "Controls whether and how file paths are shown in the breadcrumbs view."),
|
||||
type: 'string',
|
||||
default: 'on',
|
||||
enum: ['on', 'off', 'last'],
|
||||
enumDescriptions: [
|
||||
localize('filepath.on', "Show the file path in the breadcrumbs view."),
|
||||
localize('filepath.off', "Do not show the file path in the breadcrumbs view."),
|
||||
localize('filepath.last', "Only show the last element of the file path in the breadcrumbs view."),
|
||||
]
|
||||
},
|
||||
'breadcrumbs.symbolPath': {
|
||||
description: localize('symbolpath', "Controls whether and how symbols are shown in the breadcrumbs view."),
|
||||
type: 'string',
|
||||
default: 'on',
|
||||
enum: ['on', 'off', 'last'],
|
||||
enumDescriptions: [
|
||||
localize('symbolpath.on', "Show all symbols in the breadcrumbs view."),
|
||||
localize('symbolpath.off', "Do not show symbols in the breadcrumbs view."),
|
||||
localize('symbolpath.last', "Only show the current symbol in the breadcrumbs view."),
|
||||
]
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
//#endregion
|
||||
501
src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts
Normal file
501
src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts
Normal file
@@ -0,0 +1,501 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { BreadcrumbsItem, BreadcrumbsWidget, IBreadcrumbsItemEvent } from 'vs/base/browser/ui/breadcrumbs/breadcrumbsWidget';
|
||||
import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { combinedDisposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import 'vs/css!./media/breadcrumbscontrol';
|
||||
import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { symbolKindToCssClass } from 'vs/editor/common/modes';
|
||||
import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { FileKind, IFileService } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { attachBreadcrumbsStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { FileLabel } from 'vs/workbench/browser/labels';
|
||||
import { BreadcrumbsConfig, IBreadcrumbsService } from 'vs/workbench/browser/parts/editor/breadcrumbs';
|
||||
import { BreadcrumbElement, EditorBreadcrumbsModel, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
|
||||
import { createBreadcrumbsPicker, BreadcrumbsPicker } from 'vs/workbench/browser/parts/editor/breadcrumbsPicker';
|
||||
import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupView';
|
||||
import { IEditorService, SIDE_GROUP, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { localize } from 'vs/nls';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { tail } from 'vs/base/common/arrays';
|
||||
import { WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService';
|
||||
|
||||
class Item extends BreadcrumbsItem {
|
||||
|
||||
private readonly _disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
readonly element: BreadcrumbElement,
|
||||
readonly options: IBreadcrumbsControlOptions,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose(this._disposables);
|
||||
}
|
||||
|
||||
equals(other: BreadcrumbsItem): boolean {
|
||||
if (!(other instanceof Item)) {
|
||||
return false;
|
||||
}
|
||||
if (this.element instanceof FileElement && other.element instanceof FileElement) {
|
||||
return isEqual(this.element.uri, other.element.uri);
|
||||
}
|
||||
if (this.element instanceof TreeElement && other.element instanceof TreeElement) {
|
||||
return this.element.id === other.element.id;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
if (this.element instanceof FileElement) {
|
||||
// file/folder
|
||||
let label = this._instantiationService.createInstance(FileLabel, container, {});
|
||||
label.setFile(this.element.uri, {
|
||||
hidePath: true,
|
||||
hideIcon: this.element.kind === FileKind.FOLDER || !this.options.showFileIcons,
|
||||
fileKind: this.element.kind,
|
||||
fileDecorations: { colors: this.options.showDecorationColors, badges: false },
|
||||
});
|
||||
dom.addClass(container, FileKind[this.element.kind].toLowerCase());
|
||||
this._disposables.push(label);
|
||||
|
||||
} else if (this.element instanceof OutlineModel) {
|
||||
// has outline element but not in one
|
||||
let label = document.createElement('div');
|
||||
label.innerHTML = '…';
|
||||
label.className = 'hint-more';
|
||||
container.appendChild(label);
|
||||
|
||||
} else if (this.element instanceof OutlineGroup) {
|
||||
// provider
|
||||
let label = new IconLabel(container);
|
||||
label.setValue(this.element.provider.displayName);
|
||||
this._disposables.push(label);
|
||||
|
||||
} else if (this.element instanceof OutlineElement) {
|
||||
// symbol
|
||||
if (this.options.showSymbolIcons) {
|
||||
let icon = document.createElement('div');
|
||||
icon.className = symbolKindToCssClass(this.element.symbol.kind);
|
||||
container.appendChild(icon);
|
||||
dom.addClass(container, 'shows-symbol-icon');
|
||||
}
|
||||
let label = new IconLabel(container);
|
||||
let title = this.element.symbol.name.replace(/\r|\n|\r\n/g, '\u23CE');
|
||||
label.setValue(title);
|
||||
this._disposables.push(label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface IBreadcrumbsControlOptions {
|
||||
showFileIcons: boolean;
|
||||
showSymbolIcons: boolean;
|
||||
showDecorationColors: boolean;
|
||||
extraClasses: string[];
|
||||
}
|
||||
|
||||
export class BreadcrumbsControl {
|
||||
|
||||
static HEIGHT = 25;
|
||||
|
||||
static readonly Payload_Reveal = {};
|
||||
static readonly Payload_RevealAside = {};
|
||||
static readonly Payload_Pick = {};
|
||||
|
||||
static CK_BreadcrumbsVisible = new RawContextKey('breadcrumbsVisible', false);
|
||||
static CK_BreadcrumbsActive = new RawContextKey('breadcrumbsActive', false);
|
||||
|
||||
private readonly _ckBreadcrumbsVisible: IContextKey<boolean>;
|
||||
private readonly _ckBreadcrumbsActive: IContextKey<boolean>;
|
||||
|
||||
private readonly _cfUseQuickPick: BreadcrumbsConfig<boolean>;
|
||||
|
||||
readonly domNode: HTMLDivElement;
|
||||
private readonly _widget: BreadcrumbsWidget;
|
||||
|
||||
private _disposables = new Array<IDisposable>();
|
||||
private _breadcrumbsDisposables = new Array<IDisposable>();
|
||||
private _breadcrumbsPickerShowing = false;
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
private readonly _options: IBreadcrumbsControlOptions,
|
||||
private readonly _editorGroup: EditorGroupView,
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@IContextViewService private readonly _contextViewService: IContextViewService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IThemeService private readonly _themeService: IThemeService,
|
||||
@IQuickOpenService private readonly _quickOpenService: IQuickOpenService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
@IBreadcrumbsService breadcrumbsService: IBreadcrumbsService,
|
||||
) {
|
||||
this.domNode = document.createElement('div');
|
||||
dom.addClass(this.domNode, 'breadcrumbs-control');
|
||||
dom.addClasses(this.domNode, ..._options.extraClasses);
|
||||
dom.append(container, this.domNode);
|
||||
|
||||
this._widget = new BreadcrumbsWidget(this.domNode);
|
||||
this._widget.onDidSelectItem(this._onSelectEvent, this, this._disposables);
|
||||
this._widget.onDidFocusItem(this._onFocusEvent, this, this._disposables);
|
||||
this._widget.onDidChangeFocus(this._updateCkBreadcrumbsActive, this, this._disposables);
|
||||
this._disposables.push(attachBreadcrumbsStyler(this._widget, this._themeService));
|
||||
|
||||
this._ckBreadcrumbsVisible = BreadcrumbsControl.CK_BreadcrumbsVisible.bindTo(this._contextKeyService);
|
||||
this._ckBreadcrumbsActive = BreadcrumbsControl.CK_BreadcrumbsActive.bindTo(this._contextKeyService);
|
||||
|
||||
this._cfUseQuickPick = BreadcrumbsConfig.UseQuickPick.bindTo(_configurationService);
|
||||
|
||||
this._disposables.push(breadcrumbsService.register(this._editorGroup.id, this._widget));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._disposables = dispose(this._disposables);
|
||||
this._breadcrumbsDisposables = dispose(this._breadcrumbsDisposables);
|
||||
this._ckBreadcrumbsVisible.reset();
|
||||
this._ckBreadcrumbsActive.reset();
|
||||
this._cfUseQuickPick.dispose();
|
||||
this._widget.dispose();
|
||||
this.domNode.remove();
|
||||
}
|
||||
|
||||
layout(dim: dom.Dimension): void {
|
||||
this._widget.layout(dim);
|
||||
}
|
||||
|
||||
isHidden(): boolean {
|
||||
return dom.hasClass(this.domNode, 'hidden');
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this._breadcrumbsDisposables = dispose(this._breadcrumbsDisposables);
|
||||
this._ckBreadcrumbsVisible.set(false);
|
||||
dom.toggleClass(this.domNode, 'hidden', true);
|
||||
}
|
||||
|
||||
update(): boolean {
|
||||
const input = this._editorGroup.activeEditor;
|
||||
this._breadcrumbsDisposables = dispose(this._breadcrumbsDisposables);
|
||||
|
||||
if (!input || !input.getResource() || (input.getResource().scheme !== Schemas.untitled && !this._fileService.canHandleResource(input.getResource()))) {
|
||||
// cleanup and return when there is no input or when
|
||||
// we cannot handle this input
|
||||
if (!this.isHidden()) {
|
||||
this.hide();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
dom.toggleClass(this.domNode, 'hidden', false);
|
||||
this._ckBreadcrumbsVisible.set(true);
|
||||
|
||||
let control = this._editorGroup.activeControl.getControl() as ICodeEditor;
|
||||
let model = new EditorBreadcrumbsModel(input.getResource(), isCodeEditor(control) ? control : undefined, this._workspaceService, this._configurationService);
|
||||
dom.toggleClass(this.domNode, 'relative-path', model.isRelative());
|
||||
|
||||
let updateBreadcrumbs = () => {
|
||||
let items = model.getElements().map(element => new Item(element, this._options, this._instantiationService));
|
||||
this._widget.setItems(items);
|
||||
this._widget.reveal(items[items.length - 1]);
|
||||
};
|
||||
let listener = model.onDidUpdate(updateBreadcrumbs);
|
||||
updateBreadcrumbs();
|
||||
this._breadcrumbsDisposables = [model, listener];
|
||||
|
||||
// close picker on hide/update
|
||||
this._breadcrumbsDisposables.push({
|
||||
dispose: () => {
|
||||
if (this._breadcrumbsPickerShowing) {
|
||||
this._contextViewService.hideContextView(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private _onFocusEvent(event: IBreadcrumbsItemEvent): void {
|
||||
if (event.item && this._breadcrumbsPickerShowing) {
|
||||
return this._widget.setSelection(event.item);
|
||||
}
|
||||
}
|
||||
|
||||
private _onSelectEvent(event: IBreadcrumbsItemEvent): void {
|
||||
if (!event.item) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._editorGroup.focus();
|
||||
const { element } = event.item as Item;
|
||||
|
||||
const group = this._getEditorGroup(event.payload);
|
||||
if (group !== undefined) {
|
||||
// reveal the item
|
||||
this._widget.setFocused(undefined);
|
||||
this._widget.setSelection(undefined);
|
||||
this._revealInEditor(event, element, group);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._cfUseQuickPick.value) {
|
||||
// using quick pick
|
||||
this._widget.setFocused(undefined);
|
||||
this._widget.setSelection(undefined);
|
||||
this._quickOpenService.show(element instanceof TreeElement ? '@' : '');
|
||||
return;
|
||||
}
|
||||
|
||||
// show picker
|
||||
let picker: BreadcrumbsPicker;
|
||||
this._contextViewService.showContextView({
|
||||
render: (parent: HTMLElement) => {
|
||||
picker = createBreadcrumbsPicker(this._instantiationService, parent, element);
|
||||
let listener = picker.onDidPickElement(data => {
|
||||
this._contextViewService.hideContextView(this);
|
||||
this._revealInEditor(event, data.target, this._getEditorGroup(data.payload && data.payload.originalEvent));
|
||||
});
|
||||
this._breadcrumbsPickerShowing = true;
|
||||
this._updateCkBreadcrumbsActive();
|
||||
|
||||
return combinedDisposable([listener, picker]);
|
||||
},
|
||||
getAnchor() {
|
||||
|
||||
let pickerHeight = 330;
|
||||
let pickerWidth = Math.max(220, dom.getTotalWidth(event.node));
|
||||
let pickerArrowSize = 8;
|
||||
let pickerArrowOffset: number;
|
||||
|
||||
let data = dom.getDomNodePagePosition(event.node.firstChild as HTMLElement);
|
||||
let y = data.top + data.height - pickerArrowSize;
|
||||
let x = data.left;
|
||||
if (x + pickerWidth >= window.innerWidth) {
|
||||
x = window.innerWidth - pickerWidth;
|
||||
}
|
||||
if (event.payload instanceof StandardMouseEvent) {
|
||||
pickerArrowOffset = event.payload.posx - x - pickerArrowSize;
|
||||
} else {
|
||||
pickerArrowOffset = (data.left + (data.width * .3)) - x;
|
||||
}
|
||||
picker.layout(pickerHeight, pickerWidth, pickerArrowSize, Math.max(0, pickerArrowOffset));
|
||||
picker.setInput(element);
|
||||
return { x, y };
|
||||
},
|
||||
onHide: (data) => {
|
||||
this._breadcrumbsPickerShowing = false;
|
||||
this._updateCkBreadcrumbsActive();
|
||||
if (data === this) {
|
||||
this._widget.setFocused(undefined);
|
||||
this._widget.setSelection(undefined);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _updateCkBreadcrumbsActive(): void {
|
||||
const value = this._widget.isDOMFocused() || this._breadcrumbsPickerShowing;
|
||||
this._ckBreadcrumbsActive.set(value);
|
||||
}
|
||||
|
||||
private _revealInEditor(event: IBreadcrumbsItemEvent, element: any, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): void {
|
||||
if (element instanceof FileElement) {
|
||||
if (element.kind === FileKind.FILE) {
|
||||
// open file in editor
|
||||
this._editorService.openEditor({ resource: element.uri }, group);
|
||||
} else {
|
||||
// show next picker
|
||||
let items = this._widget.getItems();
|
||||
let idx = items.indexOf(event.item);
|
||||
this._widget.setFocused(items[idx + 1]);
|
||||
this._widget.setSelection(items[idx + 1], BreadcrumbsControl.Payload_Pick);
|
||||
}
|
||||
|
||||
} else if (element instanceof OutlineElement) {
|
||||
// open symbol in editor
|
||||
let model = OutlineModel.get(element);
|
||||
this._editorService.openEditor({
|
||||
resource: model.textModel.uri,
|
||||
options: { selection: Range.collapseToStart(element.symbol.selectionRange) }
|
||||
}, group);
|
||||
}
|
||||
}
|
||||
|
||||
private _getEditorGroup(data: StandardMouseEvent | object): SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined {
|
||||
if (data === BreadcrumbsControl.Payload_RevealAside || (data instanceof StandardMouseEvent && data.altKey)) {
|
||||
return SIDE_GROUP;
|
||||
} else if (data === BreadcrumbsControl.Payload_Reveal || (data instanceof StandardMouseEvent && data.metaKey)) {
|
||||
return ACTIVE_GROUP;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#region commands
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
command: {
|
||||
id: 'breadcrumbs.focusAndSelect',
|
||||
title: localize('cmd.focus', "Focus Breadcrumbs")
|
||||
}
|
||||
});
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
command: {
|
||||
id: 'breadcrumbs.toggle',
|
||||
title: localize('cmd.toggle', "Toggle Breadcrumbs")
|
||||
}
|
||||
});
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
|
||||
group: '5_editor',
|
||||
order: 99,
|
||||
command: {
|
||||
id: 'breadcrumbs.toggle',
|
||||
title: localize('cmd.toggle', "Toggle Breadcrumbs")
|
||||
}
|
||||
});
|
||||
CommandsRegistry.registerCommand('breadcrumbs.toggle', accessor => {
|
||||
let config = accessor.get(IConfigurationService);
|
||||
let value = BreadcrumbsConfig.IsEnabled.bindTo(config).value;
|
||||
BreadcrumbsConfig.IsEnabled.bindTo(config).value = !value;
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'breadcrumbs.focus',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_SEMICOLON,
|
||||
when: BreadcrumbsControl.CK_BreadcrumbsVisible,
|
||||
handler(accessor) {
|
||||
const groups = accessor.get(IEditorGroupsService);
|
||||
const breadcrumbs = accessor.get(IBreadcrumbsService);
|
||||
const widget = breadcrumbs.getWidget(groups.activeGroup.id);
|
||||
const item = tail(widget.getItems());
|
||||
widget.setFocused(item);
|
||||
}
|
||||
});
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'breadcrumbs.focusAndSelect',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_DOT,
|
||||
when: BreadcrumbsControl.CK_BreadcrumbsVisible,
|
||||
handler(accessor) {
|
||||
const groups = accessor.get(IEditorGroupsService);
|
||||
const breadcrumbs = accessor.get(IBreadcrumbsService);
|
||||
const widget = breadcrumbs.getWidget(groups.activeGroup.id);
|
||||
const item = tail(widget.getItems());
|
||||
widget.setFocused(item);
|
||||
widget.setSelection(item, BreadcrumbsControl.Payload_Pick);
|
||||
}
|
||||
});
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'breadcrumbs.focusNext',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyCode.RightArrow,
|
||||
secondary: [KeyMod.CtrlCmd | KeyCode.RightArrow],
|
||||
mac: {
|
||||
primary: KeyCode.RightArrow,
|
||||
secondary: [KeyMod.Alt | KeyCode.RightArrow],
|
||||
},
|
||||
when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive),
|
||||
handler(accessor) {
|
||||
const groups = accessor.get(IEditorGroupsService);
|
||||
const breadcrumbs = accessor.get(IBreadcrumbsService);
|
||||
breadcrumbs.getWidget(groups.activeGroup.id).focusNext();
|
||||
}
|
||||
});
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'breadcrumbs.focusPrevious',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyCode.LeftArrow,
|
||||
secondary: [KeyMod.CtrlCmd | KeyCode.LeftArrow],
|
||||
mac: {
|
||||
primary: KeyCode.LeftArrow,
|
||||
secondary: [KeyMod.Alt | KeyCode.LeftArrow],
|
||||
},
|
||||
when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive),
|
||||
handler(accessor) {
|
||||
const groups = accessor.get(IEditorGroupsService);
|
||||
const breadcrumbs = accessor.get(IBreadcrumbsService);
|
||||
breadcrumbs.getWidget(groups.activeGroup.id).focusPrev();
|
||||
}
|
||||
});
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'breadcrumbs.selectFocused',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyCode.Enter,
|
||||
secondary: [KeyCode.DownArrow],
|
||||
when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive),
|
||||
handler(accessor) {
|
||||
const groups = accessor.get(IEditorGroupsService);
|
||||
const breadcrumbs = accessor.get(IBreadcrumbsService);
|
||||
const widget = breadcrumbs.getWidget(groups.activeGroup.id);
|
||||
widget.setSelection(widget.getFocused(), BreadcrumbsControl.Payload_Pick);
|
||||
}
|
||||
});
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'breadcrumbs.revealFocused',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyCode.Space,
|
||||
secondary: [KeyMod.CtrlCmd | KeyCode.Enter],
|
||||
when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive),
|
||||
handler(accessor) {
|
||||
const groups = accessor.get(IEditorGroupsService);
|
||||
const breadcrumbs = accessor.get(IBreadcrumbsService);
|
||||
const widget = breadcrumbs.getWidget(groups.activeGroup.id);
|
||||
widget.setSelection(widget.getFocused(), BreadcrumbsControl.Payload_Reveal);
|
||||
}
|
||||
});
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'breadcrumbs.selectEditor',
|
||||
weight: KeybindingWeight.WorkbenchContrib + 1,
|
||||
primary: KeyCode.Escape,
|
||||
when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive),
|
||||
handler(accessor) {
|
||||
const groups = accessor.get(IEditorGroupsService);
|
||||
const breadcrumbs = accessor.get(IBreadcrumbsService);
|
||||
breadcrumbs.getWidget(groups.activeGroup.id).setFocused(undefined);
|
||||
breadcrumbs.getWidget(groups.activeGroup.id).setSelection(undefined);
|
||||
groups.activeGroup.activeControl.focus();
|
||||
}
|
||||
});
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'breadcrumbs.revealFocusedFromTreeAside',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.Enter,
|
||||
when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive, WorkbenchListFocusContextKey),
|
||||
handler(accessor) {
|
||||
const groups = accessor.get(IEditorGroupsService);
|
||||
const breadcrumbs = accessor.get(IBreadcrumbsService);
|
||||
const widget = breadcrumbs.getWidget(groups.activeGroup.id);
|
||||
widget.setSelection(widget.getFocused(), BreadcrumbsControl.Payload_RevealAside);
|
||||
}
|
||||
});
|
||||
//#endregion
|
||||
231
src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts
Normal file
231
src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { equals } from 'vs/base/common/arrays';
|
||||
import { TimeoutTimer } from 'vs/base/common/async';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { size } from 'vs/base/common/collections';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { debounceEvent, Emitter, Event } from 'vs/base/common/event';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as paths from 'vs/base/common/paths';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { DocumentSymbolProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
|
||||
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs';
|
||||
import { FileKind } from 'vs/platform/files/common/files';
|
||||
|
||||
export class FileElement {
|
||||
constructor(
|
||||
readonly uri: URI,
|
||||
readonly kind: FileKind
|
||||
) { }
|
||||
}
|
||||
|
||||
export type BreadcrumbElement = FileElement | OutlineModel | OutlineGroup | OutlineElement;
|
||||
|
||||
type FileInfo = { path: FileElement[], folder: IWorkspaceFolder };
|
||||
|
||||
export class EditorBreadcrumbsModel {
|
||||
|
||||
private readonly _disposables: IDisposable[] = [];
|
||||
private readonly _fileInfo: FileInfo;
|
||||
|
||||
private readonly _cfgFilePath: BreadcrumbsConfig<'on' | 'off' | 'last'>;
|
||||
private readonly _cfgSymbolPath: BreadcrumbsConfig<'on' | 'off' | 'last'>;
|
||||
|
||||
private _outlineElements: (OutlineModel | OutlineGroup | OutlineElement)[] = [];
|
||||
private _outlineDisposables: IDisposable[] = [];
|
||||
|
||||
private _onDidUpdate = new Emitter<this>();
|
||||
readonly onDidUpdate: Event<this> = this._onDidUpdate.event;
|
||||
|
||||
constructor(
|
||||
private readonly _uri: URI,
|
||||
private readonly _editor: ICodeEditor | undefined,
|
||||
@IWorkspaceContextService workspaceService: IWorkspaceContextService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
) {
|
||||
|
||||
this._cfgFilePath = BreadcrumbsConfig.FilePath.bindTo(configurationService);
|
||||
this._cfgSymbolPath = BreadcrumbsConfig.SymbolPath.bindTo(configurationService);
|
||||
|
||||
this._disposables.push(this._cfgFilePath.onDidChange(_ => this._onDidUpdate.fire(this)));
|
||||
this._disposables.push(this._cfgSymbolPath.onDidChange(_ => this._onDidUpdate.fire(this)));
|
||||
|
||||
this._fileInfo = EditorBreadcrumbsModel._initFilePathInfo(this._uri, workspaceService);
|
||||
this._bindToEditor();
|
||||
this._onDidUpdate.fire(this);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._cfgFilePath.dispose();
|
||||
this._cfgSymbolPath.dispose();
|
||||
dispose(this._disposables);
|
||||
}
|
||||
|
||||
isRelative(): boolean {
|
||||
return Boolean(this._fileInfo.folder);
|
||||
}
|
||||
|
||||
getElements(): ReadonlyArray<BreadcrumbElement> {
|
||||
let result: BreadcrumbElement[] = [];
|
||||
|
||||
// file path elements
|
||||
if (this._cfgFilePath.value === 'on') {
|
||||
result = result.concat(this._fileInfo.path);
|
||||
} else if (this._cfgFilePath.value === 'last' && this._fileInfo.path.length > 0) {
|
||||
result = result.concat(this._fileInfo.path.slice(-1));
|
||||
}
|
||||
|
||||
// symbol path elements
|
||||
if (this._cfgSymbolPath.value === 'on') {
|
||||
result = result.concat(this._outlineElements);
|
||||
} else if (this._cfgSymbolPath.value === 'last' && this._outlineElements.length > 0) {
|
||||
result = result.concat(this._outlineElements.slice(-1));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static _initFilePathInfo(uri: URI, workspaceService: IWorkspaceContextService): FileInfo {
|
||||
|
||||
if (uri.scheme === Schemas.untitled) {
|
||||
return {
|
||||
folder: undefined,
|
||||
path: []
|
||||
};
|
||||
}
|
||||
|
||||
let info: FileInfo = {
|
||||
folder: workspaceService.getWorkspaceFolder(uri),
|
||||
path: []
|
||||
};
|
||||
|
||||
while (uri.path !== '/') {
|
||||
if (info.folder && isEqual(info.folder.uri, uri)) {
|
||||
break;
|
||||
}
|
||||
info.path.unshift(new FileElement(uri, info.path.length === 0 ? FileKind.FILE : FileKind.FOLDER));
|
||||
uri = uri.with({ path: paths.dirname(uri.path) });
|
||||
}
|
||||
|
||||
if (info.folder && workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
|
||||
info.path.unshift(new FileElement(info.folder.uri, FileKind.ROOT_FOLDER));
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
private _bindToEditor(): void {
|
||||
if (!this._editor) {
|
||||
return;
|
||||
}
|
||||
// update as model changes
|
||||
this._disposables.push(DocumentSymbolProviderRegistry.onDidChange(_ => this._updateOutline()));
|
||||
this._disposables.push(this._editor.onDidChangeModel(_ => this._updateOutline()));
|
||||
this._disposables.push(this._editor.onDidChangeModelLanguage(_ => this._updateOutline()));
|
||||
this._disposables.push(debounceEvent(this._editor.onDidChangeModelContent, _ => _, 350)(_ => this._updateOutline(true)));
|
||||
this._updateOutline();
|
||||
|
||||
// stop when editor dies
|
||||
this._disposables.push(this._editor.onDidDispose(() => this._outlineDisposables = dispose(this._outlineDisposables)));
|
||||
}
|
||||
|
||||
private _updateOutline(didChangeContent?: boolean): void {
|
||||
|
||||
this._outlineDisposables = dispose(this._outlineDisposables);
|
||||
if (!didChangeContent) {
|
||||
this._updateOutlineElements([]);
|
||||
}
|
||||
|
||||
const buffer = this._editor.getModel();
|
||||
if (!buffer || !DocumentSymbolProviderRegistry.has(buffer) || !isEqual(buffer.uri, this._uri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const source = new CancellationTokenSource();
|
||||
const versionIdThen = buffer.getVersionId();
|
||||
const timeout = new TimeoutTimer();
|
||||
|
||||
this._outlineDisposables.push({
|
||||
dispose: () => {
|
||||
source.cancel();
|
||||
source.dispose();
|
||||
timeout.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
OutlineModel.create(buffer, source.token).then(model => {
|
||||
if (TreeElement.empty(model)) {
|
||||
// empty -> no outline elements
|
||||
this._updateOutlineElements([]);
|
||||
|
||||
} else {
|
||||
// copy the model
|
||||
model = model.adopt();
|
||||
|
||||
this._updateOutlineElements(this._getOutlineElements(model, this._editor.getPosition()));
|
||||
this._outlineDisposables.push(this._editor.onDidChangeCursorPosition(_ => {
|
||||
timeout.cancelAndSet(() => {
|
||||
if (!buffer.isDisposed() && versionIdThen === buffer.getVersionId()) {
|
||||
this._updateOutlineElements(this._getOutlineElements(model, this._editor.getPosition()));
|
||||
}
|
||||
}, 150);
|
||||
}));
|
||||
}
|
||||
}).catch(err => {
|
||||
this._updateOutlineElements([]);
|
||||
onUnexpectedError(err);
|
||||
});
|
||||
}
|
||||
|
||||
private _getOutlineElements(model: OutlineModel, position: IPosition): (OutlineModel | OutlineGroup | OutlineElement)[] {
|
||||
if (!model) {
|
||||
return [];
|
||||
}
|
||||
let item: OutlineGroup | OutlineElement = model.getItemEnclosingPosition(position);
|
||||
if (!item) {
|
||||
return [model];
|
||||
}
|
||||
let chain: (OutlineGroup | OutlineElement)[] = [];
|
||||
while (item) {
|
||||
chain.push(item);
|
||||
let parent = item.parent;
|
||||
if (parent instanceof OutlineModel) {
|
||||
break;
|
||||
}
|
||||
if (parent instanceof OutlineGroup && size(parent.parent.children) === 1) {
|
||||
break;
|
||||
}
|
||||
item = parent;
|
||||
}
|
||||
return chain.reverse();
|
||||
}
|
||||
|
||||
private _updateOutlineElements(elements: (OutlineModel | OutlineGroup | OutlineElement)[]): void {
|
||||
if (!equals(elements, this._outlineElements, EditorBreadcrumbsModel._outlineElementEquals)) {
|
||||
this._outlineElements = elements;
|
||||
this._onDidUpdate.fire(this);
|
||||
}
|
||||
}
|
||||
|
||||
private static _outlineElementEquals(a: OutlineModel | OutlineGroup | OutlineElement, b: OutlineModel | OutlineGroup | OutlineElement): boolean {
|
||||
if (a === b) {
|
||||
return true;
|
||||
} else if (!a || !b) {
|
||||
return false;
|
||||
} else {
|
||||
return a.id === b.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
367
src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts
Normal file
367
src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts
Normal file
@@ -0,0 +1,367 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { compareFileNames } from 'vs/base/common/comparers';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { dirname, isEqual } from 'vs/base/common/resources';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IDataSource, IRenderer, ISelectionEvent, ISorter, ITree } from 'vs/base/parts/tree/browser/tree';
|
||||
import 'vs/css!./media/breadcrumbscontrol';
|
||||
import { OutlineElement, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
|
||||
import { OutlineDataSource, OutlineItemComparator, OutlineRenderer } from 'vs/editor/contrib/documentSymbols/outlineTree';
|
||||
import { localize } from 'vs/nls';
|
||||
import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService, IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { HighlightingWorkbenchTree, IHighlightingTreeConfiguration, IHighlightingRenderer } from 'vs/platform/list/browser/listService';
|
||||
import { IThemeService, DARK } from 'vs/platform/theme/common/themeService';
|
||||
import { FileLabel } from 'vs/workbench/browser/labels';
|
||||
import { BreadcrumbElement, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { breadcrumbsPickerBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { FuzzyScore, createMatches, fuzzyScore } from 'vs/base/common/filters';
|
||||
import { IWorkspaceContextService, IWorkspace, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
export function createBreadcrumbsPicker(instantiationService: IInstantiationService, parent: HTMLElement, element: BreadcrumbElement): BreadcrumbsPicker {
|
||||
let ctor: IConstructorSignature1<HTMLElement, BreadcrumbsPicker> = element instanceof FileElement ? BreadcrumbsFilePicker : BreadcrumbsOutlinePicker;
|
||||
return instantiationService.createInstance(ctor, parent);
|
||||
}
|
||||
|
||||
export abstract class BreadcrumbsPicker {
|
||||
|
||||
protected readonly _disposables = new Array<IDisposable>();
|
||||
protected readonly _domNode: HTMLDivElement;
|
||||
protected readonly _arrow: HTMLDivElement;
|
||||
protected readonly _treeContainer: HTMLDivElement;
|
||||
protected readonly _tree: HighlightingWorkbenchTree;
|
||||
protected readonly _focus: dom.IFocusTracker;
|
||||
|
||||
private readonly _onDidPickElement = new Emitter<{ target: any, payload: any }>();
|
||||
readonly onDidPickElement: Event<{ target: any, payload: any }> = this._onDidPickElement.event;
|
||||
|
||||
constructor(
|
||||
parent: HTMLElement,
|
||||
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
|
||||
@IThemeService protected readonly _themeService: IThemeService,
|
||||
) {
|
||||
this._domNode = document.createElement('div');
|
||||
this._domNode.className = 'monaco-breadcrumbs-picker show-file-icons';
|
||||
parent.appendChild(this._domNode);
|
||||
|
||||
this._focus = dom.trackFocus(this._domNode);
|
||||
this._focus.onDidBlur(_ => this._onDidPickElement.fire({ target: undefined, payload: undefined }), undefined, this._disposables);
|
||||
|
||||
const theme = this._themeService.getTheme();
|
||||
const color = theme.getColor(breadcrumbsPickerBackground);
|
||||
|
||||
this._arrow = document.createElement('div');
|
||||
this._arrow.style.width = '0';
|
||||
this._arrow.style.borderStyle = 'solid';
|
||||
this._arrow.style.borderWidth = '8px';
|
||||
this._arrow.style.borderColor = `transparent transparent ${color.toString()}`;
|
||||
this._domNode.appendChild(this._arrow);
|
||||
|
||||
this._treeContainer = document.createElement('div');
|
||||
this._treeContainer.style.background = color.toString();
|
||||
this._treeContainer.style.paddingTop = '2px';
|
||||
this._treeContainer.style.boxShadow = `0px 5px 8px ${(theme.type === DARK ? color.darken(.6) : color.darken(.2))}`;
|
||||
this._domNode.appendChild(this._treeContainer);
|
||||
|
||||
const treeConifg = this._completeTreeConfiguration({ dataSource: undefined, renderer: undefined });
|
||||
this._tree = this._instantiationService.createInstance(
|
||||
HighlightingWorkbenchTree,
|
||||
this._treeContainer,
|
||||
treeConifg,
|
||||
{ useShadows: false },
|
||||
{ placeholder: localize('placeholder', "Find") }
|
||||
);
|
||||
this._disposables.push(this._tree.onDidChangeSelection(e => {
|
||||
if (e.payload !== this._tree) {
|
||||
const target = this._getTargetFromSelectionEvent(e);
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
setTimeout(_ => {// need to debounce here because this disposes the tree and the tree doesn't like to be disposed on click
|
||||
this._onDidPickElement.fire({ target, payload: e.payload });
|
||||
}, 0);
|
||||
}
|
||||
}));
|
||||
|
||||
this._domNode.focus();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose(this._disposables);
|
||||
this._onDidPickElement.dispose();
|
||||
this._tree.dispose();
|
||||
this._focus.dispose();
|
||||
}
|
||||
|
||||
setInput(input: any): void {
|
||||
let actualInput = this._getInput(input);
|
||||
this._tree.setInput(actualInput).then(() => {
|
||||
let selection = this._getInitialSelection(this._tree, input);
|
||||
if (selection) {
|
||||
this._tree.reveal(selection, .5).then(() => {
|
||||
this._tree.setSelection([selection], this._tree);
|
||||
this._tree.setFocus(selection);
|
||||
this._tree.domFocus();
|
||||
});
|
||||
} else {
|
||||
this._tree.focusFirst();
|
||||
this._tree.setSelection([this._tree.getFocus()], this._tree);
|
||||
this._tree.domFocus();
|
||||
}
|
||||
}, onUnexpectedError);
|
||||
}
|
||||
|
||||
layout(height: number, width: number, arrowSize: number, arrowOffset: number) {
|
||||
this._domNode.style.height = `${height}px`;
|
||||
this._domNode.style.width = `${width}px`;
|
||||
this._arrow.style.borderWidth = `${arrowSize}px`;
|
||||
this._arrow.style.marginLeft = `${arrowOffset}px`;
|
||||
|
||||
this._treeContainer.style.height = `${height - 2 * arrowSize}px`;
|
||||
this._treeContainer.style.width = `${width}px`;
|
||||
this._tree.layout();
|
||||
}
|
||||
|
||||
protected abstract _getInput(input: BreadcrumbElement): any;
|
||||
protected abstract _getInitialSelection(tree: ITree, input: BreadcrumbElement): any;
|
||||
protected abstract _completeTreeConfiguration(config: IHighlightingTreeConfiguration): IHighlightingTreeConfiguration;
|
||||
protected abstract _getTargetFromSelectionEvent(e: ISelectionEvent): any | undefined;
|
||||
}
|
||||
|
||||
//#region - Files
|
||||
|
||||
export class FileDataSource implements IDataSource {
|
||||
|
||||
private readonly _parents = new WeakMap<object, IWorkspaceFolder | IFileStat>();
|
||||
|
||||
constructor(
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
) { }
|
||||
|
||||
getId(tree: ITree, element: IWorkspace | IWorkspaceFolder | IFileStat | URI): string {
|
||||
if (URI.isUri(element)) {
|
||||
return element.toString();
|
||||
} else if (IWorkspace.isIWorkspace(element)) {
|
||||
return element.id;
|
||||
} else if (IWorkspaceFolder.isIWorkspaceFolder(element)) {
|
||||
return element.uri.toString();
|
||||
} else {
|
||||
return element.resource.toString();
|
||||
}
|
||||
}
|
||||
|
||||
hasChildren(tree: ITree, element: IWorkspace | IWorkspaceFolder | IFileStat | URI): boolean {
|
||||
return URI.isUri(element) || IWorkspace.isIWorkspace(element) || IWorkspaceFolder.isIWorkspaceFolder(element) || element.isDirectory;
|
||||
}
|
||||
|
||||
getChildren(tree: ITree, element: IWorkspace | IWorkspaceFolder | IFileStat | URI): TPromise<IWorkspaceFolder[] | IFileStat[]> {
|
||||
if (IWorkspace.isIWorkspace(element)) {
|
||||
return TPromise.as(element.folders).then(folders => {
|
||||
for (let child of folders) {
|
||||
this._parents.set(element, child);
|
||||
}
|
||||
return folders;
|
||||
});
|
||||
}
|
||||
let uri: URI;
|
||||
if (IWorkspaceFolder.isIWorkspaceFolder(element)) {
|
||||
uri = element.uri;
|
||||
} else if (URI.isUri(element)) {
|
||||
uri = element;
|
||||
} else {
|
||||
uri = element.resource;
|
||||
}
|
||||
return this._fileService.resolveFile(uri).then(stat => {
|
||||
for (let child of stat.children) {
|
||||
this._parents.set(stat, child);
|
||||
}
|
||||
return stat.children;
|
||||
});
|
||||
}
|
||||
|
||||
getParent(tree: ITree, element: IWorkspace | URI | IWorkspaceFolder | IFileStat): TPromise<IWorkspaceFolder | IFileStat> {
|
||||
return TPromise.as(this._parents.get(element));
|
||||
}
|
||||
}
|
||||
|
||||
export class FileRenderer implements IRenderer, IHighlightingRenderer {
|
||||
|
||||
private readonly _scores = new Map<string, FuzzyScore>();
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IConfigurationService private readonly _configService: IConfigurationService,
|
||||
) { }
|
||||
|
||||
getHeight(tree: ITree, element: any): number {
|
||||
return 22;
|
||||
}
|
||||
|
||||
getTemplateId(tree: ITree, element: any): string {
|
||||
return 'FileStat';
|
||||
}
|
||||
|
||||
renderTemplate(tree: ITree, templateId: string, container: HTMLElement) {
|
||||
return this._instantiationService.createInstance(FileLabel, container, { supportHighlights: true });
|
||||
}
|
||||
|
||||
renderElement(tree: ITree, element: IFileStat | IWorkspaceFolder, templateId: string, templateData: FileLabel): void {
|
||||
let fileDecorations = this._configService.getValue<{ colors: boolean, badges: boolean }>('explorer.decorations');
|
||||
let resource: URI;
|
||||
let fileKind: FileKind;
|
||||
if (IWorkspaceFolder.isIWorkspaceFolder(element)) {
|
||||
resource = element.uri;
|
||||
fileKind = FileKind.ROOT_FOLDER;
|
||||
} else {
|
||||
resource = element.resource;
|
||||
fileKind = element.isDirectory ? FileKind.FOLDER : FileKind.FILE;
|
||||
}
|
||||
templateData.setFile(resource, {
|
||||
fileKind,
|
||||
hidePath: true,
|
||||
fileDecorations: fileDecorations,
|
||||
matches: createMatches((this._scores.get(resource.toString()) || [, []])[1])
|
||||
});
|
||||
}
|
||||
|
||||
disposeTemplate(tree: ITree, templateId: string, templateData: FileLabel): void {
|
||||
templateData.dispose();
|
||||
}
|
||||
|
||||
updateHighlights(tree: ITree, pattern: string): any {
|
||||
let nav = tree.getNavigator(undefined, false);
|
||||
let topScore: FuzzyScore;
|
||||
let topElement: any;
|
||||
while (nav.next()) {
|
||||
let element = nav.current() as IFileStat | IWorkspaceFolder;
|
||||
let score = fuzzyScore(pattern, element.name, undefined, true);
|
||||
this._scores.set(IWorkspaceFolder.isIWorkspaceFolder(element) ? element.uri.toString() : element.resource.toString(), score);
|
||||
if (!topScore || score && topScore[0] < score[0]) {
|
||||
topScore = score;
|
||||
topElement = element;
|
||||
}
|
||||
}
|
||||
return topElement;
|
||||
}
|
||||
}
|
||||
|
||||
export class FileSorter implements ISorter {
|
||||
compare(tree: ITree, a: IFileStat | IWorkspaceFolder, b: IFileStat | IWorkspaceFolder): number {
|
||||
if (IWorkspaceFolder.isIWorkspaceFolder(a) && IWorkspaceFolder.isIWorkspaceFolder(b)) {
|
||||
return a.index - b.index;
|
||||
} else {
|
||||
if ((a as IFileStat).isDirectory === (b as IFileStat).isDirectory) {
|
||||
// same type -> compare on names
|
||||
return compareFileNames(a.name, b.name);
|
||||
} else if ((a as IFileStat).isDirectory) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class BreadcrumbsFilePicker extends BreadcrumbsPicker {
|
||||
|
||||
constructor(
|
||||
parent: HTMLElement,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,
|
||||
) {
|
||||
super(parent, instantiationService, themeService);
|
||||
}
|
||||
|
||||
protected _getInput(input: BreadcrumbElement): any {
|
||||
let { uri, kind } = (input as FileElement);
|
||||
if (kind === FileKind.ROOT_FOLDER) {
|
||||
return this._workspaceService.getWorkspace();
|
||||
} else {
|
||||
return dirname(uri);
|
||||
}
|
||||
}
|
||||
|
||||
protected _getInitialSelection(tree: ITree, input: BreadcrumbElement): any {
|
||||
let { uri } = (input as FileElement);
|
||||
let nav = tree.getNavigator();
|
||||
while (nav.next()) {
|
||||
let cur = nav.current();
|
||||
let candidate = IWorkspaceFolder.isIWorkspaceFolder(cur) ? cur.uri : (cur as IFileStat).resource;
|
||||
if (isEqual(uri, candidate)) {
|
||||
return cur;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected _completeTreeConfiguration(config: IHighlightingTreeConfiguration): IHighlightingTreeConfiguration {
|
||||
// todo@joh reuse explorer implementations?
|
||||
config.dataSource = this._instantiationService.createInstance(FileDataSource);
|
||||
config.renderer = this._instantiationService.createInstance(FileRenderer);
|
||||
config.sorter = new FileSorter();
|
||||
return config;
|
||||
}
|
||||
|
||||
protected _getTargetFromSelectionEvent(e: ISelectionEvent): any | undefined {
|
||||
let [first] = e.selection;
|
||||
if (first && !IWorkspaceFolder.isIWorkspaceFolder(first) && !(first as IFileStat).isDirectory) {
|
||||
return new FileElement((first as IFileStat).resource, FileKind.FILE);
|
||||
}
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region - Symbols
|
||||
|
||||
class HighlightingOutlineRenderer extends OutlineRenderer implements IHighlightingRenderer {
|
||||
|
||||
updateHighlights(tree: ITree, pattern: string): any {
|
||||
let model = OutlineModel.get(tree.getInput());
|
||||
return model.updateMatches(pattern);
|
||||
}
|
||||
}
|
||||
|
||||
export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker {
|
||||
|
||||
protected _getInput(input: BreadcrumbElement): any {
|
||||
let element = input as TreeElement;
|
||||
let model = OutlineModel.get(element);
|
||||
model.updateMatches('');
|
||||
return model;
|
||||
}
|
||||
|
||||
protected _getInitialSelection(_tree: ITree, input: BreadcrumbElement): any {
|
||||
return input instanceof OutlineModel ? undefined : input;
|
||||
}
|
||||
|
||||
protected _completeTreeConfiguration(config: IHighlightingTreeConfiguration): IHighlightingTreeConfiguration {
|
||||
config.dataSource = this._instantiationService.createInstance(OutlineDataSource);
|
||||
config.renderer = this._instantiationService.createInstance(HighlightingOutlineRenderer);
|
||||
config.sorter = new OutlineItemComparator();
|
||||
return config;
|
||||
}
|
||||
|
||||
protected _getTargetFromSelectionEvent(e: ISelectionEvent): any | undefined {
|
||||
if (e.payload && e.payload.didClickOnTwistie) {
|
||||
return;
|
||||
}
|
||||
let [first] = e.selection;
|
||||
if (first instanceof OutlineElement) {
|
||||
return first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@@ -28,19 +28,25 @@ import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import {
|
||||
CloseEditorsInOtherGroupsAction, CloseAllEditorsAction, MoveGroupLeftAction, MoveGroupRightAction, SplitEditorAction, JoinTwoGroupsAction, OpenToSideAction, RevertAndCloseEditorAction,
|
||||
NavigateBetweenGroupsAction, FocusActiveGroupAction, FocusFirstGroupAction, FocusSecondGroupAction, FocusThirdGroupAction, EvenGroupWidthsAction, MaximizeGroupAction, MinimizeOtherGroupsAction, FocusPreviousGroup, FocusNextGroup, ShowEditorsInGroupOneAction,
|
||||
toEditorQuickOpenEntry, CloseLeftEditorsInGroupAction, OpenNextEditor, OpenPreviousEditor, NavigateBackwardsAction, NavigateForwardAction, NavigateLastAction, ReopenClosedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction,
|
||||
OpenPreviousEditorFromHistoryAction, ShowAllEditorsAction, ClearEditorHistoryAction, ShowEditorsInGroupTwoAction, MoveEditorRightInGroupAction, OpenNextEditorInGroup, OpenPreviousEditorInGroup, OpenNextRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction,
|
||||
ShowEditorsInGroupThreeAction, FocusLastEditorInStackAction, OpenNextRecentlyUsedEditorInGroupAction, MoveEditorToPreviousGroupAction, MoveEditorToNextGroupAction, MoveEditorToFirstGroupAction, MoveEditorToSecondGroupAction, MoveEditorToThirdGroupAction, MoveEditorLeftInGroupAction, ClearRecentFilesAction, OpenLastEditorInGroup
|
||||
CloseEditorsInOtherGroupsAction, CloseAllEditorsAction, MoveGroupLeftAction, MoveGroupRightAction, SplitEditorAction, JoinTwoGroupsAction, OpenToSideFromQuickOpenAction, RevertAndCloseEditorAction,
|
||||
NavigateBetweenGroupsAction, FocusActiveGroupAction, FocusFirstGroupAction, ResetGroupSizesAction, MaximizeGroupAction, MinimizeOtherGroupsAction, FocusPreviousGroup, FocusNextGroup,
|
||||
toEditorQuickOpenEntry, CloseLeftEditorsInGroupAction, OpenNextEditor, OpenPreviousEditor, NavigateBackwardsAction, NavigateForwardAction, NavigateLastAction, ReopenClosedEditorAction,
|
||||
OpenPreviousRecentlyUsedEditorInGroupAction, OpenPreviousEditorFromHistoryAction, ShowAllEditorsAction, ClearEditorHistoryAction, MoveEditorRightInGroupAction, OpenNextEditorInGroup,
|
||||
OpenPreviousEditorInGroup, OpenNextRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction, OpenNextRecentlyUsedEditorInGroupAction, MoveEditorToPreviousGroupAction,
|
||||
MoveEditorToNextGroupAction, MoveEditorToFirstGroupAction, MoveEditorLeftInGroupAction, ClearRecentFilesAction, OpenLastEditorInGroup,
|
||||
ShowEditorsInActiveGroupAction, MoveEditorToLastGroupAction, OpenFirstEditorInGroup, MoveGroupUpAction, MoveGroupDownAction, FocusLastGroupAction, SplitEditorLeftAction, SplitEditorRightAction,
|
||||
SplitEditorUpAction, SplitEditorDownAction, MoveEditorToLeftGroupAction, MoveEditorToRightGroupAction, MoveEditorToAboveGroupAction, MoveEditorToBelowGroupAction, CloseAllEditorGroupsAction,
|
||||
JoinAllGroupsAction, FocusLeftGroup, FocusAboveGroup, FocusRightGroup, FocusBelowGroup, EditorLayoutSingleAction, EditorLayoutTwoColumnsAction, EditorLayoutThreeColumnsAction, EditorLayoutTwoByTwoGridAction,
|
||||
EditorLayoutTwoRowsAction, EditorLayoutThreeRowsAction, EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoRowsRightAction, NewEditorGroupLeftAction, NewEditorGroupRightAction,
|
||||
NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction
|
||||
} from 'vs/workbench/browser/parts/editor/editorActions';
|
||||
import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { getQuickNavigateHandler, inQuickOpenContext } from 'vs/workbench/browser/parts/quickopen/quickopen';
|
||||
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { GroupOnePicker, GroupTwoPicker, GroupThreePicker, AllEditorsPicker } from 'vs/workbench/browser/parts/editor/editorPicker';
|
||||
import { AllEditorsPicker, ActiveEditorGroupPicker } from 'vs/workbench/browser/parts/editor/editorPicker';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
// Register String Editor
|
||||
@@ -103,10 +109,9 @@ class UntitledEditorInputFactory implements IEditorInputFactory {
|
||||
|
||||
constructor(
|
||||
@ITextFileService private textFileService: ITextFileService
|
||||
) {
|
||||
}
|
||||
) { }
|
||||
|
||||
public serialize(editorInput: EditorInput): string {
|
||||
serialize(editorInput: EditorInput): string {
|
||||
if (!this.textFileService.isHotExitEnabled) {
|
||||
return null; // never restore untitled unless hot exit is enabled
|
||||
}
|
||||
@@ -120,7 +125,7 @@ class UntitledEditorInputFactory implements IEditorInputFactory {
|
||||
|
||||
let resource = untitledEditorInput.getResource();
|
||||
if (untitledEditorInput.hasAssociatedFilePath) {
|
||||
resource = URI.file(resource.fsPath); // untitled with associated file path use the file schema
|
||||
resource = resource.with({ scheme: Schemas.file }); // untitled with associated file path use the file schema
|
||||
}
|
||||
|
||||
const serialized: ISerializedUntitledEditorInput = {
|
||||
@@ -133,7 +138,7 @@ class UntitledEditorInputFactory implements IEditorInputFactory {
|
||||
return JSON.stringify(serialized);
|
||||
}
|
||||
|
||||
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): UntitledEditorInput {
|
||||
deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): UntitledEditorInput {
|
||||
return instantiationService.invokeFunction<UntitledEditorInput>(accessor => {
|
||||
const deserialized: ISerializedUntitledEditorInput = JSON.parse(serializedEditorInput);
|
||||
const resource = !!deserialized.resourceJSON ? URI.revive(deserialized.resourceJSON) : URI.parse(deserialized.resource);
|
||||
@@ -141,7 +146,7 @@ class UntitledEditorInputFactory implements IEditorInputFactory {
|
||||
const language = deserialized.modeId;
|
||||
const encoding = deserialized.encoding;
|
||||
|
||||
return accessor.get(IWorkbenchEditorService).createInput({ resource, filePath, language, encoding }) as UntitledEditorInput;
|
||||
return accessor.get(IEditorService).createInput({ resource, filePath, language, encoding }) as UntitledEditorInput;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -162,7 +167,7 @@ interface ISerializedSideBySideEditorInput {
|
||||
// Register Side by Side Editor Input Factory
|
||||
class SideBySideEditorInputFactory implements IEditorInputFactory {
|
||||
|
||||
public serialize(editorInput: EditorInput): string {
|
||||
serialize(editorInput: EditorInput): string {
|
||||
const input = <SideBySideEditorInput>editorInput;
|
||||
|
||||
if (input.details && input.master) {
|
||||
@@ -190,7 +195,7 @@ class SideBySideEditorInputFactory implements IEditorInputFactory {
|
||||
return null;
|
||||
}
|
||||
|
||||
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput {
|
||||
deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput {
|
||||
const deserialized: ISerializedSideBySideEditorInput = JSON.parse(serializedEditorInput);
|
||||
|
||||
const registry = Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories);
|
||||
@@ -223,25 +228,25 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeEOLAction, Chang
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL), 'Change File Encoding');
|
||||
|
||||
export class QuickOpenActionContributor extends ActionBarContributor {
|
||||
private openToSideActionInstance: OpenToSideAction;
|
||||
private openToSideActionInstance: OpenToSideFromQuickOpenAction;
|
||||
|
||||
constructor(@IInstantiationService private instantiationService: IInstantiationService) {
|
||||
super();
|
||||
}
|
||||
|
||||
public hasActions(context: any): boolean {
|
||||
hasActions(context: any): boolean {
|
||||
const entry = this.getEntry(context);
|
||||
|
||||
return !!entry;
|
||||
}
|
||||
|
||||
public getActions(context: any): IAction[] {
|
||||
getActions(context: any): IAction[] {
|
||||
const actions: Action[] = [];
|
||||
|
||||
const entry = this.getEntry(context);
|
||||
if (entry) {
|
||||
if (!this.openToSideActionInstance) {
|
||||
this.openToSideActionInstance = this.instantiationService.createInstance(OpenToSideAction);
|
||||
this.openToSideActionInstance = this.instantiationService.createInstance(OpenToSideFromQuickOpenAction);
|
||||
} else {
|
||||
this.openToSideActionInstance.updateClass();
|
||||
}
|
||||
@@ -269,50 +274,20 @@ const editorPickerContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExp
|
||||
|
||||
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
|
||||
new QuickOpenHandlerDescriptor(
|
||||
GroupOnePicker,
|
||||
GroupOnePicker.ID,
|
||||
editorCommands.NAVIGATE_IN_GROUP_ONE_PREFIX,
|
||||
ActiveEditorGroupPicker,
|
||||
ActiveEditorGroupPicker.ID,
|
||||
editorCommands.NAVIGATE_IN_ACTIVE_GROUP_PREFIX,
|
||||
editorPickerContextKey,
|
||||
[
|
||||
{
|
||||
prefix: editorCommands.NAVIGATE_IN_GROUP_ONE_PREFIX,
|
||||
prefix: editorCommands.NAVIGATE_IN_ACTIVE_GROUP_PREFIX,
|
||||
needsEditor: false,
|
||||
description: nls.localize('groupOnePicker', "Show Editors in First Group")
|
||||
},
|
||||
{
|
||||
prefix: editorCommands.NAVIGATE_IN_GROUP_TWO_PREFIX,
|
||||
needsEditor: false,
|
||||
description: nls.localize('groupTwoPicker', "Show Editors in Second Group")
|
||||
},
|
||||
{
|
||||
prefix: editorCommands.NAVIGATE_IN_GROUP_THREE_PREFIX,
|
||||
needsEditor: false,
|
||||
description: nls.localize('groupThreePicker', "Show Editors in Third Group")
|
||||
description: nls.localize('groupOnePicker', "Show Editors in Active Group")
|
||||
}
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
|
||||
new QuickOpenHandlerDescriptor(
|
||||
GroupTwoPicker,
|
||||
GroupTwoPicker.ID,
|
||||
editorCommands.NAVIGATE_IN_GROUP_TWO_PREFIX,
|
||||
editorPickerContextKey,
|
||||
[]
|
||||
)
|
||||
);
|
||||
|
||||
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
|
||||
new QuickOpenHandlerDescriptor(
|
||||
GroupThreePicker,
|
||||
GroupThreePicker.ID,
|
||||
editorCommands.NAVIGATE_IN_GROUP_THREE_PREFIX,
|
||||
editorPickerContextKey,
|
||||
[]
|
||||
)
|
||||
);
|
||||
|
||||
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
|
||||
new QuickOpenHandlerDescriptor(
|
||||
AllEditorsPicker,
|
||||
@@ -333,48 +308,73 @@ Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpen
|
||||
const category = nls.localize('view', "View");
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextEditorInGroup, OpenNextEditorInGroup.ID, OpenNextEditorInGroup.LABEL), 'View: Open Next Editor in Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousEditorInGroup, OpenPreviousEditorInGroup.ID, OpenPreviousEditorInGroup.LABEL), 'View: Open Previous Editor in Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLastEditorInGroup, OpenLastEditorInGroup.ID, OpenLastEditorInGroup.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_9 }), 'View: Open Last Editor in Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLastEditorInGroup, OpenLastEditorInGroup.ID, OpenLastEditorInGroup.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_0, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_9], mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_0, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_9] } }), 'View: Open Last Editor in Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFirstEditorInGroup, OpenFirstEditorInGroup.ID, OpenFirstEditorInGroup.LABEL), 'View: Open First Editor in Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextRecentlyUsedEditorAction, OpenNextRecentlyUsedEditorAction.ID, OpenNextRecentlyUsedEditorAction.LABEL), 'View: Open Next Recently Used Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction.ID, OpenPreviousRecentlyUsedEditorAction.LABEL), 'View: Open Previous Recently Used Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAllEditorsAction, ShowAllEditorsAction.ID, ShowAllEditorsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_P), mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Tab } }), 'View: Show All Editors', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowEditorsInGroupOneAction, ShowEditorsInGroupOneAction.ID, ShowEditorsInGroupOneAction.LABEL), 'View: Show Editors in First Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowEditorsInGroupTwoAction, ShowEditorsInGroupTwoAction.ID, ShowEditorsInGroupTwoAction.LABEL), 'View: Show Editors in Second Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowEditorsInGroupThreeAction, ShowEditorsInGroupThreeAction.ID, ShowEditorsInGroupThreeAction.LABEL), 'View: Show Editors in Third Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowEditorsInActiveGroupAction, ShowEditorsInActiveGroupAction.ID, ShowEditorsInActiveGroupAction.LABEL), 'View: Show Editors in Active Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextEditor, OpenNextEditor.ID, OpenNextEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageDown, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_CLOSE_SQUARE_BRACKET] } }), 'View: Open Next Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousEditor, OpenPreviousEditor.ID, OpenPreviousEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageUp, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_OPEN_SQUARE_BRACKET] } }), 'View: Open Previous Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ReopenClosedEditorAction, ReopenClosedEditorAction.ID, ReopenClosedEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_T }), 'View: Reopen Closed Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ClearRecentFilesAction, ClearRecentFilesAction.ID, ClearRecentFilesAction.LABEL), 'File: Clear Recently Opened', nls.localize('file', "File"));
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseAllEditorsAction, CloseAllEditorsAction.ID, CloseAllEditorsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_W) }), 'View: Close All Editors', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseLeftEditorsInGroupAction, CloseLeftEditorsInGroupAction.ID, CloseLeftEditorsInGroupAction.LABEL), 'View: Close Editors to the Left', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseAllEditorGroupsAction, CloseAllEditorGroupsAction.ID, CloseAllEditorGroupsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W) }), 'View: Close All Editor Groups', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseLeftEditorsInGroupAction, CloseLeftEditorsInGroupAction.ID, CloseLeftEditorsInGroupAction.LABEL), 'View: Close Editors in Group to the Left', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorsInOtherGroupsAction, CloseEditorsInOtherGroupsAction.ID, CloseEditorsInOtherGroupsAction.LABEL), 'View: Close Editors in Other Groups', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorAction, SplitEditorAction.ID, SplitEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_BACKSLASH }), 'View: Split Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorOrthogonalAction, SplitEditorOrthogonalAction.ID, SplitEditorOrthogonalAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.US_BACKSLASH) }), 'View: Split Editor Orthogonal', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorLeftAction, SplitEditorLeftAction.ID, SplitEditorLeftAction.LABEL), 'View: Split Editor Left', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorRightAction, SplitEditorRightAction.ID, SplitEditorRightAction.LABEL), 'View: Split Editor Right', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorUpAction, SplitEditorUpAction.ID, SplitEditorUpAction.LABEL), 'Split Editor Up', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorDownAction, SplitEditorDownAction.ID, SplitEditorDownAction.LABEL), 'View: Split Editor Down', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(JoinTwoGroupsAction, JoinTwoGroupsAction.ID, JoinTwoGroupsAction.LABEL), 'View: Join Editors of Two Groups', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(JoinAllGroupsAction, JoinAllGroupsAction.ID, JoinAllGroupsAction.LABEL), 'View: Join Editors of All Groups', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateBetweenGroupsAction, NavigateBetweenGroupsAction.ID, NavigateBetweenGroupsAction.LABEL), 'View: Navigate Between Editor Groups', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusActiveGroupAction, FocusActiveGroupAction.ID, FocusActiveGroupAction.LABEL), 'View: Focus Active Editor Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusFirstGroupAction, FocusFirstGroupAction.ID, FocusFirstGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_1 }), 'View: Focus First Editor Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusSecondGroupAction, FocusSecondGroupAction.ID, FocusSecondGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_2 }), 'View: Focus Second Editor Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusThirdGroupAction, FocusThirdGroupAction.ID, FocusThirdGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_3 }), 'View: Focus Third Editor Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusLastEditorInStackAction, FocusLastEditorInStackAction.ID, FocusLastEditorInStackAction.LABEL, { primary: KeyMod.Alt | KeyCode.KEY_0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_0 } }), 'View: Open Last Editor in Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(EvenGroupWidthsAction, EvenGroupWidthsAction.ID, EvenGroupWidthsAction.LABEL), 'View: Even Editor Group Widths', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ResetGroupSizesAction, ResetGroupSizesAction.ID, ResetGroupSizesAction.LABEL), 'View: Reset Editor Group Sizes', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MaximizeGroupAction, MaximizeGroupAction.ID, MaximizeGroupAction.LABEL), 'View: Maximize Editor Group and Hide Sidebar', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MinimizeOtherGroupsAction, MinimizeOtherGroupsAction.ID, MinimizeOtherGroupsAction.LABEL), 'View: Minimize Other Editor Groups', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MinimizeOtherGroupsAction, MinimizeOtherGroupsAction.ID, MinimizeOtherGroupsAction.LABEL), 'View: Maximize Editor Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorLeftInGroupAction, MoveEditorLeftInGroupAction.ID, MoveEditorLeftInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageUp, mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow) } }), 'View: Move Editor Left', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorRightInGroupAction, MoveEditorRightInGroupAction.ID, MoveEditorRightInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageDown, mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow) } }), 'View: Move Editor Right', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupLeftAction, MoveGroupLeftAction.ID, MoveGroupLeftAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.LeftArrow) }), 'View: Move Editor Group Left', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupRightAction, MoveGroupRightAction.ID, MoveGroupRightAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.RightArrow) }), 'View: Move Editor Group Right', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupUpAction, MoveGroupUpAction.ID, MoveGroupUpAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.UpArrow) }), 'View: Move Editor Group Up', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupDownAction, MoveGroupDownAction.ID, MoveGroupDownAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.DownArrow) }), 'View: Move Editor Group Down', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToPreviousGroupAction, MoveEditorToPreviousGroupAction.ID, MoveEditorToPreviousGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.LeftArrow } }), 'View: Move Editor into Previous Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToNextGroupAction, MoveEditorToNextGroupAction.ID, MoveEditorToNextGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.RightArrow } }), 'View: Move Editor into Next Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToFirstGroupAction, MoveEditorToFirstGroupAction.ID, MoveEditorToFirstGroupAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_1, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_1 } }), 'View: Move Editor into First Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToSecondGroupAction, MoveEditorToSecondGroupAction.ID, MoveEditorToSecondGroupAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_2, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_2 } }), 'View: Move Editor into Second Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToThirdGroupAction, MoveEditorToThirdGroupAction.ID, MoveEditorToThirdGroupAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_3, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_3 } }), 'View: Move Editor into Third Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousGroup, FocusPreviousGroup.ID, FocusPreviousGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.LeftArrow) }), 'View: Focus Previous Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextGroup, FocusNextGroup.ID, FocusNextGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.RightArrow) }), 'View: Focus Next Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToLastGroupAction, MoveEditorToLastGroupAction.ID, MoveEditorToLastGroupAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_9, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_9 } }), 'View: Move Editor into Last Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToLeftGroupAction, MoveEditorToLeftGroupAction.ID, MoveEditorToLeftGroupAction.LABEL), 'View: Move Editor into Left Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToRightGroupAction, MoveEditorToRightGroupAction.ID, MoveEditorToRightGroupAction.LABEL), 'View: Move Editor into Right Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToAboveGroupAction, MoveEditorToAboveGroupAction.ID, MoveEditorToAboveGroupAction.LABEL), 'View: Move Editor into Above Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToBelowGroupAction, MoveEditorToBelowGroupAction.ID, MoveEditorToBelowGroupAction.LABEL), 'View: Move Editor into Below Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusActiveGroupAction, FocusActiveGroupAction.ID, FocusActiveGroupAction.LABEL), 'View: Focus Active Editor Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusFirstGroupAction, FocusFirstGroupAction.ID, FocusFirstGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_1 }), 'View: Focus First Editor Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusLastGroupAction, FocusLastGroupAction.ID, FocusLastGroupAction.LABEL), 'View: Focus Last Editor Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousGroup, FocusPreviousGroup.ID, FocusPreviousGroup.LABEL), 'View: Focus Previous Editor Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextGroup, FocusNextGroup.ID, FocusNextGroup.LABEL), 'View: Focus Next Editor Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusLeftGroup, FocusLeftGroup.ID, FocusLeftGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.LeftArrow) }), 'View: Focus Left Editor Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusRightGroup, FocusRightGroup.ID, FocusRightGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.RightArrow) }), 'View: Focus Right Editor Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusAboveGroup, FocusAboveGroup.ID, FocusAboveGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.UpArrow) }), 'View: Focus Above Editor Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusBelowGroup, FocusBelowGroup.ID, FocusBelowGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.DownArrow) }), 'View: Focus Below Editor Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupLeftAction, NewEditorGroupLeftAction.ID, NewEditorGroupLeftAction.LABEL), 'View: New Editor Group to the Left', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupRightAction, NewEditorGroupRightAction.ID, NewEditorGroupRightAction.LABEL), 'View: New Editor Group to the Right', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupAboveAction, NewEditorGroupAboveAction.ID, NewEditorGroupAboveAction.LABEL), 'View: New Editor Group Above', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupBelowAction, NewEditorGroupBelowAction.ID, NewEditorGroupBelowAction.LABEL), 'View: New Editor Group Below', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateForwardAction, NavigateForwardAction.ID, NavigateForwardAction.LABEL, { primary: null, win: { primary: KeyMod.Alt | KeyCode.RightArrow }, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_MINUS } }), 'Go Forward');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateBackwardsAction, NavigateBackwardsAction.ID, NavigateBackwardsAction.LABEL, { primary: null, win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS } }), 'Go Back');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateLastAction, NavigateLastAction.ID, NavigateLastAction.LABEL), 'Go Last');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousEditorFromHistoryAction, OpenPreviousEditorFromHistoryAction.ID, OpenPreviousEditorFromHistoryAction.LABEL), 'Open Previous Editor from History');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ClearEditorHistoryAction, ClearEditorHistoryAction.ID, ClearEditorHistoryAction.LABEL), 'Clear Editor History');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(RevertAndCloseEditorAction, RevertAndCloseEditorAction.ID, RevertAndCloseEditorAction.LABEL), 'View: Revert and Close Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutSingleAction, EditorLayoutSingleAction.ID, EditorLayoutSingleAction.LABEL), 'View: Single Column Editor Layout', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoColumnsAction, EditorLayoutTwoColumnsAction.ID, EditorLayoutTwoColumnsAction.LABEL), 'View: Two Columns Editor Layout', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutThreeColumnsAction, EditorLayoutThreeColumnsAction.ID, EditorLayoutThreeColumnsAction.LABEL), 'View: Three Columns Editor Layout', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoRowsAction, EditorLayoutTwoRowsAction.ID, EditorLayoutTwoRowsAction.LABEL), 'View: Two Rows Editor Layout', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutThreeRowsAction, EditorLayoutThreeRowsAction.ID, EditorLayoutThreeRowsAction.LABEL), 'View: Three Rows Editor Layout', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoByTwoGridAction, EditorLayoutTwoByTwoGridAction.ID, EditorLayoutTwoByTwoGridAction.LABEL), 'View: Grid Editor Layout (2x2)', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoRowsRightAction, EditorLayoutTwoRowsRightAction.ID, EditorLayoutTwoRowsRightAction.LABEL), 'View: Two Rows Right Editor Layout', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoColumnsBottomAction.ID, EditorLayoutTwoColumnsBottomAction.LABEL), 'View: Two Columns Bottom Editor Layout', category);
|
||||
|
||||
// Register Editor Picker Actions including quick navigate support
|
||||
const openNextEditorKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } };
|
||||
@@ -385,7 +385,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousRecentlyUs
|
||||
const quickOpenNavigateNextInEditorPickerId = 'workbench.action.quickOpenNavigateNextInEditorPicker';
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: quickOpenNavigateNextInEditorPickerId,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(50),
|
||||
weight: KeybindingWeight.WorkbenchContrib + 50,
|
||||
handler: getQuickNavigateHandler(quickOpenNavigateNextInEditorPickerId, true),
|
||||
when: editorPickerContext,
|
||||
primary: openNextEditorKeybinding.primary,
|
||||
@@ -395,30 +395,36 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
const quickOpenNavigatePreviousInEditorPickerId = 'workbench.action.quickOpenNavigatePreviousInEditorPicker';
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: quickOpenNavigatePreviousInEditorPickerId,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(50),
|
||||
weight: KeybindingWeight.WorkbenchContrib + 50,
|
||||
handler: getQuickNavigateHandler(quickOpenNavigatePreviousInEditorPickerId, false),
|
||||
when: editorPickerContext,
|
||||
primary: openPreviousEditorKeybinding.primary,
|
||||
mac: openPreviousEditorKeybinding.mac
|
||||
});
|
||||
|
||||
|
||||
// Editor Commands
|
||||
editorCommands.setup();
|
||||
|
||||
// Touch Bar
|
||||
if (isMacintosh) {
|
||||
MenuRegistry.appendMenuItem(MenuId.TouchBarContext, {
|
||||
command: { id: NavigateBackwardsAction.ID, title: NavigateBackwardsAction.LABEL, iconPath: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/back-tb.png')).fsPath } },
|
||||
command: { id: NavigateBackwardsAction.ID, title: NavigateBackwardsAction.LABEL, iconLocation: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/back-tb.png')) } },
|
||||
group: 'navigation'
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.TouchBarContext, {
|
||||
command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, iconPath: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/forward-tb.png')).fsPath } },
|
||||
command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, iconLocation: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/forward-tb.png')) } },
|
||||
group: 'navigation'
|
||||
});
|
||||
}
|
||||
|
||||
// Empty Editor Group Context Menu
|
||||
MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: editorCommands.SPLIT_EDITOR_UP, title: nls.localize('splitUp', "Split Up") }, group: '2_split', order: 10 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: editorCommands.SPLIT_EDITOR_DOWN, title: nls.localize('splitDown', "Split Down") }, group: '2_split', order: 20 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: editorCommands.SPLIT_EDITOR_LEFT, title: nls.localize('splitLeft', "Split Left") }, group: '2_split', order: 30 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: editorCommands.SPLIT_EDITOR_RIGHT, title: nls.localize('splitRight', "Split Right") }, group: '2_split', order: 40 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: editorCommands.CLOSE_EDITOR_GROUP_COMMAND_ID, title: nls.localize('close', "Close") }, group: '3_close', order: 10, when: ContextKeyExpr.has('multipleEditorGroups') });
|
||||
|
||||
// Editor Title Context Menu
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITOR_COMMAND_ID, title: nls.localize('close', "Close") }, group: '1_close', order: 10 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeOthers', "Close Others") }, group: '1_close', order: 20 });
|
||||
@@ -426,6 +432,10 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCo
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeAllSaved', "Close Saved") }, group: '1_close', order: 40 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All") }, group: '1_close', order: 50 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: nls.localize('keepOpen', "Keep Open") }, group: '3_preview', order: 10, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_UP, title: nls.localize('splitUp', "Split Up") }, group: '5_split', order: 10 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_DOWN, title: nls.localize('splitDown', "Split Down") }, group: '5_split', order: 20 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_LEFT, title: nls.localize('splitLeft', "Split Left") }, group: '5_split', order: 30 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_RIGHT, title: nls.localize('splitRight', "Split Right") }, group: '5_split', order: 40 });
|
||||
|
||||
// Editor Title Menu
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.TOGGLE_DIFF_INLINE_MODE, title: nls.localize('toggleInlineView', "Toggle Inline View") }, group: '1_diff', order: 10, when: ContextKeyExpr.has('isInDiffEditor') });
|
||||
@@ -433,9 +443,236 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All") }, group: '5_close', order: 10, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeAllSaved', "Close Saved") }, group: '5_close', order: 20, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
|
||||
|
||||
interface IEditorToolItem { id: string; title: string; iconDark: string; iconLight: string; }
|
||||
|
||||
function appendEditorToolItem(primary: IEditorToolItem, alternative: IEditorToolItem, when: ContextKeyExpr, order: number): void {
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||
command: {
|
||||
id: primary.id,
|
||||
title: primary.title,
|
||||
iconLocation: {
|
||||
dark: URI.parse(require.toUrl(`vs/workbench/browser/parts/editor/media/${primary.iconDark}`)),
|
||||
light: URI.parse(require.toUrl(`vs/workbench/browser/parts/editor/media/${primary.iconLight}`))
|
||||
}
|
||||
},
|
||||
alt: {
|
||||
id: alternative.id,
|
||||
title: alternative.title,
|
||||
iconLocation: {
|
||||
dark: URI.parse(require.toUrl(`vs/workbench/browser/parts/editor/media/${alternative.iconDark}`)),
|
||||
light: URI.parse(require.toUrl(`vs/workbench/browser/parts/editor/media/${alternative.iconLight}`))
|
||||
}
|
||||
},
|
||||
group: 'navigation',
|
||||
when,
|
||||
order
|
||||
});
|
||||
}
|
||||
|
||||
// Editor Title Menu: Split Editor
|
||||
appendEditorToolItem(
|
||||
{
|
||||
id: SplitEditorAction.ID,
|
||||
title: nls.localize('splitEditorRight', "Split Editor Right"),
|
||||
iconDark: 'split-editor-horizontal-inverse.svg',
|
||||
iconLight: 'split-editor-horizontal.svg'
|
||||
}, {
|
||||
id: editorCommands.SPLIT_EDITOR_DOWN,
|
||||
title: nls.localize('splitEditorDown', "Split Editor Down"),
|
||||
iconDark: 'split-editor-vertical-inverse.svg',
|
||||
iconLight: 'split-editor-vertical.svg'
|
||||
},
|
||||
ContextKeyExpr.not('splitEditorsVertically'),
|
||||
100000 /* towards the end */
|
||||
);
|
||||
|
||||
appendEditorToolItem(
|
||||
{
|
||||
id: SplitEditorAction.ID,
|
||||
title: nls.localize('splitEditorDown', "Split Editor Down"),
|
||||
iconDark: 'split-editor-vertical-inverse.svg',
|
||||
iconLight: 'split-editor-vertical.svg'
|
||||
}, {
|
||||
id: editorCommands.SPLIT_EDITOR_RIGHT,
|
||||
title: nls.localize('splitEditorRight', "Split Editor Right"),
|
||||
iconDark: 'split-editor-horizontal-inverse.svg',
|
||||
iconLight: 'split-editor-horizontal.svg'
|
||||
},
|
||||
ContextKeyExpr.has('splitEditorsVertically'),
|
||||
100000 // towards the end
|
||||
);
|
||||
|
||||
// Editor Title Menu: Close Group (tabs disabled)
|
||||
appendEditorToolItem(
|
||||
{
|
||||
id: editorCommands.CLOSE_EDITOR_COMMAND_ID,
|
||||
title: nls.localize('close', "Close"),
|
||||
iconDark: 'close-big-inverse-alt.svg',
|
||||
iconLight: 'close-big-alt.svg'
|
||||
}, {
|
||||
id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID,
|
||||
title: nls.localize('closeAll', "Close All"),
|
||||
iconDark: 'closeall-editors-inverse.svg',
|
||||
iconLight: 'closeall-editors.svg'
|
||||
},
|
||||
ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ContextKeyExpr.not('groupActiveEditorDirty')),
|
||||
1000000 // towards the end
|
||||
);
|
||||
|
||||
appendEditorToolItem(
|
||||
{
|
||||
id: editorCommands.CLOSE_EDITOR_COMMAND_ID,
|
||||
title: nls.localize('close', "Close"),
|
||||
iconDark: 'close-dirty-inverse-alt.svg',
|
||||
iconLight: 'close-dirty-alt.svg'
|
||||
}, {
|
||||
id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID,
|
||||
title: nls.localize('closeAll', "Close All"),
|
||||
iconDark: 'closeall-editors-inverse.svg',
|
||||
iconLight: 'closeall-editors.svg'
|
||||
},
|
||||
ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ContextKeyExpr.has('groupActiveEditorDirty')),
|
||||
1000000 // towards the end
|
||||
);
|
||||
|
||||
// Editor Commands for Command Palette
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: nls.localize('keepEditor', "Keep Editor"), category }, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeEditorsInGroup', "Close All Editors in Group"), category } });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeSavedEditors', "Close Saved Editors in Group"), category } });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeOtherEditors', "Close Other Editors"), category } });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: nls.localize('closeRightEditors', "Close Editors to the Right"), category } });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeOtherEditors', "Close Other Editors in Group"), category } });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: nls.localize('closeRightEditors', "Close Editors to the Right in Group"), category } });
|
||||
|
||||
// File menu
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, {
|
||||
group: '1_editor',
|
||||
command: {
|
||||
id: ReopenClosedEditorAction.ID,
|
||||
title: nls.localize({ key: 'miReopenClosedEditor', comment: ['&& denotes a mnemonic'] }, "&&Reopen Closed Editor")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, {
|
||||
group: 'z_clear',
|
||||
command: {
|
||||
id: ClearRecentFilesAction.ID,
|
||||
title: nls.localize({ key: 'miClearRecentOpen', comment: ['&& denotes a mnemonic'] }, "&&Clear Recently Opened")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
// Layout menu
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
|
||||
group: '2_appearance',
|
||||
title: nls.localize({ key: 'miEditorLayout', comment: ['&& denotes a mnemonic'] }, "Editor &&Layout"),
|
||||
submenu: MenuId.MenubarLayoutMenu,
|
||||
order: 2
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
|
||||
group: '1_split',
|
||||
command: {
|
||||
id: editorCommands.SPLIT_EDITOR_UP,
|
||||
title: nls.localize({ key: 'miSplitEditorUp', comment: ['&& denotes a mnemonic'] }, "Split &&Up")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
|
||||
group: '1_split',
|
||||
command: {
|
||||
id: editorCommands.SPLIT_EDITOR_DOWN,
|
||||
title: nls.localize({ key: 'miSplitEditorDown', comment: ['&& denotes a mnemonic'] }, "Split &&Down")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
|
||||
group: '1_split',
|
||||
command: {
|
||||
id: editorCommands.SPLIT_EDITOR_LEFT,
|
||||
title: nls.localize({ key: 'miSplitEditorLeft', comment: ['&& denotes a mnemonic'] }, "Split &&Left")
|
||||
},
|
||||
order: 3
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
|
||||
group: '1_split',
|
||||
command: {
|
||||
id: editorCommands.SPLIT_EDITOR_RIGHT,
|
||||
title: nls.localize({ key: 'miSplitEditorRight', comment: ['&& denotes a mnemonic'] }, "Split &&Right")
|
||||
},
|
||||
order: 4
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
|
||||
group: '2_layouts',
|
||||
command: {
|
||||
id: EditorLayoutSingleAction.ID,
|
||||
title: nls.localize({ key: 'miSingleColumnEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Single")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
|
||||
group: '2_layouts',
|
||||
command: {
|
||||
id: EditorLayoutTwoColumnsAction.ID,
|
||||
title: nls.localize({ key: 'miTwoColumnsEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Two Columns")
|
||||
},
|
||||
order: 3
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
|
||||
group: '2_layouts',
|
||||
command: {
|
||||
id: EditorLayoutThreeColumnsAction.ID,
|
||||
title: nls.localize({ key: 'miThreeColumnsEditorLayout', comment: ['&& denotes a mnemonic'] }, "T&&hree Columns")
|
||||
},
|
||||
order: 4
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
|
||||
group: '2_layouts',
|
||||
command: {
|
||||
id: EditorLayoutTwoRowsAction.ID,
|
||||
title: nls.localize({ key: 'miTwoRowsEditorLayout', comment: ['&& denotes a mnemonic'] }, "T&&wo Rows")
|
||||
},
|
||||
order: 5
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
|
||||
group: '2_layouts',
|
||||
command: {
|
||||
id: EditorLayoutThreeRowsAction.ID,
|
||||
title: nls.localize({ key: 'miThreeRowsEditorLayout', comment: ['&& denotes a mnemonic'] }, "Three &&Rows")
|
||||
},
|
||||
order: 6
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
|
||||
group: '2_layouts',
|
||||
command: {
|
||||
id: EditorLayoutTwoByTwoGridAction.ID,
|
||||
title: nls.localize({ key: 'miTwoByTwoGridEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Grid (2x2)")
|
||||
},
|
||||
order: 7
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
|
||||
group: '2_layouts',
|
||||
command: {
|
||||
id: EditorLayoutTwoRowsRightAction.ID,
|
||||
title: nls.localize({ key: 'miTwoRowsRightEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two R&&ows Right")
|
||||
},
|
||||
order: 8
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
|
||||
group: '2_layouts',
|
||||
command: {
|
||||
id: EditorLayoutTwoColumnsBottomAction.ID,
|
||||
title: nls.localize({ key: 'miTwoColumnsBottomEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two &&Columns Bottom")
|
||||
},
|
||||
order: 9
|
||||
});
|
||||
|
||||
163
src/vs/workbench/browser/parts/editor/editor.ts
Normal file
163
src/vs/workbench/browser/parts/editor/editor.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { GroupIdentifier, IWorkbenchEditorConfiguration, IWorkbenchEditorPartConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent } from 'vs/workbench/common/editor';
|
||||
import { EditorGroup } from 'vs/workbench/common/editor/editorGroup';
|
||||
import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder, IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Dimension } from 'vs/base/browser/dom';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
import { ISerializableView } from 'vs/base/browser/ui/grid/grid';
|
||||
import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
export const EDITOR_TITLE_HEIGHT = 35;
|
||||
|
||||
export const DEFAULT_EDITOR_MIN_DIMENSIONS = new Dimension(220, 70);
|
||||
export const DEFAULT_EDITOR_MAX_DIMENSIONS = new Dimension(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY);
|
||||
|
||||
export interface IEditorPartOptions extends IWorkbenchEditorPartConfiguration {
|
||||
iconTheme?: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = {
|
||||
showTabs: true,
|
||||
tabCloseButton: 'right',
|
||||
tabSizing: 'fit',
|
||||
showIcons: true,
|
||||
enablePreview: true,
|
||||
openPositioning: 'right',
|
||||
openSideBySideDirection: 'right',
|
||||
closeEmptyGroups: true,
|
||||
labelFormat: 'default',
|
||||
iconTheme: 'vs-seti'
|
||||
};
|
||||
|
||||
export function impactsEditorPartOptions(event: IConfigurationChangeEvent): boolean {
|
||||
return event.affectsConfiguration('workbench.editor') || event.affectsConfiguration('workbench.iconTheme');
|
||||
}
|
||||
|
||||
export function getEditorPartOptions(config: IWorkbenchEditorConfiguration): IEditorPartOptions {
|
||||
const options: IEditorPartOptions = assign(Object.create(null), DEFAULT_EDITOR_PART_OPTIONS);
|
||||
|
||||
if (!config || !config.workbench) {
|
||||
return options;
|
||||
}
|
||||
|
||||
if (typeof config.workbench.iconTheme === 'string') {
|
||||
options.iconTheme = config.workbench.iconTheme;
|
||||
}
|
||||
|
||||
if (config.workbench.editor) {
|
||||
assign(options, config.workbench.editor);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
export interface IEditorPartOptionsChangeEvent {
|
||||
oldPartOptions: IEditorPartOptions;
|
||||
newPartOptions: IEditorPartOptions;
|
||||
}
|
||||
|
||||
export interface IEditorOpeningEvent extends IEditorIdentifier {
|
||||
options?: IEditorOptions;
|
||||
|
||||
/**
|
||||
* Allows to prevent the opening of an editor by providing a callback
|
||||
* that will be executed instead. By returning another editor promise
|
||||
* it is possible to override the opening with another editor. It is ok
|
||||
* to return a promise that resolves to NULL to prevent the opening
|
||||
* altogether.
|
||||
*/
|
||||
prevent(callback: () => Thenable<any>): void;
|
||||
}
|
||||
|
||||
export interface IEditorGroupsAccessor {
|
||||
readonly groups: IEditorGroupView[];
|
||||
readonly activeGroup: IEditorGroupView;
|
||||
|
||||
readonly partOptions: IEditorPartOptions;
|
||||
readonly onDidEditorPartOptionsChange: Event<IEditorPartOptionsChangeEvent>;
|
||||
|
||||
getGroup(identifier: GroupIdentifier): IEditorGroupView;
|
||||
getGroups(order: GroupsOrder): IEditorGroupView[];
|
||||
|
||||
activateGroup(identifier: IEditorGroupView | GroupIdentifier): IEditorGroupView;
|
||||
|
||||
addGroup(location: IEditorGroupView | GroupIdentifier, direction: GroupDirection, options?: IAddGroupOptions): IEditorGroupView;
|
||||
mergeGroup(group: IEditorGroupView | GroupIdentifier, target: IEditorGroupView | GroupIdentifier, options?: IMergeGroupOptions): IEditorGroupView;
|
||||
|
||||
moveGroup(group: IEditorGroupView | GroupIdentifier, location: IEditorGroupView | GroupIdentifier, direction: GroupDirection): IEditorGroupView;
|
||||
copyGroup(group: IEditorGroupView | GroupIdentifier, location: IEditorGroupView | GroupIdentifier, direction: GroupDirection): IEditorGroupView;
|
||||
|
||||
removeGroup(group: IEditorGroupView | GroupIdentifier): void;
|
||||
}
|
||||
|
||||
export interface IEditorGroupView extends IDisposable, ISerializableView, IEditorGroup {
|
||||
readonly group: EditorGroup;
|
||||
readonly whenRestored: TPromise<void>;
|
||||
readonly disposed: boolean;
|
||||
|
||||
readonly onDidFocus: Event<void>;
|
||||
readonly onWillDispose: Event<void>;
|
||||
readonly onWillOpenEditor: Event<IEditorOpeningEvent>;
|
||||
readonly onDidOpenEditorFail: Event<IEditorInput>;
|
||||
readonly onWillCloseEditor: Event<IEditorCloseEvent>;
|
||||
readonly onDidCloseEditor: Event<IEditorCloseEvent>;
|
||||
|
||||
isEmpty(): boolean;
|
||||
setActive(isActive: boolean): void;
|
||||
setLabel(label: string): void;
|
||||
relayout(): void;
|
||||
|
||||
shutdown(): void;
|
||||
}
|
||||
|
||||
export function getActiveTextEditorOptions(group: IEditorGroup, expectedActiveEditor?: IEditorInput, presetOptions?: EditorOptions): EditorOptions {
|
||||
const activeGroupCodeEditor = group.activeControl ? getCodeEditor(group.activeControl.getControl()) : void 0;
|
||||
if (activeGroupCodeEditor) {
|
||||
if (!expectedActiveEditor || expectedActiveEditor.matches(group.activeEditor)) {
|
||||
return TextEditorOptions.fromEditor(activeGroupCodeEditor, presetOptions);
|
||||
}
|
||||
}
|
||||
|
||||
return presetOptions || new EditorOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* A sub-interface of IEditorService to hide some workbench-core specific
|
||||
* events from clients.
|
||||
*/
|
||||
export interface EditorServiceImpl extends IEditorService {
|
||||
|
||||
/**
|
||||
* Emitted when an editor is closed.
|
||||
*/
|
||||
readonly onDidCloseEditor: Event<IEditorCloseEvent>;
|
||||
|
||||
/**
|
||||
* Emitted when an editor failed to open.
|
||||
*/
|
||||
readonly onDidOpenEditorFail: Event<IEditorIdentifier>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A sub-interface of IEditorGroupsService to hide some workbench-core specific
|
||||
* methods from clients.
|
||||
*/
|
||||
export interface EditorGroupsServiceImpl extends IEditorGroupsService {
|
||||
|
||||
/**
|
||||
* A promise that resolves when groups have been restored.
|
||||
*/
|
||||
readonly whenRestored: TPromise<void>;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,14 +6,11 @@
|
||||
import * as nls from 'vs/nls';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { ActiveEditorMoveArguments, ActiveEditorMovePositioning, ActiveEditorMovePositioningBy, EditorCommands, TextCompareEditorVisible, EditorInput, IEditorIdentifier, IEditorCommandsContext } from 'vs/workbench/common/editor';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditor, Position, POSITIONS, Direction, IEditorInput } from 'vs/platform/editor/common/editor';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { TextCompareEditorVisibleContext, EditorInput, IEditorIdentifier, IEditorCommandsContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, CloseDirection, IEditor, IEditorInput } from 'vs/workbench/common/editor';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor';
|
||||
import { EditorStacksModel, EditorGroup } from 'vs/workbench/common/editor/editorStacksModel';
|
||||
import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import URI from 'vs/base/common/uri';
|
||||
@@ -22,26 +19,37 @@ import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { IListService } from 'vs/platform/list/browser/listService';
|
||||
import { List } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { distinct } from 'vs/base/common/arrays';
|
||||
import { IEditorGroupsService, IEditorGroup, GroupDirection, GroupLocation, GroupsOrder, preferredSideBySideGroupDirection, EditorGroupLayout } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
|
||||
export const CLOSE_SAVED_EDITORS_COMMAND_ID = 'workbench.action.closeUnmodifiedEditors';
|
||||
export const CLOSE_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeEditorsInGroup';
|
||||
export const CLOSE_EDITORS_AND_GROUP_COMMAND_ID = 'workbench.action.closeEditorsAndGroup';
|
||||
export const CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID = 'workbench.action.closeEditorsToTheRight';
|
||||
export const CLOSE_EDITOR_COMMAND_ID = 'workbench.action.closeActiveEditor';
|
||||
export const CLOSE_EDITOR_GROUP_COMMAND_ID = 'workbench.action.closeGroup';
|
||||
export const CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeOtherEditors';
|
||||
|
||||
export const MOVE_ACTIVE_EDITOR_COMMAND_ID = 'moveActiveEditor';
|
||||
export const LAYOUT_EDITOR_GROUPS_COMMAND_ID = 'layoutEditorGroups';
|
||||
export const KEEP_EDITOR_COMMAND_ID = 'workbench.action.keepEditor';
|
||||
export const SHOW_EDITORS_IN_GROUP = 'workbench.action.showEditorsInGroup';
|
||||
export const TOGGLE_DIFF_INLINE_MODE = 'toggle.diff.editorMode';
|
||||
|
||||
export const NAVIGATE_IN_GROUP_ONE_PREFIX = 'edt one ';
|
||||
export const NAVIGATE_IN_GROUP_TWO_PREFIX = 'edt two ';
|
||||
export const NAVIGATE_IN_GROUP_THREE_PREFIX = 'edt three ';
|
||||
export const NAVIGATE_ALL_EDITORS_GROUP_PREFIX = 'edt ';
|
||||
export const SPLIT_EDITOR_UP = 'workbench.action.splitEditorUp';
|
||||
export const SPLIT_EDITOR_DOWN = 'workbench.action.splitEditorDown';
|
||||
export const SPLIT_EDITOR_LEFT = 'workbench.action.splitEditorLeft';
|
||||
export const SPLIT_EDITOR_RIGHT = 'workbench.action.splitEditorRight';
|
||||
|
||||
export function setup(): void {
|
||||
registerActiveEditorMoveCommand();
|
||||
registerDiffEditorCommands();
|
||||
registerOpenEditorAtIndexCommands();
|
||||
registerEditorCommands();
|
||||
export const NAVIGATE_ALL_EDITORS_GROUP_PREFIX = 'edt ';
|
||||
export const NAVIGATE_IN_ACTIVE_GROUP_PREFIX = 'edt active ';
|
||||
|
||||
export interface ActiveEditorMoveArguments {
|
||||
to?: 'first' | 'last' | 'left' | 'right' | 'up' | 'down' | 'center' | 'position' | 'previous' | 'next';
|
||||
by?: 'tab' | 'group';
|
||||
value?: number;
|
||||
}
|
||||
|
||||
const isActiveEditorMoveArg = function (arg: ActiveEditorMoveArguments): boolean {
|
||||
@@ -49,17 +57,15 @@ const isActiveEditorMoveArg = function (arg: ActiveEditorMoveArguments): boolean
|
||||
return false;
|
||||
}
|
||||
|
||||
const activeEditorMoveArg: ActiveEditorMoveArguments = arg;
|
||||
|
||||
if (!types.isString(activeEditorMoveArg.to)) {
|
||||
if (!types.isString(arg.to)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!types.isUndefined(activeEditorMoveArg.by) && !types.isString(activeEditorMoveArg.by)) {
|
||||
if (!types.isUndefined(arg.by) && !types.isString(arg.by)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!types.isUndefined(activeEditorMoveArg.value) && !types.isNumber(activeEditorMoveArg.value)) {
|
||||
if (!types.isUndefined(arg.value) && !types.isNumber(arg.value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -68,8 +74,8 @@ const isActiveEditorMoveArg = function (arg: ActiveEditorMoveArguments): boolean
|
||||
|
||||
function registerActiveEditorMoveCommand(): void {
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: EditorCommands.MoveActiveEditor,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
id: MOVE_ACTIVE_EDITOR_COMMAND_ID,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: EditorContextKeys.editorTextFocus,
|
||||
primary: null,
|
||||
handler: (accessor, args: any) => moveActiveEditor(args, accessor),
|
||||
@@ -78,7 +84,7 @@ function registerActiveEditorMoveCommand(): void {
|
||||
args: [
|
||||
{
|
||||
name: nls.localize('editorCommand.activeEditorMove.arg.name', "Active editor move argument"),
|
||||
description: nls.localize('editorCommand.activeEditorMove.arg.description', "Argument Properties:\n\t* 'to': String value providing where to move.\n\t* 'by': String value providing the unit for move. By tab or by group.\n\t* 'value': Number value providing how many positions or an absolute position to move."),
|
||||
description: nls.localize('editorCommand.activeEditorMove.arg.description', "Argument Properties:\n\t* 'to': String value providing where to move.\n\t* 'by': String value providing the unit for move (by tab or by group).\n\t* 'value': Number value providing how many positions or an absolute position to move."),
|
||||
constraint: isActiveEditorMoveArg
|
||||
}
|
||||
]
|
||||
@@ -86,98 +92,153 @@ function registerActiveEditorMoveCommand(): void {
|
||||
});
|
||||
}
|
||||
|
||||
function moveActiveEditor(args: ActiveEditorMoveArguments = {}, accessor: ServicesAccessor): void {
|
||||
args.to = args.to || ActiveEditorMovePositioning.RIGHT;
|
||||
args.by = args.by || ActiveEditorMovePositioningBy.TAB;
|
||||
args.value = types.isUndefined(args.value) ? 1 : args.value;
|
||||
function moveActiveEditor(args: ActiveEditorMoveArguments = Object.create(null), accessor: ServicesAccessor): void {
|
||||
args.to = args.to || 'right';
|
||||
args.by = args.by || 'tab';
|
||||
args.value = typeof args.value === 'number' ? args.value : 1;
|
||||
|
||||
const activeEditor = accessor.get(IWorkbenchEditorService).getActiveEditor();
|
||||
if (activeEditor) {
|
||||
const activeControl = accessor.get(IEditorService).activeControl;
|
||||
if (activeControl) {
|
||||
switch (args.by) {
|
||||
case ActiveEditorMovePositioningBy.TAB:
|
||||
return moveActiveTab(args, activeEditor, accessor);
|
||||
case ActiveEditorMovePositioningBy.GROUP:
|
||||
return moveActiveEditorToGroup(args, activeEditor, accessor);
|
||||
case 'tab':
|
||||
return moveActiveTab(args, activeControl, accessor);
|
||||
case 'group':
|
||||
return moveActiveEditorToGroup(args, activeControl, accessor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function moveActiveTab(args: ActiveEditorMoveArguments, activeEditor: IEditor, accessor: ServicesAccessor): void {
|
||||
const editorGroupsService: IEditorGroupService = accessor.get(IEditorGroupService);
|
||||
const editorGroup = editorGroupsService.getStacksModel().groupAt(activeEditor.position);
|
||||
let index = editorGroup.indexOf(activeEditor.input);
|
||||
function moveActiveTab(args: ActiveEditorMoveArguments, control: IEditor, accessor: ServicesAccessor): void {
|
||||
const group = control.group;
|
||||
let index = group.getIndexOfEditor(control.input);
|
||||
switch (args.to) {
|
||||
case ActiveEditorMovePositioning.FIRST:
|
||||
case 'first':
|
||||
index = 0;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.LAST:
|
||||
index = editorGroup.count - 1;
|
||||
case 'last':
|
||||
index = group.count - 1;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.LEFT:
|
||||
case 'left':
|
||||
index = index - args.value;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.RIGHT:
|
||||
case 'right':
|
||||
index = index + args.value;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.CENTER:
|
||||
index = Math.round(editorGroup.count / 2) - 1;
|
||||
case 'center':
|
||||
index = Math.round(group.count / 2) - 1;
|
||||
break;
|
||||
case ActiveEditorMovePositioning.POSITION:
|
||||
case 'position':
|
||||
index = args.value - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
index = index < 0 ? 0 : index >= editorGroup.count ? editorGroup.count - 1 : index;
|
||||
editorGroupsService.moveEditor(activeEditor.input, editorGroup, editorGroup, { index });
|
||||
index = index < 0 ? 0 : index >= group.count ? group.count - 1 : index;
|
||||
group.moveEditor(control.input, group, { index });
|
||||
}
|
||||
|
||||
function moveActiveEditorToGroup(args: ActiveEditorMoveArguments, activeEditor: IEditor, accessor: ServicesAccessor): void {
|
||||
let newPosition = activeEditor.position;
|
||||
function moveActiveEditorToGroup(args: ActiveEditorMoveArguments, control: IEditor, accessor: ServicesAccessor): void {
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
|
||||
const sourceGroup = control.group;
|
||||
let targetGroup: IEditorGroup;
|
||||
|
||||
switch (args.to) {
|
||||
case ActiveEditorMovePositioning.LEFT:
|
||||
newPosition = newPosition - 1;
|
||||
case 'left':
|
||||
targetGroup = editorGroupService.findGroup({ direction: GroupDirection.LEFT }, sourceGroup);
|
||||
if (!targetGroup) {
|
||||
targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.LEFT);
|
||||
}
|
||||
break;
|
||||
case ActiveEditorMovePositioning.RIGHT:
|
||||
newPosition = newPosition + 1;
|
||||
case 'right':
|
||||
targetGroup = editorGroupService.findGroup({ direction: GroupDirection.RIGHT }, sourceGroup);
|
||||
if (!targetGroup) {
|
||||
targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.RIGHT);
|
||||
}
|
||||
break;
|
||||
case ActiveEditorMovePositioning.FIRST:
|
||||
newPosition = Position.ONE;
|
||||
case 'up':
|
||||
targetGroup = editorGroupService.findGroup({ direction: GroupDirection.UP }, sourceGroup);
|
||||
if (!targetGroup) {
|
||||
targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.UP);
|
||||
}
|
||||
break;
|
||||
case ActiveEditorMovePositioning.LAST:
|
||||
newPosition = Position.THREE;
|
||||
case 'down':
|
||||
targetGroup = editorGroupService.findGroup({ direction: GroupDirection.DOWN }, sourceGroup);
|
||||
if (!targetGroup) {
|
||||
targetGroup = editorGroupService.addGroup(sourceGroup, GroupDirection.DOWN);
|
||||
}
|
||||
break;
|
||||
case ActiveEditorMovePositioning.CENTER:
|
||||
newPosition = Position.TWO;
|
||||
case 'first':
|
||||
targetGroup = editorGroupService.findGroup({ location: GroupLocation.FIRST }, sourceGroup);
|
||||
break;
|
||||
case ActiveEditorMovePositioning.POSITION:
|
||||
newPosition = args.value - 1;
|
||||
case 'last':
|
||||
targetGroup = editorGroupService.findGroup({ location: GroupLocation.LAST }, sourceGroup);
|
||||
break;
|
||||
case 'previous':
|
||||
targetGroup = editorGroupService.findGroup({ location: GroupLocation.PREVIOUS }, sourceGroup);
|
||||
break;
|
||||
case 'next':
|
||||
targetGroup = editorGroupService.findGroup({ location: GroupLocation.NEXT }, sourceGroup);
|
||||
if (!targetGroup) {
|
||||
targetGroup = editorGroupService.addGroup(sourceGroup, preferredSideBySideGroupDirection(configurationService));
|
||||
}
|
||||
break;
|
||||
case 'center':
|
||||
targetGroup = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)[(editorGroupService.count / 2) - 1];
|
||||
break;
|
||||
case 'position':
|
||||
targetGroup = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)[args.value - 1];
|
||||
break;
|
||||
}
|
||||
|
||||
newPosition = POSITIONS.indexOf(newPosition) !== -1 ? newPosition : activeEditor.position;
|
||||
accessor.get(IEditorGroupService).moveEditor(activeEditor.input, activeEditor.position, newPosition);
|
||||
if (targetGroup) {
|
||||
sourceGroup.moveEditor(control.input, targetGroup);
|
||||
targetGroup.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function registerEditorGroupsLayoutCommand(): void {
|
||||
CommandsRegistry.registerCommand(LAYOUT_EDITOR_GROUPS_COMMAND_ID, (accessor: ServicesAccessor, args: EditorGroupLayout) => {
|
||||
if (!args || typeof args !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
editorGroupService.applyLayout(args);
|
||||
});
|
||||
}
|
||||
|
||||
export function mergeAllGroups(editorGroupService: IEditorGroupsService): void {
|
||||
const target = editorGroupService.activeGroup;
|
||||
editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).forEach(group => {
|
||||
if (group === target) {
|
||||
return; // keep target
|
||||
}
|
||||
|
||||
editorGroupService.mergeGroup(group, target);
|
||||
});
|
||||
}
|
||||
|
||||
function registerDiffEditorCommands(): void {
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'workbench.action.compareEditor.nextChange',
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: TextCompareEditorVisible,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: TextCompareEditorVisibleContext,
|
||||
primary: null,
|
||||
handler: accessor => navigateInDiffEditor(accessor, true)
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'workbench.action.compareEditor.previousChange',
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: TextCompareEditorVisible,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: TextCompareEditorVisibleContext,
|
||||
primary: null,
|
||||
handler: accessor => navigateInDiffEditor(accessor, false)
|
||||
});
|
||||
|
||||
function navigateInDiffEditor(accessor: ServicesAccessor, next: boolean): void {
|
||||
let editorService = accessor.get(IWorkbenchEditorService);
|
||||
const candidates = [editorService.getActiveEditor(), ...editorService.getVisibleEditors()].filter(e => e instanceof TextDiffEditor);
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const candidates = [editorService.activeControl, ...editorService.visibleControls].filter(e => e instanceof TextDiffEditor);
|
||||
|
||||
if (candidates.length > 0) {
|
||||
next ? (<TextDiffEditor>candidates[0]).getDiffNavigator().next() : (<TextDiffEditor>candidates[0]).getDiffNavigator().previous();
|
||||
@@ -186,25 +247,17 @@ function registerDiffEditorCommands(): void {
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: TOGGLE_DIFF_INLINE_MODE,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: void 0,
|
||||
primary: void 0,
|
||||
handler: (accessor, resource, context: IEditorCommandsContext) => {
|
||||
const editorService = accessor.get(IWorkbenchEditorService);
|
||||
const editorGroupService = accessor.get(IEditorGroupService);
|
||||
handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
|
||||
let editor: IEditor;
|
||||
if (context) {
|
||||
const position = positionAndInput(editorGroupService, editorService, context).position;
|
||||
editor = editorService.getVisibleEditors()[position];
|
||||
} else {
|
||||
editor = editorService.getActiveEditor();
|
||||
}
|
||||
|
||||
if (editor instanceof TextDiffEditor) {
|
||||
const control = editor.getControl();
|
||||
const isInlineMode = !control.renderSideBySide;
|
||||
control.updateOptions(<IDiffEditorOptions>{
|
||||
const { control } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
|
||||
if (control instanceof TextDiffEditor) {
|
||||
const widget = control.getControl();
|
||||
const isInlineMode = !widget.renderSideBySide;
|
||||
widget.updateOptions(<IDiffEditorOptions>{
|
||||
renderSideBySide: isInlineMode
|
||||
});
|
||||
}
|
||||
@@ -221,19 +274,16 @@ function registerOpenEditorAtIndexCommands(): void {
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'workbench.action.openEditorAtIndex' + visibleIndex,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: void 0,
|
||||
primary: KeyMod.Alt | toKeyCode(visibleIndex),
|
||||
mac: { primary: KeyMod.WinCtrl | toKeyCode(visibleIndex) },
|
||||
handler: accessor => {
|
||||
const editorService = accessor.get(IWorkbenchEditorService);
|
||||
const editorGroupService = accessor.get(IEditorGroupService);
|
||||
|
||||
const active = editorService.getActiveEditor();
|
||||
if (active) {
|
||||
const group = editorGroupService.getStacksModel().groupAt(active.position);
|
||||
const editor = group.getEditor(editorIndex);
|
||||
const editorService = accessor.get(IEditorService);
|
||||
|
||||
const activeControl = editorService.activeControl;
|
||||
if (activeControl) {
|
||||
const editor = activeControl.group.getEditor(editorIndex);
|
||||
if (editor) {
|
||||
return editorService.openEditor(editor).then(() => void 0);
|
||||
}
|
||||
@@ -262,169 +312,243 @@ function registerOpenEditorAtIndexCommands(): void {
|
||||
}
|
||||
}
|
||||
|
||||
function registerEditorCommands() {
|
||||
function registerFocusEditorGroupAtIndexCommands(): void {
|
||||
|
||||
// Keybindings to focus a specific group (2-8) in the editor area
|
||||
for (let groupIndex = 1; groupIndex < 8; groupIndex++) {
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: toCommandId(groupIndex),
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: void 0,
|
||||
primary: KeyMod.CtrlCmd | toKeyCode(groupIndex),
|
||||
handler: accessor => {
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
|
||||
// To keep backwards compatibility (pre-grid), allow to focus a group
|
||||
// that does not exist as long as it is the next group after the last
|
||||
// opened group. Otherwise we return.
|
||||
if (groupIndex > editorGroupService.count) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Group exists: just focus
|
||||
const groups = editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE);
|
||||
if (groups[groupIndex]) {
|
||||
return groups[groupIndex].focus();
|
||||
}
|
||||
|
||||
// Group does not exist: create new by splitting the active one of the last group
|
||||
const direction = preferredSideBySideGroupDirection(configurationService);
|
||||
const lastGroup = editorGroupService.findGroup({ location: GroupLocation.LAST });
|
||||
const newGroup = editorGroupService.addGroup(lastGroup, direction);
|
||||
|
||||
// Focus
|
||||
newGroup.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toCommandId(index: number): string {
|
||||
switch (index) {
|
||||
case 1: return 'workbench.action.focusSecondEditorGroup';
|
||||
case 2: return 'workbench.action.focusThirdEditorGroup';
|
||||
case 3: return 'workbench.action.focusFourthEditorGroup';
|
||||
case 4: return 'workbench.action.focusFifthEditorGroup';
|
||||
case 5: return 'workbench.action.focusSixthEditorGroup';
|
||||
case 6: return 'workbench.action.focusSeventhEditorGroup';
|
||||
case 7: return 'workbench.action.focusEighthEditorGroup';
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
|
||||
function toKeyCode(index: number): KeyCode {
|
||||
switch (index) {
|
||||
case 1: return KeyCode.KEY_2;
|
||||
case 2: return KeyCode.KEY_3;
|
||||
case 3: return KeyCode.KEY_4;
|
||||
case 4: return KeyCode.KEY_5;
|
||||
case 5: return KeyCode.KEY_6;
|
||||
case 6: return KeyCode.KEY_7;
|
||||
case 7: return KeyCode.KEY_8;
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function splitEditor(editorGroupService: IEditorGroupsService, direction: GroupDirection, context?: IEditorCommandsContext): void {
|
||||
let sourceGroup: IEditorGroup;
|
||||
if (context && typeof context.groupId === 'number') {
|
||||
sourceGroup = editorGroupService.getGroup(context.groupId);
|
||||
} else {
|
||||
sourceGroup = editorGroupService.activeGroup;
|
||||
}
|
||||
|
||||
// Add group
|
||||
const newGroup = editorGroupService.addGroup(sourceGroup, direction);
|
||||
|
||||
// Split editor (if it can be split)
|
||||
let editorToCopy: IEditorInput;
|
||||
if (context && typeof context.editorIndex === 'number') {
|
||||
editorToCopy = sourceGroup.getEditor(context.editorIndex);
|
||||
} else {
|
||||
editorToCopy = sourceGroup.activeEditor;
|
||||
}
|
||||
|
||||
if (editorToCopy && (editorToCopy as EditorInput).supportsSplitEditor()) {
|
||||
sourceGroup.copyEditor(editorToCopy, newGroup);
|
||||
}
|
||||
|
||||
// Focus
|
||||
newGroup.focus();
|
||||
}
|
||||
|
||||
function registerSplitEditorCommands() {
|
||||
[
|
||||
{ id: SPLIT_EDITOR_UP, direction: GroupDirection.UP },
|
||||
{ id: SPLIT_EDITOR_DOWN, direction: GroupDirection.DOWN },
|
||||
{ id: SPLIT_EDITOR_LEFT, direction: GroupDirection.LEFT },
|
||||
{ id: SPLIT_EDITOR_RIGHT, direction: GroupDirection.RIGHT }
|
||||
].forEach(({ id, direction }) => {
|
||||
CommandsRegistry.registerCommand(id, function (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) {
|
||||
splitEditor(accessor.get(IEditorGroupsService), direction, getCommandsContext(resourceOrContext, context));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function registerCloseEditorCommands() {
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: CLOSE_SAVED_EDITORS_COMMAND_ID,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: void 0,
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_U),
|
||||
handler: (accessor, resource: URI | object, context: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupService);
|
||||
const model = editorGroupService.getStacksModel();
|
||||
const editorService = accessor.get(IWorkbenchEditorService);
|
||||
const contexts = getMultiSelectedEditorContexts(context, accessor.get(IListService));
|
||||
if (contexts.length === 0 && model.activeGroup) {
|
||||
// If command is triggered from the command palette use the active group
|
||||
contexts.push({ groupId: model.activeGroup.id });
|
||||
handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
|
||||
|
||||
const activeGroup = editorGroupService.activeGroup;
|
||||
if (contexts.length === 0) {
|
||||
contexts.push({ groupId: activeGroup.id }); // active group as fallback
|
||||
}
|
||||
|
||||
let positionOne: { savedOnly: boolean } = void 0;
|
||||
let positionTwo: { savedOnly: boolean } = void 0;
|
||||
let positionThree: { savedOnly: boolean } = void 0;
|
||||
contexts.forEach(c => {
|
||||
switch (model.positionOfGroup(model.getGroup(c.groupId))) {
|
||||
case Position.ONE: positionOne = { savedOnly: true }; break;
|
||||
case Position.TWO: positionTwo = { savedOnly: true }; break;
|
||||
case Position.THREE: positionThree = { savedOnly: true }; break;
|
||||
}
|
||||
});
|
||||
|
||||
return editorService.closeEditors({ positionOne, positionTwo, positionThree });
|
||||
return TPromise.join(distinct(contexts.map(c => c.groupId)).map(groupId =>
|
||||
editorGroupService.getGroup(groupId).closeEditors({ savedOnly: true })
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: void 0,
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_W),
|
||||
handler: (accessor, resource: URI | object, context: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupService);
|
||||
const editorService = accessor.get(IWorkbenchEditorService);
|
||||
const contexts = getMultiSelectedEditorContexts(context, accessor.get(IListService));
|
||||
handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
|
||||
const distinctGroupIds = distinct(contexts.map(c => c.groupId));
|
||||
const model = editorGroupService.getStacksModel();
|
||||
|
||||
if (distinctGroupIds.length) {
|
||||
return editorService.closeEditors(distinctGroupIds.map(gid => model.positionOfGroup(model.getGroup(gid))));
|
||||
}
|
||||
const activeEditor = editorService.getActiveEditor();
|
||||
if (activeEditor) {
|
||||
return editorService.closeEditors(activeEditor.position);
|
||||
if (distinctGroupIds.length === 0) {
|
||||
distinctGroupIds.push(editorGroupService.activeGroup.id);
|
||||
}
|
||||
|
||||
return TPromise.as(false);
|
||||
return TPromise.join(distinctGroupIds.map(groupId =>
|
||||
editorGroupService.getGroup(groupId).closeAllEditors()
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: CLOSE_EDITOR_COMMAND_ID,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
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 | object, context: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupService);
|
||||
const editorService = accessor.get(IWorkbenchEditorService);
|
||||
handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
|
||||
|
||||
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[]>();
|
||||
|
||||
groupIds.forEach(groupId => {
|
||||
const group = model.getGroup(groupId);
|
||||
const position = model.positionOfGroup(group);
|
||||
if (position >= 0) {
|
||||
const inputs = contexts.map(c => {
|
||||
if (c && groupId === c.groupId && types.isNumber(c.editorIndex)) {
|
||||
return group.getEditor(c.editorIndex);
|
||||
}
|
||||
|
||||
return group.activeEditor;
|
||||
}).filter(input => !!input);
|
||||
|
||||
if (inputs.length) {
|
||||
editorsToClose.set(position, inputs);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (editorsToClose.size === 0) {
|
||||
const activeEditor = editorService.getActiveEditor();
|
||||
if (activeEditor) {
|
||||
return editorService.closeEditor(activeEditor.position, activeEditor.input);
|
||||
}
|
||||
const activeGroup = editorGroupService.activeGroup;
|
||||
if (contexts.length === 0 && activeGroup.activeEditor) {
|
||||
contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) }); // active editor as fallback
|
||||
}
|
||||
|
||||
return editorService.closeEditors({
|
||||
positionOne: editorsToClose.get(Position.ONE),
|
||||
positionTwo: editorsToClose.get(Position.TWO),
|
||||
positionThree: editorsToClose.get(Position.THREE)
|
||||
});
|
||||
const groupIds = distinct(contexts.map(context => context.groupId));
|
||||
|
||||
return TPromise.join(groupIds.map(groupId => {
|
||||
const group = editorGroupService.getGroup(groupId);
|
||||
const editors = contexts
|
||||
.filter(context => context.groupId === groupId)
|
||||
.map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor);
|
||||
|
||||
return group.closeEditors(editors);
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: CLOSE_EDITOR_GROUP_COMMAND_ID,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext),
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
|
||||
win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
|
||||
handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
const commandsContext = getCommandsContext(resourceOrContext, context);
|
||||
|
||||
let group: IEditorGroup;
|
||||
if (commandsContext && typeof commandsContext.groupId === 'number') {
|
||||
group = editorGroupService.getGroup(commandsContext.groupId);
|
||||
} else {
|
||||
group = editorGroupService.activeGroup;
|
||||
}
|
||||
|
||||
editorGroupService.removeGroup(group);
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: void 0,
|
||||
primary: void 0,
|
||||
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T },
|
||||
handler: (accessor, resource: URI | object, context: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupService);
|
||||
const editorService = accessor.get(IWorkbenchEditorService);
|
||||
const contexts = getMultiSelectedEditorContexts(context, accessor.get(IListService));
|
||||
const model = editorGroupService.getStacksModel();
|
||||
handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
const contexts = getMultiSelectedEditorContexts(getCommandsContext(resourceOrContext, context), accessor.get(IListService), editorGroupService);
|
||||
|
||||
if (contexts.length === 0) {
|
||||
// Cover the case when run from command palette
|
||||
const activeGroup = model.activeGroup;
|
||||
const activeEditor = editorService.getActiveEditorInput();
|
||||
if (activeGroup && activeEditor) {
|
||||
contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.indexOf(activeEditor) });
|
||||
}
|
||||
const activeGroup = editorGroupService.activeGroup;
|
||||
if (contexts.length === 0 && activeGroup.activeEditor) {
|
||||
contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.getIndexOfEditor(activeGroup.activeEditor) }); // active editor as fallback
|
||||
}
|
||||
|
||||
const groupIds = distinct(contexts.map(context => context.groupId));
|
||||
const editorsToClose = new Map<Position, IEditorInput[]>();
|
||||
groupIds.forEach(groupId => {
|
||||
const group = model.getGroup(groupId);
|
||||
const inputsToSkip = contexts.map(c => {
|
||||
if (c.groupId === groupId && types.isNumber(c.editorIndex)) {
|
||||
return group.getEditor(c.editorIndex);
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}).filter(input => !!input);
|
||||
return TPromise.join(groupIds.map(groupId => {
|
||||
const group = editorGroupService.getGroup(groupId);
|
||||
const editors = contexts
|
||||
.filter(context => context.groupId === groupId)
|
||||
.map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor);
|
||||
const editorsToClose = group.editors.filter(e => editors.indexOf(e) === -1);
|
||||
|
||||
const toClose = group.getEditors().filter(input => inputsToSkip.indexOf(input) === -1);
|
||||
editorsToClose.set(model.positionOfGroup(group), toClose);
|
||||
});
|
||||
|
||||
return editorService.closeEditors({
|
||||
positionOne: editorsToClose.get(Position.ONE),
|
||||
positionTwo: editorsToClose.get(Position.TWO),
|
||||
positionThree: editorsToClose.get(Position.THREE)
|
||||
});
|
||||
return group.closeEditors(editorsToClose);
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: void 0,
|
||||
primary: void 0,
|
||||
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupService);
|
||||
const editorService = accessor.get(IWorkbenchEditorService);
|
||||
handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
|
||||
const { position, input } = positionAndInput(editorGroupService, editorService, context);
|
||||
|
||||
if (typeof position === 'number' && input) {
|
||||
return editorService.closeEditors(position, { except: input, direction: Direction.RIGHT });
|
||||
const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
|
||||
if (group && editor) {
|
||||
return group.closeEditors({ direction: CloseDirection.RIGHT, except: editor });
|
||||
}
|
||||
|
||||
return TPromise.as(false);
|
||||
@@ -433,17 +557,15 @@ function registerEditorCommands() {
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: KEEP_EDITOR_COMMAND_ID,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: void 0,
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.Enter),
|
||||
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupService);
|
||||
const editorService = accessor.get(IWorkbenchEditorService);
|
||||
handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
|
||||
const { position, input } = positionAndInput(editorGroupService, editorService, context);
|
||||
|
||||
if (typeof position === 'number' && input) {
|
||||
return editorGroupService.pinEditor(position, input);
|
||||
const { group, editor } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
|
||||
if (group && editor) {
|
||||
return group.pinEditor(editor);
|
||||
}
|
||||
|
||||
return TPromise.as(false);
|
||||
@@ -452,88 +574,98 @@ function registerEditorCommands() {
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: SHOW_EDITORS_IN_GROUP,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: void 0,
|
||||
primary: void 0,
|
||||
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupService);
|
||||
const editorService = accessor.get(IWorkbenchEditorService);
|
||||
handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
const quickOpenService = accessor.get(IQuickOpenService);
|
||||
|
||||
const stacks = editorGroupService.getStacksModel();
|
||||
const groupCount = stacks.groups.length;
|
||||
if (groupCount <= 1) {
|
||||
if (editorGroupService.count <= 1) {
|
||||
return quickOpenService.show(NAVIGATE_ALL_EDITORS_GROUP_PREFIX);
|
||||
}
|
||||
|
||||
const { position } = positionAndInput(editorGroupService, editorService, context);
|
||||
|
||||
switch (position) {
|
||||
case Position.TWO:
|
||||
return quickOpenService.show(NAVIGATE_IN_GROUP_TWO_PREFIX);
|
||||
case Position.THREE:
|
||||
return quickOpenService.show(NAVIGATE_IN_GROUP_THREE_PREFIX);
|
||||
const commandsContext = getCommandsContext(resourceOrContext, context);
|
||||
if (commandsContext && typeof commandsContext.groupId === 'number') {
|
||||
editorGroupService.activateGroup(editorGroupService.getGroup(commandsContext.groupId)); // we need the group to be active
|
||||
}
|
||||
|
||||
return quickOpenService.show(NAVIGATE_IN_GROUP_ONE_PREFIX);
|
||||
return quickOpenService.show(NAVIGATE_IN_ACTIVE_GROUP_PREFIX);
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: '_workbench.printStacksModel',
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0),
|
||||
handler(accessor: ServicesAccessor) {
|
||||
console.log(`${accessor.get(IEditorGroupService).getStacksModel().toString()}\n\n`);
|
||||
},
|
||||
when: void 0,
|
||||
primary: void 0
|
||||
});
|
||||
CommandsRegistry.registerCommand(CLOSE_EDITORS_AND_GROUP_COMMAND_ID, (accessor: ServicesAccessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: '_workbench.validateStacksModel',
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0),
|
||||
handler(accessor: ServicesAccessor) {
|
||||
(<EditorStacksModel>accessor.get(IEditorGroupService).getStacksModel()).validate();
|
||||
},
|
||||
when: void 0,
|
||||
primary: void 0
|
||||
const { group } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
|
||||
if (group) {
|
||||
return group.closeAllEditors().then(() => {
|
||||
if (group.count === 0 && editorGroupService.getGroup(group.id) /* could be gone by now */) {
|
||||
editorGroupService.removeGroup(group); // only remove group if it is now empty
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return void 0;
|
||||
});
|
||||
}
|
||||
|
||||
function positionAndInput(editorGroupService: IEditorGroupService, editorService: IWorkbenchEditorService, context?: IEditorCommandsContext): { position: Position, input: IEditorInput } {
|
||||
|
||||
// Resolve from context
|
||||
const model = editorGroupService.getStacksModel();
|
||||
const group = context ? model.getGroup(context.groupId) : undefined;
|
||||
let position = group ? model.positionOfGroup(group) : undefined;
|
||||
let input = group && types.isNumber(context.editorIndex) ? 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();
|
||||
if (active) {
|
||||
position = typeof position === 'number' ? position : active.position;
|
||||
input = input ? input : <EditorInput>active.input;
|
||||
function getCommandsContext(resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext): IEditorCommandsContext {
|
||||
if (URI.isUri(resourceOrContext)) {
|
||||
return context;
|
||||
}
|
||||
|
||||
return { position, input };
|
||||
if (resourceOrContext && typeof resourceOrContext.groupId === 'number') {
|
||||
return resourceOrContext;
|
||||
}
|
||||
|
||||
if (context && typeof context.groupId === 'number') {
|
||||
return context;
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
|
||||
export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsContext, listService: IListService): IEditorCommandsContext[] {
|
||||
function resolveCommandsContext(editorGroupService: IEditorGroupsService, context?: IEditorCommandsContext): { group: IEditorGroup, editor: IEditorInput, control: IEditor } {
|
||||
|
||||
// Resolve from context
|
||||
let group = context && typeof context.groupId === 'number' ? editorGroupService.getGroup(context.groupId) : undefined;
|
||||
let editor = group && typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : undefined;
|
||||
let control = group ? group.activeControl : undefined;
|
||||
|
||||
// Fallback to active group as needed
|
||||
if (!group) {
|
||||
group = editorGroupService.activeGroup;
|
||||
editor = <EditorInput>group.activeEditor;
|
||||
control = group.activeControl;
|
||||
}
|
||||
|
||||
return { group, editor, control };
|
||||
}
|
||||
|
||||
export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsContext, listService: IListService, editorGroupService: IEditorGroupsService): IEditorCommandsContext[] {
|
||||
|
||||
// First check for a focused list to return the selected items from
|
||||
const list = listService.lastFocusedList;
|
||||
if (list instanceof List && list.isDOMFocused()) {
|
||||
const elementToContext = (element: IEditorIdentifier | EditorGroup) =>
|
||||
element instanceof EditorGroup ? { groupId: element.id, editorIndex: undefined } : { groupId: element.group.id, editorIndex: element.group.indexOf(element.editor) };
|
||||
const onlyEditorGroupAndEditor = (e: IEditorIdentifier | EditorGroup) => e instanceof EditorGroup || ('editor' in e && 'group' in e);
|
||||
const elementToContext = (element: IEditorIdentifier | IEditorGroup) => {
|
||||
if (isEditorGroup(element)) {
|
||||
return { groupId: element.id, editorIndex: void 0 };
|
||||
}
|
||||
|
||||
const focusedElements: (IEditorIdentifier | EditorGroup)[] = list.getFocusedElements().filter(onlyEditorGroupAndEditor);
|
||||
// need to take into account when editor context is { group: group }
|
||||
const focus = editorContext ? editorContext : focusedElements.length ? focusedElements.map(elementToContext)[0] : undefined;
|
||||
return { groupId: element.groupId, editorIndex: editorGroupService.getGroup(element.groupId).getIndexOfEditor(element.editor) };
|
||||
};
|
||||
|
||||
const onlyEditorGroupAndEditor = (e: IEditorIdentifier | IEditorGroup) => isEditorGroup(e) || isEditorIdentifier(e);
|
||||
|
||||
const focusedElements: (IEditorIdentifier | IEditorGroup)[] = list.getFocusedElements().filter(onlyEditorGroupAndEditor);
|
||||
const focus = editorContext ? editorContext : focusedElements.length ? focusedElements.map(elementToContext)[0] : void 0; // need to take into account when editor context is { group: group }
|
||||
|
||||
if (focus) {
|
||||
const selection: (IEditorIdentifier | EditorGroup)[] = list.getSelectedElements().filter(onlyEditorGroupAndEditor);
|
||||
const selection: (IEditorIdentifier | IEditorGroup)[] = list.getSelectedElements().filter(onlyEditorGroupAndEditor);
|
||||
|
||||
// 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)) {
|
||||
if (selection && selection.some(s => isEditorGroup(s) ? s.id === focus.groupId : s.groupId === focus.groupId && editorGroupService.getGroup(s.groupId).getIndexOfEditor(s.editor) === focus.editorIndex)) {
|
||||
return selection.map(elementToContext);
|
||||
}
|
||||
|
||||
@@ -544,3 +676,25 @@ export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsCon
|
||||
// Otherwise go with passed in context
|
||||
return !!editorContext ? [editorContext] : [];
|
||||
}
|
||||
|
||||
function isEditorGroup(thing: any): thing is IEditorGroup {
|
||||
const group = thing as IEditorGroup;
|
||||
|
||||
return group && typeof group.id === 'number' && Array.isArray(group.editors);
|
||||
}
|
||||
|
||||
function isEditorIdentifier(thing: any): thing is IEditorIdentifier {
|
||||
const identifier = thing as IEditorIdentifier;
|
||||
|
||||
return identifier && typeof identifier.groupId === 'number';
|
||||
}
|
||||
|
||||
export function setup(): void {
|
||||
registerActiveEditorMoveCommand();
|
||||
registerEditorGroupsLayoutCommand();
|
||||
registerDiffEditorCommands();
|
||||
registerOpenEditorAtIndexCommands();
|
||||
registerCloseEditorCommands();
|
||||
registerFocusEditorGroupAtIndexCommands();
|
||||
registerSplitEditorCommands();
|
||||
}
|
||||
|
||||
247
src/vs/workbench/browser/parts/editor/editorControl.ts
Normal file
247
src/vs/workbench/browser/parts/editor/editorControl.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { dispose, Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { EditorInput, EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { Dimension, show, hide, addClass } from 'vs/base/browser/dom';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IEditorRegistry, Extensions as EditorExtensions, IEditorDescriptor } from 'vs/workbench/browser/editor';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress';
|
||||
import { toWinJsPromise } from 'vs/base/common/async';
|
||||
import { IEditorGroupView, DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
|
||||
export interface IOpenEditorResult {
|
||||
readonly control: BaseEditor;
|
||||
readonly editorChanged: boolean;
|
||||
}
|
||||
|
||||
export class EditorControl extends Disposable {
|
||||
|
||||
get minimumWidth() { return this._activeControl ? this._activeControl.minimumWidth : DEFAULT_EDITOR_MIN_DIMENSIONS.width; }
|
||||
get minimumHeight() { return this._activeControl ? this._activeControl.minimumHeight : DEFAULT_EDITOR_MIN_DIMENSIONS.height; }
|
||||
get maximumWidth() { return this._activeControl ? this._activeControl.maximumWidth : DEFAULT_EDITOR_MAX_DIMENSIONS.width; }
|
||||
get maximumHeight() { return this._activeControl ? this._activeControl.maximumHeight : DEFAULT_EDITOR_MAX_DIMENSIONS.height; }
|
||||
|
||||
private _onDidFocus: Emitter<void> = this._register(new Emitter<void>());
|
||||
get onDidFocus(): Event<void> { return this._onDidFocus.event; }
|
||||
|
||||
private _onDidSizeConstraintsChange = this._register(new Emitter<{ width: number; height: number; }>());
|
||||
get onDidSizeConstraintsChange(): Event<{ width: number; height: number; }> { return this._onDidSizeConstraintsChange.event; }
|
||||
|
||||
private _activeControl: BaseEditor;
|
||||
private controls: BaseEditor[] = [];
|
||||
|
||||
private activeControlDisposeables: IDisposable[] = [];
|
||||
private dimension: Dimension;
|
||||
private editorOperation: LongRunningOperation;
|
||||
|
||||
constructor(
|
||||
private parent: HTMLElement,
|
||||
private groupView: IEditorGroupView,
|
||||
@IPartService private partService: IPartService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IProgressService progressService: IProgressService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.editorOperation = this._register(new LongRunningOperation(progressService));
|
||||
}
|
||||
|
||||
get activeControl(): BaseEditor {
|
||||
return this._activeControl;
|
||||
}
|
||||
|
||||
openEditor(editor: EditorInput, options?: EditorOptions): TPromise<IOpenEditorResult> {
|
||||
|
||||
// Editor control
|
||||
const descriptor = Registry.as<IEditorRegistry>(EditorExtensions.Editors).getEditor(editor);
|
||||
const control = this.doShowEditorControl(descriptor, options);
|
||||
|
||||
// Set input
|
||||
return this.doSetInput(control, editor, options).then((editorChanged => (({ control, editorChanged } as IOpenEditorResult))));
|
||||
}
|
||||
|
||||
private doShowEditorControl(descriptor: IEditorDescriptor, options: EditorOptions): BaseEditor {
|
||||
|
||||
// Return early if the currently active editor control can handle the input
|
||||
if (this._activeControl && descriptor.describes(this._activeControl)) {
|
||||
return this._activeControl;
|
||||
}
|
||||
|
||||
// Hide active one first
|
||||
this.doHideActiveEditorControl();
|
||||
|
||||
// Create editor
|
||||
const control = this.doCreateEditorControl(descriptor);
|
||||
|
||||
// Set editor as active
|
||||
this.doSetActiveControl(control);
|
||||
|
||||
// Show editor
|
||||
this.parent.appendChild(control.getContainer());
|
||||
show(control.getContainer());
|
||||
|
||||
// Indicate to editor that it is now visible
|
||||
control.setVisible(true, this.groupView);
|
||||
|
||||
// Layout
|
||||
if (this.dimension) {
|
||||
control.layout(this.dimension);
|
||||
}
|
||||
|
||||
return control;
|
||||
}
|
||||
|
||||
private doCreateEditorControl(descriptor: IEditorDescriptor): BaseEditor {
|
||||
|
||||
// Instantiate editor
|
||||
const control = this.doInstantiateEditorControl(descriptor);
|
||||
|
||||
// Create editor container as needed
|
||||
if (!control.getContainer()) {
|
||||
const controlInstanceContainer = document.createElement('div');
|
||||
addClass(controlInstanceContainer, 'editor-instance');
|
||||
controlInstanceContainer.id = descriptor.getId();
|
||||
|
||||
control.create(controlInstanceContainer);
|
||||
}
|
||||
|
||||
return control;
|
||||
}
|
||||
|
||||
private doInstantiateEditorControl(descriptor: IEditorDescriptor): BaseEditor {
|
||||
|
||||
// Return early if already instantiated
|
||||
const existingControl = this.controls.filter(control => descriptor.describes(control))[0];
|
||||
if (existingControl) {
|
||||
return existingControl;
|
||||
}
|
||||
|
||||
// Otherwise instantiate new
|
||||
const control = this._register(descriptor.instantiate(this.instantiationService));
|
||||
this.controls.push(control);
|
||||
|
||||
return control;
|
||||
}
|
||||
|
||||
private doSetActiveControl(control: BaseEditor) {
|
||||
this._activeControl = control;
|
||||
|
||||
// Clear out previous active control listeners
|
||||
this.activeControlDisposeables = dispose(this.activeControlDisposeables);
|
||||
|
||||
// Listen to control changes
|
||||
if (control) {
|
||||
this.activeControlDisposeables.push(control.onDidSizeConstraintsChange(e => this._onDidSizeConstraintsChange.fire(e)));
|
||||
this.activeControlDisposeables.push(control.onDidFocus(() => this._onDidFocus.fire()));
|
||||
}
|
||||
|
||||
// Indicate that size constraints could have changed due to new editor
|
||||
this._onDidSizeConstraintsChange.fire();
|
||||
}
|
||||
|
||||
private doSetInput(control: BaseEditor, editor: EditorInput, options: EditorOptions): TPromise<boolean> {
|
||||
|
||||
// If the input did not change, return early and only apply the options
|
||||
// unless the options instruct us to force open it even if it is the same
|
||||
const forceReload = options && options.forceReload;
|
||||
const inputMatches = control.input && control.input.matches(editor);
|
||||
if (inputMatches && !forceReload) {
|
||||
|
||||
// Forward options
|
||||
control.setOptions(options);
|
||||
|
||||
// Still focus as needed
|
||||
const focus = !options || !options.preserveFocus;
|
||||
if (focus) {
|
||||
control.focus();
|
||||
}
|
||||
|
||||
return TPromise.as(false);
|
||||
}
|
||||
|
||||
// Show progress while setting input after a certain timeout. If the workbench is opening
|
||||
// be more relaxed about progress showing by increasing the delay a little bit to reduce flicker.
|
||||
const operation = this.editorOperation.start(this.partService.isCreated() ? 800 : 3200);
|
||||
|
||||
// Call into editor control
|
||||
const editorWillChange = !inputMatches;
|
||||
return toWinJsPromise(control.setInput(editor, options, operation.token)).then(() => {
|
||||
|
||||
// Focus (unless prevented or another operation is running)
|
||||
if (operation.isCurrent()) {
|
||||
const focus = !options || !options.preserveFocus;
|
||||
if (focus) {
|
||||
control.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// Operation done
|
||||
operation.stop();
|
||||
|
||||
return editorWillChange;
|
||||
}, e => {
|
||||
|
||||
// Operation done
|
||||
operation.stop();
|
||||
|
||||
return TPromise.wrapError(e);
|
||||
});
|
||||
}
|
||||
|
||||
private doHideActiveEditorControl(): void {
|
||||
if (!this._activeControl) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop any running operation
|
||||
this.editorOperation.stop();
|
||||
|
||||
// Remove control from parent and hide
|
||||
const controlInstanceContainer = this._activeControl.getContainer();
|
||||
this.parent.removeChild(controlInstanceContainer);
|
||||
hide(controlInstanceContainer);
|
||||
|
||||
// Indicate to editor control
|
||||
this._activeControl.clearInput();
|
||||
this._activeControl.setVisible(false, this.groupView);
|
||||
|
||||
// Clear active control
|
||||
this.doSetActiveControl(null);
|
||||
}
|
||||
|
||||
closeEditor(editor: EditorInput): void {
|
||||
if (this._activeControl && editor.matches(this._activeControl.input)) {
|
||||
this.doHideActiveEditorControl();
|
||||
}
|
||||
}
|
||||
|
||||
layout(dimension: Dimension): void {
|
||||
this.dimension = dimension;
|
||||
|
||||
if (this._activeControl && this.dimension) {
|
||||
this._activeControl.layout(this.dimension);
|
||||
}
|
||||
}
|
||||
|
||||
shutdown(): void {
|
||||
|
||||
// Forward to all editor controls
|
||||
this.controls.forEach(editor => editor.shutdown());
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.activeControlDisposeables = dispose(this.activeControlDisposeables);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
534
src/vs/workbench/browser/parts/editor/editorDropTarget.ts
Normal file
534
src/vs/workbench/browser/parts/editor/editorDropTarget.ts
Normal file
@@ -0,0 +1,534 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/editordroptarget';
|
||||
import { LocalSelectionTransfer, DraggedEditorIdentifier, ResourcesDropHandler, DraggedEditorGroupIdentifier, DragAndDropObserver } from 'vs/workbench/browser/dnd';
|
||||
import { addDisposableListener, EventType, EventHelper, isAncestor, toggleClass, addClass, removeClass } from 'vs/base/browser/dom';
|
||||
import { IEditorGroupsAccessor, EDITOR_TITLE_HEIGHT, IEditorGroupView, getActiveTextEditorOptions } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { EDITOR_DRAG_AND_DROP_BACKGROUND, Themable } from 'vs/workbench/common/theme';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IEditorIdentifier, EditorInput, EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { GroupDirection, MergeGroupMode } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
interface IDropOperation {
|
||||
splitDirection?: GroupDirection;
|
||||
}
|
||||
|
||||
class DropOverlay extends Themable {
|
||||
|
||||
private static OVERLAY_ID = 'monaco-workbench-editor-drop-overlay';
|
||||
|
||||
private container: HTMLElement;
|
||||
private overlay: HTMLElement;
|
||||
|
||||
private currentDropOperation: IDropOperation;
|
||||
private _disposed: boolean;
|
||||
|
||||
private readonly editorTransfer = LocalSelectionTransfer.getInstance<DraggedEditorIdentifier>();
|
||||
private readonly groupTransfer = LocalSelectionTransfer.getInstance<DraggedEditorGroupIdentifier>();
|
||||
|
||||
constructor(
|
||||
private accessor: IEditorGroupsAccessor,
|
||||
private groupView: IEditorGroupView,
|
||||
themeService: IThemeService,
|
||||
private instantiationService: IInstantiationService
|
||||
) {
|
||||
super(themeService);
|
||||
|
||||
this.create();
|
||||
}
|
||||
|
||||
get disposed(): boolean {
|
||||
return this._disposed;
|
||||
}
|
||||
|
||||
private create(): void {
|
||||
const overlayOffsetHeight = this.getOverlayOffsetHeight();
|
||||
|
||||
// Container
|
||||
this.container = document.createElement('div');
|
||||
this.container.id = DropOverlay.OVERLAY_ID;
|
||||
this.container.style.top = `${overlayOffsetHeight}px`;
|
||||
|
||||
// Parent
|
||||
this.groupView.element.appendChild(this.container);
|
||||
addClass(this.groupView.element, 'dragged-over');
|
||||
this._register(toDisposable(() => {
|
||||
this.groupView.element.removeChild(this.container);
|
||||
removeClass(this.groupView.element, 'dragged-over');
|
||||
}));
|
||||
|
||||
// Overlay
|
||||
this.overlay = document.createElement('div');
|
||||
addClass(this.overlay, 'editor-group-overlay-indicator');
|
||||
this.container.appendChild(this.overlay);
|
||||
|
||||
// Overlay Event Handling
|
||||
this.registerListeners();
|
||||
|
||||
// Styles
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
|
||||
// Overlay drop background
|
||||
this.overlay.style.backgroundColor = this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND);
|
||||
|
||||
// Overlay contrast border (if any)
|
||||
const activeContrastBorderColor = this.getColor(activeContrastBorder);
|
||||
this.overlay.style.outlineColor = activeContrastBorderColor;
|
||||
this.overlay.style.outlineOffset = activeContrastBorderColor ? '-2px' : null;
|
||||
this.overlay.style.outlineStyle = activeContrastBorderColor ? 'dashed' : null;
|
||||
this.overlay.style.outlineWidth = activeContrastBorderColor ? '2px' : null;
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(new DragAndDropObserver(this.container, {
|
||||
onDragEnter: e => void 0,
|
||||
onDragOver: e => {
|
||||
const isDraggingGroup = this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype);
|
||||
const isDraggingEditor = this.editorTransfer.hasData(DraggedEditorIdentifier.prototype);
|
||||
|
||||
// Update the dropEffect to "copy" if there is no local data to be dragged because
|
||||
// in that case we can only copy the data into and not move it from its source
|
||||
if (!isDraggingEditor && !isDraggingGroup) {
|
||||
e.dataTransfer.dropEffect = 'copy';
|
||||
}
|
||||
|
||||
// Find out if operation is valid
|
||||
const isCopy = isDraggingGroup ? this.isCopyOperation(e) : isDraggingEditor ? this.isCopyOperation(e, this.editorTransfer.getData(DraggedEditorIdentifier.prototype)[0].identifier) : true;
|
||||
if (!isCopy) {
|
||||
const sourceGroupView = this.findSourceGroupView();
|
||||
if (sourceGroupView === this.groupView) {
|
||||
if (isDraggingGroup || (isDraggingEditor && sourceGroupView.count < 2)) {
|
||||
this.hideOverlay();
|
||||
return; // do not allow to drop group/editor on itself if this results in an empty group
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Position overlay
|
||||
this.positionOverlay(e.offsetX, e.offsetY, isDraggingGroup);
|
||||
},
|
||||
|
||||
onDragLeave: e => this.dispose(),
|
||||
onDragEnd: e => this.dispose(),
|
||||
|
||||
onDrop: e => {
|
||||
EventHelper.stop(e, true);
|
||||
|
||||
// Dispose overlay
|
||||
this.dispose();
|
||||
|
||||
// Handle drop if we have a valid operation
|
||||
if (this.currentDropOperation) {
|
||||
this.handleDrop(e, this.currentDropOperation.splitDirection);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(addDisposableListener(this.container, EventType.MOUSE_OVER, () => {
|
||||
// Under some circumstances we have seen reports where the drop overlay is not being
|
||||
// cleaned up and as such the editor area remains under the overlay so that you cannot
|
||||
// type into the editor anymore. This seems related to using VMs and DND via host and
|
||||
// guest OS, though some users also saw it without VMs.
|
||||
// To protect against this issue we always destroy the overlay as soon as we detect a
|
||||
// mouse event over it. The delay is used to guarantee we are not interfering with the
|
||||
// actual DROP event that can also trigger a mouse over event.
|
||||
setTimeout(() => {
|
||||
this.dispose();
|
||||
}, 300);
|
||||
}));
|
||||
}
|
||||
|
||||
private findSourceGroupView(): IEditorGroupView {
|
||||
|
||||
// Check for group transfer
|
||||
if (this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype)) {
|
||||
return this.accessor.getGroup(this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype)[0].identifier);
|
||||
}
|
||||
|
||||
// Check for editor transfer
|
||||
else if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) {
|
||||
return this.accessor.getGroup(this.editorTransfer.getData(DraggedEditorIdentifier.prototype)[0].identifier.groupId);
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
|
||||
private handleDrop(event: DragEvent, splitDirection?: GroupDirection): void {
|
||||
|
||||
// Determine target group
|
||||
const ensureTargetGroup = () => {
|
||||
let targetGroup: IEditorGroupView;
|
||||
if (typeof splitDirection === 'number') {
|
||||
targetGroup = this.accessor.addGroup(this.groupView, splitDirection);
|
||||
} else {
|
||||
targetGroup = this.groupView;
|
||||
}
|
||||
|
||||
return targetGroup;
|
||||
};
|
||||
|
||||
// Check for group transfer
|
||||
if (this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype)) {
|
||||
const draggedEditorGroup = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype)[0].identifier;
|
||||
|
||||
// Return if the drop is a no-op
|
||||
const sourceGroup = this.accessor.getGroup(draggedEditorGroup);
|
||||
if (typeof splitDirection !== 'number' && sourceGroup === this.groupView) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Split to new group
|
||||
let targetGroup: IEditorGroupView;
|
||||
if (typeof splitDirection === 'number') {
|
||||
if (this.isCopyOperation(event)) {
|
||||
targetGroup = this.accessor.copyGroup(sourceGroup, this.groupView, splitDirection);
|
||||
} else {
|
||||
targetGroup = this.accessor.moveGroup(sourceGroup, this.groupView, splitDirection);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge into existing group
|
||||
else {
|
||||
if (this.isCopyOperation(event)) {
|
||||
targetGroup = this.accessor.mergeGroup(sourceGroup, this.groupView, { mode: MergeGroupMode.COPY_EDITORS });
|
||||
} else {
|
||||
targetGroup = this.accessor.mergeGroup(sourceGroup, this.groupView);
|
||||
}
|
||||
}
|
||||
|
||||
this.accessor.activateGroup(targetGroup);
|
||||
this.groupTransfer.clearData(DraggedEditorGroupIdentifier.prototype);
|
||||
}
|
||||
|
||||
// Check for editor transfer
|
||||
else if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) {
|
||||
const draggedEditor = this.editorTransfer.getData(DraggedEditorIdentifier.prototype)[0].identifier;
|
||||
const targetGroup = ensureTargetGroup();
|
||||
|
||||
// Return if the drop is a no-op
|
||||
const sourceGroup = this.accessor.getGroup(draggedEditor.groupId);
|
||||
if (sourceGroup === targetGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Open in target group
|
||||
const options = getActiveTextEditorOptions(sourceGroup, draggedEditor.editor, EditorOptions.create({ pinned: true }));
|
||||
targetGroup.openEditor(draggedEditor.editor, options);
|
||||
|
||||
// Ensure target has focus
|
||||
targetGroup.focus();
|
||||
|
||||
// Close in source group unless we copy
|
||||
const copyEditor = this.isCopyOperation(event, draggedEditor);
|
||||
if (!copyEditor) {
|
||||
sourceGroup.closeEditor(draggedEditor.editor);
|
||||
}
|
||||
|
||||
this.editorTransfer.clearData(DraggedEditorIdentifier.prototype);
|
||||
}
|
||||
|
||||
// Check for URI transfer
|
||||
else {
|
||||
const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: true /* open workspace instead of file if dropped */ });
|
||||
dropHandler.handleDrop(event, () => ensureTargetGroup(), targetGroup => targetGroup.focus());
|
||||
}
|
||||
}
|
||||
|
||||
private isCopyOperation(e: DragEvent, draggedEditor?: IEditorIdentifier): boolean {
|
||||
if (draggedEditor && !(draggedEditor.editor as EditorInput).supportsSplitEditor()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (e.ctrlKey && !isMacintosh) || (e.altKey && isMacintosh);
|
||||
}
|
||||
|
||||
private positionOverlay(mousePosX: number, mousePosY: number, isDraggingGroup: boolean): void {
|
||||
const preferSplitVertically = this.accessor.partOptions.openSideBySideDirection === 'right';
|
||||
|
||||
const editorControlWidth = this.groupView.element.clientWidth;
|
||||
const editorControlHeight = this.groupView.element.clientHeight - this.getOverlayOffsetHeight();
|
||||
|
||||
let edgeWidthThresholdFactor: number;
|
||||
if (isDraggingGroup) {
|
||||
edgeWidthThresholdFactor = preferSplitVertically ? 0.3 : 0.1; // give larger threshold when dragging group depending on preferred split direction
|
||||
} else {
|
||||
edgeWidthThresholdFactor = 0.1; // 10% threshold to split if dragging editors
|
||||
}
|
||||
|
||||
let edgeHeightThresholdFactor: number;
|
||||
if (isDraggingGroup) {
|
||||
edgeHeightThresholdFactor = preferSplitVertically ? 0.1 : 0.3; // give larger threshold when dragging group depending on preferred split direction
|
||||
} else {
|
||||
edgeHeightThresholdFactor = 0.1; // 10% threshold to split if dragging editors
|
||||
}
|
||||
|
||||
const edgeWidthThreshold = editorControlWidth * edgeWidthThresholdFactor;
|
||||
const edgeHeightThreshold = editorControlHeight * edgeHeightThresholdFactor;
|
||||
|
||||
const splitWidthThreshold = editorControlWidth / 3; // offer to split left/right at 33%
|
||||
const splitHeightThreshold = editorControlHeight / 3; // offer to split up/down at 33%
|
||||
|
||||
// Enable to debug the drop threshold square
|
||||
// let child = this.overlay.children.item(0) as HTMLElement || this.overlay.appendChild(document.createElement('div'));
|
||||
// child.style.backgroundColor = 'red';
|
||||
// child.style.position = 'absolute';
|
||||
// child.style.width = (groupViewWidth - (2 * edgeWidthThreshold)) + 'px';
|
||||
// child.style.height = (groupViewHeight - (2 * edgeHeightThreshold)) + 'px';
|
||||
// child.style.left = edgeWidthThreshold + 'px';
|
||||
// child.style.top = edgeHeightThreshold + 'px';
|
||||
|
||||
// No split if mouse is above certain threshold in the center of the view
|
||||
let splitDirection: GroupDirection;
|
||||
if (
|
||||
mousePosX > edgeWidthThreshold && mousePosX < editorControlWidth - edgeWidthThreshold &&
|
||||
mousePosY > edgeHeightThreshold && mousePosY < editorControlHeight - edgeHeightThreshold
|
||||
) {
|
||||
splitDirection = void 0;
|
||||
}
|
||||
|
||||
// Offer to split otherwise
|
||||
else {
|
||||
|
||||
// User prefers to split vertically: offer a larger hitzone
|
||||
// for this direction like so:
|
||||
// ----------------------------------------------
|
||||
// | | SPLIT UP | |
|
||||
// | SPLIT |-----------------------| SPLIT |
|
||||
// | | MERGE | |
|
||||
// | LEFT |-----------------------| RIGHT |
|
||||
// | | SPLIT DOWN | |
|
||||
// ----------------------------------------------
|
||||
if (preferSplitVertically) {
|
||||
if (mousePosX < splitWidthThreshold) {
|
||||
splitDirection = GroupDirection.LEFT;
|
||||
} else if (mousePosX > splitWidthThreshold * 2) {
|
||||
splitDirection = GroupDirection.RIGHT;
|
||||
} else if (mousePosY < editorControlHeight / 2) {
|
||||
splitDirection = GroupDirection.UP;
|
||||
} else {
|
||||
splitDirection = GroupDirection.DOWN;
|
||||
}
|
||||
}
|
||||
|
||||
// User prefers to split horizontally: offer a larger hitzone
|
||||
// for this direction like so:
|
||||
// ----------------------------------------------
|
||||
// | SPLIT UP |
|
||||
// |--------------------------------------------|
|
||||
// | SPLIT LEFT | MERGE | SPLIT RIGHT |
|
||||
// |--------------------------------------------|
|
||||
// | SPLIT DOWN |
|
||||
// ----------------------------------------------
|
||||
else {
|
||||
if (mousePosY < splitHeightThreshold) {
|
||||
splitDirection = GroupDirection.UP;
|
||||
} else if (mousePosY > splitHeightThreshold * 2) {
|
||||
splitDirection = GroupDirection.DOWN;
|
||||
} else if (mousePosX < editorControlWidth / 2) {
|
||||
splitDirection = GroupDirection.LEFT;
|
||||
} else {
|
||||
splitDirection = GroupDirection.RIGHT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw overlay based on split direction
|
||||
switch (splitDirection) {
|
||||
case GroupDirection.UP:
|
||||
this.doPositionOverlay({ top: '0', left: '0', width: '100%', height: '50%' });
|
||||
break;
|
||||
case GroupDirection.DOWN:
|
||||
this.doPositionOverlay({ top: '50%', left: '0', width: '100%', height: '50%' });
|
||||
break;
|
||||
case GroupDirection.LEFT:
|
||||
this.doPositionOverlay({ top: '0', left: '0', width: '50%', height: '100%' });
|
||||
break;
|
||||
case GroupDirection.RIGHT:
|
||||
this.doPositionOverlay({ top: '0', left: '50%', width: '50%', height: '100%' });
|
||||
break;
|
||||
default:
|
||||
this.doPositionOverlay({ top: '0', left: '0', width: '100%', height: '100%' });
|
||||
}
|
||||
|
||||
// Make sure the overlay is visible now
|
||||
this.overlay.style.opacity = '1';
|
||||
|
||||
// Enable transition after a timeout to prevent initial animation
|
||||
setTimeout(() => addClass(this.overlay, 'overlay-move-transition'), 0);
|
||||
|
||||
// Remember as current split direction
|
||||
this.currentDropOperation = { splitDirection };
|
||||
}
|
||||
|
||||
private doPositionOverlay(options: { top: string, left: string, width: string, height: string }): void {
|
||||
|
||||
// Container
|
||||
const offsetHeight = this.getOverlayOffsetHeight();
|
||||
if (offsetHeight) {
|
||||
this.container.style.height = `calc(100% - ${offsetHeight}px)`;
|
||||
} else {
|
||||
this.container.style.height = '100%';
|
||||
}
|
||||
|
||||
// Overlay
|
||||
this.overlay.style.top = options.top;
|
||||
this.overlay.style.left = options.left;
|
||||
this.overlay.style.width = options.width;
|
||||
this.overlay.style.height = options.height;
|
||||
}
|
||||
|
||||
private getOverlayOffsetHeight(): number {
|
||||
if (!this.groupView.isEmpty() && this.accessor.partOptions.showTabs) {
|
||||
return EDITOR_TITLE_HEIGHT; // show overlay below title if group shows tabs
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private hideOverlay(): void {
|
||||
|
||||
// Reset overlay
|
||||
this.doPositionOverlay({ top: '0', left: '0', width: '100%', height: '100%' });
|
||||
this.overlay.style.opacity = '0';
|
||||
removeClass(this.overlay, 'overlay-move-transition');
|
||||
|
||||
// Reset current operation
|
||||
this.currentDropOperation = void 0;
|
||||
}
|
||||
|
||||
contains(element: HTMLElement): boolean {
|
||||
return element === this.container || element === this.overlay;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this._disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
export class EditorDropTarget extends Themable {
|
||||
|
||||
private _overlay: DropOverlay;
|
||||
|
||||
private counter = 0;
|
||||
|
||||
private readonly editorTransfer = LocalSelectionTransfer.getInstance<DraggedEditorIdentifier>();
|
||||
private readonly groupTransfer = LocalSelectionTransfer.getInstance<DraggedEditorGroupIdentifier>();
|
||||
|
||||
constructor(
|
||||
private accessor: IEditorGroupsAccessor,
|
||||
private container: HTMLElement,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService
|
||||
) {
|
||||
super(themeService);
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private get overlay(): DropOverlay {
|
||||
if (this._overlay && !this._overlay.disposed) {
|
||||
return this._overlay;
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(addDisposableListener(this.container, EventType.DRAG_ENTER, e => this.onDragEnter(e)));
|
||||
this._register(addDisposableListener(this.container, EventType.DRAG_LEAVE, () => this.onDragLeave()));
|
||||
[this.container, window].forEach(node => this._register(addDisposableListener(node as HTMLElement, EventType.DRAG_END, () => this.onDragEnd())));
|
||||
}
|
||||
|
||||
private onDragEnter(event: DragEvent): void {
|
||||
this.counter++;
|
||||
|
||||
// Validate transfer
|
||||
if (
|
||||
!this.editorTransfer.hasData(DraggedEditorIdentifier.prototype) &&
|
||||
!this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype) &&
|
||||
!event.dataTransfer.types.length // see https://github.com/Microsoft/vscode/issues/25789
|
||||
) {
|
||||
event.dataTransfer.dropEffect = 'none';
|
||||
return; // unsupported transfer
|
||||
}
|
||||
|
||||
// Signal DND start
|
||||
this.updateContainer(true);
|
||||
|
||||
const target = event.target as HTMLElement;
|
||||
if (target) {
|
||||
|
||||
// Somehow we managed to move the mouse quickly out of the current overlay, so destroy it
|
||||
if (this.overlay && !this.overlay.contains(target)) {
|
||||
this.disposeOverlay();
|
||||
}
|
||||
|
||||
// Create overlay over target
|
||||
if (!this.overlay) {
|
||||
const targetGroupView = this.findTargetGroupView(target);
|
||||
if (targetGroupView) {
|
||||
this._overlay = new DropOverlay(this.accessor, targetGroupView, this.themeService, this.instantiationService);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onDragLeave(): void {
|
||||
this.counter--;
|
||||
|
||||
if (this.counter === 0) {
|
||||
this.updateContainer(false);
|
||||
}
|
||||
}
|
||||
|
||||
private onDragEnd(): void {
|
||||
this.counter = 0;
|
||||
|
||||
this.updateContainer(false);
|
||||
this.disposeOverlay();
|
||||
}
|
||||
|
||||
private findTargetGroupView(child: HTMLElement): IEditorGroupView {
|
||||
const groups = this.accessor.groups;
|
||||
for (let i = 0; i < groups.length; i++) {
|
||||
const groupView = groups[i];
|
||||
|
||||
if (isAncestor(child, groupView.element)) {
|
||||
return groupView;
|
||||
}
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
|
||||
private updateContainer(isDraggedOver: boolean): void {
|
||||
toggleClass(this.container, 'dragged-over', isDraggedOver);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this.disposeOverlay();
|
||||
}
|
||||
|
||||
private disposeOverlay(): void {
|
||||
if (this.overlay) {
|
||||
this.overlay.dispose();
|
||||
this._overlay = void 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
1451
src/vs/workbench/browser/parts/editor/editorGroupView.ts
Normal file
1451
src/vs/workbench/browser/parts/editor/editorGroupView.ts
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user