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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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>;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -8,7 +8,6 @@ import 'vs/css!./media/editorpicker';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as nls from 'vs/nls';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { IAutoFocus, Mode, IEntryRunContext, IQuickNavigateConfiguration, IModel } from 'vs/base/parts/quickopen/common/quickOpen';
|
||||
import { QuickOpenModel, QuickOpenEntry, QuickOpenEntryGroup, QuickOpenItemAccessor } from 'vs/base/parts/quickopen/browser/quickOpenModel';
|
||||
@@ -16,61 +15,55 @@ import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { getIconClasses } from 'vs/workbench/browser/labels';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { QuickOpenHandler } from 'vs/workbench/browser/quickopen';
|
||||
import { Position } from 'vs/platform/editor/common/editor';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorGroupsService, IEditorGroup, EditorsOrder, GroupsOrder } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { EditorInput, toResource, IEditorGroup, IEditorStacksModel } from 'vs/workbench/common/editor';
|
||||
import { EditorInput, toResource } from 'vs/workbench/common/editor';
|
||||
import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer';
|
||||
|
||||
export class EditorPickerEntry extends QuickOpenEntryGroup {
|
||||
private stacks: IEditorStacksModel;
|
||||
|
||||
constructor(
|
||||
private editor: EditorInput,
|
||||
private _group: IEditorGroup,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IModeService private modeService: IModeService,
|
||||
@IModelService private modelService: IModelService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService
|
||||
@IModelService private modelService: IModelService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.stacks = editorGroupService.getStacksModel();
|
||||
}
|
||||
|
||||
public getLabelOptions(): IIconLabelValueOptions {
|
||||
getLabelOptions(): IIconLabelValueOptions {
|
||||
return {
|
||||
extraClasses: getIconClasses(this.modelService, this.modeService, this.getResource()),
|
||||
italic: this._group.isPreview(this.editor)
|
||||
italic: !this._group.isPinned(this.editor)
|
||||
};
|
||||
}
|
||||
|
||||
public getLabel(): string {
|
||||
getLabel(): string {
|
||||
return this.editor.getName();
|
||||
}
|
||||
|
||||
public getIcon(): string {
|
||||
getIcon(): string {
|
||||
return this.editor.isDirty() ? 'dirty' : '';
|
||||
}
|
||||
|
||||
public get group(): IEditorGroup {
|
||||
get group(): IEditorGroup {
|
||||
return this._group;
|
||||
}
|
||||
|
||||
public getResource(): URI {
|
||||
getResource(): URI {
|
||||
return toResource(this.editor, { supportSideBySide: true });
|
||||
}
|
||||
|
||||
public getAriaLabel(): string {
|
||||
getAriaLabel(): string {
|
||||
return nls.localize('entryAriaLabel', "{0}, editor group picker", this.getLabel());
|
||||
}
|
||||
|
||||
public getDescription(): string {
|
||||
getDescription(): string {
|
||||
return this.editor.getDescription();
|
||||
}
|
||||
|
||||
public run(mode: Mode, context: IEntryRunContext): boolean {
|
||||
run(mode: Mode, context: IEntryRunContext): boolean {
|
||||
if (mode === Mode.OPEN) {
|
||||
return this.runOpen(context);
|
||||
}
|
||||
@@ -79,7 +72,7 @@ export class EditorPickerEntry extends QuickOpenEntryGroup {
|
||||
}
|
||||
|
||||
private runOpen(context: IEntryRunContext): boolean {
|
||||
this.editorService.openEditor(this.editor, null, this.stacks.positionOfGroup(this.group)).done(null, errors.onUnexpectedError);
|
||||
this._group.openEditor(this.editor);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -90,15 +83,15 @@ export abstract class BaseEditorPicker extends QuickOpenHandler {
|
||||
|
||||
constructor(
|
||||
@IInstantiationService protected instantiationService: IInstantiationService,
|
||||
@IWorkbenchEditorService protected editorService: IWorkbenchEditorService,
|
||||
@IEditorGroupService protected editorGroupService: IEditorGroupService
|
||||
@IEditorService protected editorService: IEditorService,
|
||||
@IEditorGroupsService protected editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.scorerCache = Object.create(null);
|
||||
}
|
||||
|
||||
public getResults(searchValue: string): TPromise<QuickOpenModel> {
|
||||
getResults(searchValue: string): TPromise<QuickOpenModel> {
|
||||
const editorEntries = this.getEditorEntries();
|
||||
if (!editorEntries.length) {
|
||||
return TPromise.as(null);
|
||||
@@ -123,11 +116,11 @@ export abstract class BaseEditorPicker extends QuickOpenHandler {
|
||||
});
|
||||
|
||||
// Sorting
|
||||
const stacks = this.editorGroupService.getStacksModel();
|
||||
if (query.value) {
|
||||
const groups = this.editorGroupService.getGroups(GroupsOrder.CREATION_TIME);
|
||||
entries.sort((e1, e2) => {
|
||||
if (e1.group !== e2.group) {
|
||||
return stacks.positionOfGroup(e1.group) - stacks.positionOfGroup(e2.group);
|
||||
return groups.indexOf(e1.group) - groups.indexOf(e2.group); // older groups first
|
||||
}
|
||||
|
||||
return compareItemsByScore(e1, e2, query, true, QuickOpenItemAccessor, this.scorerCache);
|
||||
@@ -135,11 +128,11 @@ export abstract class BaseEditorPicker extends QuickOpenHandler {
|
||||
}
|
||||
|
||||
// Grouping (for more than one group)
|
||||
if (stacks.groups.length > 1) {
|
||||
if (this.editorGroupService.count > 1) {
|
||||
let lastGroup: IEditorGroup;
|
||||
entries.forEach(e => {
|
||||
if (!lastGroup || lastGroup !== e.group) {
|
||||
e.setGroupLabel(nls.localize('groupLabel', "Group: {0}", e.group.label));
|
||||
e.setGroupLabel(e.group.label);
|
||||
e.setShowBorder(!!lastGroup);
|
||||
lastGroup = e.group;
|
||||
}
|
||||
@@ -149,28 +142,26 @@ export abstract class BaseEditorPicker extends QuickOpenHandler {
|
||||
return TPromise.as(new QuickOpenModel(entries));
|
||||
}
|
||||
|
||||
public onClose(canceled: boolean): void {
|
||||
onClose(canceled: boolean): void {
|
||||
this.scorerCache = Object.create(null);
|
||||
}
|
||||
|
||||
protected abstract getEditorEntries(): EditorPickerEntry[];
|
||||
}
|
||||
|
||||
export abstract class EditorGroupPicker extends BaseEditorPicker {
|
||||
export class ActiveEditorGroupPicker extends BaseEditorPicker {
|
||||
|
||||
static readonly ID = 'workbench.picker.activeEditors';
|
||||
|
||||
protected getEditorEntries(): EditorPickerEntry[] {
|
||||
const stacks = this.editorGroupService.getStacksModel();
|
||||
const group = stacks.groupAt(this.getPosition());
|
||||
if (!group) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return group.getEditors(true).map((editor, index) => this.instantiationService.createInstance(EditorPickerEntry, editor, group));
|
||||
return this.group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).map((editor, index) => this.instantiationService.createInstance(EditorPickerEntry, editor, this.group));
|
||||
}
|
||||
|
||||
protected abstract getPosition(): Position;
|
||||
private get group(): IEditorGroup {
|
||||
return this.editorGroupService.activeGroup;
|
||||
}
|
||||
|
||||
public getEmptyLabel(searchString: string): string {
|
||||
getEmptyLabel(searchString: string): string {
|
||||
if (searchString) {
|
||||
return nls.localize('noResultsFoundInGroup', "No matching opened editor found in group");
|
||||
}
|
||||
@@ -178,76 +169,45 @@ export abstract class EditorGroupPicker extends BaseEditorPicker {
|
||||
return nls.localize('noOpenedEditors', "List of opened editors is currently empty in group");
|
||||
}
|
||||
|
||||
public getAutoFocus(searchValue: string, context: { model: IModel<QuickOpenEntry>, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus {
|
||||
getAutoFocus(searchValue: string, context: { model: IModel<QuickOpenEntry>, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus {
|
||||
if (searchValue || !context.quickNavigateConfiguration) {
|
||||
return {
|
||||
autoFocusFirstEntry: true
|
||||
};
|
||||
}
|
||||
|
||||
const stacks = this.editorGroupService.getStacksModel();
|
||||
const group = stacks.groupAt(this.getPosition());
|
||||
if (!group) {
|
||||
return super.getAutoFocus(searchValue, context);
|
||||
}
|
||||
|
||||
const isShiftNavigate = (context.quickNavigateConfiguration && context.quickNavigateConfiguration.keybindings.some(k => {
|
||||
const [firstPart, chordPart] = k.getParts();
|
||||
if (chordPart) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return firstPart.shiftKey;
|
||||
}));
|
||||
|
||||
if (isShiftNavigate) {
|
||||
return {
|
||||
autoFocusLastEntry: true
|
||||
};
|
||||
}
|
||||
|
||||
const editors = this.group.count;
|
||||
return {
|
||||
autoFocusFirstEntry: group.count === 1,
|
||||
autoFocusSecondEntry: group.count > 1
|
||||
autoFocusFirstEntry: editors === 1,
|
||||
autoFocusSecondEntry: editors > 1
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class GroupOnePicker extends EditorGroupPicker {
|
||||
|
||||
public static readonly ID = 'workbench.picker.editors.one';
|
||||
|
||||
protected getPosition(): Position {
|
||||
return Position.ONE;
|
||||
}
|
||||
}
|
||||
|
||||
export class GroupTwoPicker extends EditorGroupPicker {
|
||||
|
||||
public static readonly ID = 'workbench.picker.editors.two';
|
||||
|
||||
protected getPosition(): Position {
|
||||
return Position.TWO;
|
||||
}
|
||||
}
|
||||
|
||||
export class GroupThreePicker extends EditorGroupPicker {
|
||||
|
||||
public static readonly ID = 'workbench.picker.editors.three';
|
||||
|
||||
protected getPosition(): Position {
|
||||
return Position.THREE;
|
||||
}
|
||||
}
|
||||
|
||||
export class AllEditorsPicker extends BaseEditorPicker {
|
||||
|
||||
public static readonly ID = 'workbench.picker.editors';
|
||||
static readonly ID = 'workbench.picker.editors';
|
||||
|
||||
protected getEditorEntries(): EditorPickerEntry[] {
|
||||
const entries: EditorPickerEntry[] = [];
|
||||
|
||||
const stacks = this.editorGroupService.getStacksModel();
|
||||
stacks.groups.forEach((group, position) => {
|
||||
group.getEditors().forEach((editor, index) => {
|
||||
this.editorGroupService.getGroups(GroupsOrder.CREATION_TIME).forEach(group => {
|
||||
group.editors.forEach(editor => {
|
||||
entries.push(this.instantiationService.createInstance(EditorPickerEntry, editor, group));
|
||||
});
|
||||
});
|
||||
@@ -255,7 +215,7 @@ export class AllEditorsPicker extends BaseEditorPicker {
|
||||
return entries;
|
||||
}
|
||||
|
||||
public getEmptyLabel(searchString: string): string {
|
||||
getEmptyLabel(searchString: string): string {
|
||||
if (searchString) {
|
||||
return nls.localize('noResultsFound', "No matching opened editor found");
|
||||
}
|
||||
@@ -263,7 +223,7 @@ export class AllEditorsPicker extends BaseEditorPicker {
|
||||
return nls.localize('noOpenedEditorsAllGroups', "List of opened editors is currently empty");
|
||||
}
|
||||
|
||||
public getAutoFocus(searchValue: string, context: { model: IModel<QuickOpenEntry>, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus {
|
||||
getAutoFocus(searchValue: string, context: { model: IModel<QuickOpenEntry>, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus {
|
||||
if (searchValue) {
|
||||
return {
|
||||
autoFocusFirstEntry: true
|
||||
|
||||
@@ -20,7 +20,7 @@ import { language, LANGUAGE_DEFAULT, AccessibilitySupport } from 'vs/base/common
|
||||
import * as browser from 'vs/base/browser/browser';
|
||||
import { IMode } from 'vs/editor/common/modes';
|
||||
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
|
||||
import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput } from 'vs/workbench/common/editor';
|
||||
import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput, IEditor as IBaseEditor, IEditorInput } from 'vs/workbench/common/editor';
|
||||
import { IDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import { IEditorAction } from 'vs/editor/common/editorCommon';
|
||||
@@ -30,21 +30,19 @@ import { TrimTrailingWhitespaceAction } from 'vs/editor/contrib/linesOperations/
|
||||
import { IndentUsingSpaces, IndentUsingTabs, DetectIndentation, IndentationToSpacesAction, IndentationToTabsAction } from 'vs/editor/contrib/indentation/indentation';
|
||||
import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/binaryEditor';
|
||||
import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor';
|
||||
import { IEditor as IBaseEditor, IEditorInput } from 'vs/platform/editor/common/editor';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IQuickOpenService, IPickOpenEntry, IFilePickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import { SUPPORTED_ENCODINGS, IFileService, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { TabFocus } from 'vs/editor/common/config/commonEditorConfig';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { getCodeEditor as getEditorWidget, getCodeOrDiffEditor } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { IConfigurationChangedEvent, IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
@@ -54,7 +52,7 @@ import { attachButtonStyler } from 'vs/platform/theme/common/styler';
|
||||
import { widgetShadow, editorWidgetBackground, foreground, darken, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { deepClone } from 'vs/base/common/objects';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ICodeEditor, isCodeEditor, isDiffEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IAnchor } from 'vs/base/browser/ui/contextview/contextview';
|
||||
@@ -64,11 +62,11 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr
|
||||
class SideBySideEditorEncodingSupport implements IEncodingSupport {
|
||||
constructor(private master: IEncodingSupport, private details: IEncodingSupport) { }
|
||||
|
||||
public getEncoding(): string {
|
||||
getEncoding(): string {
|
||||
return this.master.getEncoding(); // always report from modified (right hand) side
|
||||
}
|
||||
|
||||
public setEncoding(encoding: string, mode: EncodingMode): void {
|
||||
setEncoding(encoding: string, mode: EncodingMode): void {
|
||||
[this.master, this.details].forEach(s => s.setEncoding(encoding, mode));
|
||||
}
|
||||
}
|
||||
@@ -133,7 +131,7 @@ class StateChange {
|
||||
this.metadata = false;
|
||||
}
|
||||
|
||||
public combine(other: StateChange) {
|
||||
combine(other: StateChange) {
|
||||
this.indentation = this.indentation || other.indentation;
|
||||
this.selectionStatus = this.selectionStatus || other.selectionStatus;
|
||||
this.mode = this.mode || other.mode;
|
||||
@@ -158,28 +156,28 @@ interface StateDelta {
|
||||
|
||||
class State {
|
||||
private _selectionStatus: string;
|
||||
public get selectionStatus(): string { return this._selectionStatus; }
|
||||
get selectionStatus(): string { return this._selectionStatus; }
|
||||
|
||||
private _mode: string;
|
||||
public get mode(): string { return this._mode; }
|
||||
get mode(): string { return this._mode; }
|
||||
|
||||
private _encoding: string;
|
||||
public get encoding(): string { return this._encoding; }
|
||||
get encoding(): string { return this._encoding; }
|
||||
|
||||
private _EOL: string;
|
||||
public get EOL(): string { return this._EOL; }
|
||||
get EOL(): string { return this._EOL; }
|
||||
|
||||
private _indentation: string;
|
||||
public get indentation(): string { return this._indentation; }
|
||||
get indentation(): string { return this._indentation; }
|
||||
|
||||
private _tabFocusMode: boolean;
|
||||
public get tabFocusMode(): boolean { return this._tabFocusMode; }
|
||||
get tabFocusMode(): boolean { return this._tabFocusMode; }
|
||||
|
||||
private _screenReaderMode: boolean;
|
||||
public get screenReaderMode(): boolean { return this._screenReaderMode; }
|
||||
get screenReaderMode(): boolean { return this._screenReaderMode; }
|
||||
|
||||
private _metadata: string;
|
||||
public get metadata(): string { return this._metadata; }
|
||||
get metadata(): string { return this._metadata; }
|
||||
|
||||
constructor() {
|
||||
this._selectionStatus = null;
|
||||
@@ -191,7 +189,7 @@ class State {
|
||||
this._metadata = null;
|
||||
}
|
||||
|
||||
public update(update: StateDelta): StateChange {
|
||||
update(update: StateDelta): StateChange {
|
||||
const e = new StateChange();
|
||||
let somethingChanged = false;
|
||||
|
||||
@@ -282,7 +280,6 @@ function hide(el: HTMLElement): void {
|
||||
}
|
||||
|
||||
export class EditorStatus implements IStatusbarItem {
|
||||
|
||||
private state: State;
|
||||
private element: HTMLElement;
|
||||
private tabFocusModeElement: HTMLElement;
|
||||
@@ -300,8 +297,7 @@ export class EditorStatus implements IStatusbarItem {
|
||||
private screenReaderExplanation: ScreenReaderDetectedExplanation;
|
||||
|
||||
constructor(
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IEditorGroupService private editorGroupService: IEditorGroupService,
|
||||
@IEditorService private editorService: IEditorService,
|
||||
@IQuickOpenService private quickOpenService: IQuickOpenService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
|
||||
@@ -314,7 +310,7 @@ export class EditorStatus implements IStatusbarItem {
|
||||
this.state = new State();
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): IDisposable {
|
||||
render(container: HTMLElement): IDisposable {
|
||||
this.element = append(container, $('.editor-statusbar-item'));
|
||||
|
||||
this.tabFocusModeElement = append(this.element, $('a.editor-status-tabfocusmode.status-bar-info'));
|
||||
@@ -370,7 +366,7 @@ export class EditorStatus implements IStatusbarItem {
|
||||
}
|
||||
}
|
||||
},
|
||||
this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged()),
|
||||
this.editorService.onDidActiveEditorChange(() => this.updateStatusBar()),
|
||||
this.untitledEditorService.onDidChangeEncoding(r => this.onResourceEncodingChange(r)),
|
||||
this.textFileService.models.onModelEncodingChanged(e => this.onResourceEncodingChange(e.resource)),
|
||||
TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange()),
|
||||
@@ -545,73 +541,81 @@ export class EditorStatus implements IStatusbarItem {
|
||||
TabFocus.setTabFocusMode(false);
|
||||
}
|
||||
|
||||
private onEditorsChanged(): void {
|
||||
const activeEditor = this.editorService.getActiveEditor();
|
||||
const control = getEditorWidget(activeEditor);
|
||||
private updateStatusBar(): void {
|
||||
const activeControl = this.editorService.activeControl;
|
||||
const activeCodeEditor = activeControl ? getCodeEditor(activeControl.getControl()) : void 0;
|
||||
|
||||
// Update all states
|
||||
this.onScreenReaderModeChange(control);
|
||||
this.onSelectionChange(control);
|
||||
this.onModeChange(control);
|
||||
this.onEOLChange(control);
|
||||
this.onEncodingChange(activeEditor);
|
||||
this.onIndentationChange(control);
|
||||
this.onMetadataChange(activeEditor);
|
||||
this.onScreenReaderModeChange(activeCodeEditor);
|
||||
this.onSelectionChange(activeCodeEditor);
|
||||
this.onModeChange(activeCodeEditor);
|
||||
this.onEOLChange(activeCodeEditor);
|
||||
this.onEncodingChange(activeControl);
|
||||
this.onIndentationChange(activeCodeEditor);
|
||||
this.onMetadataChange(activeControl);
|
||||
|
||||
// Dispose old active editor listeners
|
||||
dispose(this.activeEditorListeners);
|
||||
|
||||
// Attach new listeners to active editor
|
||||
if (control) {
|
||||
if (activeCodeEditor) {
|
||||
|
||||
// Hook Listener for Configuration changes
|
||||
this.activeEditorListeners.push(control.onDidChangeConfiguration((event: IConfigurationChangedEvent) => {
|
||||
this.activeEditorListeners.push(activeCodeEditor.onDidChangeConfiguration((event: IConfigurationChangedEvent) => {
|
||||
if (event.accessibilitySupport) {
|
||||
this.onScreenReaderModeChange(control);
|
||||
this.onScreenReaderModeChange(activeCodeEditor);
|
||||
}
|
||||
}));
|
||||
|
||||
// Hook Listener for Selection changes
|
||||
this.activeEditorListeners.push(control.onDidChangeCursorPosition((event: ICursorPositionChangedEvent) => {
|
||||
this.onSelectionChange(control);
|
||||
this.activeEditorListeners.push(activeCodeEditor.onDidChangeCursorPosition((event: ICursorPositionChangedEvent) => {
|
||||
this.onSelectionChange(activeCodeEditor);
|
||||
}));
|
||||
|
||||
// Hook Listener for mode changes
|
||||
this.activeEditorListeners.push(control.onDidChangeModelLanguage((event: IModelLanguageChangedEvent) => {
|
||||
this.onModeChange(control);
|
||||
this.activeEditorListeners.push(activeCodeEditor.onDidChangeModelLanguage((event: IModelLanguageChangedEvent) => {
|
||||
this.onModeChange(activeCodeEditor);
|
||||
}));
|
||||
|
||||
// Hook Listener for content changes
|
||||
this.activeEditorListeners.push(control.onDidChangeModelContent((e) => {
|
||||
this.onEOLChange(control);
|
||||
this.activeEditorListeners.push(activeCodeEditor.onDidChangeModelContent((e) => {
|
||||
this.onEOLChange(activeCodeEditor);
|
||||
|
||||
let selections = activeCodeEditor.getSelections();
|
||||
for (let i = 0; i < e.changes.length; i++) {
|
||||
if (selections.some(selection => Range.areIntersecting(selection, e.changes[i].range))) {
|
||||
this.onSelectionChange(activeCodeEditor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Hook Listener for content options changes
|
||||
this.activeEditorListeners.push(control.onDidChangeModelOptions((event: IModelOptionsChangedEvent) => {
|
||||
this.onIndentationChange(control);
|
||||
this.activeEditorListeners.push(activeCodeEditor.onDidChangeModelOptions((event: IModelOptionsChangedEvent) => {
|
||||
this.onIndentationChange(activeCodeEditor);
|
||||
}));
|
||||
}
|
||||
|
||||
// Handle binary editors
|
||||
else if (activeEditor instanceof BaseBinaryResourceEditor || activeEditor instanceof BinaryResourceDiffEditor) {
|
||||
else if (activeControl instanceof BaseBinaryResourceEditor || activeControl instanceof BinaryResourceDiffEditor) {
|
||||
const binaryEditors: BaseBinaryResourceEditor[] = [];
|
||||
if (activeEditor instanceof BinaryResourceDiffEditor) {
|
||||
const details = activeEditor.getDetailsEditor();
|
||||
if (activeControl instanceof BinaryResourceDiffEditor) {
|
||||
const details = activeControl.getDetailsEditor();
|
||||
if (details instanceof BaseBinaryResourceEditor) {
|
||||
binaryEditors.push(details);
|
||||
}
|
||||
|
||||
const master = activeEditor.getMasterEditor();
|
||||
const master = activeControl.getMasterEditor();
|
||||
if (master instanceof BaseBinaryResourceEditor) {
|
||||
binaryEditors.push(master);
|
||||
}
|
||||
} else {
|
||||
binaryEditors.push(activeEditor);
|
||||
binaryEditors.push(activeControl);
|
||||
}
|
||||
|
||||
binaryEditors.forEach(editor => {
|
||||
this.activeEditorListeners.push(editor.onMetadataChanged(metadata => {
|
||||
this.onMetadataChange(activeEditor);
|
||||
this.onMetadataChange(activeControl);
|
||||
}));
|
||||
});
|
||||
}
|
||||
@@ -742,7 +746,7 @@ export class EditorStatus implements IStatusbarItem {
|
||||
this.updateState(info);
|
||||
}
|
||||
|
||||
private onEncodingChange(e: IBaseEditor): void {
|
||||
private onEncodingChange(e?: IBaseEditor): void {
|
||||
if (e && !this.isActiveEditor(e)) {
|
||||
return;
|
||||
}
|
||||
@@ -750,7 +754,7 @@ export class EditorStatus implements IStatusbarItem {
|
||||
const info: StateDelta = { encoding: null };
|
||||
|
||||
// We only support text based editors
|
||||
if (getEditorWidget(e)) {
|
||||
if (e && (isCodeEditor(e.getControl()) || isDiffEditor(e.getControl()))) {
|
||||
const encodingSupport: IEncodingSupport = toEditorWithEncodingSupport(e.input);
|
||||
if (encodingSupport) {
|
||||
const rawEncoding = encodingSupport.getEncoding();
|
||||
@@ -767,11 +771,11 @@ export class EditorStatus implements IStatusbarItem {
|
||||
}
|
||||
|
||||
private onResourceEncodingChange(resource: uri): void {
|
||||
const activeEditor = this.editorService.getActiveEditor();
|
||||
if (activeEditor) {
|
||||
const activeResource = toResource(activeEditor.input, { supportSideBySide: true });
|
||||
const activeControl = this.editorService.activeControl;
|
||||
if (activeControl) {
|
||||
const activeResource = toResource(activeControl.input, { supportSideBySide: true });
|
||||
if (activeResource && activeResource.toString() === resource.toString()) {
|
||||
return this.onEncodingChange(<IBaseEditor>activeEditor); // only update if the encoding changed for the active resource
|
||||
return this.onEncodingChange(<IBaseEditor>activeControl); // only update if the encoding changed for the active resource
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -782,10 +786,10 @@ export class EditorStatus implements IStatusbarItem {
|
||||
this.updateState(info);
|
||||
}
|
||||
|
||||
private isActiveEditor(e: IBaseEditor): boolean {
|
||||
const activeEditor = this.editorService.getActiveEditor();
|
||||
private isActiveEditor(control: IBaseEditor): boolean {
|
||||
const activeControl = this.editorService.activeControl;
|
||||
|
||||
return activeEditor && e && activeEditor === e;
|
||||
return activeControl && activeControl === control;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -798,7 +802,7 @@ function isWritableCodeEditor(codeEditor: ICodeEditor): boolean {
|
||||
}
|
||||
|
||||
function isWritableBaseEditor(e: IBaseEditor): boolean {
|
||||
return isWritableCodeEditor(getEditorWidget(e));
|
||||
return e && isWritableCodeEditor(getCodeEditor(e.getControl()));
|
||||
}
|
||||
|
||||
export class ShowLanguageExtensionsAction extends Action {
|
||||
@@ -822,15 +826,15 @@ export class ShowLanguageExtensionsAction extends Action {
|
||||
|
||||
export class ChangeModeAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.editor.changeLanguageMode';
|
||||
public static readonly LABEL = nls.localize('changeMode', "Change Language Mode");
|
||||
static readonly ID = 'workbench.action.editor.changeLanguageMode';
|
||||
static readonly LABEL = nls.localize('changeMode', "Change Language Mode");
|
||||
|
||||
constructor(
|
||||
actionId: string,
|
||||
actionLabel: string,
|
||||
@IModeService private modeService: IModeService,
|
||||
@IModelService private modelService: IModelService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IEditorService private editorService: IEditorService,
|
||||
@IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService,
|
||||
@IQuickOpenService private quickOpenService: IQuickOpenService,
|
||||
@IPreferencesService private preferencesService: IPreferencesService,
|
||||
@@ -840,15 +844,14 @@ export class ChangeModeAction extends Action {
|
||||
super(actionId, actionLabel);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
let activeEditor = this.editorService.getActiveEditor();
|
||||
const editorWidget = getEditorWidget(activeEditor);
|
||||
if (!editorWidget) {
|
||||
run(): TPromise<any> {
|
||||
const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget);
|
||||
if (!activeTextEditorWidget) {
|
||||
return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
}
|
||||
|
||||
const textModel = editorWidget.getModel();
|
||||
const resource = toResource(activeEditor.input, { supportSideBySide: true });
|
||||
const textModel = activeTextEditorWidget.getModel();
|
||||
const resource = toResource(this.editorService.activeEditor, { supportSideBySide: true });
|
||||
|
||||
let hasLanguageSupport = !!resource;
|
||||
if (resource.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(resource)) {
|
||||
@@ -946,17 +949,16 @@ export class ChangeModeAction extends Action {
|
||||
}
|
||||
|
||||
// Change mode for active editor
|
||||
activeEditor = this.editorService.getActiveEditor();
|
||||
const codeOrDiffEditor = getCodeOrDiffEditor(activeEditor);
|
||||
const activeEditor = this.editorService.activeControl;
|
||||
const activeTextEditorWidget = this.editorService.activeTextEditorWidget;
|
||||
const models: ITextModel[] = [];
|
||||
if (codeOrDiffEditor.codeEditor) {
|
||||
const codeEditorModel = codeOrDiffEditor.codeEditor.getModel();
|
||||
if (isCodeEditor(activeTextEditorWidget)) {
|
||||
const codeEditorModel = activeTextEditorWidget.getModel();
|
||||
if (codeEditorModel) {
|
||||
models.push(codeEditorModel);
|
||||
}
|
||||
}
|
||||
if (codeOrDiffEditor.diffEditor) {
|
||||
const diffEditorModel = codeOrDiffEditor.diffEditor.getModel();
|
||||
} else if (isDiffEditor(activeTextEditorWidget)) {
|
||||
const diffEditorModel = activeTextEditorWidget.getModel();
|
||||
if (diffEditorModel) {
|
||||
if (diffEditorModel.original) {
|
||||
models.push(diffEditorModel.original);
|
||||
@@ -1045,42 +1047,42 @@ export interface IChangeEOLEntry extends IPickOpenEntry {
|
||||
|
||||
class ChangeIndentationAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.editor.changeIndentation';
|
||||
public static readonly LABEL = nls.localize('changeIndentation', "Change Indentation");
|
||||
static readonly ID = 'workbench.action.editor.changeIndentation';
|
||||
static readonly LABEL = nls.localize('changeIndentation', "Change Indentation");
|
||||
|
||||
constructor(
|
||||
actionId: string,
|
||||
actionLabel: string,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IEditorService private editorService: IEditorService,
|
||||
@IQuickOpenService private quickOpenService: IQuickOpenService
|
||||
) {
|
||||
super(actionId, actionLabel);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
const activeEditor = this.editorService.getActiveEditor();
|
||||
const control = getEditorWidget(activeEditor);
|
||||
if (!control) {
|
||||
run(): TPromise<any> {
|
||||
const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget);
|
||||
if (!activeTextEditorWidget) {
|
||||
return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
}
|
||||
if (!isWritableCodeEditor(control)) {
|
||||
|
||||
if (!isWritableCodeEditor(activeTextEditorWidget)) {
|
||||
return this.quickOpenService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
|
||||
}
|
||||
|
||||
const picks = [
|
||||
control.getAction(IndentUsingSpaces.ID),
|
||||
control.getAction(IndentUsingTabs.ID),
|
||||
control.getAction(DetectIndentation.ID),
|
||||
control.getAction(IndentationToSpacesAction.ID),
|
||||
control.getAction(IndentationToTabsAction.ID),
|
||||
control.getAction(TrimTrailingWhitespaceAction.ID)
|
||||
activeTextEditorWidget.getAction(IndentUsingSpaces.ID),
|
||||
activeTextEditorWidget.getAction(IndentUsingTabs.ID),
|
||||
activeTextEditorWidget.getAction(DetectIndentation.ID),
|
||||
activeTextEditorWidget.getAction(IndentationToSpacesAction.ID),
|
||||
activeTextEditorWidget.getAction(IndentationToTabsAction.ID),
|
||||
activeTextEditorWidget.getAction(TrimTrailingWhitespaceAction.ID)
|
||||
].map((a: IEditorAction) => {
|
||||
return {
|
||||
id: a.id,
|
||||
label: a.label,
|
||||
detail: (language === LANGUAGE_DEFAULT) ? null : a.alias,
|
||||
run: () => {
|
||||
control.focus();
|
||||
activeTextEditorWidget.focus();
|
||||
a.run();
|
||||
}
|
||||
};
|
||||
@@ -1095,30 +1097,29 @@ class ChangeIndentationAction extends Action {
|
||||
|
||||
export class ChangeEOLAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.editor.changeEOL';
|
||||
public static readonly LABEL = nls.localize('changeEndOfLine', "Change End of Line Sequence");
|
||||
static readonly ID = 'workbench.action.editor.changeEOL';
|
||||
static readonly LABEL = nls.localize('changeEndOfLine', "Change End of Line Sequence");
|
||||
|
||||
constructor(
|
||||
actionId: string,
|
||||
actionLabel: string,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IEditorService private editorService: IEditorService,
|
||||
@IQuickOpenService private quickOpenService: IQuickOpenService
|
||||
) {
|
||||
super(actionId, actionLabel);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
let activeEditor = this.editorService.getActiveEditor();
|
||||
const editorWidget = getEditorWidget(activeEditor);
|
||||
if (!editorWidget) {
|
||||
run(): TPromise<any> {
|
||||
const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget);
|
||||
if (!activeTextEditorWidget) {
|
||||
return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
}
|
||||
|
||||
if (!isWritableCodeEditor(editorWidget)) {
|
||||
if (!isWritableCodeEditor(activeTextEditorWidget)) {
|
||||
return this.quickOpenService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
|
||||
}
|
||||
|
||||
const textModel = editorWidget.getModel();
|
||||
const textModel = activeTextEditorWidget.getModel();
|
||||
|
||||
const EOLOptions: IChangeEOLEntry[] = [
|
||||
{ label: nlsEOLLF, eol: EndOfLineSequence.LF },
|
||||
@@ -1129,11 +1130,10 @@ export class ChangeEOLAction extends Action {
|
||||
|
||||
return this.quickOpenService.pick(EOLOptions, { placeHolder: nls.localize('pickEndOfLine', "Select End of Line Sequence"), autoFocus: { autoFocusIndex: selectedIndex } }).then(eol => {
|
||||
if (eol) {
|
||||
activeEditor = this.editorService.getActiveEditor();
|
||||
const editorWidget = getEditorWidget(activeEditor);
|
||||
if (editorWidget && isWritableCodeEditor(editorWidget)) {
|
||||
const textModel = editorWidget.getModel();
|
||||
textModel.setEOL(eol.eol);
|
||||
const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorWidget);
|
||||
if (activeCodeEditor && isWritableCodeEditor(activeCodeEditor)) {
|
||||
const textModel = activeCodeEditor.getModel();
|
||||
textModel.pushEOL(eol.eol);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1142,13 +1142,13 @@ export class ChangeEOLAction extends Action {
|
||||
|
||||
export class ChangeEncodingAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.editor.changeEncoding';
|
||||
public static readonly LABEL = nls.localize('changeEncoding', "Change File Encoding");
|
||||
static readonly ID = 'workbench.action.editor.changeEncoding';
|
||||
static readonly LABEL = nls.localize('changeEncoding', "Change File Encoding");
|
||||
|
||||
constructor(
|
||||
actionId: string,
|
||||
actionLabel: string,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IEditorService private editorService: IEditorService,
|
||||
@IQuickOpenService private quickOpenService: IQuickOpenService,
|
||||
@ITextResourceConfigurationService private textResourceConfigurationService: ITextResourceConfigurationService,
|
||||
@IFileService private fileService: IFileService
|
||||
@@ -1156,13 +1156,13 @@ export class ChangeEncodingAction extends Action {
|
||||
super(actionId, actionLabel);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
let activeEditor = this.editorService.getActiveEditor();
|
||||
if (!getEditorWidget(activeEditor) || !activeEditor.input) {
|
||||
run(): TPromise<any> {
|
||||
if (!getCodeEditor(this.editorService.activeTextEditorWidget)) {
|
||||
return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
}
|
||||
|
||||
let encodingSupport: IEncodingSupport = toEditorWithEncodingSupport(activeEditor.input);
|
||||
let activeControl = this.editorService.activeControl;
|
||||
let encodingSupport: IEncodingSupport = toEditorWithEncodingSupport(activeControl.input);
|
||||
if (!encodingSupport) {
|
||||
return this.quickOpenService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]);
|
||||
}
|
||||
@@ -1181,7 +1181,7 @@ export class ChangeEncodingAction extends Action {
|
||||
|
||||
if (encodingSupport instanceof UntitledEditorInput) {
|
||||
pickActionPromise = TPromise.as(saveWithEncodingPick);
|
||||
} else if (!isWritableBaseEditor(activeEditor)) {
|
||||
} else if (!isWritableBaseEditor(activeControl)) {
|
||||
pickActionPromise = TPromise.as(reopenWithEncodingPick);
|
||||
} else {
|
||||
pickActionPromise = this.quickOpenService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true });
|
||||
@@ -1192,7 +1192,7 @@ export class ChangeEncodingAction extends Action {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
const resource = toResource(activeEditor.input, { supportSideBySide: true });
|
||||
const resource = toResource(activeControl.input, { supportSideBySide: true });
|
||||
|
||||
return TPromise.timeout(50 /* quick open is sensitive to being opened so soon after another */)
|
||||
.then(() => {
|
||||
@@ -1249,8 +1249,8 @@ export class ChangeEncodingAction extends Action {
|
||||
autoFocus: { autoFocusIndex: typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : void 0 }
|
||||
}).then(encoding => {
|
||||
if (encoding) {
|
||||
activeEditor = this.editorService.getActiveEditor();
|
||||
encodingSupport = toEditorWithEncodingSupport(activeEditor.input);
|
||||
activeControl = this.editorService.activeControl;
|
||||
encodingSupport = toEditorWithEncodingSupport(activeControl.input);
|
||||
if (encodingSupport && encodingSupport.getEncoding() !== encoding.id) {
|
||||
encodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding
|
||||
}
|
||||
@@ -1274,7 +1274,7 @@ class ScreenReaderDetectedExplanation extends Themable {
|
||||
super(themeService);
|
||||
}
|
||||
|
||||
public get visible(): boolean {
|
||||
get visible(): boolean {
|
||||
return this._visible;
|
||||
}
|
||||
|
||||
@@ -1294,7 +1294,7 @@ class ScreenReaderDetectedExplanation extends Themable {
|
||||
}
|
||||
}
|
||||
|
||||
public show(anchorElement: HTMLElement): void {
|
||||
show(anchorElement: HTMLElement): void {
|
||||
this._visible = true;
|
||||
|
||||
this.contextViewService.showContextView({
|
||||
@@ -1318,7 +1318,7 @@ class ScreenReaderDetectedExplanation extends Themable {
|
||||
});
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
hide(): void {
|
||||
this.contextViewService.hideContextView();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench>.part.editor>.content .editor-group-container .breadcrumbs-control.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-workbench>.part.editor>.content .editor-group-container:not(.active) .breadcrumbs-control {
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
.monaco-workbench>.part.editor>.content .editor-group-container .breadcrumbs-control .monaco-breadcrumb-item.selected .monaco-icon-label,
|
||||
.monaco-workbench>.part.editor>.content .editor-group-container .breadcrumbs-control .monaco-breadcrumb-item.focused .monaco-icon-label {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.monaco-workbench>.part.editor>.content .editor-group-container .breadcrumbs-control .monaco-breadcrumb-item.selected .hint-more,
|
||||
.monaco-workbench>.part.editor>.content .editor-group-container .breadcrumbs-control .monaco-breadcrumb-item.focused .hint-more {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
/* todo@joh move somewhere else */
|
||||
|
||||
.monaco-workbench .monaco-breadcrumbs-picker .highlighting-tree {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.monaco-workbench .monaco-breadcrumbs-picker .highlighting-tree>.input {
|
||||
padding: 5px 9px;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.monaco-workbench .monaco-breadcrumbs-picker .highlighting-tree>.tree {
|
||||
height: calc(100% - 36px);
|
||||
}
|
||||
|
||||
.monaco-workbench .monaco-breadcrumbs-picker .highlighting-tree.inactive>.input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-workbench .monaco-breadcrumbs-picker .highlighting-tree.inactive>.tree {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench .monaco-breadcrumbs-picker .highlighting-tree .monaco-highlighted-label .highlight{
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.784 8L13 11.217 11.215 13 8.001 9.786 4.785 13 3 11.216l3.214-3.215L3 4.785 4.784 3 8 6.216 11.216 3 13 4.785 9.784 8.001z" fill="#424242"/></svg>
|
||||
|
After Width: | Height: | Size: 253 B |
@@ -0,0 +1 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.784 8L13 11.217 11.215 13 8.001 9.786 4.785 13 3 11.216l3.214-3.215L3 4.785 4.784 3 8 6.216 11.216 3 13 4.785 9.784 8.001z" fill="#C5C5C5"/></svg>
|
||||
|
After Width: | Height: | Size: 253 B |
@@ -0,0 +1 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.784 8L13 11.217 11.215 13 8.001 9.786 4.785 13 3 11.216l3.214-3.215L3 4.785 4.784 3 8 6.216 11.216 3 13 4.785 9.784 8.001z" fill="#C5C5C5"/></svg>
|
||||
|
After Width: | Height: | Size: 253 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#424242" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.784 8L13 11.217 11.215 13 8.001 9.786 4.785 13 3 11.216l3.214-3.215L3 4.785 4.784 3 8 6.216 11.216 3 13 4.785 9.784 8.001z" fill="#424242"/></svg>
|
||||
|
Before Width: | Height: | Size: 307 B After Width: | Height: | Size: 253 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><circle fill="#424242" cx="8" cy="8" r="4"/></svg>
|
||||
|
After Width: | Height: | Size: 167 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><circle fill="#C5C5C5" cx="8" cy="8" r="4"/></svg>
|
||||
|
After Width: | Height: | Size: 167 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#e8e8e8" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.428 8L12 10.573 10.572 12 8 9.428 5.428 12 4 10.573 6.572 8 4 5.428 5.427 4 8 6.572 10.573 4 12 5.428 9.428 8z" fill="#E8E8E8"/></svg>
|
||||
|
Before Width: | Height: | Size: 307 B After Width: | Height: | Size: 241 B |
|
Before Width: | Height: | Size: 307 B After Width: | Height: | Size: 307 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#424242" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>
|
||||
|
After Width: | Height: | Size: 307 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#424242" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.428 8L12 10.573 10.572 12 8 9.428 5.428 12 4 10.573 6.572 8 4 5.428 5.427 4 8 6.572 10.573 4 12 5.428 9.428 8z" fill="#424242"/></svg>
|
||||
|
Before Width: | Height: | Size: 307 B After Width: | Height: | Size: 241 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><path fill="#1E1E1E" d="M16 12h-2v2h-2v2H0V5h2V3h2V1h12v11z"/><g fill="#C5C5C5"><path d="M3 5h9v8h1V4H3zM5 2v1h9v8h1V2zM1 6v9h10V6H1zm8 7H7.5L6 11.5 4.5 13H3l2.3-2.3L3 8.5h1.5L6 10l1.5-1.5H9l-2.3 2.3L9 13z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 335 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><path fill="#F6F6F6" d="M16 12h-2v2h-2v2H0V5h2V3h2V1h12v11z"/><g fill="#424242"><path d="M3 5h9v8h1V4H3zM5 2v1h9v8h1V2zM1 6v9h10V6H1zm8 7H7.5L6 11.5 4.5 13H3l2.3-2.3L3 8.5h1.5L6 10l1.5-1.5H9l-2.3 2.3L9 13z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 335 B |
@@ -1,92 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench > .editor > .content.dragging > .monaco-sash {
|
||||
display: none; /* hide sashes while dragging editors around */
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor .one-editor-silo.centered .editor-container {
|
||||
border-style: none solid;
|
||||
border-width: 0px 1px;
|
||||
}
|
||||
|
||||
#monaco-workbench-editor-move-overlay,
|
||||
#monaco-workbench-editor-drop-overlay {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
#monaco-workbench-editor-drop-overlay {
|
||||
opacity: 0; /* initially not visible until moving around */
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content > .one-editor-silo {
|
||||
position: absolute;
|
||||
box-sizing: border-box; /* use border box to be able to draw a border as separator between editors */
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content > .one-editor-silo.editor-one {
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-three {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-three {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content > .one-editor-silo.dragging {
|
||||
z-index: 70;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.dragging {
|
||||
border-left: 1px solid;
|
||||
border-right: 1px solid;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.dragging {
|
||||
border-top: 1px solid;
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-two,
|
||||
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-three {
|
||||
border-left: 1px solid;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-two,
|
||||
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-three {
|
||||
border-top: 1px solid;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.draggedunder {
|
||||
transition: left 200ms ease-out;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.vertical-layout > .editor-three.draggedunder {
|
||||
transition-property: right;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.draggedunder {
|
||||
transition: top 200ms ease-out;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.horizontal-layout > .editor-three.draggedunder {
|
||||
transition-property: bottom;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content > .one-editor-silo > .container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content > .one-editor-silo > .container > .editor-container {
|
||||
height: calc(100% - 35px); /* Editor is below editor title */
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
#monaco-workbench-editor-drop-overlay {
|
||||
position: absolute;
|
||||
z-index: 10000;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#monaco-workbench-editor-drop-overlay > .editor-group-overlay-indicator {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
pointer-events: none; /* very important to not take events away from the parent */
|
||||
|
||||
opacity: 0; /* hidden initially */
|
||||
transition: opacity 150ms ease-out;
|
||||
}
|
||||
|
||||
#monaco-workbench-editor-drop-overlay > .editor-group-overlay-indicator.overlay-move-transition {
|
||||
transition: top 70ms ease-out, left 70ms ease-out, width 70ms ease-out, height 70ms ease-out, opacity 150ms ease-out;
|
||||
}
|
||||
120
src/vs/workbench/browser/parts/editor/media/editorgroupview.css
Normal file
@@ -0,0 +1,120 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* Container */
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container.empty {
|
||||
opacity: 0.5; /* dimmed to indicate inactive state */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container.empty.active,
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container.empty.dragged-over {
|
||||
opacity: 1; /* indicate active/dragged-over group through undimmed state */
|
||||
}
|
||||
|
||||
/* Letterpress */
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .editor-group-letterpress {
|
||||
display: none; /* only visible when empty */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container.empty > .editor-group-letterpress {
|
||||
display: block;
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
height: calc(100% - 70px); /* centered below toolbar */
|
||||
max-width: 260px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
background-size: 70% 70%;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content.empty .editor-group-container.empty > .editor-group-letterpress {
|
||||
background-size: 100% 100%; /* larger for empty editor part */
|
||||
height: 100%; /* no toolbar in this case */
|
||||
}
|
||||
|
||||
/* Title */
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.tabs {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.title-border-bottom::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 5;
|
||||
pointer-events: none;
|
||||
background-color: var(--title-border-bottom-color);
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container.empty > .title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Toolbar */
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .editor-group-container-toolbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content:not(.empty) .editor-group-container.empty > .editor-group-container-toolbar {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .editor-group-container-toolbar .action-label {
|
||||
display: block;
|
||||
height: 35px;
|
||||
line-height: 35px;
|
||||
min-width: 28px;
|
||||
background-size: 16px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench > .part.editor > .content .editor-group-container > .editor-group-container-toolbar .close-editor-group {
|
||||
background-image: url('close-big.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .part.editor > .content .editor-group-container > .editor-group-container-toolbar .close-editor-group,
|
||||
.hc-black .monaco-workbench > .part.editor > .content .editor-group-container > .editor-group-container-toolbar .close-editor-group {
|
||||
background-image: url('close-big-inverse.svg');
|
||||
}
|
||||
|
||||
/* Editor */
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .editor-container {
|
||||
height: calc(100% - 35px); /* below title control */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container.empty > .editor-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .editor-container > .editor-instance {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .grid-view-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/** Letter press styling for empty editor */
|
||||
.monaco-workbench > .part.editor.empty {
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
background-size: 260px 260px;
|
||||
}
|
||||
@@ -70,10 +70,10 @@
|
||||
}
|
||||
|
||||
.monaco-shell.vs .screen-reader-detected-explanation .cancel {
|
||||
background: url('close-big.svg') center center no-repeat;
|
||||
background: url('close-statusview.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.monaco-shell.vs-dark .screen-reader-detected-explanation .cancel,
|
||||
.monaco-shell.hc-black .screen-reader-detected-explanation .cancel {
|
||||
background: url('close-big-dark.svg') center center no-repeat;
|
||||
background: url('close-statusview-inverse.svg') center center no-repeat;
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* Title Label */
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label {
|
||||
line-height: 35px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
position: relative;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .monaco-icon-label::before {
|
||||
height: 35px; /* tweak the icon size of the editor labels when icons are enabled */
|
||||
}
|
||||
|
||||
/* Title Actions */
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-actions {
|
||||
display: flex;
|
||||
flex: initial;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .title-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action {
|
||||
background: url('close-dirty.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action,
|
||||
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action {
|
||||
background: url('close-dirty-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action:hover {
|
||||
background: url('close.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action:hover,
|
||||
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action:hover {
|
||||
background: url('close-inverse.svg') center center no-repeat;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title > .label-container {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
/* Title Label */
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .title-label {
|
||||
line-height: 35px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
position: relative;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .no-tabs.title-label {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .monaco-icon-label::before {
|
||||
height: 35px; /* tweak the icon size of the editor labels when icons are enabled */
|
||||
}
|
||||
|
||||
/* Breadcrumbs */
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control {
|
||||
flex: 1 50%;
|
||||
overflow: hidden;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control.preview .monaco-breadcrumb-item {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item::before {
|
||||
content: '/';
|
||||
opacity: 1;
|
||||
height: inherit;
|
||||
width: inherit;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
/* {{SQL CARBON EDIT}} */
|
||||
.monaco-workbench.windows > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item::before {
|
||||
content: '/';
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item.root_folder::before,
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item.root_folder + .monaco-breadcrumb-item::before,
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control.relative-path .monaco-breadcrumb-item:nth-child(2)::before {
|
||||
/* workspace folder, item following workspace folder, or relative path -> hide first seperator */
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item.root_folder::after {
|
||||
/* use dot separator for workspace folder */
|
||||
content: '•';
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child {
|
||||
padding-right: 4px; /* does not have trailing separator*/
|
||||
}
|
||||
|
||||
/* Title Actions */
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .title-actions {
|
||||
display: flex;
|
||||
flex: initial;
|
||||
opacity: 0.5;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container.active > .title .title-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 552 B After Width: | Height: | Size: 552 B |
|
Before Width: | Height: | Size: 552 B After Width: | Height: | Size: 552 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2b282e}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 15H0V1h16v14z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M1 2v12h14V2H1zm13 11H2v-3h12v3zm0-5H2V5h12v3z" id="iconBg"/><g id="iconFg" style="display: none;"><path class="icon-vs-fg" d="M14 8H2V5h12v3zm0 2H2v3h12v-3z"/></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>SplitScreenVertical_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M16,1V15H0V1Z" style="display: none;"/></g><g id="iconBg"><path class="icon-vs-bg" d="M1,2V14H15V2ZM7,13H2V5H7Zm7,0H9V5h5Z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 578 B After Width: | Height: | Size: 519 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 15H0V1h16v14z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M1 2v12h14V2H1zm13 11H2v-3h12v3zm0-5H2V5h12v3z" id="iconBg"/><g id="iconFg" style="display: none;"><path class="icon-vs-fg" d="M14 8H2V5h12v3zm0 2H2v3h12v-3z"/></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>SplitScreenVertical_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M16,1V15H0V1Z" style="display: none;"/></g><g id="iconBg"><path class="icon-vs-bg" d="M1,2V14H15V2ZM7,13H2V5H7Zm7,0H9V5h5Z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 578 B After Width: | Height: | Size: 519 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 -1 16 16" enable-background="new 0 -1 16 16"><path fill="#C5C5C5" d="M1 1v12h14v-12h-14zm1 3h4.999v8h-4.999v-8zm12 8h-5.001v-8h5.001v8z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>SplitScreenHorizontal_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M16,1V15H0V1Z" style="display: none;"/></g><g id="iconBg"><path class="icon-vs-bg" d="M1,2V14H15V2ZM14,13H2V10H14Zm0-5H2V5H14Z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 218 B After Width: | Height: | Size: 525 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 -1 16 16" enable-background="new 0 -1 16 16"><path fill="#656565" d="M1 1v12h14v-12h-14zm1 3h4.999v8h-4.999v-8zm12 8h-5.001v-8h5.001v8z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>SplitScreenHorizontal_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M16,1V15H0V1Z" style="display: none;"/></g><g id="iconBg"><path class="icon-vs-bg" d="M1,2V14H15V2ZM14,13H2V10H14Zm0-5H2V5H14Z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 218 B After Width: | Height: | Size: 525 B |
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#C5C5C5;}
|
||||
</style>
|
||||
<g id="outline">
|
||||
</g>
|
||||
<g id="icon_x5F_bg">
|
||||
<path class="st0" d="M7.6,5H3c0,0-1,0-1,1c0,0.8,0,5.4,0,8c0,1,1,1,1,1s1.5,0,3,0s3,0,3,0s1,0,1-1c0-2.6,0-6.9,0-6.9L7.6,5z M9,14
|
||||
H3V6h4v2h2V14z"/>
|
||||
<path class="st0" d="M9.6,3H5c0,0-1,0-1,1h5v0.9L10.2,6H11v7c1,0,1-1,1-1V5.1L9.6,3z"/>
|
||||
<path class="st0" d="M11.6,1H7c0,0-1,0-1,1h5v0.9L12.2,4H13v7c1,0,1-1,1-1V3.1L11.6,1z"/>
|
||||
</g>
|
||||
<g id="color_x5F_action">
|
||||
</g>
|
||||
<g id="icon_x5F_fg">
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 822 B |
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#656565;}
|
||||
</style>
|
||||
<g id="outline">
|
||||
</g>
|
||||
<g id="icon_x5F_bg">
|
||||
<path class="st0" d="M7.6,5H3c0,0-1,0-1,1c0,0.8,0,5.4,0,8c0,1,1,1,1,1s1.5,0,3,0s3,0,3,0s1,0,1-1c0-2.6,0-6.9,0-6.9L7.6,5z M9,14
|
||||
H3V6h4v2h2V14z"/>
|
||||
<path class="st0" d="M9.6,3H5c0,0-1,0-1,1h5v0.9L10.2,6H11v7c1,0,1-1,1-1V5.1L9.6,3z"/>
|
||||
<path class="st0" d="M11.6,1H7c0,0-1,0-1,1h5v0.9L12.2,4H13v7c1,0,1-1,1-1V3.1L11.6,1z"/>
|
||||
</g>
|
||||
<g id="color_x5F_action">
|
||||
</g>
|
||||
<g id="icon_x5F_fg">
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 822 B |
@@ -1,228 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* Title Container */
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.empty {
|
||||
background: inherit !important; /* prevents some ugly flickering when opening first tab */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.tabs > .monaco-scrollable-element {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.tabs > .monaco-scrollable-element .scrollbar {
|
||||
z-index: 3; /* on top of tabs */
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Tabs Container */
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container {
|
||||
display: flex;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container.scroll {
|
||||
overflow: scroll !important;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Tab */
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab {
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
height: 35px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid transparent;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink.has-icon-theme.close-button-right,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink.has-icon-theme.close-button-off {
|
||||
padding-left: 5px; /* reduce padding when we show icons and are in shrinking mode and tab close button is not left */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-fit {
|
||||
width: 120px;
|
||||
min-width: fit-content;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink {
|
||||
min-width: 60px;
|
||||
flex-basis: 0; /* all tabs are even */
|
||||
flex-grow: 1; /* all tabs grow even */
|
||||
max-width: fit-content;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink.close-button-left::after,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink.close-button-off::after {
|
||||
content: "";
|
||||
display: flex;
|
||||
flex: 0;
|
||||
width: 5px; /* Reserve space to hide tab fade when close button is left or off (fixes https://github.com/Microsoft/vscode/issues/45728) */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink.close-button-left {
|
||||
min-width: 80px; /* make more room for close button when it shows to the left */
|
||||
padding-right: 5px; /* we need less room when sizing is shrink */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dragged {
|
||||
will-change: transform; /* forces tab to be drawn on a separate layer (fixes https://github.com/Microsoft/vscode/issues/18733) */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dragged-over div {
|
||||
pointer-events: none; /* prevents cursor flickering (fixes https://github.com/Microsoft/vscode/issues/38753) */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-left {
|
||||
flex-direction: row-reverse;
|
||||
padding-left: 0;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
/* Tab Label */
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink .tab-label {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink > .tab-label::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
width: 5px;
|
||||
opacity: 1;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label > .monaco-icon-label-description-container {
|
||||
overflow: visible; /* fixes https://github.com/Microsoft/vscode/issues/20182 */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink > .monaco-icon-label > .monaco-icon-label-description-container {
|
||||
text-overflow: clip;
|
||||
}
|
||||
|
||||
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink > .monaco-icon-label > .monaco-icon-label-description-container {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .monaco-icon-label::before {
|
||||
height: 16px; /* tweak the icon size of the editor labels when icons are enabled */
|
||||
}
|
||||
|
||||
/* Tab Close */
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab > .tab-close {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
width: 28px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-right.sizing-shrink > .tab-close {
|
||||
flex: 0;
|
||||
overflow: hidden; /* let the close button be pushed out of view when sizing is set to shrink to make more room... */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty.close-button-right.sizing-shrink > .tab-close,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-right.sizing-shrink:hover > .tab-close {
|
||||
overflow: visible; /* ...but still show the close button on hover and when dirty */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-off > .tab-close {
|
||||
display: none; /* hide the close action bar when we are configured to hide it */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab.active > .tab-close .action-label, /* always show it for active tab */
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab > .tab-close .action-label:focus, /* always show it on focus */
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab:hover > .tab-close .action-label, /* always show it on hover */
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab.active:hover > .tab-close .action-label, /* always show it on hover */
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab.dirty > .tab-close .action-label { /* always show it for dirty tabs */
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active > .tab-close .action-label, /* show dimmed for inactive group */
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active:hover > .tab-close .action-label, /* show dimmed for inactive group */
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty > .tab-close .action-label, /* show dimmed for inactive group */
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab:hover > .tab-close .action-label { /* show dimmed for inactive group */
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab > .tab-close .action-label {
|
||||
opacity: 0;
|
||||
display: block;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-size: 16px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action {
|
||||
background: url('close-dirty.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action,
|
||||
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action {
|
||||
background: url('close-dirty-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action:hover {
|
||||
background: url('close.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action:hover,
|
||||
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action:hover {
|
||||
background: url('close-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
/* No Tab Close Button */
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-off {
|
||||
padding-right: 10px; /* give a little bit more room if close button is off */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink.close-button-off {
|
||||
padding-right: 5px; /* we need less room when sizing is shrink */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-off.dirty {
|
||||
background-repeat: no-repeat;
|
||||
background-position-y: center;
|
||||
background-position-x: calc(100% - 6px); /* to the right of the tab label */
|
||||
padding-right: 28px; /* make room for dirty indication when we are running without close button */
|
||||
}
|
||||
|
||||
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-off.dirty {
|
||||
background-image: url('close-dirty.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-off.dirty,
|
||||
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.close-button-off.dirty {
|
||||
background-image: url('close-dirty-inverse.svg');
|
||||
}
|
||||
|
||||
/* Editor Actions */
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .editor-actions {
|
||||
cursor: default;
|
||||
flex: initial;
|
||||
padding-left: 4px;
|
||||
}
|
||||
281
src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css
Normal file
@@ -0,0 +1,281 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* Title Container */
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.tabs > .monaco-scrollable-element {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.tabs > .monaco-scrollable-element .scrollbar {
|
||||
z-index: 3; /* on top of tabs */
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Tabs Container */
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container {
|
||||
display: flex;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container.scroll {
|
||||
overflow: scroll !important;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Tab */
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab {
|
||||
position: relative;
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
height: 35px;
|
||||
box-sizing: border-box;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon-theme.close-button-right,
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.has-icon-theme.close-button-off {
|
||||
padding-left: 5px; /* reduce padding when we show icons and are in shrinking mode and tab close button is not left */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit {
|
||||
width: 120px;
|
||||
min-width: fit-content;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink {
|
||||
min-width: 60px;
|
||||
flex-basis: 0; /* all tabs are even */
|
||||
flex-grow: 1; /* all tabs grow even */
|
||||
max-width: fit-content;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-button-left::after,
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-button-off::after {
|
||||
content: '';
|
||||
display: flex;
|
||||
flex: 0;
|
||||
width: 5px; /* Reserve space to hide tab fade when close button is left or off (fixes https://github.com/Microsoft/vscode/issues/45728) */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-button-left {
|
||||
min-width: 80px; /* make more room for close button when it shows to the left */
|
||||
padding-right: 5px; /* we need less room when sizing is shrink */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dragged {
|
||||
will-change: transform; /* forces tab to be drawn on a separate layer (fixes https://github.com/Microsoft/vscode/issues/18733) */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dragged-over div {
|
||||
pointer-events: none; /* prevents cursor flickering (fixes https://github.com/Microsoft/vscode/issues/38753) */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-left {
|
||||
flex-direction: row-reverse;
|
||||
padding-left: 0;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
/* Tab border top/bottom */
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-border-top-container,
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-border-bottom-container {
|
||||
display: none; /* hidden by default until a color is provided (see below) */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top > .tab-border-top-container {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 6; /* over possible title border */
|
||||
pointer-events: none;
|
||||
background-color: var(--tab-border-top-color);
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 6; /* over possible title border */
|
||||
pointer-events: none;
|
||||
background-color: var(--tab-border-bottom-color);
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
/* Tab Label */
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab .tab-label {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink .tab-label {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink > .tab-label::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
width: 5px;
|
||||
opacity: 1;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label,
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label > .monaco-icon-label-description-container {
|
||||
overflow: visible; /* fixes https://github.com/Microsoft/vscode/issues/20182 */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink > .monaco-icon-label > .monaco-icon-label-description-container {
|
||||
text-overflow: clip;
|
||||
}
|
||||
|
||||
.hc-black .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink > .monaco-icon-label > .monaco-icon-label-description-container {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab .monaco-icon-label::before {
|
||||
height: 16px; /* tweak the icon size of the editor labels when icons are enabled */
|
||||
}
|
||||
|
||||
/* Tab Close */
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-close {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
width: 28px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-right.sizing-shrink > .tab-close {
|
||||
flex: 0;
|
||||
overflow: hidden; /* let the close button be pushed out of view when sizing is set to shrink to make more room... */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty.close-button-right.sizing-shrink > .tab-close,
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-right.sizing-shrink:hover > .tab-close {
|
||||
overflow: visible; /* ...but still show the close button on hover and when dirty */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off > .tab-close {
|
||||
display: none; /* hide the close action bar when we are configured to hide it */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active > .tab-close .action-label, /* always show it for active tab */
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab > .tab-close .action-label:focus, /* always show it on focus */
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab:hover > .tab-close .action-label, /* always show it on hover */
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.active:hover > .tab-close .action-label, /* always show it on hover */
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.dirty > .tab-close .action-label { /* always show it for dirty tabs */
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active > .tab-close .action-label, /* show dimmed for inactive group */
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active:hover > .tab-close .action-label, /* show dimmed for inactive group */
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-close .action-label, /* show dimmed for inactive group */
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab:hover > .tab-close .action-label { /* show dimmed for inactive group */
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab > .tab-close .action-label {
|
||||
opacity: 0;
|
||||
display: block;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
background-size: 16px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty .close-editor-action {
|
||||
background: url('close-dirty.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty .close-editor-action,
|
||||
.hc-black .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty .close-editor-action {
|
||||
background: url('close-dirty-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty .close-editor-action:hover {
|
||||
background: url('close.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty .close-editor-action:hover,
|
||||
.hc-black .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty .close-editor-action:hover {
|
||||
background: url('close-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
/* No Tab Close Button */
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off {
|
||||
padding-right: 10px; /* give a little bit more room if close button is off */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink.close-button-off {
|
||||
padding-right: 5px; /* we need less room when sizing is shrink */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty {
|
||||
background-repeat: no-repeat;
|
||||
background-position-y: center;
|
||||
background-position-x: calc(100% - 6px); /* to the right of the tab label */
|
||||
padding-right: 28px; /* make room for dirty indication when we are running without close button */
|
||||
}
|
||||
|
||||
.vs .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty {
|
||||
background-image: url('close-dirty.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty,
|
||||
.hc-black .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty {
|
||||
background-image: url('close-dirty-inverse.svg');
|
||||
}
|
||||
|
||||
/* Editor Actions */
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .editor-actions {
|
||||
cursor: default;
|
||||
flex: initial;
|
||||
padding-left: 4px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
/* Breadcrumbs */
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control {
|
||||
flex: 1 100%;
|
||||
height: 25px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-icon-label::before {
|
||||
height: 18px; /* tweak the icon size of the editor labels when icons are enabled */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item {
|
||||
max-width: 260px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child {
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
/* .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:not(:last-child):not(:hover):not(.focused):not(.file) {
|
||||
min-width: 33px;
|
||||
} */
|
||||
|
||||
@@ -23,12 +23,12 @@
|
||||
|
||||
.vs .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace {
|
||||
opacity: 1;
|
||||
background: url('Paragraph_16x_nohalo.svg') center center no-repeat;
|
||||
background: url('paragraph.svg') center center no-repeat;
|
||||
}
|
||||
.vs-dark .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace,
|
||||
.hc-black .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace {
|
||||
opacity: 1;
|
||||
background: url('Paragraph_16x_nohalo_inversep.svg') center center no-repeat;
|
||||
background: url('paragraph-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace.is-checked {
|
||||
|
||||
@@ -5,36 +5,31 @@
|
||||
|
||||
/* Editor Label */
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label {
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .title-label,
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab .tab-label {
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo.centered > .container > .title .title-label {
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label a,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label a {
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .title-label a,
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab .tab-label a {
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .monaco-icon-label::before,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .monaco-icon-label::before,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label a,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label a,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label span,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label span {
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .monaco-icon-label::before,
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab .monaco-icon-label::before,
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .title-label a,
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab .tab-label a,
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .title-label h2,
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab .tab-label span {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Title Actions */
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-actions .action-label,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .editor-actions .action-label {
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .title-actions .action-label,
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .editor-actions .action-label {
|
||||
display: block;
|
||||
height: 35px;
|
||||
line-height: 35px;
|
||||
@@ -44,69 +39,38 @@
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-actions .action-label,
|
||||
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .editor-actions .action-label {
|
||||
.hc-black .monaco-workbench > .part.editor > .content .editor-group-container > .title .title-actions .action-label,
|
||||
.hc-black .monaco-workbench > .part.editor > .content .editor-group-container > .title .editor-actions .action-label {
|
||||
line-height: initial;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .editor-actions .action-label .label,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-actions .action-label .label {
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .editor-actions .action-label .label,
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .title-actions .action-label .label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Drag Cursor */
|
||||
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo > .container > .title,
|
||||
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo > .container > .title.tabs .scrollbar .slider,
|
||||
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo > .container > .title .monaco-icon-label::before,
|
||||
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo > .container > .title .title-label a,
|
||||
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo > .container > .title .title-label span {
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title {
|
||||
cursor: -webkit-grab;
|
||||
}
|
||||
|
||||
#monaco-workbench-editor-move-overlay,
|
||||
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo.drag,
|
||||
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo.drag > .container > .title,
|
||||
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo.drag > .container > .title.tabs .scrollbar .slider,
|
||||
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo.drag > .container > .title .monaco-icon-label::before,
|
||||
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo.drag > .container > .title .title-label a,
|
||||
.monaco-workbench > .part.editor > .content.multiple-groups > .one-editor-silo.drag > .container > .title .title-label span {
|
||||
cursor: -webkit-grabbing;
|
||||
}
|
||||
|
||||
/* Actions */
|
||||
|
||||
.monaco-workbench .close-editor-action {
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .close-editor-action {
|
||||
background: url('close.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .close-editor-action,
|
||||
.hc-black .monaco-workbench .close-editor-action {
|
||||
.vs-dark .monaco-workbench > .part.editor > .content .editor-group-container > .title .close-editor-action,
|
||||
.hc-black .monaco-workbench > .part.editor > .content .editor-group-container > .title .close-editor-action {
|
||||
background: url('close-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content.vertical-layout > .one-editor-silo > .container > .title .split-editor-action {
|
||||
background: url('split-editor-vertical.svg') center center no-repeat;
|
||||
}
|
||||
/* Drag and Drop Feedback */
|
||||
|
||||
.vs-dark .monaco-workbench > .part.editor > .content.vertical-layout > .one-editor-silo > .container > .title .split-editor-action,
|
||||
.hc-black .monaco-workbench > .part.editor > .content.vertical-layout > .one-editor-silo > .container > .title .split-editor-action {
|
||||
background: url('split-editor-vertical-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content.horizontal-layout > .one-editor-silo > .container > .title .split-editor-action {
|
||||
background: url('split-editor-horizontal.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .part.editor > .content.horizontal-layout > .one-editor-silo > .container > .title .split-editor-action,
|
||||
.hc-black .monaco-workbench > .part.editor > .content.horizontal-layout > .one-editor-silo > .container > .title .split-editor-action {
|
||||
background: url('split-editor-horizontal-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.monaco-workbench .show-group-editors-action {
|
||||
background: url('stackview.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .show-group-editors-action,
|
||||
.hc-black .monaco-workbench .show-group-editors-action {
|
||||
background: url('stackview-inverse.svg') center center no-repeat;
|
||||
}
|
||||
.monaco-editor-group-drag-image {
|
||||
display: inline-block;
|
||||
padding: 1px 7px;
|
||||
border-radius: 10px;
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
}
|
||||
@@ -5,147 +5,222 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/notabstitle';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { toResource } from 'vs/workbench/common/editor';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl';
|
||||
import 'vs/css!./media/notabstitlecontrol';
|
||||
import { toResource, Verbosity, IEditorInput } from 'vs/workbench/common/editor';
|
||||
import { TitleControl, IToolbarActions } from 'vs/workbench/browser/parts/editor/titleControl';
|
||||
import { ResourceLabel } from 'vs/workbench/browser/labels';
|
||||
import { Verbosity } from 'vs/platform/editor/common/editor';
|
||||
import { TAB_ACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch';
|
||||
import { addDisposableListener, EventType, addClass, EventHelper, removeClass, toggleClass } from 'vs/base/browser/dom';
|
||||
import { IEditorPartOptions, EDITOR_TITLE_HEIGHT } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { CLOSE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
|
||||
export class NoTabsTitleControl extends TitleControl {
|
||||
private titleContainer: HTMLElement;
|
||||
private editorLabel: ResourceLabel;
|
||||
private lastRenderedActiveEditor: IEditorInput;
|
||||
|
||||
public create(parent: HTMLElement): void {
|
||||
super.create(parent);
|
||||
|
||||
protected create(parent: HTMLElement): void {
|
||||
this.titleContainer = parent;
|
||||
this.titleContainer.draggable = true;
|
||||
|
||||
//Container listeners
|
||||
this.registerContainerListeners();
|
||||
|
||||
// Gesture Support
|
||||
Gesture.addTarget(this.titleContainer);
|
||||
|
||||
// Pin on double click
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.titleContainer, DOM.EventType.DBLCLICK, (e: MouseEvent) => this.onTitleDoubleClick(e)));
|
||||
|
||||
// Detect mouse click
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.titleContainer, DOM.EventType.CLICK, (e: MouseEvent) => this.onTitleClick(e)));
|
||||
|
||||
// Detect touch
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.titleContainer, TouchEventType.Tap, (e: GestureEvent) => this.onTitleClick(e)));
|
||||
const labelContainer = document.createElement('div');
|
||||
addClass(labelContainer, 'label-container');
|
||||
this.titleContainer.appendChild(labelContainer);
|
||||
|
||||
// Editor Label
|
||||
this.editorLabel = this.instantiationService.createInstance(ResourceLabel, this.titleContainer, void 0);
|
||||
this.toUnbind.push(this.editorLabel);
|
||||
this.toUnbind.push(this.editorLabel.onClick(e => this.onTitleLabelClick(e)));
|
||||
this.editorLabel = this._register(this.instantiationService.createInstance(ResourceLabel, labelContainer, void 0));
|
||||
this._register(this.editorLabel.onClick(e => this.onTitleLabelClick(e)));
|
||||
|
||||
// Breadcrumbs
|
||||
this.createBreadcrumbsControl(labelContainer, { showFileIcons: false, showSymbolIcons: true, showDecorationColors: false, extraClasses: ['no-tabs-breadcrumbs'] });
|
||||
|
||||
// Right Actions Container
|
||||
const actionsContainer = document.createElement('div');
|
||||
DOM.addClass(actionsContainer, 'title-actions');
|
||||
addClass(actionsContainer, 'title-actions');
|
||||
this.titleContainer.appendChild(actionsContainer);
|
||||
|
||||
// Editor actions toolbar
|
||||
this.createEditorActionsToolBar(actionsContainer);
|
||||
}
|
||||
|
||||
private registerContainerListeners(): void {
|
||||
|
||||
// Group dragging
|
||||
this.enableGroupDragging(this.titleContainer);
|
||||
|
||||
// Pin on double click
|
||||
this._register(addDisposableListener(this.titleContainer, EventType.DBLCLICK, (e: MouseEvent) => this.onTitleDoubleClick(e)));
|
||||
|
||||
// Detect mouse click
|
||||
this._register(addDisposableListener(this.titleContainer, EventType.CLICK, (e: MouseEvent) => this.onTitleClick(e)));
|
||||
|
||||
// Detect touch
|
||||
this._register(addDisposableListener(this.titleContainer, TouchEventType.Tap, (e: GestureEvent) => this.onTitleClick(e)));
|
||||
|
||||
// Context Menu
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.titleContainer, DOM.EventType.CONTEXT_MENU, (e: Event) => this.onContextMenu({ group: this.context, editor: this.context.activeEditor }, e, this.titleContainer)));
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.titleContainer, TouchEventType.Contextmenu, (e: Event) => this.onContextMenu({ group: this.context, editor: this.context.activeEditor }, e, this.titleContainer)));
|
||||
this._register(addDisposableListener(this.titleContainer, EventType.CONTEXT_MENU, (e: Event) => this.onContextMenu(this.group.activeEditor, e, this.titleContainer)));
|
||||
this._register(addDisposableListener(this.titleContainer, TouchEventType.Contextmenu, (e: Event) => this.onContextMenu(this.group.activeEditor, e, this.titleContainer)));
|
||||
}
|
||||
|
||||
private onTitleLabelClick(e: MouseEvent): void {
|
||||
DOM.EventHelper.stop(e, false);
|
||||
if (!this.dragged) {
|
||||
setTimeout(() => this.quickOpenService.show()); // delayed to let the onTitleClick() come first which can cause a focus change which can close quick open
|
||||
}
|
||||
EventHelper.stop(e, false);
|
||||
|
||||
// delayed to let the onTitleClick() come first which can cause a focus change which can close quick open
|
||||
setTimeout(() => this.quickOpenService.show());
|
||||
}
|
||||
|
||||
private onTitleDoubleClick(e: MouseEvent): void {
|
||||
DOM.EventHelper.stop(e);
|
||||
if (!this.context) {
|
||||
return;
|
||||
}
|
||||
EventHelper.stop(e);
|
||||
|
||||
const group = this.context;
|
||||
|
||||
this.editorGroupService.pinEditor(group, group.activeEditor);
|
||||
this.group.pinEditor();
|
||||
}
|
||||
|
||||
private onTitleClick(e: MouseEvent | GestureEvent): void {
|
||||
if (!this.context) {
|
||||
return;
|
||||
}
|
||||
|
||||
const group = this.context;
|
||||
|
||||
// Close editor on middle mouse click
|
||||
if (e instanceof MouseEvent && e.button === 1 /* Middle Button */) {
|
||||
this.closeOneEditorAction.run({ groupId: group.id, editorIndex: group.indexOf(group.activeEditor) }).done(null, errors.onUnexpectedError);
|
||||
}
|
||||
|
||||
// Focus editor group unless:
|
||||
// - click on toolbar: should trigger actions within
|
||||
// - mouse click: do not focus group if there are more than one as it otherwise makes group DND funky
|
||||
// - touch: always focus
|
||||
else if ((this.stacks.groups.length === 1 || !(e instanceof MouseEvent)) && !DOM.isAncestor(((e as GestureEvent).initialTarget || e.target || e.srcElement) as HTMLElement, this.editorActionsToolbar.getContainer())) {
|
||||
this.editorGroupService.focusGroup(group);
|
||||
this.group.closeEditor(this.group.activeEditor);
|
||||
}
|
||||
}
|
||||
|
||||
protected doRefresh(): void {
|
||||
const group = this.context;
|
||||
const editor = group && group.activeEditor;
|
||||
getPreferredHeight(): number {
|
||||
return EDITOR_TITLE_HEIGHT;
|
||||
}
|
||||
|
||||
openEditor(editor: IEditorInput): void {
|
||||
this.ifActiveEditorChanged(() => this.redraw());
|
||||
}
|
||||
|
||||
closeEditor(editor: IEditorInput): void {
|
||||
this.ifActiveEditorChanged(() => this.redraw());
|
||||
}
|
||||
|
||||
closeEditors(editors: IEditorInput[]): void {
|
||||
this.ifActiveEditorChanged(() => this.redraw());
|
||||
}
|
||||
|
||||
closeAllEditors(): void {
|
||||
this.redraw();
|
||||
}
|
||||
|
||||
moveEditor(editor: IEditorInput, fromIndex: number, targetIndex: number): void {
|
||||
this.ifActiveEditorChanged(() => this.redraw());
|
||||
}
|
||||
|
||||
pinEditor(editor: IEditorInput): void {
|
||||
this.ifEditorIsActive(editor, () => this.redraw());
|
||||
}
|
||||
|
||||
setActive(isActive: boolean): void {
|
||||
this.redraw();
|
||||
}
|
||||
|
||||
updateEditorLabel(editor: IEditorInput): void {
|
||||
this.ifEditorIsActive(editor, () => this.redraw());
|
||||
}
|
||||
|
||||
updateEditorDirty(editor: IEditorInput): void {
|
||||
this.ifEditorIsActive(editor, () => {
|
||||
if (editor.isDirty()) {
|
||||
addClass(this.titleContainer, 'dirty');
|
||||
} else {
|
||||
removeClass(this.titleContainer, 'dirty');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void {
|
||||
if (oldOptions.labelFormat !== newOptions.labelFormat) {
|
||||
this.redraw();
|
||||
}
|
||||
}
|
||||
|
||||
updateStyles(): void {
|
||||
this.redraw();
|
||||
}
|
||||
|
||||
private ifActiveEditorChanged(fn: () => void): void {
|
||||
if (
|
||||
!this.lastRenderedActiveEditor && this.group.activeEditor || // active editor changed from null => editor
|
||||
this.lastRenderedActiveEditor && !this.group.activeEditor || // active editor changed from editor => null
|
||||
!this.group.isActive(this.lastRenderedActiveEditor) // active editor changed from editorA => editorB
|
||||
) {
|
||||
fn();
|
||||
}
|
||||
}
|
||||
|
||||
private ifEditorIsActive(editor: IEditorInput, fn: () => void): void {
|
||||
if (this.group.isActive(editor)) {
|
||||
fn(); // only run if editor is current active
|
||||
}
|
||||
}
|
||||
|
||||
private redraw(): void {
|
||||
const editor = this.group.activeEditor;
|
||||
this.lastRenderedActiveEditor = editor;
|
||||
|
||||
const isEditorPinned = this.group.isPinned(this.group.activeEditor);
|
||||
const isGroupActive = this.accessor.activeGroup === this.group;
|
||||
|
||||
// Update Breadcrumbs
|
||||
if (this.breadcrumbsControl) {
|
||||
if (isGroupActive) {
|
||||
this.breadcrumbsControl.update();
|
||||
toggleClass(this.breadcrumbsControl.domNode, 'preview', !isEditorPinned);
|
||||
} else {
|
||||
this.breadcrumbsControl.hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Clear if there is no editor
|
||||
if (!editor) {
|
||||
removeClass(this.titleContainer, 'dirty');
|
||||
this.editorLabel.clear();
|
||||
this.clearEditorActionsToolbar();
|
||||
|
||||
return; // return early if we are being closed
|
||||
}
|
||||
|
||||
const isPinned = group.isPinned(group.activeEditor);
|
||||
const isActive = this.stacks.isActive(group);
|
||||
// Otherwise render it
|
||||
else {
|
||||
// Dirty state
|
||||
this.updateEditorDirty(editor);
|
||||
|
||||
// Activity state
|
||||
if (isActive) {
|
||||
DOM.addClass(this.titleContainer, 'active');
|
||||
} else {
|
||||
DOM.removeClass(this.titleContainer, 'active');
|
||||
// Editor Label
|
||||
const resource = toResource(editor, { supportSideBySide: true });
|
||||
const name = editor.getName() || '';
|
||||
|
||||
const { labelFormat } = this.accessor.partOptions;
|
||||
let description: string;
|
||||
if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) {
|
||||
description = ''; // hide description when showing breadcrumbs
|
||||
} else if (labelFormat === 'default' && !isGroupActive) {
|
||||
description = ''; // hide description when group is not active and style is 'default'
|
||||
} else {
|
||||
description = editor.getDescription(this.getVerbosity(labelFormat)) || '';
|
||||
}
|
||||
|
||||
let title = editor.getTitle(Verbosity.LONG);
|
||||
if (description === title) {
|
||||
title = ''; // dont repeat what is already shown
|
||||
}
|
||||
|
||||
this.editorLabel.setLabel({ name, description, resource }, { title, italic: !isEditorPinned, extraClasses: ['no-tabs', 'title-label'] });
|
||||
if (isGroupActive) {
|
||||
this.editorLabel.element.style.color = this.getColor(TAB_ACTIVE_FOREGROUND);
|
||||
} else {
|
||||
this.editorLabel.element.style.color = this.getColor(TAB_UNFOCUSED_ACTIVE_FOREGROUND);
|
||||
}
|
||||
|
||||
// Update Editor Actions Toolbar
|
||||
this.updateEditorActionsToolbar();
|
||||
}
|
||||
|
||||
// Dirty state
|
||||
if (editor.isDirty()) {
|
||||
DOM.addClass(this.titleContainer, 'dirty');
|
||||
} else {
|
||||
DOM.removeClass(this.titleContainer, 'dirty');
|
||||
}
|
||||
|
||||
// Editor Label
|
||||
const resource = toResource(editor, { supportSideBySide: true });
|
||||
const name = editor.getName() || '';
|
||||
|
||||
const labelFormat = this.editorGroupService.getTabOptions().labelFormat;
|
||||
let description: string;
|
||||
if (labelFormat === 'default' && !isActive) {
|
||||
description = ''; // hide description when group is not active and style is 'default'
|
||||
} else {
|
||||
description = editor.getDescription(this.getVerbosity(labelFormat)) || '';
|
||||
}
|
||||
|
||||
let title = editor.getTitle(Verbosity.LONG);
|
||||
if (description === title) {
|
||||
title = ''; // dont repeat what is already shown
|
||||
}
|
||||
|
||||
this.editorLabel.setLabel({ name, description, resource }, { title, italic: !isPinned, extraClasses: ['title-label'] });
|
||||
if (isActive) {
|
||||
this.editorLabel.element.style.color = this.getColor(TAB_ACTIVE_FOREGROUND);
|
||||
} else {
|
||||
this.editorLabel.element.style.color = this.getColor(TAB_UNFOCUSED_ACTIVE_FOREGROUND);
|
||||
}
|
||||
|
||||
// Update Editor Actions Toolbar
|
||||
this.updateEditorActionsToolbar();
|
||||
}
|
||||
|
||||
private getVerbosity(style: string): Verbosity {
|
||||
@@ -155,4 +230,16 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
default: return Verbosity.MEDIUM;
|
||||
}
|
||||
}
|
||||
|
||||
protected prepareEditorActions(editorActions: IToolbarActions): { primaryEditorActions: IAction[], secondaryEditorActions: IAction[] } {
|
||||
const isGroupActive = this.accessor.activeGroup === this.group;
|
||||
|
||||
// Group active: show all actions
|
||||
if (isGroupActive) {
|
||||
return super.prepareEditorActions(editorActions);
|
||||
}
|
||||
|
||||
// Group inactive: only show close action
|
||||
return { primaryEditorActions: editorActions.primary.filter(action => action.id === CLOSE_EDITOR_COMMAND_ID), secondaryEditorActions: [] };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
@@ -19,27 +19,29 @@ export interface IRangeHighlightDecoration {
|
||||
isWholeLine?: boolean;
|
||||
}
|
||||
|
||||
export class RangeHighlightDecorations implements IDisposable {
|
||||
export class RangeHighlightDecorations extends Disposable {
|
||||
|
||||
private rangeHighlightDecorationId: string = null;
|
||||
private editor: ICodeEditor = null;
|
||||
private editorDisposables: IDisposable[] = [];
|
||||
|
||||
private readonly _onHighlightRemoved: Emitter<void> = new Emitter<void>();
|
||||
public readonly onHighlghtRemoved: Event<void> = this._onHighlightRemoved.event;
|
||||
private readonly _onHighlightRemoved: Emitter<void> = this._register(new Emitter<void>());
|
||||
get onHighlghtRemoved(): Event<void> { return this._onHighlightRemoved.event; }
|
||||
|
||||
constructor(@IWorkbenchEditorService private editorService: IWorkbenchEditorService) {
|
||||
constructor(@IEditorService private editorService: IEditorService) {
|
||||
super();
|
||||
}
|
||||
|
||||
public removeHighlightRange() {
|
||||
removeHighlightRange() {
|
||||
if (this.editor && this.editor.getModel() && this.rangeHighlightDecorationId) {
|
||||
this.editor.deltaDecorations([this.rangeHighlightDecorationId], []);
|
||||
this._onHighlightRemoved.fire();
|
||||
}
|
||||
|
||||
this.rangeHighlightDecorationId = null;
|
||||
}
|
||||
|
||||
public highlightRange(range: IRangeHighlightDecoration, editor?: ICodeEditor) {
|
||||
highlightRange(range: IRangeHighlightDecoration, editor?: ICodeEditor) {
|
||||
editor = editor ? editor : this.getEditor(range);
|
||||
if (editor) {
|
||||
this.doHighlightRange(editor, range);
|
||||
@@ -48,20 +50,23 @@ export class RangeHighlightDecorations implements IDisposable {
|
||||
|
||||
private doHighlightRange(editor: ICodeEditor, selectionRange: IRangeHighlightDecoration) {
|
||||
this.removeHighlightRange();
|
||||
|
||||
editor.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => {
|
||||
this.rangeHighlightDecorationId = changeAccessor.addDecoration(selectionRange.range, this.createRangeHighlightDecoration(selectionRange.isWholeLine));
|
||||
});
|
||||
|
||||
this.setEditor(editor);
|
||||
}
|
||||
|
||||
private getEditor(resourceRange: IRangeHighlightDecoration): ICodeEditor {
|
||||
const activeInput = this.editorService.getActiveEditorInput();
|
||||
const resource = activeInput && activeInput.getResource();
|
||||
const activeEditor = this.editorService.activeEditor;
|
||||
const resource = activeEditor && activeEditor.getResource();
|
||||
if (resource) {
|
||||
if (resource.toString() === resourceRange.resource.toString()) {
|
||||
return <ICodeEditor>this.editorService.getActiveEditor().getControl();
|
||||
return this.editorService.activeTextEditorWidget as ICodeEditor;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -107,7 +112,9 @@ export class RangeHighlightDecorations implements IDisposable {
|
||||
return (isWholeLine ? RangeHighlightDecorations._WHOLE_LINE_RANGE_HIGHLIGHT : RangeHighlightDecorations._RANGE_HIGHLIGHT);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
dispose() {
|
||||
super.dispose();
|
||||
|
||||
if (this.editor && this.editor.getModel()) {
|
||||
this.removeHighlightRange();
|
||||
this.disposeEditorListeners();
|
||||
|
||||
@@ -3,13 +3,10 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/resourceviewer';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as mimes from 'vs/base/common/mime';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import * as paths from 'vs/base/common/paths';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
@@ -24,79 +21,26 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
|
||||
interface MapExtToMediaMimes {
|
||||
[index: string]: string;
|
||||
}
|
||||
|
||||
// Known media mimes that we can handle
|
||||
const mapExtToMediaMimes: MapExtToMediaMimes = {
|
||||
'.bmp': 'image/bmp',
|
||||
'.gif': 'image/gif',
|
||||
'.jpg': 'image/jpg',
|
||||
'.jpeg': 'image/jpg',
|
||||
'.jpe': 'image/jpg',
|
||||
'.png': 'image/png',
|
||||
'.tiff': 'image/tiff',
|
||||
'.tif': 'image/tiff',
|
||||
'.ico': 'image/x-icon',
|
||||
'.tga': 'image/x-tga',
|
||||
'.psd': 'image/vnd.adobe.photoshop',
|
||||
'.webp': 'image/webp',
|
||||
'.mid': 'audio/midi',
|
||||
'.midi': 'audio/midi',
|
||||
'.mp4a': 'audio/mp4',
|
||||
'.mpga': 'audio/mpeg',
|
||||
'.mp2': 'audio/mpeg',
|
||||
'.mp2a': 'audio/mpeg',
|
||||
'.mp3': 'audio/mpeg',
|
||||
'.m2a': 'audio/mpeg',
|
||||
'.m3a': 'audio/mpeg',
|
||||
'.oga': 'audio/ogg',
|
||||
'.ogg': 'audio/ogg',
|
||||
'.spx': 'audio/ogg',
|
||||
'.aac': 'audio/x-aac',
|
||||
'.wav': 'audio/x-wav',
|
||||
'.wma': 'audio/x-ms-wma',
|
||||
'.mp4': 'video/mp4',
|
||||
'.mp4v': 'video/mp4',
|
||||
'.mpg4': 'video/mp4',
|
||||
'.mpeg': 'video/mpeg',
|
||||
'.mpg': 'video/mpeg',
|
||||
'.mpe': 'video/mpeg',
|
||||
'.m1v': 'video/mpeg',
|
||||
'.m2v': 'video/mpeg',
|
||||
'.ogv': 'video/ogg',
|
||||
'.qt': 'video/quicktime',
|
||||
'.mov': 'video/quicktime',
|
||||
'.webm': 'video/webm',
|
||||
'.mkv': 'video/x-matroska',
|
||||
'.mk3d': 'video/x-matroska',
|
||||
'.mks': 'video/x-matroska',
|
||||
'.wmv': 'video/x-ms-wmv',
|
||||
'.flv': 'video/x-flv',
|
||||
'.avi': 'video/x-msvideo',
|
||||
'.movie': 'video/x-sgi-movie'
|
||||
};
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
|
||||
export interface IResourceDescriptor {
|
||||
resource: URI;
|
||||
name: string;
|
||||
size: number;
|
||||
etag: string;
|
||||
mime: string;
|
||||
readonly resource: URI;
|
||||
readonly name: string;
|
||||
readonly size: number;
|
||||
readonly etag: string;
|
||||
readonly mime: string;
|
||||
}
|
||||
|
||||
class BinarySize {
|
||||
public static readonly KB = 1024;
|
||||
public static readonly MB = BinarySize.KB * BinarySize.KB;
|
||||
public static readonly GB = BinarySize.MB * BinarySize.KB;
|
||||
public static readonly TB = BinarySize.GB * BinarySize.KB;
|
||||
static readonly KB = 1024;
|
||||
static readonly MB = BinarySize.KB * BinarySize.KB;
|
||||
static readonly GB = BinarySize.MB * BinarySize.KB;
|
||||
static readonly TB = BinarySize.GB * BinarySize.KB;
|
||||
|
||||
public static formatSize(size: number): string {
|
||||
static formatSize(size: number): string {
|
||||
if (size < BinarySize.KB) {
|
||||
return nls.localize('sizeB', "{0}B", size);
|
||||
}
|
||||
@@ -129,8 +73,9 @@ export class ResourceViewer {
|
||||
|
||||
private static readonly MAX_OPEN_INTERNAL_SIZE = BinarySize.MB * 200; // max size until we offer an action to open internally
|
||||
|
||||
public static show(
|
||||
static show(
|
||||
descriptor: IResourceDescriptor,
|
||||
fileService: IFileService,
|
||||
container: HTMLElement,
|
||||
scrollbar: DomScrollableElement,
|
||||
openInternalClb: (uri: URI) => void,
|
||||
@@ -143,7 +88,7 @@ export class ResourceViewer {
|
||||
|
||||
// Images
|
||||
if (ResourceViewer.isImageResource(descriptor)) {
|
||||
return ImageView.create(container, descriptor, scrollbar, openExternalClb, metadataClb);
|
||||
return ImageView.create(container, descriptor, fileService, scrollbar, openExternalClb, metadataClb);
|
||||
}
|
||||
|
||||
// Large Files
|
||||
@@ -160,37 +105,26 @@ export class ResourceViewer {
|
||||
}
|
||||
|
||||
private static isImageResource(descriptor: IResourceDescriptor) {
|
||||
const mime = ResourceViewer.getMime(descriptor);
|
||||
const mime = getMime(descriptor);
|
||||
|
||||
return mime.indexOf('image/') >= 0;
|
||||
}
|
||||
|
||||
private static getMime(descriptor: IResourceDescriptor): string {
|
||||
let mime = descriptor.mime;
|
||||
if (!mime && descriptor.resource.scheme !== Schemas.data) {
|
||||
const ext = paths.extname(descriptor.resource.toString());
|
||||
if (ext) {
|
||||
mime = mapExtToMediaMimes[ext.toLowerCase()];
|
||||
}
|
||||
}
|
||||
|
||||
return mime || mimes.MIME_BINARY;
|
||||
}
|
||||
}
|
||||
|
||||
class ImageView {
|
||||
private static readonly MAX_IMAGE_SIZE = BinarySize.MB; // showing images inline is memory intense, so we have a limit
|
||||
private static readonly BASE64_MARKER = 'base64,';
|
||||
|
||||
public static create(
|
||||
static create(
|
||||
container: HTMLElement,
|
||||
descriptor: IResourceDescriptor,
|
||||
fileService: IFileService,
|
||||
scrollbar: DomScrollableElement,
|
||||
openExternalClb: (uri: URI) => void,
|
||||
metadataClb: (meta: string) => void
|
||||
): ResourceViewerContext | null {
|
||||
if (ImageView.shouldShowImageInline(descriptor)) {
|
||||
return InlineImageView.create(container, descriptor, scrollbar, metadataClb);
|
||||
return InlineImageView.create(container, descriptor, fileService, scrollbar, metadataClb);
|
||||
}
|
||||
|
||||
LargeImageView.create(container, descriptor, openExternalClb);
|
||||
@@ -219,7 +153,7 @@ class ImageView {
|
||||
}
|
||||
|
||||
class LargeImageView {
|
||||
public static create(
|
||||
static create(
|
||||
container: HTMLElement,
|
||||
descriptor: IResourceDescriptor,
|
||||
openExternalClb: (uri: URI) => void
|
||||
@@ -245,7 +179,7 @@ class LargeImageView {
|
||||
}
|
||||
|
||||
class FileTooLargeFileView {
|
||||
public static create(
|
||||
static create(
|
||||
container: HTMLElement,
|
||||
descriptor: IResourceDescriptor,
|
||||
scrollbar: DomScrollableElement,
|
||||
@@ -268,7 +202,7 @@ class FileTooLargeFileView {
|
||||
}
|
||||
|
||||
class FileSeemsBinaryFileView {
|
||||
public static create(
|
||||
static create(
|
||||
container: HTMLElement,
|
||||
descriptor: IResourceDescriptor,
|
||||
scrollbar: DomScrollableElement,
|
||||
@@ -302,29 +236,32 @@ class FileSeemsBinaryFileView {
|
||||
type Scale = number | 'fit';
|
||||
|
||||
class ZoomStatusbarItem extends Themable implements IStatusbarItem {
|
||||
|
||||
static instance: ZoomStatusbarItem;
|
||||
|
||||
showTimeout: number;
|
||||
public static instance: ZoomStatusbarItem;
|
||||
|
||||
private statusBarItem: HTMLElement;
|
||||
|
||||
private onSelectScale?: (scale: Scale) => void;
|
||||
|
||||
constructor(
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService,
|
||||
@IEditorService editorService: IEditorService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(themeService);
|
||||
|
||||
ZoomStatusbarItem.instance = this;
|
||||
this.toUnbind.push(editorGroupService.onEditorsChanged(() => this.onEditorsChanged()));
|
||||
|
||||
this._register(editorService.onDidActiveEditorChange(() => this.onActiveEditorChanged()));
|
||||
}
|
||||
|
||||
private onEditorsChanged(): void {
|
||||
private onActiveEditorChanged(): void {
|
||||
this.hide();
|
||||
this.onSelectScale = void 0;
|
||||
}
|
||||
|
||||
public show(scale: Scale, onSelectScale: (scale: number) => void) {
|
||||
show(scale: Scale, onSelectScale: (scale: number) => void) {
|
||||
clearTimeout(this.showTimeout);
|
||||
this.showTimeout = setTimeout(() => {
|
||||
this.onSelectScale = onSelectScale;
|
||||
@@ -333,11 +270,11 @@ class ZoomStatusbarItem extends Themable implements IStatusbarItem {
|
||||
}, 0);
|
||||
}
|
||||
|
||||
public hide() {
|
||||
hide() {
|
||||
this.statusBarItem.style.display = 'none';
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): IDisposable {
|
||||
render(container: HTMLElement): IDisposable {
|
||||
if (!this.statusBarItem && container) {
|
||||
this.statusBarItem = $(container).a()
|
||||
.addClass('.zoom-statusbar-item')
|
||||
@@ -419,22 +356,15 @@ class InlineImageView {
|
||||
*/
|
||||
private static readonly PIXELATION_THRESHOLD = 3;
|
||||
|
||||
/**
|
||||
* Chrome is caching images very aggressively and so we use the ETag information to find out if
|
||||
* we need to bypass the cache or not. We could always bypass the cache everytime we show the image
|
||||
* however that has very bad impact on memory consumption because each time the image gets shown,
|
||||
* memory grows (see also https://github.com/electron/electron/issues/6275)
|
||||
*/
|
||||
private static IMAGE_RESOURCE_ETAG_CACHE = new LRUCache<string, { etag: string, src: string }>(100);
|
||||
|
||||
/**
|
||||
* Store the scale and position of an image so it can be restored when changing editor tabs
|
||||
*/
|
||||
private static readonly imageStateCache = new LRUCache<string, ImageState>(100);
|
||||
|
||||
public static create(
|
||||
static create(
|
||||
container: HTMLElement,
|
||||
descriptor: IResourceDescriptor,
|
||||
fileService: IFileService,
|
||||
scrollbar: DomScrollableElement,
|
||||
metadataClb: (meta: string) => void
|
||||
) {
|
||||
@@ -601,7 +531,7 @@ class InlineImageView {
|
||||
$(container)
|
||||
.empty()
|
||||
.addClass('image', 'zoom-in')
|
||||
.img({ src: InlineImageView.imageSrc(descriptor) })
|
||||
.img({})
|
||||
.style('visibility', 'hidden')
|
||||
.addClass('scale-to-fit')
|
||||
.on(DOM.EventType.LOAD, (e, i) => {
|
||||
@@ -619,27 +549,33 @@ class InlineImageView {
|
||||
}
|
||||
});
|
||||
|
||||
InlineImageView.imageSrc(descriptor, fileService).then(dataUri => {
|
||||
const imgs = container.getElementsByTagName('img');
|
||||
if (imgs.length) {
|
||||
imgs[0].src = dataUri;
|
||||
}
|
||||
});
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
private static imageSrc(descriptor: IResourceDescriptor): string {
|
||||
private static imageSrc(descriptor: IResourceDescriptor, fileService: IFileService): TPromise<string> {
|
||||
if (descriptor.resource.scheme === Schemas.data) {
|
||||
return descriptor.resource.toString(true /* skip encoding */);
|
||||
return TPromise.as(descriptor.resource.toString(true /* skip encoding */));
|
||||
}
|
||||
|
||||
const src = descriptor.resource.toString();
|
||||
return fileService.resolveContent(descriptor.resource, { encoding: 'base64' }).then(data => {
|
||||
const mime = getMime(descriptor);
|
||||
|
||||
let cached = InlineImageView.IMAGE_RESOURCE_ETAG_CACHE.get(src);
|
||||
if (!cached) {
|
||||
cached = { etag: descriptor.etag, src };
|
||||
InlineImageView.IMAGE_RESOURCE_ETAG_CACHE.set(src, cached);
|
||||
}
|
||||
|
||||
if (cached.etag !== descriptor.etag) {
|
||||
cached.etag = descriptor.etag;
|
||||
cached.src = `${src}?${Date.now()}`; // bypass cache with this trick
|
||||
}
|
||||
|
||||
return cached.src;
|
||||
return `data:${mime};base64,${data.value}`;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getMime(descriptor: IResourceDescriptor) {
|
||||
let mime = descriptor.mime;
|
||||
if (!mime && descriptor.resource.scheme !== Schemas.data) {
|
||||
mime = mimes.getMediaMime(descriptor.resource.toString());
|
||||
}
|
||||
return mime || mimes.MIME_BINARY;
|
||||
}
|
||||
|
||||
@@ -6,29 +6,55 @@
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { EditorInput, EditorOptions, SideBySideEditorInput } from 'vs/workbench/common/editor';
|
||||
import { EditorInput, EditorOptions, SideBySideEditorInput, IEditorControl, IEditor } from 'vs/workbench/common/editor';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { IEditorControl, Position, IEditor } from 'vs/platform/editor/common/editor';
|
||||
import { VSash } from 'vs/base/browser/ui/sash/sash';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { scrollbarShadow } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { SplitView, Sizing, Orientation } from 'vs/base/browser/ui/splitview/splitview';
|
||||
import { Event, Relay, anyEvent, mapEvent, Emitter } from 'vs/base/common/event';
|
||||
|
||||
export class SideBySideEditor extends BaseEditor {
|
||||
|
||||
public static readonly ID: string = 'workbench.editor.sidebysideEditor';
|
||||
static readonly ID: string = 'workbench.editor.sidebysideEditor';
|
||||
|
||||
private dimension: DOM.Dimension;
|
||||
get minimumMasterWidth() { return this.masterEditor ? this.masterEditor.minimumWidth : 0; }
|
||||
get maximumMasterWidth() { return this.masterEditor ? this.masterEditor.maximumWidth : Number.POSITIVE_INFINITY; }
|
||||
get minimumMasterHeight() { return this.masterEditor ? this.masterEditor.minimumHeight : 0; }
|
||||
get maximumMasterHeight() { return this.masterEditor ? this.masterEditor.maximumHeight : Number.POSITIVE_INFINITY; }
|
||||
|
||||
get minimumDetailsWidth() { return this.detailsEditor ? this.detailsEditor.minimumWidth : 0; }
|
||||
get maximumDetailsWidth() { return this.detailsEditor ? this.detailsEditor.maximumWidth : Number.POSITIVE_INFINITY; }
|
||||
get minimumDetailsHeight() { return this.detailsEditor ? this.detailsEditor.minimumHeight : 0; }
|
||||
get maximumDetailsHeight() { return this.detailsEditor ? this.detailsEditor.maximumHeight : Number.POSITIVE_INFINITY; }
|
||||
|
||||
// these setters need to exist because this extends from BaseEditor
|
||||
set minimumWidth(value: number) { /* noop */ }
|
||||
set maximumWidth(value: number) { /* noop */ }
|
||||
set minimumHeight(value: number) { /* noop */ }
|
||||
set maximumHeight(value: number) { /* noop */ }
|
||||
|
||||
get minimumWidth() { return this.minimumMasterWidth + this.minimumDetailsWidth; }
|
||||
get maximumWidth() { return this.maximumMasterWidth + this.maximumDetailsWidth; }
|
||||
get minimumHeight() { return this.minimumMasterHeight + this.minimumDetailsHeight; }
|
||||
get maximumHeight() { return this.maximumMasterHeight + this.maximumDetailsHeight; }
|
||||
|
||||
protected masterEditor: BaseEditor;
|
||||
private masterEditorContainer: HTMLElement;
|
||||
|
||||
protected detailsEditor: BaseEditor;
|
||||
|
||||
private masterEditorContainer: HTMLElement;
|
||||
private detailsEditorContainer: HTMLElement;
|
||||
|
||||
private sash: VSash;
|
||||
private splitview: SplitView;
|
||||
private dimension: DOM.Dimension = new DOM.Dimension(0, 0);
|
||||
|
||||
private onDidCreateEditors = this._register(new Emitter<{ width: number; height: number; }>());
|
||||
private _onDidSizeConstraintsChange = this._register(new Relay<{ width: number; height: number; }>());
|
||||
readonly onDidSizeConstraintsChange: Event<{ width: number; height: number; }> = anyEvent(this.onDidCreateEditors.event, this._onDidSizeConstraintsChange.event);
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@@ -40,36 +66,54 @@ export class SideBySideEditor extends BaseEditor {
|
||||
|
||||
protected createEditor(parent: HTMLElement): void {
|
||||
DOM.addClass(parent, 'side-by-side-editor');
|
||||
this.createSash(parent);
|
||||
|
||||
this.splitview = this._register(new SplitView(parent, { orientation: Orientation.HORIZONTAL }));
|
||||
this._register(this.splitview.onDidSashReset(() => this.splitview.distributeViewSizes()));
|
||||
|
||||
this.detailsEditorContainer = DOM.$('.details-editor-container');
|
||||
this.splitview.addView({
|
||||
element: this.detailsEditorContainer,
|
||||
layout: size => this.detailsEditor && this.detailsEditor.layout(new DOM.Dimension(size, this.dimension.height)),
|
||||
minimumSize: 220,
|
||||
maximumSize: Number.POSITIVE_INFINITY,
|
||||
onDidChange: Event.None
|
||||
}, Sizing.Distribute);
|
||||
|
||||
this.masterEditorContainer = DOM.$('.master-editor-container');
|
||||
this.splitview.addView({
|
||||
element: this.masterEditorContainer,
|
||||
layout: size => this.masterEditor && this.masterEditor.layout(new DOM.Dimension(size, this.dimension.height)),
|
||||
minimumSize: 220,
|
||||
maximumSize: Number.POSITIVE_INFINITY,
|
||||
onDidChange: Event.None
|
||||
}, Sizing.Distribute);
|
||||
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
public setInput(newInput: SideBySideEditorInput, options?: EditorOptions): TPromise<void> {
|
||||
setInput(newInput: SideBySideEditorInput, options: EditorOptions, token: CancellationToken): Thenable<void> {
|
||||
const oldInput = <SideBySideEditorInput>this.input;
|
||||
return super.setInput(newInput, options)
|
||||
.then(() => this.updateInput(oldInput, newInput, options));
|
||||
return super.setInput(newInput, options, token)
|
||||
.then(() => this.updateInput(oldInput, newInput, options, token));
|
||||
}
|
||||
|
||||
protected setEditorVisible(visible: boolean, position: Position): void {
|
||||
setOptions(options: EditorOptions): void {
|
||||
if (this.masterEditor) {
|
||||
this.masterEditor.setVisible(visible, position);
|
||||
this.masterEditor.setOptions(options);
|
||||
}
|
||||
}
|
||||
|
||||
protected setEditorVisible(visible: boolean, group: IEditorGroup): void {
|
||||
if (this.masterEditor) {
|
||||
this.masterEditor.setVisible(visible, group);
|
||||
}
|
||||
if (this.detailsEditor) {
|
||||
this.detailsEditor.setVisible(visible, position);
|
||||
this.detailsEditor.setVisible(visible, group);
|
||||
}
|
||||
super.setEditorVisible(visible, position);
|
||||
super.setEditorVisible(visible, group);
|
||||
}
|
||||
|
||||
public changePosition(position: Position): void {
|
||||
if (this.masterEditor) {
|
||||
this.masterEditor.changePosition(position);
|
||||
}
|
||||
if (this.detailsEditor) {
|
||||
this.detailsEditor.changePosition(position);
|
||||
}
|
||||
super.changePosition(position);
|
||||
}
|
||||
|
||||
public clearInput(): void {
|
||||
clearInput(): void {
|
||||
if (this.masterEditor) {
|
||||
this.masterEditor.clearInput();
|
||||
}
|
||||
@@ -80,57 +124,49 @@ export class SideBySideEditor extends BaseEditor {
|
||||
super.clearInput();
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
focus(): void {
|
||||
if (this.masterEditor) {
|
||||
this.masterEditor.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public layout(dimension: DOM.Dimension): void {
|
||||
layout(dimension: DOM.Dimension): void {
|
||||
this.dimension = dimension;
|
||||
this.sash.setDimenesion(this.dimension);
|
||||
this.splitview.layout(dimension.width);
|
||||
}
|
||||
|
||||
public getControl(): IEditorControl {
|
||||
getControl(): IEditorControl {
|
||||
if (this.masterEditor) {
|
||||
return this.masterEditor.getControl();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public getMasterEditor(): IEditor {
|
||||
getMasterEditor(): IEditor {
|
||||
return this.masterEditor;
|
||||
}
|
||||
|
||||
public getDetailsEditor(): IEditor {
|
||||
getDetailsEditor(): IEditor {
|
||||
return this.detailsEditor;
|
||||
}
|
||||
|
||||
public supportsCenteredLayout(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
private updateInput(oldInput: SideBySideEditorInput, newInput: SideBySideEditorInput, options?: EditorOptions): void {
|
||||
private updateInput(oldInput: SideBySideEditorInput, newInput: SideBySideEditorInput, options: EditorOptions, token: CancellationToken): Thenable<void> {
|
||||
if (!newInput.matches(oldInput)) {
|
||||
if (oldInput) {
|
||||
this.disposeEditors();
|
||||
}
|
||||
this.createEditorContainers();
|
||||
|
||||
return this.setNewInput(newInput, options);
|
||||
} else {
|
||||
this.detailsEditor.setInput(newInput.details);
|
||||
this.masterEditor.setInput(newInput.master, options);
|
||||
|
||||
return void 0;
|
||||
return this.setNewInput(newInput, options, token);
|
||||
}
|
||||
|
||||
return TPromise.join([this.detailsEditor.setInput(newInput.details, null, token), this.masterEditor.setInput(newInput.master, options, token)]).then(() => void 0);
|
||||
}
|
||||
|
||||
private setNewInput(newInput: SideBySideEditorInput, options?: EditorOptions): void {
|
||||
private setNewInput(newInput: SideBySideEditorInput, options: EditorOptions, token: CancellationToken): Thenable<void> {
|
||||
const detailsEditor = this._createEditor(<EditorInput>newInput.details, this.detailsEditorContainer);
|
||||
const masterEditor = this._createEditor(<EditorInput>newInput.master, this.masterEditorContainer);
|
||||
|
||||
this.onEditorsCreated(detailsEditor, masterEditor, newInput.details, newInput.master, options);
|
||||
return this.onEditorsCreated(detailsEditor, masterEditor, newInput.details, newInput.master, options, token);
|
||||
}
|
||||
|
||||
private _createEditor(editorInput: EditorInput, container: HTMLElement): BaseEditor {
|
||||
@@ -138,29 +174,26 @@ export class SideBySideEditor extends BaseEditor {
|
||||
|
||||
const editor = descriptor.instantiate(this.instantiationService);
|
||||
editor.create(container);
|
||||
editor.setVisible(this.isVisible(), this.position);
|
||||
editor.setVisible(this.isVisible(), this.group);
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
private onEditorsCreated(details: BaseEditor, master: BaseEditor, detailsInput: EditorInput, masterInput: EditorInput, options: EditorOptions): TPromise<void> {
|
||||
private onEditorsCreated(details: BaseEditor, master: BaseEditor, detailsInput: EditorInput, masterInput: EditorInput, options: EditorOptions, token: CancellationToken): TPromise<void> {
|
||||
this.detailsEditor = details;
|
||||
this.masterEditor = master;
|
||||
this.dolayout(this.sash.getVerticalSashLeft());
|
||||
return TPromise.join([this.detailsEditor.setInput(detailsInput), this.masterEditor.setInput(masterInput, options)]).then(() => this.focus());
|
||||
|
||||
this._onDidSizeConstraintsChange.input = anyEvent(
|
||||
mapEvent(details.onDidSizeConstraintsChange, () => undefined),
|
||||
mapEvent(master.onDidSizeConstraintsChange, () => undefined)
|
||||
);
|
||||
|
||||
this.onDidCreateEditors.fire();
|
||||
|
||||
return TPromise.join([this.detailsEditor.setInput(detailsInput, null, token), this.masterEditor.setInput(masterInput, options, token)]).then(() => this.focus());
|
||||
}
|
||||
|
||||
private createEditorContainers(): void {
|
||||
const parentElement = this.getContainer();
|
||||
this.detailsEditorContainer = DOM.append(parentElement, DOM.$('.details-editor-container'));
|
||||
this.detailsEditorContainer.style.position = 'absolute';
|
||||
this.masterEditorContainer = DOM.append(parentElement, DOM.$('.master-editor-container'));
|
||||
this.masterEditorContainer.style.position = 'absolute';
|
||||
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
public updateStyles(): void {
|
||||
updateStyles(): void {
|
||||
super.updateStyles();
|
||||
|
||||
if (this.masterEditorContainer) {
|
||||
@@ -168,52 +201,24 @@ export class SideBySideEditor extends BaseEditor {
|
||||
}
|
||||
}
|
||||
|
||||
private createSash(parentElement: HTMLElement): void {
|
||||
this.sash = this._register(new VSash(parentElement, 220));
|
||||
this._register(this.sash.onPositionChange(position => this.dolayout(position)));
|
||||
}
|
||||
|
||||
private dolayout(splitPoint: number): void {
|
||||
if (!this.detailsEditor || !this.masterEditor || !this.dimension) {
|
||||
return;
|
||||
}
|
||||
const masterEditorWidth = this.dimension.width - splitPoint;
|
||||
const detailsEditorWidth = this.dimension.width - masterEditorWidth;
|
||||
|
||||
this.detailsEditorContainer.style.width = `${detailsEditorWidth}px`;
|
||||
this.detailsEditorContainer.style.height = `${this.dimension.height}px`;
|
||||
this.detailsEditorContainer.style.left = '0px';
|
||||
|
||||
this.masterEditorContainer.style.width = `${masterEditorWidth}px`;
|
||||
this.masterEditorContainer.style.height = `${this.dimension.height}px`;
|
||||
this.masterEditorContainer.style.left = `${splitPoint}px`;
|
||||
|
||||
this.detailsEditor.layout(new DOM.Dimension(detailsEditorWidth, this.dimension.height));
|
||||
this.masterEditor.layout(new DOM.Dimension(masterEditorWidth, this.dimension.height));
|
||||
}
|
||||
|
||||
private disposeEditors(): void {
|
||||
const parentContainer = this.getContainer();
|
||||
if (this.detailsEditor) {
|
||||
this.detailsEditor.dispose();
|
||||
this.detailsEditor = null;
|
||||
}
|
||||
|
||||
if (this.masterEditor) {
|
||||
this.masterEditor.dispose();
|
||||
this.masterEditor = null;
|
||||
}
|
||||
if (this.detailsEditorContainer) {
|
||||
parentContainer.removeChild(this.detailsEditorContainer);
|
||||
this.detailsEditorContainer = null;
|
||||
}
|
||||
if (this.masterEditorContainer) {
|
||||
parentContainer.removeChild(this.masterEditorContainer);
|
||||
this.masterEditorContainer = null;
|
||||
}
|
||||
|
||||
this.detailsEditorContainer.innerHTML = '';
|
||||
this.masterEditorContainer.innerHTML = '';
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
dispose(): void {
|
||||
this.disposeEditors();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,11 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { IDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { IDiffEditorOptions, IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor';
|
||||
import { TextEditorOptions, EditorInput, EditorOptions, TEXT_DIFF_EDITOR_ID, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor';
|
||||
import { TextEditorOptions, EditorInput, EditorOptions, TEXT_DIFF_EDITOR_ID, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, ITextDiffEditor, IEditorMemento } from 'vs/workbench/common/editor';
|
||||
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
|
||||
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
|
||||
import { DiffNavigator } from 'vs/editor/browser/widget/diffNavigator';
|
||||
@@ -26,28 +25,28 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IWorkbenchEditorService, DelegatingWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ScrollType, IDiffEditorViewState, IDiffEditorModel } from 'vs/editor/common/editorCommon';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { getCodeOrDiffEditor } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { once } from 'vs/base/common/event';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { EditorMemento } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
|
||||
/**
|
||||
* The text editor that leverages the diff text editor for the editing experience.
|
||||
*/
|
||||
export class TextDiffEditor extends BaseTextEditor {
|
||||
export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
|
||||
|
||||
public static readonly ID = TEXT_DIFF_EDITOR_ID;
|
||||
static readonly ID = TEXT_DIFF_EDITOR_ID;
|
||||
|
||||
private diffNavigator: DiffNavigator;
|
||||
private diffNavigatorDisposables: IDisposable[];
|
||||
private diffNavigatorDisposables: IDisposable[] = [];
|
||||
private nextDiffAction: NavigateAction;
|
||||
private previousDiffAction: NavigateAction;
|
||||
private toggleIgnoreTrimWhitespaceAction: ToggleIgnoreTrimWhitespaceAction;
|
||||
@@ -58,22 +57,25 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
@IStorageService storageService: IStorageService,
|
||||
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
|
||||
@IConfigurationService private readonly _actualConfigurationService: IConfigurationService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IEditorService editorService: IEditorService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService,
|
||||
@ITextFileService textFileService: ITextFileService
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService,
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
) {
|
||||
super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorGroupService);
|
||||
super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService);
|
||||
|
||||
this.diffNavigatorDisposables = [];
|
||||
this.toUnbind.push(this._actualConfigurationService.onDidChangeConfiguration((e) => {
|
||||
this._register(this._actualConfigurationService.onDidChangeConfiguration((e) => {
|
||||
if (e.affectsConfiguration('diffEditor.ignoreTrimWhitespace')) {
|
||||
this.updateIgnoreTrimWhitespaceAction();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public getTitle(): string {
|
||||
protected getEditorMemento<T>(storageService: IStorageService, editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento<T> {
|
||||
return new EditorMemento(this.getId(), key, Object.create(null), limit, editorGroupService); // do not persist in storage as diff editors are never persisted
|
||||
}
|
||||
|
||||
getTitle(): string {
|
||||
if (this.input) {
|
||||
return this.input.getName();
|
||||
}
|
||||
@@ -81,7 +83,7 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
return nls.localize('textDiffEditor', "Text Diff Editor");
|
||||
}
|
||||
|
||||
public createEditorControl(parent: HTMLElement, configuration: IEditorOptions): IDiffEditor {
|
||||
createEditorControl(parent: HTMLElement, configuration: ICodeEditorOptions): IDiffEditor {
|
||||
|
||||
// Actions
|
||||
this.nextDiffAction = new NavigateAction(this, true);
|
||||
@@ -89,55 +91,10 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
this.toggleIgnoreTrimWhitespaceAction = new ToggleIgnoreTrimWhitespaceAction(this._actualConfigurationService);
|
||||
this.updateIgnoreTrimWhitespaceAction();
|
||||
|
||||
// Support navigation within the diff editor by overriding the editor service within
|
||||
const delegatingEditorService = this.instantiationService.createInstance(DelegatingWorkbenchEditorService);
|
||||
delegatingEditorService.setEditorOpenHandler((input: EditorInput, options?: EditorOptions, arg3?: any) => {
|
||||
|
||||
// Check if arg4 is a position argument that differs from this editors position
|
||||
if (types.isUndefinedOrNull(arg3) || arg3 === false || arg3 === this.position) {
|
||||
const activeDiffInput = <DiffEditorInput>this.input;
|
||||
if (input && options && activeDiffInput) {
|
||||
|
||||
// Input matches modified side of the diff editor: perform the action on modified side
|
||||
if (input.matches(activeDiffInput.modifiedInput)) {
|
||||
return this.setInput(this.input, options).then(() => this);
|
||||
}
|
||||
|
||||
// Input matches original side of the diff editor: perform the action on original side
|
||||
else if (input.matches(activeDiffInput.originalInput)) {
|
||||
const originalEditor = this.getControl().getOriginalEditor();
|
||||
if (options instanceof TextEditorOptions) {
|
||||
(<TextEditorOptions>options).apply(originalEditor, ScrollType.Smooth);
|
||||
|
||||
return TPromise.as(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TPromise.as(null);
|
||||
});
|
||||
|
||||
// Create a special child of instantiator that will delegate all calls to openEditor() to the same diff editor if the input matches with the modified one
|
||||
const diffEditorInstantiator = this.instantiationService.createChild(new ServiceCollection([IWorkbenchEditorService, delegatingEditorService]));
|
||||
|
||||
return diffEditorInstantiator.createInstance(DiffEditorWidget, parent, configuration);
|
||||
return this.instantiationService.createInstance(DiffEditorWidget, parent, configuration);
|
||||
}
|
||||
|
||||
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
|
||||
|
||||
// Return early for same input unless we force to open
|
||||
const forceOpen = options && options.forceOpen;
|
||||
if (!forceOpen && input.matches(this.input)) {
|
||||
|
||||
// Still apply options if any (avoiding instanceof here for a reason, do not change!)
|
||||
const textOptions = <TextEditorOptions>options;
|
||||
if (textOptions && types.isFunction(textOptions.apply)) {
|
||||
textOptions.apply(<IDiffEditor>this.getControl(), ScrollType.Smooth);
|
||||
}
|
||||
|
||||
return TPromise.wrap<void>(null);
|
||||
}
|
||||
setInput(input: EditorInput, options: EditorOptions, token: CancellationToken): Thenable<void> {
|
||||
|
||||
// Dispose previous diff navigator
|
||||
this.diffNavigatorDisposables = dispose(this.diffNavigatorDisposables);
|
||||
@@ -146,27 +103,28 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
this.saveTextDiffEditorViewState(this.input);
|
||||
|
||||
// Set input and resolve
|
||||
return super.setInput(input, options).then(() => {
|
||||
return input.resolve(true).then(resolvedModel => {
|
||||
return super.setInput(input, options, token).then(() => {
|
||||
return input.resolve().then(resolvedModel => {
|
||||
|
||||
// Check for cancellation
|
||||
if (token.isCancellationRequested) {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
// Assert Model Instance
|
||||
if (!(resolvedModel instanceof TextDiffEditorModel) && this.openAsBinary(input, options)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Assert that the current input is still the one we expect. This prevents a race condition when loading a diff takes long and another input was set meanwhile
|
||||
if (!this.input || this.input !== input) {
|
||||
return null;
|
||||
return void 0;
|
||||
}
|
||||
|
||||
// Set Editor Model
|
||||
const diffEditor = <IDiffEditor>this.getControl();
|
||||
diffEditor.setModel((<TextDiffEditorModel>resolvedModel).textDiffEditorModel);
|
||||
const diffEditor = this.getControl();
|
||||
const resolvedDiffEditorModel = <TextDiffEditorModel>resolvedModel;
|
||||
diffEditor.setModel(resolvedDiffEditorModel.textDiffEditorModel);
|
||||
|
||||
// Apply Options from TextOptions
|
||||
let optionsGotApplied = false;
|
||||
if (options && types.isFunction((<TextEditorOptions>options).apply)) {
|
||||
optionsGotApplied = (<TextEditorOptions>options).apply(<IDiffEditor>diffEditor, ScrollType.Immediate);
|
||||
optionsGotApplied = (<TextEditorOptions>options).apply(diffEditor, ScrollType.Immediate);
|
||||
}
|
||||
|
||||
// Otherwise restore View State
|
||||
@@ -175,6 +133,7 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
hasPreviousViewState = this.restoreTextDiffEditorViewState(input);
|
||||
}
|
||||
|
||||
// Diff navigator
|
||||
this.diffNavigator = new DiffNavigator(diffEditor, {
|
||||
alwaysRevealFirst: !optionsGotApplied && !hasPreviousViewState // only reveal first change if we had no options or viewstate
|
||||
});
|
||||
@@ -185,7 +144,11 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
this.previousDiffAction.updateEnablement();
|
||||
}));
|
||||
|
||||
// Enablement of actions
|
||||
this.updateIgnoreTrimWhitespaceAction();
|
||||
|
||||
// Readonly flag
|
||||
diffEditor.updateOptions({ readOnly: resolvedDiffEditorModel.isReadonly() });
|
||||
}, error => {
|
||||
|
||||
// In case we tried to open a file and the response indicates that this is not a text file, fallback to binary diff.
|
||||
@@ -199,8 +162,11 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
});
|
||||
}
|
||||
|
||||
public supportsCenteredLayout(): boolean {
|
||||
return false;
|
||||
setOptions(options: EditorOptions): void {
|
||||
const textOptions = <TextEditorOptions>options;
|
||||
if (textOptions && types.isFunction(textOptions.apply)) {
|
||||
textOptions.apply(this.getControl(), ScrollType.Smooth);
|
||||
}
|
||||
}
|
||||
|
||||
private restoreTextDiffEditorViewState(input: EditorInput): boolean {
|
||||
@@ -243,7 +209,7 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
modifiedInput.setForceOpenAsBinary();
|
||||
}
|
||||
|
||||
this.editorService.openEditor(binaryDiffInput, options, this.position).done(null, onUnexpectedError);
|
||||
this.editorService.openEditor(binaryDiffInput, options, this.group);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -251,7 +217,7 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected computeConfiguration(configuration: IEditorConfiguration): IEditorOptions {
|
||||
protected computeConfiguration(configuration: IEditorConfiguration): ICodeEditorOptions {
|
||||
const editorConfiguration = super.computeConfiguration(configuration);
|
||||
|
||||
// Handle diff editor specially by merging in diffEditor configuration
|
||||
@@ -262,7 +228,7 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
return editorConfiguration;
|
||||
}
|
||||
|
||||
protected getConfigurationOverrides(): IEditorOptions {
|
||||
protected getConfigurationOverrides(): ICodeEditorOptions {
|
||||
const options: IDiffEditorOptions = super.getConfigurationOverrides();
|
||||
|
||||
options.readOnly = this.isReadOnly();
|
||||
@@ -304,7 +270,7 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
return (<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_IS_BINARY;
|
||||
}
|
||||
|
||||
public clearInput(): void {
|
||||
clearInput(): void {
|
||||
|
||||
// Dispose previous diff navigator
|
||||
this.diffNavigatorDisposables = dispose(this.diffNavigatorDisposables);
|
||||
@@ -319,11 +285,11 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
super.clearInput();
|
||||
}
|
||||
|
||||
public getDiffNavigator(): DiffNavigator {
|
||||
getDiffNavigator(): DiffNavigator {
|
||||
return this.diffNavigator;
|
||||
}
|
||||
|
||||
public getActions(): IAction[] {
|
||||
getActions(): IAction[] {
|
||||
return [
|
||||
this.toggleIgnoreTrimWhitespaceAction,
|
||||
this.previousDiffAction,
|
||||
@@ -331,7 +297,7 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
];
|
||||
}
|
||||
|
||||
public getControl(): IDiffEditor {
|
||||
getControl(): IDiffEditor {
|
||||
return super.getControl() as IDiffEditor;
|
||||
}
|
||||
|
||||
@@ -370,12 +336,8 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
}
|
||||
|
||||
private retrieveTextDiffEditorViewState(resource: URI): IDiffEditorViewState {
|
||||
const editor = getCodeOrDiffEditor(this).diffEditor;
|
||||
if (!editor) {
|
||||
return null; // not supported for non-diff editors
|
||||
}
|
||||
|
||||
const model = editor.getModel();
|
||||
const control = this.getControl();
|
||||
const model = control.getModel();
|
||||
if (!model || !model.modified || !model.original) {
|
||||
return null; // view state always needs a model
|
||||
}
|
||||
@@ -389,7 +351,7 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
return null; // prevent saving view state for a model that is not the expected one
|
||||
}
|
||||
|
||||
return editor.saveViewState();
|
||||
return control.saveViewState();
|
||||
}
|
||||
|
||||
private toDiffEditorViewStateResource(modelOrInput: IDiffEditorModel | DiffEditorInput): URI {
|
||||
@@ -412,7 +374,7 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
return URI.from({ scheme: 'diff', path: `${btoa(original.toString())}${btoa(modified.toString())}` });
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
dispose(): void {
|
||||
this.diffNavigatorDisposables = dispose(this.diffNavigatorDisposables);
|
||||
|
||||
super.dispose();
|
||||
@@ -437,7 +399,7 @@ class NavigateAction extends Action {
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
run(): TPromise<any> {
|
||||
if (this.next) {
|
||||
this.editor.getDiffNavigator().next();
|
||||
} else {
|
||||
@@ -447,7 +409,7 @@ class NavigateAction extends Action {
|
||||
return null;
|
||||
}
|
||||
|
||||
public updateEnablement(): void {
|
||||
updateEnablement(): void {
|
||||
this.enabled = this.editor.getDiffNavigator().canNavigate();
|
||||
}
|
||||
}
|
||||
@@ -464,12 +426,12 @@ class ToggleIgnoreTrimWhitespaceAction extends Action {
|
||||
this.label = nls.localize('toggleIgnoreTrimWhitespace.label', "Ignore Trim Whitespace");
|
||||
}
|
||||
|
||||
public updateClassName(ignoreTrimWhitespace: boolean): void {
|
||||
updateClassName(ignoreTrimWhitespace: boolean): void {
|
||||
this._isChecked = ignoreTrimWhitespace;
|
||||
this.class = `textdiff-editor-action toggleIgnoreTrimWhitespace${this._isChecked ? ' is-checked' : ''}`;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
run(): TPromise<any> {
|
||||
this._configurationService.updateValue(`diffEditor.ignoreTrimWhitespace`, !this._isChecked);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -6,28 +6,26 @@
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { CodeEditor } from 'vs/editor/browser/codeEditor';
|
||||
import { EditorInput, EditorOptions, EditorViewStateMemento } from 'vs/workbench/common/editor';
|
||||
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||
import { EditorInput, EditorOptions, IEditorMemento, ITextEditor } from 'vs/workbench/common/editor';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { IEditorViewState, IEditor } from 'vs/editor/common/editorCommon';
|
||||
import { Position } from 'vs/platform/editor/common/editor';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { Scope } from 'vs/workbench/common/memento';
|
||||
import { getCodeEditor, getCodeOrDiffEditor } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { ITextFileService, SaveReason, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { isDiffEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { isDiffEditor, isCodeEditor, ICodeEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
const TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'textEditorViewState';
|
||||
|
||||
@@ -40,12 +38,12 @@ export interface IEditorConfiguration {
|
||||
* The base class of editors that leverage the text editor for the editing experience. This class is only intended to
|
||||
* be subclassed and not instantiated.
|
||||
*/
|
||||
export abstract class BaseTextEditor extends BaseEditor {
|
||||
export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
|
||||
private editorControl: IEditor;
|
||||
private _editorContainer: HTMLElement;
|
||||
private hasPendingConfigurationChange: boolean;
|
||||
private lastAppliedEditorOptions: IEditorOptions;
|
||||
private editorViewStateMemento: EditorViewStateMemento<IEditorViewState>;
|
||||
private editorMemento: IEditorMemento<IEditorViewState>;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@@ -55,13 +53,14 @@ export abstract class BaseTextEditor extends BaseEditor {
|
||||
@ITextResourceConfigurationService private readonly _configurationService: ITextResourceConfigurationService,
|
||||
@IThemeService protected themeService: IThemeService,
|
||||
@ITextFileService private readonly _textFileService: ITextFileService,
|
||||
@IEditorGroupService protected editorGroupService: IEditorGroupService
|
||||
@IEditorService protected editorService: IEditorService,
|
||||
@IEditorGroupsService protected editorGroupService: IEditorGroupsService,
|
||||
) {
|
||||
super(id, telemetryService, themeService);
|
||||
|
||||
this.editorViewStateMemento = new EditorViewStateMemento<IEditorViewState>(this.getMemento(storageService, Scope.WORKSPACE), TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100);
|
||||
this.editorMemento = this.getEditorMemento<IEditorViewState>(storageService, editorGroupService, TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100);
|
||||
|
||||
this.toUnbind.push(this.configurationService.onDidChangeConfiguration(e => this.handleConfigurationChangeEvent(this.configurationService.getValue<IEditorConfiguration>(this.getResource()))));
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => this.handleConfigurationChangeEvent(this.configurationService.getValue<IEditorConfiguration>(this.getResource()))));
|
||||
}
|
||||
|
||||
protected get instantiationService(): IInstantiationService {
|
||||
@@ -107,8 +106,10 @@ export abstract class BaseTextEditor extends BaseEditor {
|
||||
let ariaLabel = this.getAriaLabel();
|
||||
|
||||
// Apply group information to help identify in which group we are
|
||||
if (ariaLabel && typeof this.position === 'number') {
|
||||
ariaLabel = nls.localize('editorLabelWithGroup', "{0}, Group {1}.", ariaLabel, this.position + 1);
|
||||
if (ariaLabel) {
|
||||
if (this.group) {
|
||||
ariaLabel = nls.localize('editorLabelWithGroup', "{0}, {1}.", ariaLabel, this.group.label);
|
||||
}
|
||||
}
|
||||
|
||||
return ariaLabel;
|
||||
@@ -129,25 +130,25 @@ export abstract class BaseTextEditor extends BaseEditor {
|
||||
|
||||
// Editor for Text
|
||||
this._editorContainer = parent;
|
||||
this.editorControl = this.createEditorControl(parent, this.computeConfiguration(this.configurationService.getValue<IEditorConfiguration>(this.getResource())));
|
||||
this.editorControl = this._register(this.createEditorControl(parent, this.computeConfiguration(this.configurationService.getValue<IEditorConfiguration>(this.getResource()))));
|
||||
|
||||
// Model & Language changes
|
||||
const codeEditor = getCodeEditor(this);
|
||||
const codeEditor = getCodeEditor(this.editorControl);
|
||||
if (codeEditor) {
|
||||
this.toUnbind.push(codeEditor.onDidChangeModelLanguage(e => this.updateEditorConfiguration()));
|
||||
this.toUnbind.push(codeEditor.onDidChangeModel(e => this.updateEditorConfiguration()));
|
||||
this._register(codeEditor.onDidChangeModelLanguage(e => this.updateEditorConfiguration()));
|
||||
this._register(codeEditor.onDidChangeModel(e => this.updateEditorConfiguration()));
|
||||
}
|
||||
|
||||
// Application & Editor focus change to respect auto save settings
|
||||
if (isCodeEditor(this.editorControl)) {
|
||||
this.toUnbind.push(this.editorControl.onDidBlurEditor(() => this.onEditorFocusLost()));
|
||||
this._register(this.editorControl.onDidBlurEditorWidget(() => this.onEditorFocusLost()));
|
||||
} else if (isDiffEditor(this.editorControl)) {
|
||||
this.toUnbind.push(this.editorControl.getOriginalEditor().onDidBlurEditor(() => this.onEditorFocusLost()));
|
||||
this.toUnbind.push(this.editorControl.getModifiedEditor().onDidBlurEditor(() => this.onEditorFocusLost()));
|
||||
this._register(this.editorControl.getOriginalEditor().onDidBlurEditorWidget(() => this.onEditorFocusLost()));
|
||||
this._register(this.editorControl.getModifiedEditor().onDidBlurEditorWidget(() => this.onEditorFocusLost()));
|
||||
}
|
||||
|
||||
this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorFocusLost()));
|
||||
this.toUnbind.push(DOM.addDisposableListener(window, DOM.EventType.BLUR, () => this.onWindowFocusLost()));
|
||||
this._register(this.editorService.onDidActiveEditorChange(() => this.onEditorFocusLost()));
|
||||
this._register(DOM.addDisposableListener(window, DOM.EventType.BLUR, () => this.onWindowFocusLost()));
|
||||
}
|
||||
|
||||
private onEditorFocusLost(): void {
|
||||
@@ -182,11 +183,11 @@ export abstract class BaseTextEditor extends BaseEditor {
|
||||
protected createEditorControl(parent: HTMLElement, configuration: IEditorOptions): IEditor {
|
||||
|
||||
// Use a getter for the instantiation service since some subclasses might use scoped instantiation services
|
||||
return this.instantiationService.createInstance(CodeEditor, parent, configuration);
|
||||
return this.instantiationService.createInstance(CodeEditorWidget, parent, configuration, {});
|
||||
}
|
||||
|
||||
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
|
||||
return super.setInput(input, options).then(() => {
|
||||
setInput(input: EditorInput, options: EditorOptions, token: CancellationToken): Thenable<void> {
|
||||
return super.setInput(input, options, token).then(() => {
|
||||
|
||||
// Update editor options after having set the input. We do this because there can be
|
||||
// editor input specific options (e.g. an ARIA label depending on the input showing)
|
||||
@@ -195,16 +196,7 @@ export abstract class BaseTextEditor extends BaseEditor {
|
||||
});
|
||||
}
|
||||
|
||||
public changePosition(position: Position): void {
|
||||
super.changePosition(position);
|
||||
|
||||
// Make sure to update ARIA label if the position of this editor changed
|
||||
if (this.editorControl) {
|
||||
this.editorControl.updateOptions({ ariaLabel: this.computeAriaLabel() });
|
||||
}
|
||||
}
|
||||
|
||||
protected setEditorVisible(visible: boolean, position: Position = null): void {
|
||||
protected setEditorVisible(visible: boolean, group: IEditorGroup): void {
|
||||
|
||||
// Pass on to Editor
|
||||
if (visible) {
|
||||
@@ -214,20 +206,20 @@ export abstract class BaseTextEditor extends BaseEditor {
|
||||
this.editorControl.onHide();
|
||||
}
|
||||
|
||||
super.setEditorVisible(visible, position);
|
||||
super.setEditorVisible(visible, group);
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
focus(): void {
|
||||
this.editorControl.focus();
|
||||
}
|
||||
|
||||
public layout(dimension: DOM.Dimension): void {
|
||||
layout(dimension: DOM.Dimension): void {
|
||||
|
||||
// Pass on to Editor
|
||||
this.editorControl.layout(dimension);
|
||||
}
|
||||
|
||||
public getControl(): IEditor {
|
||||
getControl(): IEditor {
|
||||
return this.editorControl;
|
||||
}
|
||||
|
||||
@@ -240,16 +232,12 @@ export abstract class BaseTextEditor extends BaseEditor {
|
||||
return;
|
||||
}
|
||||
|
||||
this.editorViewStateMemento.saveState(resource, this.position, editorViewState);
|
||||
this.editorMemento.saveState(this.group, resource, editorViewState);
|
||||
}
|
||||
|
||||
protected retrieveTextEditorViewState(resource: URI): IEditorViewState {
|
||||
const editor = getCodeOrDiffEditor(this).codeEditor;
|
||||
if (!editor) {
|
||||
return null; // not supported for diff editors
|
||||
}
|
||||
|
||||
const model = editor.getModel();
|
||||
const control = this.getControl() as ICodeEditor;
|
||||
const model = control.getModel();
|
||||
if (!model) {
|
||||
return null; // view state always needs a model
|
||||
}
|
||||
@@ -263,7 +251,7 @@ export abstract class BaseTextEditor extends BaseEditor {
|
||||
return null; // prevent saving view state for a model that is not the expected one
|
||||
}
|
||||
|
||||
return editor.saveViewState();
|
||||
return control.saveViewState();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,7 +259,7 @@ export abstract class BaseTextEditor extends BaseEditor {
|
||||
*/
|
||||
protected clearTextEditorViewState(resources: URI[]): void {
|
||||
resources.forEach(resource => {
|
||||
this.editorViewStateMemento.clearState(resource);
|
||||
this.editorMemento.clearState(resource);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -279,7 +267,7 @@ export abstract class BaseTextEditor extends BaseEditor {
|
||||
* Loads the text editor view state for the given resource and returns it.
|
||||
*/
|
||||
protected loadTextEditorViewState(resource: URI): IEditorViewState {
|
||||
return this.editorViewStateMemento.loadState(resource, this.position);
|
||||
return this.editorMemento.loadState(this.group, resource);
|
||||
}
|
||||
|
||||
private updateEditorConfiguration(configuration = this.configurationService.getValue<IEditorConfiguration>(this.getResource())): void {
|
||||
@@ -304,7 +292,7 @@ export abstract class BaseTextEditor extends BaseEditor {
|
||||
}
|
||||
|
||||
protected getResource(): URI {
|
||||
const codeEditor = getCodeEditor(this);
|
||||
const codeEditor = getCodeEditor(this.editorControl);
|
||||
if (codeEditor) {
|
||||
const model = codeEditor.getModel();
|
||||
if (model) {
|
||||
@@ -321,17 +309,8 @@ export abstract class BaseTextEditor extends BaseEditor {
|
||||
|
||||
protected abstract getAriaLabel(): string;
|
||||
|
||||
protected saveMemento(): void {
|
||||
|
||||
// ensure to first save our view state memento
|
||||
this.editorViewStateMemento.save();
|
||||
|
||||
super.saveMemento();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
dispose(): void {
|
||||
this.lastAppliedEditorOptions = void 0;
|
||||
this.editorControl.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -19,10 +19,12 @@ import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { once } from 'vs/base/common/event';
|
||||
import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
/**
|
||||
* An editor implementation that is capable of showing the contents of resource inputs. Uses
|
||||
@@ -37,13 +39,14 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
|
||||
@IStorageService storageService: IStorageService,
|
||||
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService,
|
||||
@ITextFileService textFileService: ITextFileService
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService,
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@IEditorService editorService: IEditorService
|
||||
) {
|
||||
super(id, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorGroupService);
|
||||
super(id, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService);
|
||||
}
|
||||
|
||||
public getTitle(): string {
|
||||
getTitle(): string {
|
||||
if (this.input) {
|
||||
return this.input.getName();
|
||||
}
|
||||
@@ -51,38 +54,25 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
|
||||
return nls.localize('textEditor', "Text Editor");
|
||||
}
|
||||
|
||||
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
|
||||
|
||||
// Return early for same input unless we force to open
|
||||
const forceOpen = options && options.forceOpen;
|
||||
if (!forceOpen && input.matches(this.input)) {
|
||||
|
||||
// Still apply options if any (avoiding instanceof here for a reason, do not change!)
|
||||
const textOptions = <TextEditorOptions>options;
|
||||
if (textOptions && types.isFunction(textOptions.apply)) {
|
||||
textOptions.apply(this.getControl(), ScrollType.Smooth);
|
||||
}
|
||||
|
||||
return TPromise.wrap<void>(null);
|
||||
}
|
||||
setInput(input: EditorInput, options: EditorOptions, token: CancellationToken): Thenable<void> {
|
||||
|
||||
// Remember view settings if input changes
|
||||
this.saveTextResourceEditorViewState(this.input);
|
||||
|
||||
// Set input and resolve
|
||||
return super.setInput(input, options).then(() => {
|
||||
return input.resolve(true).then((resolvedModel: EditorModel) => {
|
||||
return super.setInput(input, options, token).then(() => {
|
||||
return input.resolve().then((resolvedModel: EditorModel) => {
|
||||
|
||||
// Check for cancellation
|
||||
if (token.isCancellationRequested) {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
// Assert Model instance
|
||||
if (!(resolvedModel instanceof BaseTextEditorModel)) {
|
||||
return TPromise.wrapError<void>(new Error('Unable to open file as text'));
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Set Editor Model
|
||||
const textEditor = this.getControl();
|
||||
const textEditorModel = resolvedModel.textEditorModel;
|
||||
@@ -114,6 +104,13 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
|
||||
}
|
||||
}
|
||||
|
||||
setOptions(options: EditorOptions): void {
|
||||
const textOptions = <TextEditorOptions>options;
|
||||
if (textOptions && types.isFunction(textOptions.apply)) {
|
||||
textOptions.apply(this.getControl(), ScrollType.Smooth);
|
||||
}
|
||||
}
|
||||
|
||||
protected getConfigurationOverrides(): IEditorOptions {
|
||||
const options = super.getConfigurationOverrides();
|
||||
|
||||
@@ -139,18 +136,23 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
|
||||
|
||||
/**
|
||||
* Reveals the last line of this editor if it has a model set.
|
||||
* When smart is true only scroll if the cursor is currently on the last line of the output panel.
|
||||
* This allows users to click on the output panel to stop scrolling when they see something of interest.
|
||||
* To resume, they should scroll to the end of the output panel again.
|
||||
*/
|
||||
public revealLastLine(): void {
|
||||
revealLastLine(smart: boolean): void {
|
||||
const codeEditor = <ICodeEditor>this.getControl();
|
||||
const model = codeEditor.getModel();
|
||||
|
||||
if (model) {
|
||||
const lastLine = model.getLineCount();
|
||||
codeEditor.revealPosition({ lineNumber: lastLine, column: model.getLineMaxColumn(lastLine) }, ScrollType.Smooth);
|
||||
if (!smart || codeEditor.getPosition().lineNumber === lastLine) {
|
||||
codeEditor.revealPosition({ lineNumber: lastLine, column: model.getLineMaxColumn(lastLine) }, ScrollType.Smooth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public clearInput(): void {
|
||||
clearInput(): void {
|
||||
|
||||
// Keep editor view state in settings to restore when coming back
|
||||
this.saveTextResourceEditorViewState(this.input);
|
||||
@@ -161,7 +163,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
|
||||
super.clearInput();
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
shutdown(): void {
|
||||
|
||||
// Save View State (only for untitled)
|
||||
if (this.input instanceof UntitledEditorInput) {
|
||||
@@ -198,7 +200,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
|
||||
|
||||
export class TextResourceEditor extends AbstractTextResourceEditor {
|
||||
|
||||
public static readonly ID = 'workbench.editors.textResourceEditor';
|
||||
static readonly ID = 'workbench.editors.textResourceEditor';
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@@ -206,9 +208,10 @@ export class TextResourceEditor extends AbstractTextResourceEditor {
|
||||
@IStorageService storageService: IStorageService,
|
||||
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService,
|
||||
@ITextFileService textFileService: ITextFileService
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@IEditorService editorService: IEditorService,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorGroupService, textFileService);
|
||||
super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorGroupService, textFileService, editorService);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,234 +5,133 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/titlecontrol';
|
||||
import * as nls from 'vs/nls';
|
||||
import { prepareActions } from 'vs/workbench/browser/actions';
|
||||
import { IAction, Action, IRunEvent } from 'vs/base/common/actions';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { IEditorStacksModel, IEditorGroup, IEditorIdentifier, EditorInput, IStacksModelChangeEvent, toResource, IEditorCommandsContext } from 'vs/workbench/common/editor';
|
||||
import { IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { applyDragImage } from 'vs/base/browser/dnd';
|
||||
import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ActionsOrientation, IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
|
||||
import { Action, IAction, IRunEvent } from 'vs/base/common/actions';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import 'vs/css!./media/titlecontrol';
|
||||
import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { localize } from 'vs/nls';
|
||||
import { createActionItem, fillInActionBarActions, fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem';
|
||||
import { ExecuteCommandAction, IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { SplitEditorAction, CloseOneEditorAction } from 'vs/workbench/browser/parts/editor/editorActions';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { createActionItem, fillInActions } from 'vs/platform/actions/browser/menuItemActionItem';
|
||||
import { IMenuService, MenuId, IMenu, ExecuteCommandAction } from 'vs/platform/actions/common/actions';
|
||||
import { ResourceContextKey } from 'vs/workbench/common/resources';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { Themable } from 'vs/workbench/common/theme';
|
||||
import { isDiffEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { Dimension, findParentWithClass } from 'vs/base/browser/dom';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { listActiveSelectionBackground, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { prepareActions } from 'vs/workbench/browser/actions';
|
||||
import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, fillResourceDataTransfers, LocalSelectionTransfer } from 'vs/workbench/browser/dnd';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs';
|
||||
import { BreadcrumbsControl, IBreadcrumbsControlOptions } from 'vs/workbench/browser/parts/editor/breadcrumbsControl';
|
||||
import { EDITOR_TITLE_HEIGHT, IEditorGroupsAccessor, IEditorGroupView, IEditorPartOptions } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput, toResource } from 'vs/workbench/common/editor';
|
||||
import { ResourceContextKey } from 'vs/workbench/common/resources';
|
||||
import { Themable } from 'vs/workbench/common/theme';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
|
||||
export interface IToolbarActions {
|
||||
primary: IAction[];
|
||||
secondary: IAction[];
|
||||
}
|
||||
|
||||
export interface ITitleAreaControl {
|
||||
setContext(group: IEditorGroup): void;
|
||||
hasContext(): boolean;
|
||||
allowDragging(element: HTMLElement): boolean;
|
||||
setDragged(dragged: boolean): void;
|
||||
create(parent: HTMLElement): void;
|
||||
getContainer(): HTMLElement;
|
||||
refresh(instant?: boolean): void;
|
||||
update(instant?: boolean): void;
|
||||
updateEditorActionsToolbar(): void;
|
||||
layout(dimension: Dimension): void;
|
||||
dispose(): void;
|
||||
}
|
||||
export abstract class TitleControl extends Themable {
|
||||
|
||||
export abstract class TitleControl extends Themable implements ITitleAreaControl {
|
||||
protected readonly groupTransfer = LocalSelectionTransfer.getInstance<DraggedEditorGroupIdentifier>();
|
||||
protected readonly editorTransfer = LocalSelectionTransfer.getInstance<DraggedEditorIdentifier>();
|
||||
|
||||
protected stacks: IEditorStacksModel;
|
||||
protected context: IEditorGroup;
|
||||
|
||||
protected dragged: boolean;
|
||||
|
||||
protected closeOneEditorAction: CloseOneEditorAction;
|
||||
protected splitEditorAction: SplitEditorAction;
|
||||
|
||||
private parent: HTMLElement;
|
||||
protected breadcrumbsControl: BreadcrumbsControl;
|
||||
|
||||
private currentPrimaryEditorActionIds: string[] = [];
|
||||
private currentSecondaryEditorActionIds: string[] = [];
|
||||
protected editorActionsToolbar: ToolBar;
|
||||
|
||||
private mapActionsToEditors: { [editorId: string]: IToolbarActions; };
|
||||
private titleAreaUpdateScheduler: RunOnceScheduler;
|
||||
private titleAreaToolbarUpdateScheduler: RunOnceScheduler;
|
||||
private refreshScheduled: boolean;
|
||||
private mapEditorToActions: Map<string, IToolbarActions> = new Map();
|
||||
|
||||
private resourceContext: ResourceContextKey;
|
||||
private disposeOnEditorActions: IDisposable[] = [];
|
||||
private editorToolBarMenuDisposables: IDisposable[] = [];
|
||||
|
||||
private contextMenu: IMenu;
|
||||
|
||||
constructor(
|
||||
@IContextMenuService protected contextMenuService: IContextMenuService,
|
||||
parent: HTMLElement,
|
||||
protected accessor: IEditorGroupsAccessor,
|
||||
protected group: IEditorGroupView,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IInstantiationService protected instantiationService: IInstantiationService,
|
||||
@IWorkbenchEditorService protected editorService: IWorkbenchEditorService,
|
||||
@IEditorGroupService protected editorGroupService: IEditorGroupService,
|
||||
@IContextKeyService protected contextKeyService: IContextKeyService,
|
||||
@IKeybindingService protected keybindingService: IKeybindingService,
|
||||
@ITelemetryService protected telemetryService: ITelemetryService,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@IKeybindingService private keybindingService: IKeybindingService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService,
|
||||
@INotificationService private notificationService: INotificationService,
|
||||
@IMenuService protected menuService: IMenuService,
|
||||
@IMenuService private menuService: IMenuService,
|
||||
@IQuickOpenService protected quickOpenService: IQuickOpenService,
|
||||
@IThemeService protected themeService: IThemeService
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IExtensionService private extensionService: IExtensionService,
|
||||
@IConfigurationService protected configurationService: IConfigurationService
|
||||
) {
|
||||
super(themeService);
|
||||
|
||||
this.stacks = editorGroupService.getStacksModel();
|
||||
this.mapActionsToEditors = Object.create(null);
|
||||
|
||||
this.titleAreaUpdateScheduler = new RunOnceScheduler(() => this.onSchedule(), 0);
|
||||
this.toUnbind.push(this.titleAreaUpdateScheduler);
|
||||
|
||||
this.titleAreaToolbarUpdateScheduler = new RunOnceScheduler(() => this.updateEditorActionsToolbar(), 0);
|
||||
this.toUnbind.push(this.titleAreaToolbarUpdateScheduler);
|
||||
|
||||
this.resourceContext = instantiationService.createInstance(ResourceContextKey);
|
||||
this.contextMenu = this._register(this.menuService.createMenu(MenuId.EditorTitleContext, this.contextKeyService));
|
||||
|
||||
this.contextMenu = this.menuService.createMenu(MenuId.EditorTitleContext, this.contextKeyService);
|
||||
this.toUnbind.push(this.contextMenu);
|
||||
|
||||
this.initActions(this.instantiationService);
|
||||
this.create(parent);
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
public setDragged(dragged: boolean): void {
|
||||
this.dragged = dragged;
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.toUnbind.push(this.stacks.onModelChanged(e => this.onStacksChanged(e)));
|
||||
this._register(this.extensionService.onDidRegisterExtensions(() => this.updateEditorActionsToolbar()));
|
||||
}
|
||||
|
||||
private onStacksChanged(e: IStacksModelChangeEvent): void {
|
||||
if (e.structural) {
|
||||
this.updateSplitActionEnablement();
|
||||
protected abstract create(parent: HTMLElement): void;
|
||||
|
||||
protected createBreadcrumbsControl(container: HTMLElement, options: IBreadcrumbsControlOptions): void {
|
||||
const config = this._register(BreadcrumbsConfig.IsEnabled.bindTo(this.configurationService));
|
||||
config.onDidChange(value => {
|
||||
if (!value && this.breadcrumbsControl) {
|
||||
this.breadcrumbsControl.dispose();
|
||||
this.breadcrumbsControl = undefined;
|
||||
this.group.relayout();
|
||||
} else if (value && !this.breadcrumbsControl) {
|
||||
this.breadcrumbsControl = this.instantiationService.createInstance(BreadcrumbsControl, container, options, this.group);
|
||||
this.breadcrumbsControl.update();
|
||||
this.group.relayout();
|
||||
}
|
||||
});
|
||||
if (config.value) {
|
||||
this.breadcrumbsControl = this.instantiationService.createInstance(BreadcrumbsControl, container, options, this.group);
|
||||
}
|
||||
}
|
||||
|
||||
private updateSplitActionEnablement(): void {
|
||||
if (!this.context) {
|
||||
return;
|
||||
}
|
||||
|
||||
const groupCount = this.stacks.groups.length;
|
||||
|
||||
// Split editor
|
||||
this.splitEditorAction.enabled = groupCount < 3;
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
super.updateStyles();
|
||||
|
||||
this.update(true); // run an update when the theme changes to new styles
|
||||
}
|
||||
|
||||
private onSchedule(): void {
|
||||
if (this.refreshScheduled) {
|
||||
this.doRefresh();
|
||||
} else {
|
||||
this.doUpdate();
|
||||
}
|
||||
|
||||
this.refreshScheduled = false;
|
||||
}
|
||||
|
||||
public setContext(group: IEditorGroup): void {
|
||||
this.context = group;
|
||||
|
||||
this.editorActionsToolbar.context = { groupId: group ? group.id : void 0 } as IEditorCommandsContext;
|
||||
}
|
||||
|
||||
public hasContext(): boolean {
|
||||
return !!this.context;
|
||||
}
|
||||
|
||||
public update(instant?: boolean): void {
|
||||
if (instant) {
|
||||
this.titleAreaUpdateScheduler.cancel();
|
||||
this.onSchedule();
|
||||
} else {
|
||||
this.titleAreaUpdateScheduler.schedule();
|
||||
}
|
||||
|
||||
this.titleAreaToolbarUpdateScheduler.cancel(); // a title area update will always refresh the toolbar too
|
||||
}
|
||||
|
||||
public refresh(instant?: boolean) {
|
||||
this.refreshScheduled = true;
|
||||
|
||||
if (instant) {
|
||||
this.titleAreaUpdateScheduler.cancel();
|
||||
this.onSchedule();
|
||||
} else {
|
||||
this.titleAreaUpdateScheduler.schedule();
|
||||
}
|
||||
|
||||
this.titleAreaToolbarUpdateScheduler.cancel(); // a title area update will always refresh the toolbar too
|
||||
}
|
||||
|
||||
public create(parent: HTMLElement): void {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public getContainer(): HTMLElement {
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
protected abstract doRefresh(): void;
|
||||
|
||||
protected doUpdate(): void {
|
||||
this.doRefresh();
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
// Subclasses can opt in to react on layout
|
||||
}
|
||||
|
||||
public allowDragging(element: HTMLElement): boolean {
|
||||
return !findParentWithClass(element, 'monaco-action-bar', 'one-editor-silo');
|
||||
}
|
||||
|
||||
protected initActions(services: IInstantiationService): void {
|
||||
this.closeOneEditorAction = services.createInstance(CloseOneEditorAction, CloseOneEditorAction.ID, CloseOneEditorAction.LABEL);
|
||||
this.splitEditorAction = services.createInstance(SplitEditorAction, SplitEditorAction.ID, SplitEditorAction.LABEL);
|
||||
}
|
||||
|
||||
protected createEditorActionsToolBar(container: HTMLElement): void {
|
||||
this.editorActionsToolbar = new ToolBar(container, this.contextMenuService, {
|
||||
actionItemProvider: (action: Action) => this.actionItemProvider(action),
|
||||
const context = { groupId: this.group.id } as IEditorCommandsContext;
|
||||
|
||||
this.editorActionsToolbar = this._register(new ToolBar(container, this.contextMenuService, {
|
||||
actionItemProvider: action => this.actionItemProvider(action as Action),
|
||||
orientation: ActionsOrientation.HORIZONTAL,
|
||||
ariaLabel: nls.localize('araLabelEditorActions', "Editor actions"),
|
||||
getKeyBinding: (action) => this.getKeybinding(action)
|
||||
});
|
||||
ariaLabel: localize('araLabelEditorActions', "Editor actions"),
|
||||
getKeyBinding: action => this.getKeybinding(action),
|
||||
actionRunner: this._register(new EditorCommandsContextActionRunner(context))
|
||||
}));
|
||||
|
||||
// Context
|
||||
this.editorActionsToolbar.context = context;
|
||||
|
||||
// Action Run Handling
|
||||
this.toUnbind.push(this.editorActionsToolbar.actionRunner.onDidRun((e: IRunEvent) => {
|
||||
this._register(this.editorActionsToolbar.actionRunner.onDidRun((e: IRunEvent) => {
|
||||
|
||||
// Check for Error
|
||||
if (e.error && !errors.isPromiseCanceledError(e.error)) {
|
||||
this.notificationService.error(e.error);
|
||||
}
|
||||
// Notify for Error
|
||||
this.notificationService.error(e.error);
|
||||
|
||||
// Log in telemetry
|
||||
if (this.telemetryService) {
|
||||
@@ -247,20 +146,13 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
|
||||
}));
|
||||
}
|
||||
|
||||
protected actionItemProvider(action: Action): IActionItem {
|
||||
if (!this.context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const group = this.context;
|
||||
const position = this.stacks.positionOfGroup(group);
|
||||
const editor = this.editorService.getVisibleEditors()[position];
|
||||
|
||||
let actionItem: IActionItem;
|
||||
private actionItemProvider(action: Action): IActionItem {
|
||||
const activeControl = this.group.activeControl;
|
||||
|
||||
// Check Active Editor
|
||||
if (editor instanceof BaseEditor) {
|
||||
actionItem = editor.getActionItem(action);
|
||||
let actionItem: IActionItem;
|
||||
if (activeControl instanceof BaseEditor) {
|
||||
actionItem = activeControl.getActionItem(action);
|
||||
}
|
||||
|
||||
// Check extensions
|
||||
@@ -271,86 +163,13 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
|
||||
return actionItem;
|
||||
}
|
||||
|
||||
protected getEditorActions(identifier: IEditorIdentifier): IToolbarActions {
|
||||
const primary: IAction[] = [];
|
||||
const secondary: IAction[] = [];
|
||||
|
||||
const { group } = identifier;
|
||||
const position = this.stacks.positionOfGroup(group);
|
||||
|
||||
// Update the resource context
|
||||
this.resourceContext.set(group && toResource(group.activeEditor, { supportSideBySide: true }));
|
||||
|
||||
// Editor actions require the editor control to be there, so we retrieve it via service
|
||||
const control = this.editorService.getVisibleEditors()[position];
|
||||
if (control instanceof BaseEditor && control.input && typeof control.position === 'number') {
|
||||
|
||||
// Editor Control Actions
|
||||
let editorActions = this.mapActionsToEditors[control.getId()];
|
||||
if (!editorActions) {
|
||||
editorActions = { primary: control.getActions(), secondary: control.getSecondaryActions() };
|
||||
this.mapActionsToEditors[control.getId()] = editorActions;
|
||||
}
|
||||
primary.push(...editorActions.primary);
|
||||
secondary.push(...editorActions.secondary);
|
||||
|
||||
// MenuItems
|
||||
// TODO This isn't very proper but needed as we have failed to
|
||||
// use the correct context key service per editor only once. Don't
|
||||
// take this code as sample of how to work with menus
|
||||
this.disposeOnEditorActions = dispose(this.disposeOnEditorActions);
|
||||
const widget = control.getControl();
|
||||
const codeEditor = isCodeEditor(widget) && widget || isDiffEditor(widget) && widget.getModifiedEditor();
|
||||
const scopedContextKeyService = codeEditor && codeEditor.invokeWithinContext(accessor => accessor.get(IContextKeyService)) || this.contextKeyService;
|
||||
const titleBarMenu = this.menuService.createMenu(MenuId.EditorTitle, scopedContextKeyService);
|
||||
this.disposeOnEditorActions.push(titleBarMenu, titleBarMenu.onDidChange(_ => {
|
||||
// schedule the update for the title area toolbar only if no other
|
||||
// update to the title area is scheduled which will always also
|
||||
// update the toolbar
|
||||
if (!this.titleAreaUpdateScheduler.isScheduled()) {
|
||||
this.titleAreaToolbarUpdateScheduler.schedule();
|
||||
}
|
||||
}));
|
||||
|
||||
fillInActions(titleBarMenu, { arg: this.resourceContext.get(), shouldForwardArgs: true }, { primary, secondary }, this.contextMenuService);
|
||||
}
|
||||
|
||||
return { primary, secondary };
|
||||
}
|
||||
|
||||
public updateEditorActionsToolbar(): void {
|
||||
const group = this.context;
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
|
||||
const editor = group && group.activeEditor;
|
||||
const isActive = this.stacks.isActive(group);
|
||||
protected updateEditorActionsToolbar(): void {
|
||||
|
||||
// Update Editor Actions Toolbar
|
||||
let primaryEditorActions: IAction[] = [];
|
||||
let secondaryEditorActions: IAction[] = [];
|
||||
|
||||
const editorActions = this.getEditorActions({ group, editor });
|
||||
|
||||
// Primary actions only for the active group
|
||||
if (isActive) {
|
||||
primaryEditorActions = prepareActions(editorActions.primary);
|
||||
if (editor instanceof EditorInput && editor.supportsSplitEditor()) {
|
||||
this.updateSplitActionEnablement();
|
||||
primaryEditorActions.push(this.splitEditorAction);
|
||||
}
|
||||
}
|
||||
|
||||
secondaryEditorActions = prepareActions(editorActions.secondary);
|
||||
|
||||
const tabOptions = this.editorGroupService.getTabOptions();
|
||||
const { primaryEditorActions, secondaryEditorActions } = this.prepareEditorActions(this.getEditorActions());
|
||||
|
||||
// Only update if something actually has changed
|
||||
const primaryEditorActionIds = primaryEditorActions.map(a => a.id);
|
||||
if (!tabOptions.showTabs) {
|
||||
primaryEditorActionIds.push(this.closeOneEditorAction.id); // always show "Close" when tabs are disabled
|
||||
}
|
||||
|
||||
const secondaryEditorActionIds = secondaryEditorActions.map(a => a.id);
|
||||
if (
|
||||
!arrays.equals(primaryEditorActionIds, this.currentPrimaryEditorActionIds) ||
|
||||
@@ -360,15 +179,66 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
|
||||
) {
|
||||
this.editorActionsToolbar.setActions(primaryEditorActions, secondaryEditorActions)();
|
||||
|
||||
if (!tabOptions.showTabs) {
|
||||
this.editorActionsToolbar.addPrimaryAction(this.closeOneEditorAction)();
|
||||
}
|
||||
|
||||
this.currentPrimaryEditorActionIds = primaryEditorActionIds;
|
||||
this.currentSecondaryEditorActionIds = secondaryEditorActionIds;
|
||||
}
|
||||
}
|
||||
|
||||
protected prepareEditorActions(editorActions: IToolbarActions): { primaryEditorActions: IAction[]; secondaryEditorActions: IAction[]; } {
|
||||
let primaryEditorActions: IAction[];
|
||||
let secondaryEditorActions: IAction[];
|
||||
|
||||
// Primary actions only for the active group
|
||||
if (this.accessor.activeGroup === this.group) {
|
||||
primaryEditorActions = prepareActions(editorActions.primary);
|
||||
} else {
|
||||
primaryEditorActions = [];
|
||||
}
|
||||
|
||||
// Secondary actions for all groups
|
||||
secondaryEditorActions = prepareActions(editorActions.secondary);
|
||||
|
||||
return { primaryEditorActions, secondaryEditorActions };
|
||||
}
|
||||
|
||||
private getEditorActions(): IToolbarActions {
|
||||
const primary: IAction[] = [];
|
||||
const secondary: IAction[] = [];
|
||||
|
||||
// Dispose previous listeners
|
||||
this.editorToolBarMenuDisposables = dispose(this.editorToolBarMenuDisposables);
|
||||
|
||||
// Update the resource context
|
||||
this.resourceContext.set(toResource(this.group.activeEditor, { supportSideBySide: true }));
|
||||
|
||||
// Editor actions require the editor control to be there, so we retrieve it via service
|
||||
const activeControl = this.group.activeControl;
|
||||
if (activeControl instanceof BaseEditor) {
|
||||
|
||||
// Editor Control Actions
|
||||
let editorActions = this.mapEditorToActions.get(activeControl.getId());
|
||||
if (!editorActions) {
|
||||
editorActions = { primary: activeControl.getActions(), secondary: activeControl.getSecondaryActions() };
|
||||
this.mapEditorToActions.set(activeControl.getId(), editorActions);
|
||||
}
|
||||
primary.push(...editorActions.primary);
|
||||
secondary.push(...editorActions.secondary);
|
||||
|
||||
// Contributed Actions
|
||||
const codeEditor = getCodeEditor(activeControl.getControl());
|
||||
const scopedContextKeyService = codeEditor && codeEditor.invokeWithinContext(accessor => accessor.get(IContextKeyService)) || this.contextKeyService;
|
||||
const titleBarMenu = this.menuService.createMenu(MenuId.EditorTitle, scopedContextKeyService);
|
||||
this.editorToolBarMenuDisposables.push(titleBarMenu);
|
||||
this.editorToolBarMenuDisposables.push(titleBarMenu.onDidChange(() => {
|
||||
this.updateEditorActionsToolbar(); // Update editor toolbar whenever contributed actions change
|
||||
}));
|
||||
|
||||
fillInActionBarActions(titleBarMenu, { arg: this.resourceContext.get(), shouldForwardArgs: true }, { primary, secondary });
|
||||
}
|
||||
|
||||
return { primary, secondary };
|
||||
}
|
||||
|
||||
protected clearEditorActionsToolbar(): void {
|
||||
this.editorActionsToolbar.setActions([], [])();
|
||||
|
||||
@@ -376,11 +246,46 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
|
||||
this.currentSecondaryEditorActionIds = [];
|
||||
}
|
||||
|
||||
protected onContextMenu(identifier: IEditorIdentifier, e: Event, node: HTMLElement): void {
|
||||
protected enableGroupDragging(element: HTMLElement): void {
|
||||
|
||||
// Drag start
|
||||
this._register(addDisposableListener(element, EventType.DRAG_START, (e: DragEvent) => {
|
||||
if (e.target !== element) {
|
||||
return; // only if originating from tabs container
|
||||
}
|
||||
|
||||
// Set editor group as transfer
|
||||
this.groupTransfer.setData([new DraggedEditorGroupIdentifier(this.group.id)], DraggedEditorGroupIdentifier.prototype);
|
||||
e.dataTransfer.effectAllowed = 'copyMove';
|
||||
|
||||
// If tabs are disabled, treat dragging as if an editor tab was dragged
|
||||
if (!this.accessor.partOptions.showTabs) {
|
||||
const resource = toResource(this.group.activeEditor, { supportSideBySide: true });
|
||||
if (resource) {
|
||||
this.instantiationService.invokeFunction(fillResourceDataTransfers, [resource], e);
|
||||
}
|
||||
}
|
||||
|
||||
// Drag Image
|
||||
let label = this.group.activeEditor.getName();
|
||||
if (this.accessor.partOptions.showTabs && this.group.count > 1) {
|
||||
label = localize('draggedEditorGroup', "{0} (+{1})", label, this.group.count - 1);
|
||||
}
|
||||
|
||||
applyDragImage(e, label, 'monaco-editor-group-drag-image');
|
||||
}));
|
||||
|
||||
// Drag end
|
||||
this._register(addDisposableListener(element, EventType.DRAG_END, () => {
|
||||
this.groupTransfer.clearData(DraggedEditorGroupIdentifier.prototype);
|
||||
}));
|
||||
}
|
||||
|
||||
protected onContextMenu(editor: IEditorInput, e: Event, node: HTMLElement): void {
|
||||
|
||||
// Update the resource context
|
||||
const currentContext = this.resourceContext.get();
|
||||
this.resourceContext.set(toResource(identifier.editor, { supportSideBySide: true }));
|
||||
this.resourceContext.set(toResource(editor, { supportSideBySide: true }));
|
||||
|
||||
// Find target anchor
|
||||
let anchor: HTMLElement | { x: number, y: number } = node;
|
||||
@@ -391,24 +296,21 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
|
||||
|
||||
// Fill in contributed actions
|
||||
const actions: IAction[] = [];
|
||||
fillInActions(this.contextMenu, { shouldForwardArgs: true, arg: this.resourceContext.get() }, actions, this.contextMenuService);
|
||||
fillInContextMenuActions(this.contextMenu, { shouldForwardArgs: true, arg: this.resourceContext.get() }, actions, this.contextMenuService);
|
||||
|
||||
// Show it
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => TPromise.as(actions),
|
||||
getActionsContext: () => ({ groupId: identifier.group.id, editorIndex: identifier.group.indexOf(identifier.editor) } as IEditorCommandsContext),
|
||||
getActionsContext: () => ({ groupId: this.group.id, editorIndex: this.group.getIndexOfEditor(editor) } as IEditorCommandsContext),
|
||||
getKeyBinding: (action) => this.getKeybinding(action),
|
||||
onHide: (cancel) => {
|
||||
onHide: () => {
|
||||
|
||||
// restore previous context
|
||||
this.resourceContext.set(currentContext);
|
||||
|
||||
// restore focus to active editor if any
|
||||
const editor = this.editorService.getActiveEditor();
|
||||
if (editor) {
|
||||
editor.focus();
|
||||
}
|
||||
// restore focus to active group
|
||||
this.accessor.activeGroup.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -423,18 +325,61 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
|
||||
return keybinding ? keybinding.getLabel() : void 0;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
//#region ITitleAreaControl
|
||||
|
||||
// Actions
|
||||
[
|
||||
this.splitEditorAction,
|
||||
this.closeOneEditorAction
|
||||
].forEach((action) => {
|
||||
action.dispose();
|
||||
});
|
||||
abstract openEditor(editor: IEditorInput): void;
|
||||
|
||||
// Toolbar
|
||||
this.editorActionsToolbar.dispose();
|
||||
abstract closeEditor(editor: IEditorInput): void;
|
||||
|
||||
abstract closeEditors(editors: IEditorInput[]): void;
|
||||
|
||||
abstract closeAllEditors(): void;
|
||||
|
||||
abstract moveEditor(editor: IEditorInput, fromIndex: number, targetIndex: number): void;
|
||||
|
||||
abstract pinEditor(editor: IEditorInput): void;
|
||||
|
||||
abstract setActive(isActive: boolean): void;
|
||||
|
||||
abstract updateEditorLabel(editor: IEditorInput): void;
|
||||
|
||||
abstract updateEditorDirty(editor: IEditorInput): void;
|
||||
|
||||
abstract updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void;
|
||||
|
||||
abstract updateStyles(): void;
|
||||
|
||||
layout(dimension: Dimension): void {
|
||||
// Optionally implemented in subclasses
|
||||
|
||||
if (this.breadcrumbsControl) {
|
||||
this.breadcrumbsControl.layout(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
getPreferredHeight(): number {
|
||||
return EDITOR_TITLE_HEIGHT + (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden() ? BreadcrumbsControl.HEIGHT : 0);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.breadcrumbsControl = dispose(this.breadcrumbsControl);
|
||||
this.editorToolBarMenuDisposables = dispose(this.editorToolBarMenuDisposables);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
|
||||
// Drag Feedback
|
||||
const dragImageBackground = theme.getColor(listActiveSelectionBackground);
|
||||
const dragImageForeground = theme.getColor(listActiveSelectionForeground);
|
||||
collector.addRule(`
|
||||
.monaco-editor-group-drag-image {
|
||||
background: ${dragImageBackground};
|
||||
color: ${dragImageForeground};
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||