Merge from vscode fb5dc0083bfa9a0e3da7ed1f86e1ecb9836fcc8b

This commit is contained in:
ADS Merger
2020-03-13 05:35:18 +00:00
parent 7658a5df28
commit a7e56d334f
88 changed files with 1627 additions and 553 deletions

179
.github/commands.json vendored Normal file
View File

@@ -0,0 +1,179 @@
[
{
"type": "comment",
"name": "question",
"allowUsers": [
"cleidigh",
"usernamehw",
"gjsjohnmurray",
"IllusionMH"
],
"action": "updateLabels",
"addLabel": "*question"
},
{
"type": "label",
"name": "*question",
"action": "close",
"comment": "Please ask your question on [StackOverflow](https://aka.ms/vscodestackoverflow). We have a great community over [there](https://aka.ms/vscodestackoverflow). They have already answered thousands of questions and are happy to answer yours as well. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!"
},
{
"type": "label",
"name": "*dev-question",
"action": "close",
"comment": "We have a great developer community [over on slack](https://aka.ms/vscode-dev-community) where extension authors help each other. This is a great place for you to ask questions and find support.\n\nHappy Coding!"
},
{
"type": "label",
"name": "*extension-candidate",
"action": "close",
"comment": "We try to keep VS Code lean and we think the functionality you're asking for is great for a VS Code extension. Maybe you can already find one that suits you in the [VS Code Marketplace](https://aka.ms/vscodemarketplace). Just in case, in a few simple steps you can get started [writing your own extension](https://aka.ms/vscodewritingextensions). See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!"
},
{
"type": "label",
"name": "*not-reproducible",
"action": "close",
"comment": "We closed this issue because we are unable to reproduce the problem with the steps you describe. Chances are we've already fixed your problem in a recent version of VS Code. If not, please ask us to reopen the issue and provide us with more detail. Our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines might help you with that.\n\nHappy Coding!"
},
{
"type": "label",
"name": "*out-of-scope",
"action": "close",
"comment": "We closed this issue because we don't plan to address it in the foreseeable future. You can find more detailed information about our decision-making process [here](https://aka.ms/vscode-out-of-scope). If you disagree and feel that this issue is crucial: We are happy to listen and to reconsider.\n\nIf you wonder what we are up to, please see our [roadmap](https://aka.ms/vscoderoadmap) and [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nThanks for your understanding and happy coding!"
},
{
"type": "comment",
"name": "causedByExtension",
"allowUsers": [
"cleidigh",
"usernamehw",
"gjsjohnmurray",
"IllusionMH"
],
"action": "updateLabels",
"addLabel": "*caused-by-extension"
},
{
"type": "label",
"name": "*caused-by-extension",
"action": "close",
"comment": "This issue is caused by an extension, please file it with the repository (or contact) the extension has linked in its overview in VS Code or the [marketplace](https://aka.ms/vscodemarketplace) for VS Code. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!"
},
{
"type": "label",
"name": "*as-designed",
"action": "close",
"comment": "The described behavior is how it is expected to work. If you disagree, please explain what is expected and what is not in more detail. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!"
},
{
"type": "label",
"name": "*english-please",
"action": "close",
"comment": "This issue is being closed because its description is not in English, that makes it hard for us to work on it. Please open a new issue with an English description. You might find [Bing Translator](https://www.bing.com/translator) useful."
},
{
"type": "comment",
"name": "duplicate",
"allowUsers": [
"cleidigh",
"usernamehw",
"gjsjohnmurray",
"IllusionMH"
],
"action": "updateLabels",
"addLabel": "*duplicate"
},
{
"type": "label",
"name": "*duplicate",
"action": "close",
"comment": "Thanks for creating this issue! We figured it's covering the same as another one we already have. Thus, we closed this one as a duplicate. You can search for existing issues [here](https://aka.ms/vscodeissuesearch). See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!"
},
{
"type": "comment",
"name": "confirm",
"allowUsers": [
"cleidigh",
"usernamehw",
"gjsjohnmurray",
"IllusionMH"
],
"action": "updateLabels",
"addLabel": "confirmed",
"removeLabel": "confirmation-pending"
},
{
"type": "comment",
"name": "confirmationPending",
"allowUsers": [
"cleidigh",
"usernamehw",
"gjsjohnmurray",
"IllusionMH"
],
"action": "updateLabels",
"addLabel": "confirmation-pending",
"removeLabel": "confirmed"
},
{
"type": "comment",
"name": "findDuplicates",
"allowUsers": [
"cleidigh",
"usernamehw",
"gjsjohnmurray",
"IllusionMH"
],
"action": "comment",
"comment": "Potential duplicates:\n${potentialDuplicates}"
},
{
"type": "comment",
"name": "needsMoreInfo",
"allowUsers": [
"cleidigh",
"usernamehw",
"gjsjohnmurray",
"IllusionMH"
],
"action": "updateLabels",
"addLabel": "~needs more info"
},
{
"type": "label",
"name": "~needs version info",
"action": "updateLabels",
"addLabel": "needs more info",
"removeLabel": "~needs version info",
"comment": "Thanks for creating this issue! We figured it's missing some basic information, such as a version number, or in some other way doesn't follow our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines. Please take the time to review these and update the issue.\n\nHappy Coding!"
},
{
"type": "label",
"name": "~needs more info",
"action": "updateLabels",
"addLabel": "needs more info",
"removeLabel": "~needs more info",
"comment": "Thanks for creating this issue! We figured it's missing some basic information or in some other way doesn't follow our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines. Please take the time to review these and update the issue.\n\nHappy Coding!"
},
{
"type": "comment",
"name": "a11ymas",
"allowUsers": [
"AccessibilityTestingTeam-TCS",
"dixitsonali95",
"Mohini78",
"ChitrarupaSharma",
"mspatil110",
"umasarath52",
"v-umnaik"
],
"action": "updateLabels",
"addLabel": "a11ymas"
},
{
"type": "label",
"name": "*off-topic",
"action": "close",
"comment": "Thanks for creating this issue. We think this issue is unactionable or unrelated to the goals of this project. Please follow our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!"
}
]

11
.github/commands.yml vendored
View File

@@ -1,11 +0,0 @@
{
perform: true,
commands: [
{
type: 'label',
name: 'Needs Logs',
action: 'comment',
comment: "We need more info to debug your particular issue. If you could attach your logs to the issue (ensure no private data is in them), it would help us fix the issue much faster.\n\nTo find your logs:\n\n- Open command palette (Click **View** -> **Command Palette**)\n- Run the command: **`Developer: Open Logs Folder`**\n\nThis will open the log file locally. Please include renderer.log"
}
]
}

5
.github/copycat.yml vendored
View File

@@ -1,5 +0,0 @@
{
perform: true,
target_owner: 'anthonydresser',
target_repo: 'testissues'
}

21
.github/workflows/commands.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Commands
on:
issue_comment:
types: [created]
issues:
types: [labeled]
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: Checkout Actions
uses: actions/checkout@v2
with:
repository: 'JacksonKearl/vscode-triage-github-actions'
ref: v2
- name: Run Commands
uses: ./commands
with:
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}
config-path: commands

26
.github/workflows/copycat.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: CopyCat
on:
issues:
types: [opened]
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: Checkout Actions
uses: actions/checkout@v2
with:
repository: 'JacksonKearl/vscode-triage-github-actions'
ref: v2
- name: Run CopyCat (JacksonKearl/testissues)
uses: ./copycat
with:
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}
owner: JacksonKearl
repo: testissues
- name: Run CopyCat (chrmarti/testissues)
uses: ./copycat
with:
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}
owner: chrmarti
repo: testissues

View File

@@ -0,0 +1,20 @@
name: Needs Version Info
on:
issues:
types: [opened]
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: Checkout Actions
uses: actions/checkout@v2
with:
repository: 'JacksonKearl/vscode-triage-github-actions'
ref: v2
- name: Run Needs Version Info
uses: ./needs-more-info
with:
token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}}
matcher: '\b(\d\.\d{2,3}\.\d|insiders?|1\.\d\d\d?)\b'
label: ~needs version info

View File

@@ -27,8 +27,8 @@ export class PreviewManager implements vscode.CustomEditorProvider {
private readonly zoomStatusBarEntry: ZoomStatusBarEntry,
) { }
public async resolveCustomDocument(_document: vscode.CustomDocument): Promise<vscode.CustomEditorCapabilities> {
return {};
public async resolveCustomDocument(_document: vscode.CustomDocument): Promise<void> {
// noop
}
public async resolveCustomEditor(

View File

@@ -345,6 +345,9 @@ function getSchemaAssociations(_context: ExtensionContext): ISchemaAssociation[]
fileMatch = [fileMatch];
}
if (Array.isArray(fileMatch) && url) {
if (url[0] === '.' && url[1] === '/') {
url = Uri.file(path.join(extension.extensionPath, url)).toString();
}
fileMatch = fileMatch.map(fm => {
if (fm[0] === '%') {
fm = fm.replace(/%APP_SETTINGS_HOME%/, '/User');

View File

@@ -148,8 +148,8 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
this.registerDynamicPreview(preview);
}
public async resolveCustomDocument(_document: vscode.CustomDocument): Promise<vscode.CustomEditorCapabilities> {
return {};
public async resolveCustomDocument(_document: vscode.CustomDocument): Promise<void> {
// noop
}
public async resolveCustomTextEditor(

View File

@@ -46,7 +46,8 @@
}
.monaco-action-bar .action-item .codicon {
vertical-align: middle;
display: flex;
align-items: center;
}
.monaco-action-bar .action-label {

View File

@@ -510,7 +510,7 @@ export class ActionBar extends Disposable implements IActionRunner {
} else if (event.equals(nextKey)) {
this.focusNext();
} else if (event.equals(KeyCode.Escape)) {
this.cancel();
this._onDidCancel.fire();
} else if (this.isTriggerKeyEvent(event)) {
// Staying out of the else branch even if not triggered
if (this.options.triggerKeys && this.options.triggerKeys.keyDown) {
@@ -813,14 +813,6 @@ export class ActionBar extends Disposable implements IActionRunner {
}
}
private cancel(): void {
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur(); // remove focus from focused action
}
this._onDidCancel.fire();
}
run(action: IAction, context?: unknown): Promise<void> {
return this._actionRunner.run(action, context);
}

View File

@@ -5,7 +5,7 @@
@font-face {
font-family: "codicon";
src: url("./codicon.ttf?df9e07bbeddc0cf98f4d7a7c92bef3d8") format("truetype");
src: url("./codicon.ttf?5490083fcec741c6a0a08a366d2f9c98") format("truetype");
}
.codicon[class*='codicon-'] {
@@ -419,3 +419,4 @@
.codicon-bell-dot:before { content: "\f101" }
.codicon-debug-alt-2:before { content: "\f102" }
.codicon-debug-alt:before { content: "\f103" }
.codicon-run-all:before { content: "\f104" }

View File

@@ -397,18 +397,6 @@ export class InputBox extends Widget {
const styles = this.stylesForType(this.message.type);
this.element.style.border = styles.border ? `1px solid ${styles.border}` : '';
// ARIA Support
let alertText: string;
if (message.type === MessageType.ERROR) {
alertText = nls.localize('alertErrorMessage', "Error: {0}", message.content);
} else if (message.type === MessageType.WARNING) {
alertText = nls.localize('alertWarningMessage', "Warning: {0}", message.content);
} else {
alertText = nls.localize('alertInfoMessage', "Info: {0}", message.content);
}
aria.alert(alertText);
if (this.hasFocus() || force) {
this._showMessage();
}
@@ -519,6 +507,18 @@ export class InputBox extends Widget {
layout: layout
});
// ARIA Support
let alertText: string;
if (this.message.type === MessageType.ERROR) {
alertText = nls.localize('alertErrorMessage', "Error: {0}", this.message.content);
} else if (this.message.type === MessageType.WARNING) {
alertText = nls.localize('alertWarningMessage', "Warning: {0}", this.message.content);
} else {
alertText = nls.localize('alertInfoMessage', "Info: {0}", this.message.content);
}
aria.alert(alertText);
this.state = 'open';
}

View File

@@ -186,6 +186,11 @@
align-items: center;
}
.quick-input-list .quick-input-list-rows > .quick-input-list-row .monaco-icon-label,
.quick-input-list .quick-input-list-rows > .quick-input-list-row .monaco-icon-label .monaco-icon-label-container > .monaco-icon-name-container {
flex: 1; /* make sure the icon label grows within the row */
}
.quick-input-list .quick-input-list-rows > .quick-input-list-row .codicon {
vertical-align: sub;
}
@@ -194,6 +199,10 @@
opacity: 1;
}
.quick-input-list .quick-input-list-entry .quick-input-list-entry-keybinding {
margin-right: 8px; /* separate from the separator label or scrollbar if any */
}
.quick-input-list .quick-input-list-label-meta {
opacity: 0.7;
line-height: normal;
@@ -205,17 +214,13 @@
font-weight: bold;
}
.quick-input-list .quick-input-list-separator {
margin-right: 18px;
}
.quick-input-list .quick-input-list-entry.has-actions:hover .quick-input-list-separator,
.quick-input-list .monaco-list-row.focused .quick-input-list-entry.has-actions .quick-input-list-separator {
margin-right: 0;
.quick-input-list .quick-input-list-entry .quick-input-list-separator {
margin-right: 8px; /* separate from keybindings or actions */
}
.quick-input-list .quick-input-list-entry-action-bar {
display: none;
display: flex;
visibility: hidden; /* not using display: none here to not flicker too much */
flex: 0;
overflow: visible;
}
@@ -231,16 +236,16 @@
margin-top: 1px;
}
.quick-input-list .quick-input-list-entry-action-bar ul:first-child .action-label.codicon {
margin-left: 2px;
.quick-input-list .quick-input-list-entry-action-bar {
margin-right: 4px; /* separate from scrollbar */
}
.quick-input-list .quick-input-list-entry-action-bar ul:last-child .action-label.codicon {
margin-right: 8px;
.quick-input-list .quick-input-list-entry-action-bar .action-label.codicon {
margin-right: 4px; /* separate actions */
}
.quick-input-list .quick-input-list-entry.always-visible-actions .quick-input-list-entry-action-bar,
.quick-input-list .quick-input-list-entry:hover .quick-input-list-entry-action-bar,
.quick-input-list .monaco-list-row.focused .quick-input-list-entry-action-bar {
display: flex;
visibility: visible;
}

View File

@@ -26,6 +26,7 @@ import { getIconClass } from 'vs/base/parts/quickinput/browser/quickInputUtils';
import { withNullAsUndefined } from 'vs/base/common/types';
import { IQuickInputOptions } from 'vs/base/parts/quickinput/browser/quickInput';
import { IListOptions, List, IListStyles, IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel';
const $ = dom.$;
@@ -79,6 +80,7 @@ interface IListElementTemplateData {
entry: HTMLDivElement;
checkbox: HTMLInputElement;
label: IconLabel;
keybinding: KeybindingLabel;
detail: HighlightedLabel;
separator: HTMLDivElement;
actionBar: ActionBar;
@@ -118,6 +120,10 @@ class ListElementRenderer implements IListRenderer<ListElement, IListElementTemp
// Label
data.label = new IconLabel(row1, { supportHighlights: true, supportDescriptionHighlights: true, supportCodicons: true });
// Keybinding
const keybindingContainer = dom.append(row1, $('.quick-input-list-entry-keybinding'));
data.keybinding = new KeybindingLabel(keybindingContainer, platform.OS);
// Detail
const detailContainer = dom.append(row2, $('.quick-input-list-label-meta'));
data.detail = new HighlightedLabel(detailContainer, true);
@@ -150,6 +156,9 @@ class ListElementRenderer implements IListRenderer<ListElement, IListElementTemp
options.italic = element.item.italic;
data.label.setLabel(element.saneLabel, element.saneDescription, options);
// Keybinding
data.keybinding.set(element.item.keybinding);
// Meta
data.detail.set(element.saneDetail, detailHighlights);

View File

@@ -24,6 +24,7 @@ export interface IQuickPickItem {
ariaLabel?: string;
description?: string;
detail?: string;
keybinding?: ResolvedKeybinding;
iconClasses?: string[];
italic?: boolean;
highlights?: IQuickPickItemHighlights;

View File

@@ -54,6 +54,7 @@ export namespace QuickCommandNLS {
export const ariaLabelEntry = nls.localize('ariaLabelEntry', "{0}, commands");
export const quickCommandActionInput = nls.localize('quickCommandActionInput', "Type the name of an action you want to execute");
export const quickCommandActionLabel = nls.localize('quickCommandActionLabel', "Command Palette");
export const quickCommandHelp = nls.localize('quickCommandActionHelp', "Show And Run Commands");
}
export namespace QuickOutlineNLS {

View File

@@ -419,7 +419,7 @@ registerEditorAction(class ShowLensesInCurrentLine extends EditorAction {
super({
id: 'codelens.showLensesInCurrentLine',
precondition: EditorContextKeys.hasCodeLensProvider,
label: localize('showLensOnLine', "Show Code Lens Command For Current Line"),
label: localize('showLensOnLine', "Show Code Lens Commands For Current Line"),
alias: 'Show Code Lens Commands For Current Line',
});
}

View File

@@ -0,0 +1,49 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AbstractCommandsQuickAccessProvider, ICommandQuickPick, ICommandsQuickAccessOptions } from 'vs/platform/quickinput/browser/commandsQuickAccess';
import { IEditor } from 'vs/editor/common/editorCommon';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ICommandService } from 'vs/platform/commands/common/commands';
export abstract class AbstractEditorCommandsQuickAccessProvider extends AbstractCommandsQuickAccessProvider {
constructor(
options: ICommandsQuickAccessOptions,
instantiationService: IInstantiationService,
keybindingService: IKeybindingService,
commandService: ICommandService,
telemetryService: ITelemetryService,
notificationService: INotificationService
) {
super(options, instantiationService, keybindingService, commandService, telemetryService, notificationService);
}
/**
* Subclasses to provide the current active editor control.
*/
abstract activeTextEditorControl: IEditor | undefined;
protected getCodeEditorCommandPicks(): ICommandQuickPick[] {
const activeTextEditorControl = this.activeTextEditorControl;
if (!activeTextEditorControl) {
return [];
}
const editorCommandPicks: ICommandQuickPick[] = [];
for (const editorAction of activeTextEditorControl.getSupportedActions()) {
editorCommandPicks.push({
commandId: editorAction.id,
commandAlias: editorAction.alias,
label: editorAction.label || editorAction.id,
});
}
return editorCommandPicks;
}
}

View File

@@ -12,6 +12,7 @@ import 'vs/editor/standalone/browser/quickOpen/quickCommand';
import 'vs/editor/standalone/browser/quickOpen/quickOutline';
import 'vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess';
import 'vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess';
import 'vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess';
import 'vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch';
import 'vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast';

View File

@@ -0,0 +1,44 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Registry } from 'vs/platform/registry/common/platform';
import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess';
import { QuickCommandNLS } from 'vs/editor/common/standaloneStrings';
import { ICommandQuickPick } from 'vs/platform/quickinput/browser/commandsQuickAccess';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { AbstractEditorCommandsQuickAccessProvider } from 'vs/editor/contrib/quickAccess/commandsQuickAccess';
import { IEditor } from 'vs/editor/common/editorCommon';
import { withNullAsUndefined } from 'vs/base/common/types';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { INotificationService } from 'vs/platform/notification/common/notification';
export class StandaloneCommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider {
get activeTextEditorControl(): IEditor | undefined { return withNullAsUndefined(this.codeEditorService.getFocusedCodeEditor()); }
constructor(
@IInstantiationService instantiationService: IInstantiationService,
@ICodeEditorService private readonly codeEditorService: ICodeEditorService,
@IKeybindingService keybindingService: IKeybindingService,
@ICommandService commandService: ICommandService,
@ITelemetryService telemetryService: ITelemetryService,
@INotificationService notificationService: INotificationService
) {
super({ showAlias: false }, instantiationService, keybindingService, commandService, telemetryService, notificationService);
}
protected async getCommandPicks(): Promise<Array<ICommandQuickPick>> {
return this.getCodeEditorCommandPicks();
}
}
Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess).registerQuickAccessProvider({
ctor: StandaloneCommandsQuickAccessProvider,
prefix: StandaloneCommandsQuickAccessProvider.PREFIX,
helpEntries: [{ description: QuickCommandNLS.quickCommandHelp, needsEditor: true }]
});

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoLine';
import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoLineQuickAccess';
import { Registry } from 'vs/platform/registry/common/platform';
import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';

View File

@@ -12,7 +12,7 @@ import { IdGenerator } from 'vs/base/common/idGenerator';
import { IDisposable, toDisposable, MutableDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { localize } from 'vs/nls';
import { ICommandAction, IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions';
import { ICommandAction, IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemAction, Icon } from 'vs/platform/actions/common/actions';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { INotificationService } from 'vs/platform/notification/common/notification';
@@ -217,9 +217,10 @@ export class MenuEntryActionViewItem extends ActionViewItem {
const keybinding = this._keybindingService.lookupKeybinding(this._commandAction.id);
const keybindingLabel = keybinding && keybinding.getLabel();
const tooltip = this._commandAction.tooltip || this._commandAction.label;
this.label.title = keybindingLabel
? localize('titleAndKb', "{0} ({1})", this._commandAction.label, keybindingLabel)
: this._commandAction.label;
? localize('titleAndKb', "{0} ({1})", tooltip, keybindingLabel)
: tooltip;
}
}
@@ -238,9 +239,11 @@ export class MenuEntryActionViewItem extends ActionViewItem {
_updateItemClass(item: ICommandAction): void {
this._itemClassDispose.value = undefined;
if (ThemeIcon.isThemeIcon(item.icon)) {
const icon = this._commandAction.checked && (item.toggled as { icon?: Icon })?.icon ? (item.toggled as { icon: Icon }).icon : item.icon;
if (ThemeIcon.isThemeIcon(icon)) {
// theme icons
const iconClass = ThemeIcon.asClassName(item.icon);
const iconClass = ThemeIcon.asClassName(icon);
if (this.label && iconClass) {
addClasses(this.label, iconClass);
this._itemClassDispose.value = toDisposable(() => {
@@ -250,20 +253,20 @@ export class MenuEntryActionViewItem extends ActionViewItem {
});
}
} else if (item.icon) {
} else if (icon) {
// icon path
let iconClass: string;
if (item.icon?.dark?.scheme) {
if (icon?.dark?.scheme) {
const iconPathMapKey = item.icon.dark.toString();
const iconPathMapKey = icon.dark.toString();
if (MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) {
iconClass = MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!;
} else {
iconClass = ids.nextId();
createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.light || item.icon.dark)}`);
createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.dark)}`);
createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(icon.light || icon.dark)}`);
createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(icon.dark)}`);
MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass);
}

View File

@@ -20,13 +20,16 @@ export interface ILocalizedString {
original: string;
}
export type Icon = { dark?: URI; light?: URI; } | ThemeIcon;
export interface ICommandAction {
id: string;
title: string | ILocalizedString;
category?: string | ILocalizedString;
icon?: { dark?: URI; light?: URI; } | ThemeIcon;
tooltip?: string | ILocalizedString;
icon?: Icon;
precondition?: ContextKeyExpression;
toggled?: ContextKeyExpression;
toggled?: ContextKeyExpression | { condition: ContextKeyExpression, icon?: Icon, tooltip?: string | ILocalizedString };
}
export type ISerializableCommandAction = UriDto<ICommandAction>;
@@ -280,9 +283,20 @@ export class MenuItemAction extends ExecuteCommandAction {
@ICommandService commandService: ICommandService
) {
typeof item.title === 'string' ? super(item.id, item.title, commandService) : super(item.id, item.title.value, commandService);
this._cssClass = undefined;
this._enabled = !item.precondition || contextKeyService.contextMatchesRules(item.precondition);
this._checked = Boolean(item.toggled && contextKeyService.contextMatchesRules(item.toggled));
this._tooltip = item.tooltip ? typeof item.tooltip === 'string' ? item.tooltip : item.tooltip.value : undefined;
if (item.toggled) {
const toggled = ((item.toggled as { condition: ContextKeyExpression }).condition ? item.toggled : { condition: item.toggled }) as {
condition: ContextKeyExpression, icon?: Icon, tooltip?: string | ILocalizedString
};
this._checked = contextKeyService.contextMatchesRules(toggled.condition);
if (this._checked && toggled.tooltip) {
this._tooltip = typeof toggled.tooltip === 'string' ? toggled.tooltip : toggled.tooltip.value;
}
}
this._options = options || {};

View File

@@ -94,7 +94,8 @@ class Menu implements IMenu {
// keep toggled keys for event if applicable
if (isIMenuItem(item) && item.command.toggled) {
Menu._fillInKbExprKeys(item.command.toggled, this._contextKeys);
const toggledExpression: ContextKeyExpression = (item.command.toggled as { condition: ContextKeyExpression }).condition || item.command.toggled as ContextKeyExpression; // {{SQL CARBON EDIT}} strict-null-checks
Menu._fillInKbExprKeys(toggledExpression, this._contextKeys);
}
}
this._onDidChange.fire(this);

View File

@@ -1051,7 +1051,7 @@ export class ContextKeyLessThanEqualsExpr implements IContextKeyExpression {
}
public equals(other: ContextKeyExpression): boolean {
if (other instanceof ContextKeyLessThanEqualsExpr) {
if (other.type === this.type) {
return (this.key === other.key && this.value === other.value);
}
return false;

View File

@@ -39,7 +39,6 @@ export interface ParsedArgs {
'builtin-extensions-dir'?: string;
extensionDevelopmentPath?: string[]; // // undefined or array of 1 or more local paths or URIs
extensionTestsPath?: string; // either a local path or a URI
'extension-development-confirm-save'?: boolean;
'inspect-extensions'?: string;
'inspect-brk-extensions'?: string;
debugId?: string;

View File

@@ -80,7 +80,6 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
'locate-extension': { type: 'string[]' },
'extensionDevelopmentPath': { type: 'string[]' },
'extensionTestsPath': { type: 'string' },
'extension-development-confirm-save': { type: 'boolean' },
'debugId': { type: 'string' },
'inspect-search': { type: 'string', deprecates: 'debugSearch' },
'inspect-brk-search': { type: 'string', deprecates: 'debugBrkSearch' },

View File

@@ -0,0 +1,305 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { PickerQuickAccessProvider, IPickerQuickAccessItem } from 'vs/platform/quickinput/common/quickAccess';
import { distinct } from 'vs/base/common/arrays';
import { CancellationToken } from 'vs/base/common/cancellation';
import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { or, matchesPrefix, matchesWords, matchesContiguousSubString } from 'vs/base/common/filters';
import { withNullAsUndefined } from 'vs/base/common/types';
import { LRUCache } from 'vs/base/common/map';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { isFirefox } from 'vs/base/browser/browser';
import { timeout } from 'vs/base/common/async';
export interface ICommandQuickPick extends IPickerQuickAccessItem {
commandId: string;
commandAlias: string | undefined;
}
export interface ICommandsQuickAccessOptions {
showAlias: boolean;
}
export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAccessProvider<ICommandQuickPick> implements IDisposable {
static PREFIX = '>';
private static WORD_FILTER = or(matchesPrefix, matchesWords, matchesContiguousSubString);
private readonly disposables = new DisposableStore();
private readonly commandsHistory = this.disposables.add(this.instantiationService.createInstance(CommandsHistory));
constructor(
private options: ICommandsQuickAccessOptions,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@ICommandService private readonly commandService: ICommandService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@INotificationService private readonly notificationService: INotificationService
) {
super(AbstractCommandsQuickAccessProvider.PREFIX);
}
protected async getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Promise<Array<ICommandQuickPick | IQuickPickSeparator>> {
// Ask subclass for all command picks
const allCommandPicks = await this.getCommandPicks(disposables, token);
// Filter
const filteredCommandPicks: ICommandQuickPick[] = [];
for (const commandPick of allCommandPicks) {
const labelHighlights = withNullAsUndefined(AbstractCommandsQuickAccessProvider.WORD_FILTER(filter, commandPick.label));
const aliasHighlights = commandPick.commandAlias ? withNullAsUndefined(AbstractCommandsQuickAccessProvider.WORD_FILTER(filter, commandPick.commandAlias)) : undefined;
if (labelHighlights || aliasHighlights) {
commandPick.highlights = {
label: labelHighlights,
detail: this.options.showAlias ? aliasHighlights : undefined
};
filteredCommandPicks.push(commandPick);
}
}
// Remove duplicates
const distinctCommandPicks = distinct(filteredCommandPicks, pick => `${pick.label}${pick.commandId}`);
// Add description to commands that have duplicate labels
const mapLabelToCommand = new Map<string, ICommandQuickPick>();
for (const commandPick of distinctCommandPicks) {
const existingCommandForLabel = mapLabelToCommand.get(commandPick.label);
if (existingCommandForLabel) {
commandPick.description = commandPick.commandId;
existingCommandForLabel.description = existingCommandForLabel.commandId;
} else {
mapLabelToCommand.set(commandPick.label, commandPick);
}
}
// Sort by MRU order and fallback to name otherwise
distinctCommandPicks.sort((commandPickA, commandPickB) => {
const commandACounter = this.commandsHistory.peek(commandPickA.commandId);
const commandBCounter = this.commandsHistory.peek(commandPickB.commandId);
if (commandACounter && commandBCounter) {
return commandACounter > commandBCounter ? -1 : 1; // use more recently used command before older
}
if (commandACounter) {
return -1; // first command was used, so it wins over the non used one
}
if (commandBCounter) {
return 1; // other command was used so it wins over the command
}
// both commands were never used, so we sort by name
return commandPickA.label.localeCompare(commandPickB.label);
});
const commandPicks: Array<ICommandQuickPick | IQuickPickSeparator> = [];
let addSeparator = false;
for (let i = 0; i < distinctCommandPicks.length; i++) {
const commandPick = distinctCommandPicks[i];
const keybinding = this.keybindingService.lookupKeybinding(commandPick.commandId);
const ariaLabel = keybinding ?
localize('commandPickAriaLabelWithKeybinding', "{0}, {1}, commands picker", commandPick.label, keybinding.getAriaLabel()) :
localize('commandPickAriaLabel', "{0}, commands picker", commandPick.label);
// Separator: recently used
if (i === 0 && this.commandsHistory.peek(commandPick.commandId)) {
commandPicks.push({ type: 'separator', label: localize('recentlyUsed', "recently used") });
addSeparator = true;
}
// Separator: other commands
if (i !== 0 && addSeparator && !this.commandsHistory.peek(commandPick.commandId)) {
commandPicks.push({ type: 'separator', label: localize('morecCommands', "other commands") });
addSeparator = false; // only once
}
// Command
commandPicks.push({
...commandPick,
ariaLabel,
detail: this.options.showAlias ? commandPick.commandAlias : undefined,
keybinding,
accept: async () => {
// Add to history
this.commandsHistory.push(commandPick.commandId);
if (!isFirefox) {
// Use a timeout to give the quick open widget a chance to close itself first
// Firefox: since the browser is quite picky for certain commands, we do not
// use a timeout (https://github.com/microsoft/vscode/issues/83288)
await timeout(50);
}
// Telementry
this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', {
id: commandPick.commandId,
from: 'quick open'
});
// Run
try {
await this.commandService.executeCommand(commandPick.commandId);
} catch (error) {
if (!isPromiseCanceledError(error)) {
this.notificationService.error(localize('canNotRun', "Command '{0}' resulted in an error ({1})", commandPick.label, toErrorMessage(error)));
}
}
}
});
}
return commandPicks;
}
protected abstract getCommandPicks(disposables: DisposableStore, token: CancellationToken): Promise<Array<ICommandQuickPick>>;
dispose(): void {
this.disposables.dispose();
}
}
interface ISerializedCommandHistory {
usesLRU?: boolean;
entries: { key: string; value: number }[];
}
interface ICommandsQuickAccessConfiguration {
workbench: {
commandPalette: {
history: number;
preserveInput: boolean;
}
};
}
class CommandsHistory extends Disposable {
static readonly DEFAULT_COMMANDS_HISTORY_LENGTH = 50;
private static readonly PREF_KEY_CACHE = 'commandPalette.mru.cache';
private static readonly PREF_KEY_COUNTER = 'commandPalette.mru.counter';
private static cache: LRUCache<string, number> | undefined;
private static counter = 1;
private configuredCommandsHistoryLength = 0;
constructor(
@IStorageService private readonly storageService: IStorageService,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super();
this.updateConfiguration();
this.load();
this.registerListeners();
}
private registerListeners(): void {
this._register(this.configurationService.onDidChangeConfiguration(() => this.updateConfiguration()));
}
private updateConfiguration(): void {
this.configuredCommandsHistoryLength = CommandsHistory.getConfiguredCommandHistoryLength(this.configurationService);
if (CommandsHistory.cache && CommandsHistory.cache.limit !== this.configuredCommandsHistoryLength) {
CommandsHistory.cache.limit = this.configuredCommandsHistoryLength;
CommandsHistory.saveState(this.storageService);
}
}
private load(): void {
const raw = this.storageService.get(CommandsHistory.PREF_KEY_CACHE, StorageScope.GLOBAL);
let serializedCache: ISerializedCommandHistory | undefined;
if (raw) {
try {
serializedCache = JSON.parse(raw);
} catch (error) {
// invalid data
}
}
const cache = CommandsHistory.cache = new LRUCache<string, number>(this.configuredCommandsHistoryLength, 1);
if (serializedCache) {
let entries: { key: string; value: number }[];
if (serializedCache.usesLRU) {
entries = serializedCache.entries;
} else {
entries = serializedCache.entries.sort((a, b) => a.value - b.value);
}
entries.forEach(entry => cache.set(entry.key, entry.value));
}
CommandsHistory.counter = this.storageService.getNumber(CommandsHistory.PREF_KEY_COUNTER, StorageScope.GLOBAL, CommandsHistory.counter);
}
push(commandId: string): void {
if (!CommandsHistory.cache) {
return;
}
CommandsHistory.cache.set(commandId, CommandsHistory.counter++); // set counter to command
CommandsHistory.saveState(this.storageService);
}
peek(commandId: string): number | undefined {
return CommandsHistory.cache?.peek(commandId);
}
static saveState(storageService: IStorageService): void {
if (!CommandsHistory.cache) {
return;
}
const serializedCache: ISerializedCommandHistory = { usesLRU: true, entries: [] };
CommandsHistory.cache.forEach((value, key) => serializedCache.entries.push({ key, value }));
storageService.store(CommandsHistory.PREF_KEY_CACHE, JSON.stringify(serializedCache), StorageScope.GLOBAL);
storageService.store(CommandsHistory.PREF_KEY_COUNTER, CommandsHistory.counter, StorageScope.GLOBAL);
}
static getConfiguredCommandHistoryLength(configurationService: IConfigurationService): number {
const config = <ICommandsQuickAccessConfiguration>configurationService.getValue();
const configuredCommandHistoryLength = config.workbench?.commandPalette?.history;
if (typeof configuredCommandHistoryLength === 'number') {
return configuredCommandHistoryLength;
}
return CommandsHistory.DEFAULT_COMMANDS_HISTORY_LENGTH;
}
static clearHistory(configurationService: IConfigurationService, storageService: IStorageService): void {
const commandHistoryLength = CommandsHistory.getConfiguredCommandHistoryLength(configurationService);
CommandsHistory.cache = new LRUCache<string, number>(commandHistoryLength);
CommandsHistory.counter = 1;
CommandsHistory.saveState(storageService);
}
}

View File

@@ -205,7 +205,7 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
picksCts = new CancellationTokenSource(token);
// Collect picks and support both long running and short
const res = this.getPicks(picker.value.substr(this.prefix.length).trim(), picksCts.token);
const res = this.getPicks(picker.value.substr(this.prefix.length).trim(), disposables.add(new DisposableStore()), picksCts.token);
if (Array.isArray(res)) {
picker.items = res;
} else {
@@ -272,8 +272,14 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
*
* The implementor is responsible for filtering and sorting the picks given the
* provided `filter`.
*
* @param filter a filter to apply to the picks.
* @param disposables can be used to register disposables that should be cleaned
* up when the picker closes.
* @param token for long running tasks, implementors need to check on cancellation
* through this token.
*/
protected abstract getPicks(filter: string, token: CancellationToken): Array<T | IQuickPickSeparator> | Promise<Array<T | IQuickPickSeparator>>;
protected abstract getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Array<T | IQuickPickSeparator> | Promise<Array<T | IQuickPickSeparator>>;
}
//#endregion

View File

@@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
import { hash } from 'vs/base/common/hash';
import { Disposable } from 'vs/base/common/lifecycle';
export const IResourceIdentityService = createDecorator<IResourceIdentityService>('IResourceIdentityService');
export interface IResourceIdentityService {
_serviceBrand: undefined;
resolveResourceIdentity(resource: URI): Promise<string>;
}
export class WebResourceIdentityService extends Disposable implements IResourceIdentityService {
_serviceBrand: undefined;
async resolveResourceIdentity(resource: URI): Promise<string> {
return hash(resource.toString()).toString(16);
}
}

View File

@@ -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.
*--------------------------------------------------------------------------------------------*/
import { createHash } from 'crypto';
import { stat } from 'vs/base/node/pfs';
import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import { IResourceIdentityService } from 'vs/platform/resource/common/resourceIdentityService';
import { Disposable } from 'vs/base/common/lifecycle';
import { ResourceMap } from 'vs/base/common/map';
export class NativeResourceIdentityService extends Disposable implements IResourceIdentityService {
_serviceBrand: undefined;
private readonly cache: ResourceMap<Promise<string>> = new ResourceMap<Promise<string>>();
resolveResourceIdentity(resource: URI): Promise<string> {
let promise = this.cache.get(resource);
if (!promise) {
promise = this.createIdentity(resource);
this.cache.set(resource, promise);
}
return promise;
}
private async createIdentity(resource: URI): Promise<string> {
// Return early the folder is not local
if (resource.scheme !== Schemas.file) {
return createHash('md5').update(resource.toString()).digest('hex');
}
const fileStat = await stat(resource.fsPath);
let ctime: number | undefined;
if (isLinux) {
ctime = fileStat.ino; // Linux: birthtime is ctime, so we cannot use it! We use the ino instead!
} else if (isMacintosh) {
ctime = fileStat.birthtime.getTime(); // macOS: birthtime is fine to use as is
} else if (isWindows) {
if (typeof fileStat.birthtimeMs === 'number') {
ctime = Math.floor(fileStat.birthtimeMs); // Windows: fix precision issue in node.js 8.x to get 7.x results (see https://github.com/nodejs/node/issues/19897)
} else {
ctime = fileStat.birthtime.getTime();
}
}
// we use the ctime as extra salt to the ID so that we catch the case of a folder getting
// deleted and recreated. in that case we do not want to carry over previous state
return createHash('md5').update(resource.fsPath).update(ctime ? String(ctime) : '').digest('hex');
}
}

View File

@@ -1222,81 +1222,71 @@ declare module 'vscode' {
// - Should we expose edits?
// - More properties from `TextDocument`?
/**
* Defines the capabilities of a custom webview editor.
*/
interface CustomEditorCapabilities {
/**
* Defines the editing capability of a custom webview document.
*
* When not provided, the document is considered readonly.
*/
readonly editing?: CustomEditorEditingCapability;
}
/**
* Defines the editing capability of a custom webview editor. This allows the webview editor to hook into standard
* editor events such as `undo` or `save`.
*
* @param EditType Type of edits.
*/
interface CustomEditorEditingCapability<EditType = unknown> {
interface CustomEditorEditingDelegate<EditType = unknown> {
/**
* Save the resource.
*
* @param document Document to save.
* @param cancellation Token that signals the save is no longer required (for example, if another save was triggered).
*
* @return Thenable signaling that the save has completed.
*/
save(cancellation: CancellationToken): Thenable<void>;
save(document: CustomDocument, cancellation: CancellationToken): Thenable<void>;
/**
* Save the existing resource at a new path.
*
* @param document Document to save.
* @param targetResource Location to save to.
*
* @return Thenable signaling that the save has completed.
*/
saveAs(targetResource: Uri): Thenable<void>;
saveAs(document: CustomDocument, targetResource: Uri): Thenable<void>;
/**
* Event triggered by extensions to signal to VS Code that an edit has occurred.
*/
readonly onDidEdit: Event<EditType>;
readonly onDidEdit: Event<CustomDocumentEditEvent<EditType>>;
/**
* Apply a set of edits.
*
* Note that is not invoked when `onDidEdit` is called because `onDidEdit` implies also updating the view to reflect the edit.
*
* @param document Document to apply edits to.
* @param edit Array of edits. Sorted from oldest to most recent.
*
* @return Thenable signaling that the change has completed.
*/
applyEdits(edits: readonly EditType[]): Thenable<void>;
applyEdits(document: CustomDocument, edits: readonly EditType[]): Thenable<void>;
/**
* Undo a set of edits.
*
* This is triggered when a user undoes an edit.
*
* @param document Document to undo edits from.
* @param edit Array of edits. Sorted from most recent to oldest.
*
* @return Thenable signaling that the change has completed.
*/
undoEdits(edits: readonly EditType[]): Thenable<void>;
undoEdits(document: CustomDocument, edits: readonly EditType[]): Thenable<void>;
/**
* Revert the file to its last saved state.
*
* @param change Added or applied edits.
* @param document Document to revert.
* @param edits Added or applied edits.
*
* @return Thenable signaling that the change has completed.
*/
revert(change: {
readonly undoneEdits: readonly EditType[];
readonly appliedEdits: readonly EditType[];
}): Thenable<void>;
revert(document: CustomDocument, edits: CustomDocumentRevert<EditType>): Thenable<void>;
/**
* Back up the resource in its current state.
@@ -1311,12 +1301,50 @@ declare module 'vscode' {
* made in quick succession, `backup` is only triggered after the last one. `backup` is not invoked when
* `auto save` is enabled (since auto save already persists resource ).
*
* @param document Document to revert.
* @param cancellation Token that signals the current backup since a new backup is coming in. It is up to your
* extension to decided how to respond to cancellation. If for example your extension is backing up a large file
* in an operation that takes time to complete, your extension may decide to finish the ongoing backup rather
* than cancelling it to ensure that VS Code has some valid backup.
*/
backup(cancellation: CancellationToken): Thenable<void>;
backup(document: CustomDocument, cancellation: CancellationToken): Thenable<void>;
}
/**
* Event triggered by extensions to signal to VS Code that an edit has occurred on a CustomDocument``.
*/
interface CustomDocumentEditEvent<EditType = unknown> {
/**
* Document the edit is for.
*/
readonly document: CustomDocument;
/**
* Object that describes the edit.
*
* Edit objects are passed back to your extension in `undoEdits`, `applyEdits`, and `revert`.
*/
readonly edit: EditType;
/**
* Display name describing the edit.
*/
readonly label?: string;
}
/**
* Data about a revert for a `CustomDocument`.
*/
interface CustomDocumentRevert<EditType = unknown> {
/**
* List of edits that were undone to get the document back to its on disk state.
*/
readonly undoneEdits: readonly EditType[];
/**
* List of edits that were reapplied to get the document back to its on disk state.
*/
readonly appliedEdits: readonly EditType[];
}
/**
@@ -1375,7 +1403,7 @@ declare module 'vscode' {
*
* @return The capabilities of the resolved document.
*/
resolveCustomDocument(document: CustomDocument): Thenable<CustomEditorCapabilities>;
resolveCustomDocument(document: CustomDocument): Thenable<void>;
/**
* Resolve a webview editor for a given resource.
@@ -1393,6 +1421,13 @@ declare module 'vscode' {
* @return Thenable indicating that the webview editor has been resolved.
*/
resolveCustomEditor(document: CustomDocument, webviewPanel: WebviewPanel): Thenable<void>;
/**
* Defines the editing capability of a custom webview document.
*
* When not provided, the document is considered readonly.
*/
readonly editingDelegate?: CustomEditorEditingDelegate;
}
/**
@@ -1438,7 +1473,7 @@ declare module 'vscode' {
export function registerCustomEditorProvider(
viewType: string,
provider: CustomEditorProvider | CustomTextEditorProvider,
webviewOptions?: WebviewPanelOptions,
webviewOptions?: WebviewPanelOptions, // TODO: move this onto provider?
): Disposable;
}

View File

@@ -6,7 +6,7 @@
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, DisposableStore, IDisposable, IReference, dispose } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore, dispose, IDisposable, IReference } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { basename } from 'vs/base/common/path';
import { isWeb } from 'vs/base/common/platform';
@@ -21,6 +21,7 @@ import { ILabelService } from 'vs/platform/label/common/label';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IProductService } from 'vs/platform/product/common/productService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IUndoRedoService, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo';
import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol';
import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor';
import { IEditorInput, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor';
@@ -365,12 +366,14 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
return this._customEditorService.models.add(resource, viewType, model);
}
public async $onDidChangeCustomDocumentState(resource: UriComponents, viewType: string, state: { dirty: boolean }) {
const model = await this._customEditorService.models.get(URI.revive(resource), viewType);
public async $onDidEdit(resourceComponents: UriComponents, viewType: string, editId: number, label: string | undefined): Promise<void> {
const resource = URI.revive(resourceComponents);
const model = await this._customEditorService.models.get(resource, viewType);
if (!model || !(model instanceof MainThreadCustomEditorModel)) {
throw new Error('Could not find model for webview editor');
}
model.setDirty(state.dirty);
model.pushEdit(editId, label);
}
private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewPanelHandle, input: WebviewInput) {
@@ -536,7 +539,9 @@ namespace HotExitState {
class MainThreadCustomEditorModel extends Disposable implements ICustomEditorModel, IWorkingCopy {
private _hotExitState: HotExitState.State = HotExitState.Allowed;
private _dirty = false;
private _currentEditIndex: number = -1;
private _savePoint: number = -1;
private readonly _edits: Array<number> = [];
public static async create(
instantiationService: IInstantiationService,
@@ -556,19 +561,25 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
@IWorkingCopyService workingCopyService: IWorkingCopyService,
@ILabelService private readonly _labelService: ILabelService,
@IFileService private readonly _fileService: IFileService,
@IUndoRedoService private readonly _undoService: IUndoRedoService,
) {
super();
this._register(workingCopyService.registerWorkingCopy(this));
if (_editable) {
this._register(workingCopyService.registerWorkingCopy(this));
}
}
dispose() {
if (this._editable) {
this._undoService.removeElements(this.resource);
}
this._proxy.$disposeWebviewCustomEditorDocument(this.resource, this._viewType);
super.dispose();
}
//#region IWorkingCopy
public get resource() { return this._resource; }
public get resource() { return this._resource; } // custom://viewType/path/file
public get name() {
return basename(this._labelService.getUriLabel(this._resource));
@@ -579,7 +590,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
}
public isDirty(): boolean {
return this._dirty;
return this._edits.length > 0 && this._savePoint !== this._currentEditIndex;
}
private readonly _onDidChangeDirty: Emitter<void> = this._register(new Emitter<void>());
@@ -594,31 +605,106 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
return this._viewType;
}
public setDirty(dirty: boolean): void {
public pushEdit(editId: number, label: string | undefined) {
if (!this._editable) {
throw new Error('Document is not editable');
}
this.change(() => {
this.spliceEdits(editId);
this._currentEditIndex = this._edits.length - 1;
});
this._undoService.pushElement({
type: UndoRedoElementType.Resource,
resource: this.resource,
label: label ?? localize('defaultEditLabel', "Edit"),
undo: () => this.undo(),
redo: () => this.redo(),
});
}
private async undo(): Promise<void> {
if (!this._editable) {
return;
}
if (this._currentEditIndex < 0) {
// nothing to undo
return;
}
const undoneEdit = this._edits[this._currentEditIndex];
await this._proxy.$undo(this.resource, this.viewType, undoneEdit);
this.change(() => {
--this._currentEditIndex;
});
}
private async redo(): Promise<void> {
if (!this._editable) {
return;
}
if (this._currentEditIndex >= this._edits.length - 1) {
// nothing to redo
return;
}
const redoneEdit = this._edits[this._currentEditIndex + 1];
await this._proxy.$redo(this.resource, this.viewType, redoneEdit);
this.change(() => {
++this._currentEditIndex;
});
}
private spliceEdits(editToInsert?: number) {
const start = this._currentEditIndex + 1;
const toRemove = this._edits.length - this._currentEditIndex;
const removedEdits = typeof editToInsert === 'number'
? this._edits.splice(start, toRemove, editToInsert)
: this._edits.splice(start, toRemove);
if (removedEdits.length) {
this._proxy.$disposeEdits(this.resource, this._viewType, removedEdits);
}
}
private change(makeEdit: () => void): void {
const wasDirty = this.isDirty();
makeEdit();
this._onDidChangeContent.fire();
if (this._dirty !== dirty) {
this._dirty = dirty;
if (this.isDirty() !== wasDirty) {
this._onDidChangeDirty.fire();
}
}
public async revert(_options?: IRevertOptions) {
if (this._editable) {
this._proxy.$revert(this.resource, this.viewType);
if (!this._editable) {
return;
}
}
public undo() {
if (this._editable) {
this._proxy.$undo(this.resource, this.viewType);
if (this._currentEditIndex === this._savePoint) {
return;
}
}
public redo() {
if (this._editable) {
this._proxy.$redo(this.resource, this.viewType);
let editsToUndo: number[] = [];
let editsToRedo: number[] = [];
if (this._currentEditIndex >= this._savePoint) {
editsToUndo = this._edits.slice(this._savePoint, this._currentEditIndex).reverse();
} else if (this._currentEditIndex < this._savePoint) {
editsToRedo = this._edits.slice(this._currentEditIndex, this._savePoint);
}
this._proxy.$revert(this.resource, this.viewType, { undoneEdits: editsToUndo, redoneEdits: editsToRedo });
this.change(() => {
this._currentEditIndex = this._savePoint;
this.spliceEdits();
});
}
public async save(_options?: ISaveOptions): Promise<boolean> {
@@ -626,14 +712,18 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod
return false;
}
await createCancelablePromise(token => this._proxy.$onSave(this.resource, this.viewType, token));
this.setDirty(false);
this.change(() => {
this._savePoint = this._currentEditIndex;
});
return true;
}
public async saveAs(resource: URI, targetResource: URI, _options?: ISaveOptions): Promise<boolean> {
if (this._editable) {
await this._proxy.$onSaveAs(this.resource, this.viewType, targetResource);
this.setDirty(false);
this.change(() => {
this._savePoint = this._currentEditIndex;
});
return true;
} else {
// Since the editor is readonly, just copy the file over

View File

@@ -596,7 +596,7 @@ export interface MainThreadWebviewsShape extends IDisposable {
$registerCustomEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void;
$unregisterEditorProvider(viewType: string): void;
$onDidChangeCustomDocumentState(resource: UriComponents, viewType: string, state: { dirty: boolean }): void;
$onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void;
}
export interface WebviewPanelViewStateData {
@@ -619,9 +619,11 @@ export interface ExtHostWebviewsShape {
$createWebviewCustomEditorDocument(resource: UriComponents, viewType: string): Promise<{ editable: boolean }>;
$disposeWebviewCustomEditorDocument(resource: UriComponents, viewType: string): Promise<void>;
$undo(resource: UriComponents, viewType: string): void;
$redo(resource: UriComponents, viewType: string): void;
$revert(resource: UriComponents, viewType: string): void;
$undo(resource: UriComponents, viewType: string, editId: number): Promise<void>;
$redo(resource: UriComponents, viewType: string, editId: number): Promise<void>;
$revert(resource: UriComponents, viewType: string, changes: { undoneEdits: number[], redoneEdits: number[] }): Promise<void>;
$disposeEdits(resourceComponents: UriComponents, viewType: string, editIds: number[]): void;
$onSave(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise<void>;
$onSaveAs(resource: UriComponents, viewType: string, targetResource: UriComponents): Promise<void>;

View File

@@ -5,7 +5,7 @@
import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import * as modes from 'vs/editor/common/modes';
@@ -18,6 +18,7 @@ import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor';
import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview';
import type * as vscode from 'vscode';
import { Cache } from './cache';
import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewExtensionDescription, WebviewPanelHandle, WebviewPanelViewStateData } from './extHost.protocol';
import { Disposable as VSCodeDisposable } from './extHostTypes';
@@ -245,31 +246,33 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa
}
}
type EditType = unknown;
class CustomDocument extends Disposable implements vscode.CustomDocument {
public static create(proxy: MainThreadWebviewsShape, viewType: string, uri: vscode.Uri) {
return Object.seal(new CustomDocument(proxy, viewType, uri));
public static create(
viewType: string,
uri: vscode.Uri,
editingDelegate: vscode.CustomEditorEditingDelegate | undefined
) {
return Object.seal(new CustomDocument(viewType, uri, editingDelegate));
}
// Explicitly initialize all properties as we seal the object after creation!
#currentEditIndex: number = -1;
#savePoint: number = -1;
readonly #edits: Array<EditType> = [];
readonly #_edits = new Cache<unknown>('edits');
readonly #proxy: MainThreadWebviewsShape;
readonly #viewType: string;
readonly #uri: vscode.Uri;
readonly #editingDelegate: vscode.CustomEditorEditingDelegate | undefined;
#capabilities: vscode.CustomEditorCapabilities | undefined = undefined;
private constructor(proxy: MainThreadWebviewsShape, viewType: string, uri: vscode.Uri) {
private constructor(
viewType: string,
uri: vscode.Uri,
editingDelegate: vscode.CustomEditorEditingDelegate | undefined,
) {
super();
this.#proxy = proxy;
this.#viewType = viewType;
this.#uri = uri;
this.#editingDelegate = editingDelegate;
}
dispose() {
@@ -292,107 +295,54 @@ class CustomDocument extends Disposable implements vscode.CustomDocument {
//#region Internal
/** @internal*/ _setCapabilities(capabilities: vscode.CustomEditorCapabilities) {
if (this.#capabilities) {
throw new Error('Capabilities already provided');
}
this.#capabilities = capabilities;
capabilities.editing?.onDidEdit(edit => {
this.pushEdit(edit);
});
/** @internal*/ async _revert(changes: { undoneEdits: number[], redoneEdits: number[] }) {
const editing = this.getEditingDelegate();
const undoneEdits = changes.undoneEdits.map(id => this.#_edits.get(id, 0));
const appliedEdits = changes.redoneEdits.map(id => this.#_edits.get(id, 0));
return editing.revert(this, { undoneEdits, appliedEdits });
}
/** @internal*/ async _revert() {
const editing = this.getEditingCapability();
if (this.#currentEditIndex === this.#savePoint) {
return true;
}
let undoneEdits: EditType[] = [];
let appliedEdits: EditType[] = [];
if (this.#currentEditIndex >= this.#savePoint) {
undoneEdits = this.#edits.slice(this.#savePoint, this.#currentEditIndex).reverse();
} else if (this.#currentEditIndex < this.#savePoint) {
appliedEdits = this.#edits.slice(this.#currentEditIndex, this.#savePoint);
}
this.#currentEditIndex = this.#savePoint;
this.spliceEdits();
await editing.revert({ undoneEdits, appliedEdits });
this.updateState();
return true;
/** @internal*/ _undo(editId: number) {
const editing = this.getEditingDelegate();
const edit = this.#_edits.get(editId, 0);
return editing.undoEdits(this, [edit]);
}
/** @internal*/ _undo() {
const editing = this.getEditingCapability();
if (this.#currentEditIndex < 0) {
// nothing to undo
return;
}
const undoneEdit = this.#edits[this.#currentEditIndex];
--this.#currentEditIndex;
editing.undoEdits([undoneEdit]);
this.updateState();
}
/** @internal*/ _redo() {
const editing = this.getEditingCapability();
if (this.#currentEditIndex >= this.#edits.length - 1) {
// nothing to redo
return;
}
++this.#currentEditIndex;
const redoneEdit = this.#edits[this.#currentEditIndex];
editing.applyEdits([redoneEdit]);
this.updateState();
/** @internal*/ _redo(editId: number) {
const editing = this.getEditingDelegate();
const edit = this.#_edits.get(editId, 0);
return editing.applyEdits(this, [edit]);
}
/** @internal*/ _save(cancellation: CancellationToken) {
return this.getEditingCapability().save(cancellation);
return this.getEditingDelegate().save(this, cancellation);
}
/** @internal*/ _saveAs(target: vscode.Uri) {
return this.getEditingCapability().saveAs(target);
return this.getEditingDelegate().saveAs(this, target);
}
/** @internal*/ _backup(cancellation: CancellationToken) {
return this.getEditingCapability().backup(cancellation);
return this.getEditingDelegate().backup(this, cancellation);
}
/** @internal*/ _disposeEdits(editIds: number[]) {
for (const editId of editIds) {
this.#_edits.delete(editId);
}
}
/** @internal*/ _pushEdit(edit: unknown): number {
return this.#_edits.add([edit]);
}
//#endregion
private pushEdit(edit: EditType) {
this.spliceEdits(edit);
this.#currentEditIndex = this.#edits.length - 1;
this.updateState();
}
private updateState() {
const dirty = this.#edits.length > 0 && this.#savePoint !== this.#currentEditIndex;
this.#proxy.$onDidChangeCustomDocumentState(this.uri, this.viewType, { dirty });
}
private spliceEdits(editToInsert?: EditType) {
const start = this.#currentEditIndex + 1;
const toRemove = this.#edits.length - this.#currentEditIndex;
editToInsert
? this.#edits.splice(start, toRemove, editToInsert)
: this.#edits.splice(start, toRemove);
}
private getEditingCapability(): vscode.CustomEditorEditingCapability {
if (!this.#capabilities?.editing) {
private getEditingDelegate(): vscode.CustomEditorEditingDelegate {
if (!this.#editingDelegate) {
throw new Error('Document is not editable');
}
return this.#capabilities.editing;
return this.#editingDelegate;
}
}
@@ -535,17 +485,24 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
provider: vscode.CustomEditorProvider | vscode.CustomTextEditorProvider,
options: vscode.WebviewPanelOptions | undefined = {}
): vscode.Disposable {
let disposable: vscode.Disposable;
const disposables = new DisposableStore();
if ('resolveCustomTextEditor' in provider) {
disposable = this._editorProviders.addTextProvider(viewType, extension, provider);
disposables.add(this._editorProviders.addTextProvider(viewType, extension, provider));
this._proxy.$registerTextEditorProvider(toExtensionData(extension), viewType, options);
} else {
disposable = this._editorProviders.addCustomProvider(viewType, extension, provider);
disposables.add(this._editorProviders.addCustomProvider(viewType, extension, provider));
this._proxy.$registerCustomEditorProvider(toExtensionData(extension), viewType, options);
if (provider.editingDelegate) {
disposables.add(provider.editingDelegate.onDidEdit(e => {
const document = e.document;
const editId = (document as CustomDocument)._pushEdit(e.edit);
this._proxy.$onDidEdit(document.uri, document.viewType, editId, e.label);
}));
}
}
return VSCodeDisposable.from(
disposable,
disposables,
new VSCodeDisposable(() => {
this._proxy.$unregisterEditorProvider(viewType);
}));
@@ -640,12 +597,11 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
}
const revivedResource = URI.revive(resource);
const document = CustomDocument.create(this._proxy, viewType, revivedResource);
const capabilities = await entry.provider.resolveCustomDocument(document);
document._setCapabilities(capabilities);
const document = CustomDocument.create(viewType, revivedResource, entry.provider.editingDelegate);
await entry.provider.resolveCustomDocument(document);
this._documents.add(document);
return {
editable: !!capabilities.editing
editable: !!entry.provider.editingDelegate,
};
}
@@ -702,24 +658,29 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
}
}
async $undo(resourceComponents: UriComponents, viewType: string): Promise<void> {
$disposeEdits(resourceComponents: UriComponents, viewType: string, editIds: number[]): void {
const document = this.getCustomDocument(viewType, resourceComponents);
document._undo();
document._disposeEdits(editIds);
}
async $redo(resourceComponents: UriComponents, viewType: string): Promise<void> {
async $undo(resourceComponents: UriComponents, viewType: string, editId: number): Promise<void> {
const document = this.getCustomDocument(viewType, resourceComponents);
document._redo();
return document._undo(editId);
}
async $revert(resourceComponents: UriComponents, viewType: string): Promise<void> {
async $redo(resourceComponents: UriComponents, viewType: string, editId: number): Promise<void> {
const document = this.getCustomDocument(viewType, resourceComponents);
document._revert();
return document._redo(editId);
}
async $revert(resourceComponents: UriComponents, viewType: string, changes: { undoneEdits: number[], redoneEdits: number[] }): Promise<void> {
const document = this.getCustomDocument(viewType, resourceComponents);
return document._revert(changes);
}
async $onSave(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise<void> {
const document = this.getCustomDocument(viewType, resourceComponents);
document._save(cancellation);
return document._save(cancellation);
}
async $onSaveAs(resourceComponents: UriComponents, viewType: string, targetResource: UriComponents): Promise<void> {

View File

@@ -55,7 +55,7 @@ export class CloseSidebarAction extends Action {
}
}
registry.registerWorkbenchAction(SyncActionDescriptor.create(CloseSidebarAction, CloseSidebarAction.ID, CloseSidebarAction.LABEL), 'View: Close Side Bar ', viewCategory);
registry.registerWorkbenchAction(SyncActionDescriptor.create(CloseSidebarAction, CloseSidebarAction.ID, CloseSidebarAction.LABEL), 'View: Close Side Bar', viewCategory);
// --- Toggle Activity Bar
@@ -237,7 +237,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
export class ToggleEditorVisibilityAction extends Action {
static readonly ID = 'workbench.action.toggleEditorVisibility';
static readonly LABEL = nls.localize('toggleEditor', "Toggle Editor Area");
static readonly LABEL = nls.localize('toggleEditor', "Toggle Editor Area Visibility");
constructor(
id: string,

View File

@@ -3,9 +3,10 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-workbench .sidebar > .content {
/* Removed to allow progress bar positioning to escape */
/* .monaco-workbench .sidebar > .content {
overflow: hidden;
}
} */
.monaco-workbench.nosidebar > .part.sidebar {
display: none !important;

View File

@@ -28,10 +28,14 @@
margin-left: 7px;
}
.monaco-pane-view .pane > .pane-header .monaco-progress-container {
.monaco-pane-view .pane .monaco-progress-container {
position: absolute;
left: 0;
bottom: 0;
top: -2px;
z-index: 5;
height: 2px;
}
.monaco-pane-view .pane:not(.merged-header) .monaco-progress-container {
top: 20px;
}

View File

@@ -225,6 +225,15 @@ export abstract class ViewPane extends Pane implements IView {
this.viewWelcomeController = new ViewWelcomeController(this.id, contextKeyService);
}
get headerVisible(): boolean {
return super.headerVisible;
}
set headerVisible(visible: boolean) {
super.headerVisible = visible;
toggleClass(this.element, 'merged-header', !visible);
}
setVisible(visible: boolean): void {
if (this._isVisible !== visible) {
this._isVisible = visible;
@@ -294,13 +303,6 @@ export abstract class ViewPane extends Pane implements IView {
const onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewPane.AlwaysShowActionsConfig));
this._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this));
this.updateActionsVisibility();
if (this.progressBar !== undefined) {
// Progress bar
this.progressBar = this._register(new ProgressBar(this.headerContainer));
this._register(attachProgressBarStyler(this.progressBar, this.themeService));
this.progressBar.hide();
}
}
protected renderTwisties(container: HTMLElement): void {
@@ -333,13 +335,9 @@ export abstract class ViewPane extends Pane implements IView {
}
getProgressIndicator() {
if (!this.headerContainer) {
return undefined;
}
if (this.progressBar === undefined) {
// Progress bar
this.progressBar = this._register(new ProgressBar(this.headerContainer));
this.progressBar = this._register(new ProgressBar(this.element));
this._register(attachProgressBarStyler(this.progressBar, this.themeService));
this.progressBar.hide();
}

View File

@@ -33,7 +33,6 @@ import { WorkspaceService } from 'vs/workbench/services/configuration/browser/co
import { ConfigurationCache } from 'vs/workbench/services/configuration/browser/configurationCache';
import { ISignService } from 'vs/platform/sign/common/sign';
import { SignService } from 'vs/platform/sign/browser/signService';
import { hash } from 'vs/base/common/hash';
import { IWorkbenchConstructionOptions, IWorkspace } from 'vs/workbench/workbench.web.api';
import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider';
import { BACKUPS } from 'vs/platform/environment/common/environment';
@@ -51,6 +50,7 @@ import { isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/wi
import { getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces';
import { coalesce } from 'vs/base/common/arrays';
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
import { WebResourceIdentityService, IResourceIdentityService } from 'vs/platform/resource/common/resourceIdentityService';
class BrowserMain extends Disposable {
@@ -157,7 +157,11 @@ class BrowserMain extends Disposable {
const logService = new BufferLogService(this.configuration.logLevel);
serviceCollection.set(ILogService, logService);
const payload = this.resolveWorkspaceInitializationPayload();
// Resource Identity
const resourceIdentityService = this._register(new WebResourceIdentityService());
serviceCollection.set(IResourceIdentityService, resourceIdentityService);
const payload = await this.resolveWorkspaceInitializationPayload(resourceIdentityService);
// Environment
const environmentService = new BrowserWorkbenchEnvironmentService({ workspaceId: payload.id, logsPath, ...this.configuration });
@@ -292,7 +296,7 @@ class BrowserMain extends Disposable {
}
}
private resolveWorkspaceInitializationPayload(): IWorkspaceInitializationPayload {
private async resolveWorkspaceInitializationPayload(resourceIdentityService: IResourceIdentityService): Promise<IWorkspaceInitializationPayload> {
let workspace: IWorkspace | undefined = undefined;
if (this.configuration.workspaceProvider) {
workspace = this.configuration.workspaceProvider.workspace;
@@ -305,7 +309,8 @@ class BrowserMain extends Disposable {
// Single-folder workspace
if (workspace && isFolderToOpen(workspace)) {
return { id: hash(workspace.folderUri.toString()).toString(16), folder: workspace.folderUri };
const id = await resourceIdentityService.resolveResourceIdentity(workspace.folderUri);
return { id, folder: workspace.folderUri };
}
return { id: 'empty-window' };

View File

@@ -335,6 +335,7 @@ export class Workbench extends Layout {
addClasses(this.container, ...workbenchClasses);
addClass(document.body, platformClass); // used by our fonts
this.container.setAttribute('role', 'application');
if (isWeb) {
addClass(document.body, 'web');

View File

@@ -9,7 +9,6 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { ILifecycleService, LifecyclePhase, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { ConfirmResult, IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs';
import Severity from 'vs/base/common/severity';
import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
@@ -20,6 +19,7 @@ import { BackupTracker } from 'vs/workbench/contrib/backup/common/backupTracker'
import { ILogService } from 'vs/platform/log/common/log';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { SaveReason } from 'vs/workbench/common/editor';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
export class NativeBackupTracker extends BackupTracker implements IWorkbenchContribution {
@@ -28,13 +28,13 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont
@IFilesConfigurationService filesConfigurationService: IFilesConfigurationService,
@IWorkingCopyService workingCopyService: IWorkingCopyService,
@ILifecycleService lifecycleService: ILifecycleService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IFileDialogService private readonly fileDialogService: IFileDialogService,
@IDialogService private readonly dialogService: IDialogService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IElectronService private readonly electronService: IElectronService,
@ILogService logService: ILogService,
@IEditorService private readonly editorService: IEditorService
@IEditorService private readonly editorService: IEditorService,
@IEnvironmentService private readonly environmentService: IEnvironmentService
) {
super(backupFileService, filesConfigurationService, workingCopyService, logService, lifecycleService);
}
@@ -120,32 +120,36 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont
// ever activated when quit is requested.
let doBackup: boolean | undefined;
switch (reason) {
case ShutdownReason.CLOSE:
if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) {
doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured
} else if (await this.electronService.getWindowCount() > 1 || isMacintosh) {
doBackup = false; // do not backup if a window is closed that does not cause quitting of the application
} else {
doBackup = true; // backup if last window is closed on win/linux where the application quits right after
}
break;
if (this.environmentService.isExtensionDevelopment) {
doBackup = true; // always backup closing extension development window without asking to speed up debugging
} else {
switch (reason) {
case ShutdownReason.CLOSE:
if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) {
doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured
} else if (await this.electronService.getWindowCount() > 1 || isMacintosh) {
doBackup = false; // do not backup if a window is closed that does not cause quitting of the application
} else {
doBackup = true; // backup if last window is closed on win/linux where the application quits right after
}
break;
case ShutdownReason.QUIT:
doBackup = true; // backup because next start we restore all backups
break;
case ShutdownReason.QUIT:
doBackup = true; // backup because next start we restore all backups
break;
case ShutdownReason.RELOAD:
doBackup = true; // backup because after window reload, backups restore
break;
case ShutdownReason.RELOAD:
doBackup = true; // backup because after window reload, backups restore
break;
case ShutdownReason.LOAD:
if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) {
doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured
} else {
doBackup = false; // do not backup because we are switching contexts
}
break;
case ShutdownReason.LOAD:
if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.filesConfigurationService.hotExitConfiguration === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) {
doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured
} else {
doBackup = false; // do not backup because we are switching contexts
}
break;
}
}
// Perform a backup of all dirty working copies unless a backup already exists
@@ -247,10 +251,6 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont
return false; // if editors have not restored, we are not up to speed with backups and thus should not discard them
}
if (this.environmentService.isExtensionDevelopment) {
return false; // extension development does not track any backups
}
return Promise.all(backupsToDiscard.map(workingCopy => this.backupFileService.discardBackup(workingCopy.resource))).then(() => false, () => false);
}
}

View File

@@ -32,7 +32,6 @@ import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/wo
import { ILogService } from 'vs/platform/log/common/log';
import { HotExitConfiguration } from 'vs/platform/files/common/files';
import { ShutdownReason, ILifecycleService, BeforeShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IFileDialogService, ConfirmResult, IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IWorkspaceContextService, Workspace } from 'vs/platform/workspace/common/workspace';
import { IElectronService } from 'vs/platform/electron/node/electron';
@@ -42,9 +41,10 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { TestFilesConfigurationService, TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices';
import { TestFilesConfigurationService } from 'vs/workbench/test/browser/workbenchTestServices';
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
const userdataDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backuprestorer');
const backupHome = path.join(userdataDir, 'Backups');
@@ -60,15 +60,15 @@ class TestBackupTracker extends NativeBackupTracker {
@IFilesConfigurationService filesConfigurationService: IFilesConfigurationService,
@IWorkingCopyService workingCopyService: IWorkingCopyService,
@ILifecycleService lifecycleService: ILifecycleService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IFileDialogService fileDialogService: IFileDialogService,
@IDialogService dialogService: IDialogService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IElectronService electronService: IElectronService,
@ILogService logService: ILogService,
@IEditorService editorService: IEditorService
@IEditorService editorService: IEditorService,
@IEnvironmentService environmentService: IEnvironmentService
) {
super(backupFileService, filesConfigurationService, workingCopyService, lifecycleService, environmentService, fileDialogService, dialogService, contextService, electronService, logService, editorService);
super(backupFileService, filesConfigurationService, workingCopyService, lifecycleService, fileDialogService, dialogService, contextService, electronService, logService, editorService, environmentService);
// Reduce timeout for tests
BackupTracker.BACKUP_FROM_CONTENT_CHANGE_DELAY = 10;
@@ -131,8 +131,7 @@ suite.skip('BackupTracker', () => { // {{SQL CARBON EDIT}} skip failing tests
instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService(
<IContextKeyService>instantiationService.createInstance(MockContextKeyService),
configurationService,
TestEnvironmentService
configurationService
));
const part = instantiationService.createInstance(EditorPart);

View File

@@ -284,7 +284,11 @@ class ShowAccessibilityHelpAction extends EditorAction {
kbOpts: {
kbExpr: EditorContextKeys.focus,
primary: KeyMod.Alt | KeyCode.F1,
weight: KeybindingWeight.EditorContrib
weight: KeybindingWeight.EditorContrib,
linux: {
primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F1,
secondary: [KeyMod.Alt | KeyCode.F1]
}
}
});
}

View File

@@ -8,7 +8,7 @@ import { IKeyMods } from 'vs/platform/quickinput/common/quickInput';
import { IEditor } from 'vs/editor/common/editorCommon';
import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IRange } from 'vs/editor/common/core/range';
import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoLine';
import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoLineQuickAccess';
import { Registry } from 'vs/platform/registry/common/platform';
import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess';

View File

@@ -40,7 +40,7 @@ CommandsRegistry.registerCommand('_workbench.openWith', (accessor: ServicesAcces
// #region Reopen With
const REOPEN_WITH_COMMAND_ID = 'reOpenWith';
const REOPEN_WITH_TITLE = { value: nls.localize('reopenWith.title', 'Reopen With...'), original: 'Reopen With' };
const REOPEN_WITH_TITLE = { value: nls.localize('reopenWith.title', 'Reopen With...'), original: 'Reopen With...' };
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: REOPEN_WITH_COMMAND_ID,

View File

@@ -15,6 +15,7 @@ import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { GroupIdentifier, IEditorInput, IRevertOptions, ISaveOptions, Verbosity } from 'vs/workbench/common/editor';
import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor';
import { IWebviewService, WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview';
@@ -44,6 +45,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
@IFileDialogService private readonly fileDialogService: IFileDialogService,
@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService,
@IEditorService private readonly editorService: IEditorService,
@IUndoRedoService private readonly undoRedoService: IUndoRedoService,
) {
super(id, viewType, '', webview, webviewService, webviewWorkbenchService);
this._editorResource = resource;
@@ -175,10 +177,12 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
}
public undo(): void {
assertIsDefined(this._modelRef).object.undo();
assertIsDefined(this._modelRef);
this.undoRedoService.undo(this.resource);
}
public redo(): void {
assertIsDefined(this._modelRef).object.redo();
assertIsDefined(this._modelRef);
this.undoRedoService.redo(this.resource);
}
}

View File

@@ -52,8 +52,6 @@ export interface ICustomEditorModel extends IDisposable {
isDirty(): boolean;
readonly onDidChangeDirty: Event<void>;
undo(): void;
redo(): void;
revert(options?: IRevertOptions): Promise<void>;
save(options?: ISaveOptions): Promise<boolean>;

View File

@@ -64,14 +64,6 @@ export class CustomTextEditorModel extends Disposable implements ICustomEditorMo
return this.textFileService.revert(this.resource, options);
}
public undo() {
this.textFileService.files.get(this.resource)?.textEditorModel?.undo();
}
public redo() {
this.textFileService.files.get(this.resource)?.textEditorModel?.redo();
}
public async save(options?: ISaveOptions): Promise<boolean> {
return !!await this.textFileService.save(this.resource, options);
}

View File

@@ -118,7 +118,7 @@ registerCommands();
// register action to open viewlet
const registry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionRegistryExtensions.WorkbenchActions);
registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenDebugPanelAction, OpenDebugPanelAction.ID, OpenDebugPanelAction.LABEL, openPanelKb), 'View: Debug Console', nls.localize('view', "View"));
registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenDebugViewletAction, OpenDebugViewletAction.ID, OpenDebugViewletAction.LABEL, openViewletKb), 'View: Show Debug', nls.localize('view', "View"));
registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenDebugViewletAction, OpenDebugViewletAction.ID, OpenDebugViewletAction.LABEL, openViewletKb), 'View: Show Run and Debug', nls.localize('view', "View"));
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugToolBar, LifecyclePhase.Restored);
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugContentProvider, LifecyclePhase.Eventually);

View File

@@ -125,7 +125,8 @@
font-style: italic;
}
.monaco-workbench .monaco-list-row .expression .error {
.monaco-workbench .monaco-list-row .expression .error,
.monaco-workbench .debug-pane .debug-variables .scope .error {
color: #e51400;
}
@@ -145,7 +146,8 @@
color: rgba(204, 204, 204, 0.6);
}
.vs-dark .monaco-workbench .monaco-list-row .expression .error {
.vs-dark .monaco-workbench .monaco-list-row .expression .error,
.vs-dark .monaco-workbench .debug-pane .debug-variables .scope .error {
color: #f48771;
}
@@ -173,7 +175,8 @@
color: #ce9178;
}
.hc-black .monaco-workbench .monaco-list-row .expression .error {
.hc-black .monaco-workbench .monaco-list-row .expression .error,
.hc-black .monaco-workbench .debug-pane .debug-variables .scope .error {
color: #f48771;
}

View File

@@ -316,6 +316,14 @@
animation-name: debugViewletValueChanged;
}
.debug-pane .debug-variables .scope .error {
font-style: italic;
text-overflow: ellipsis;
overflow: hidden;
font-family: var(--monaco-monospace-font);
font-weight: normal;
}
/* Breakpoints */
.debug-pane .monaco-list-row {

View File

@@ -9,7 +9,7 @@ import * as dom from 'vs/base/browser/dom';
import { CollapseAction } from 'vs/workbench/browser/viewlet';
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { IDebugService, IExpression, IScope, CONTEXT_VARIABLES_FOCUSED, IViewModel } from 'vs/workbench/contrib/debug/common/debug';
import { Variable, Scope } from 'vs/workbench/contrib/debug/common/debugModel';
import { Variable, Scope, ErrorScope } from 'vs/workbench/contrib/debug/common/debugModel';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { renderViewTree, renderVariable, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView';
@@ -97,7 +97,7 @@ export class VariablesView extends ViewPane {
const treeContainer = renderViewTree(container);
this.tree = <WorkbenchAsyncDataTree<IViewModel | IExpression | IScope, IExpression | IScope, FuzzyScore>>this.instantiationService.createInstance(WorkbenchAsyncDataTree, 'VariablesView', treeContainer, new VariablesDelegate(),
[this.instantiationService.createInstance(VariablesRenderer), new ScopesRenderer()],
[this.instantiationService.createInstance(VariablesRenderer), new ScopesRenderer(), new ScopeErrorRenderer()],
new VariablesDataSource(), {
ariaLabel: nls.localize('variablesAriaTreeLabel', "Debug Variables"),
accessibilityProvider: new VariablesAccessibilityProvider(),
@@ -217,7 +217,7 @@ function isViewModel(obj: any): obj is IViewModel {
export class VariablesDataSource implements IAsyncDataSource<IViewModel, IExpression | IScope> {
hasChildren(element: IViewModel | IExpression | IScope): boolean {
if (isViewModel(element) || element instanceof Scope) {
if (isViewModel(element)) {
return true;
}
@@ -246,6 +246,10 @@ class VariablesDelegate implements IListVirtualDelegate<IExpression | IScope> {
}
getTemplateId(element: IExpression | IScope): string {
if (element instanceof ErrorScope) {
return ScopeErrorRenderer.ID;
}
if (element instanceof Scope) {
return ScopesRenderer.ID;
}
@@ -278,6 +282,33 @@ class ScopesRenderer implements ITreeRenderer<IScope, FuzzyScore, IScopeTemplate
}
}
interface IScopeErrorTemplateData {
error: HTMLElement;
}
class ScopeErrorRenderer implements ITreeRenderer<IScope, FuzzyScore, IScopeErrorTemplateData> {
static readonly ID = 'scopeError';
get templateId(): string {
return ScopeErrorRenderer.ID;
}
renderTemplate(container: HTMLElement): IScopeErrorTemplateData {
const wrapper = dom.append(container, $('.scope'));
const error = dom.append(wrapper, $('.error'));
return { error };
}
renderElement(element: ITreeNode<IScope, FuzzyScore>, index: number, templateData: IScopeErrorTemplateData): void {
templateData.error.innerText = element.element.name;
}
disposeTemplate(): void {
// noop
}
}
export class VariablesRenderer extends AbstractExpressionsRenderer {
static readonly ID = 'variable';

View File

@@ -190,9 +190,7 @@ export class WatchExpressionsView extends ViewPane {
this.debugService.getViewModel().setSelectedExpression(expression);
return Promise.resolve();
}));
if (!expression.hasChildren) {
actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, expression.value, 'watch'));
}
actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, expression.value, 'watch'));
actions.push(new Separator());
actions.push(new Action('debug.removeWatchExpression', nls.localize('removeWatchExpression', "Remove Expression"), undefined, true, () => {
@@ -204,9 +202,7 @@ export class WatchExpressionsView extends ViewPane {
actions.push(new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService));
if (element instanceof Variable) {
const variable = element as Variable;
if (!variable.hasChildren) {
actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable, 'watch'));
}
actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable, 'watch'));
actions.push(new Separator());
}
actions.push(new RemoveAllWatchExpressionsAction(RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL, this.debugService, this.keybindingService));

View File

@@ -88,22 +88,26 @@ export class WelcomeView extends ViewPane {
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, {
content: localize('openAFileWhichCanBeDebugged', "[Open a file](command:{0}) which can be debugged or run.", isMacintosh ? OpenFileFolderAction.ID : OpenFileAction.ID),
content: localize({ key: 'openAFileWhichCanBeDebugged', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] },
"[Open a file](command:{0}) which can be debugged or run.", isMacintosh ? OpenFileFolderAction.ID : OpenFileAction.ID),
when: CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR.toNegated()
});
let debugKeybindingLabel = '';
viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, {
content: localize('runAndDebugAction', "[Run and Debug{0}](command:{1})", debugKeybindingLabel, StartAction.ID),
content: localize({ key: 'runAndDebugAction', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] },
"[Run and Debug{0}](command:{1})", debugKeybindingLabel, StartAction.ID),
preconditions: [CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR]
});
viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, {
content: localize('customizeRunAndDebug', "To customize Run and Debug [create a launch.json file](command:{0}).", ConfigureAction.ID),
content: localize({ key: 'customizeRunAndDebug', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] },
"To customize Run and Debug [create a launch.json file](command:{0}).", ConfigureAction.ID),
when: WorkbenchStateContext.notEqualsTo('empty')
});
viewsRegistry.registerViewWelcomeContent(WelcomeView.ID, {
content: localize('customizeRunAndDebugOpenFolder', "To customize Run and Debug, [open a folder](command:{0}) and create a launch.json file.", isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID),
content: localize({ key: 'customizeRunAndDebugOpenFolder', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] },
"To customize Run and Debug, [open a folder](command:{0}) and create a launch.json file.", isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID),
when: WorkbenchStateContext.isEqualTo('empty')
});

View File

@@ -301,6 +301,7 @@ export interface IScope extends IExpressionContainer {
readonly name: string;
readonly expensive: boolean;
readonly range?: IRange;
readonly hasChildren: boolean;
}
export interface IStackFrame extends ITreeElement {

View File

@@ -269,6 +269,21 @@ export class Scope extends ExpressionContainer implements IScope {
}
}
export class ErrorScope extends Scope {
constructor(
stackFrame: IStackFrame,
index: number,
message: string,
) {
super(stackFrame, index, message, 0, false);
}
toString(): string {
return this.name;
}
}
export class StackFrame implements IStackFrame {
private scopes: Promise<Scope[]> | undefined;
@@ -293,7 +308,7 @@ export class StackFrame implements IStackFrame {
return response && response.body && response.body.scopes ?
response.body.scopes.map((rs, index) => new Scope(this, index, rs.name, rs.variablesReference, rs.expensive, rs.namedVariables, rs.indexedVariables,
rs.line && rs.column && rs.endLine && rs.endColumn ? new Range(rs.line, rs.column, rs.endLine, rs.endColumn) : undefined)) : [];
}, err => []);
}, err => [new ErrorScope(this, 0, err.message)]);
}
return this.scopes;

View File

@@ -70,7 +70,9 @@ declare module DebugProtocol {
}
/** Cancel request; value of command field is 'cancel'.
The 'cancel' request is used by the frontend to indicate that it is no longer interested in the result produced by a specific request issued earlier.
The 'cancel' request is used by the frontend in two situations:
- to indicate that it is no longer interested in the result produced by a specific request issued earlier
- to cancel a progress indicator.
This request has a hint characteristic: a debug adapter can only be expected to make a 'best effort' in honouring this request but there are no guarantees.
The 'cancel' request may return an error if it could not cancel an operation but a frontend should refrain from presenting this error to end users.
A frontend client should only call this request if the capability 'supportsCancelRequest' is true.
@@ -85,8 +87,10 @@ declare module DebugProtocol {
/** Arguments for 'cancel' request. */
export interface CancelArguments {
/** The ID (attribute 'seq') of the request to cancel. */
/** The ID (attribute 'seq') of the request to cancel. If missing no request is cancelled. Both a 'requestId' and a 'progressId' can be specified in one request. */
requestId?: number;
/** The ID (attribute 'progressId') of the progress to cancel. If missing no progress is cancelled. Both a 'requestId' and a 'progressId' can be specified in one request. */
progressId?: string;
}
/** Response to 'cancel' request. This is just an acknowledgement, so no body field is required. */
@@ -302,6 +306,64 @@ declare module DebugProtocol {
};
}
/** Event message for 'progressStart' event type.
The event signals that a long running operation is about to start and
provides additional information for the client to set up a corresponding progress and cancellation UI.
The client is free to delay the showing of the UI in order to reduce flicker.
*/
export interface ProgressStartEvent extends Event {
// event: 'progressStart';
body: {
/** An ID that must be used in subsequent 'progressUpdate' and 'progressEnd' events to make them refer to the same progress reporting. IDs must be unique within a debug session. */
progressId: string;
/** Mandatory (short) title of the progress reporting. Shown in the UI to describe the long running operation. */
title: string;
/** The request ID that this progress report is related to. If specified a debug adapter is expected to emit
progress events for the long running request until the request has been either completed or cancelled.
If the request ID is omitted, the progress report is assumed to be related to some general activity of the debug adapter.
*/
requestId?: number;
/** If true, the request that reports progress may be canceled with a 'cancel' request.
So this property basically controls whether the client should use UX that supports cancellation.
Clients that don't support cancellation are allowed to ignore the setting.
*/
cancellable?: boolean;
/** Optional, more detailed progress message. */
message?: string;
/** Optional progress percentage to display (value range: 0 to 100). If omitted no percentage will be shown. */
percentage?: number;
};
}
/** Event message for 'progressUpdate' event type.
The event signals that the progress reporting needs to updated with a new message and/or percentage.
The client does not have to update the UI immediately, but the clients needs to keep track of the message and/or percentage values.
*/
export interface ProgressUpdateEvent extends Event {
// event: 'progressUpdate';
body: {
/** The ID that was introduced in the initial 'progressStart' event. */
progressId: string;
/** Optional, more detailed progress message. If omitted, the previous message (if any) is used. */
message?: string;
/** Optional progress percentage to display (value range: 0 to 100). If omitted no percentage will be shown. */
percentage?: number;
};
}
/** Event message for 'progressEnd' event type.
The event signals the end of the progress reporting with an optional final message.
*/
export interface ProgressEndEvent extends Event {
// event: 'progressEnd';
body: {
/** The ID that was introduced in the initial 'ProgressStartEvent'. */
progressId: string;
/** Optional, more detailed progress message. If omitted, the previous message (if any) is used. */
message?: string;
};
}
/** RunInTerminal request; value of command field is 'runInTerminal'.
This request is sent from the debug adapter to the client to run a command in a terminal. This is typically used to launch the debuggee in a terminal provided by the client.
*/
@@ -370,6 +432,8 @@ declare module DebugProtocol {
supportsRunInTerminalRequest?: boolean;
/** Client supports memory references. */
supportsMemoryReferences?: boolean;
/** Client supports progress reporting. */
supportsProgressReporting?: boolean;
}
/** Response to 'initialize' request. */
@@ -1100,6 +1164,7 @@ declare module DebugProtocol {
'watch': evaluate is run in a watch.
'repl': evaluate is run from REPL console.
'hover': evaluate is run from a data hover.
'clipboard': evaluate is run to generate the value that will be stored in the clipboard.
etc.
*/
context?: string;
@@ -1409,6 +1474,8 @@ declare module DebugProtocol {
supportsCancelRequest?: boolean;
/** The debug adapter supports the 'breakpointLocations' request. */
supportsBreakpointLocationsRequest?: boolean;
/** The debug adapter supports the 'clipboard' context value in the 'evaluate' request. */
supportsClipboardContext?: boolean;
}
/** An ExceptionBreakpointsFilter is shown in the UI as an option for configuring how exceptions are dealt with. */

View File

@@ -15,7 +15,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { dispose, Disposable } from 'vs/base/common/lifecycle';
import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG, TOGGLE_IGNORE_EXTENSION_ACTION_ID } from 'vs/workbench/contrib/extensions/common/extensions';
import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate';
import { ExtensionsLabel, IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension, INSTALL_ERROR_NOT_SUPPORTED } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension, INSTALL_ERROR_NOT_SUPPORTED } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionTipsService, IExtensionRecommendation, IExtensionsConfigContent, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtensionType, ExtensionIdentifier, IExtensionDescription, IExtensionManifest, isLanguagePackExtension, ExtensionsPolicy, ExtensionsPolicyKey } from 'vs/platform/extensions/common/extensions'; // {{SQL CARBON EDIT}}
@@ -1960,7 +1960,7 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: ConfigureWorkspaceRecommendedExtensionsAction.ID,
title: { value: `${ExtensionsLabel}: ${ConfigureWorkspaceRecommendedExtensionsAction.LABEL}`, original: 'Configure Recommended Extensions (Workspace)' },
title: { value: ConfigureWorkspaceRecommendedExtensionsAction.LABEL, original: 'Configure Recommended Extensions (Workspace)' },
category: localize('extensions', "Extensions")
},
when: this.workspaceContextKey
@@ -1972,7 +1972,7 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: ConfigureWorkspaceFolderRecommendedExtensionsAction.ID,
title: { value: `${ExtensionsLabel}: ${ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL}`, original: 'Configure Recommended Extensions (Workspace Folder)' },
title: { value: ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL, original: 'Configure Recommended Extensions (Workspace Folder)' },
category: localize('extensions', "Extensions")
},
when: this.workspaceFolderContextKey
@@ -1986,7 +1986,7 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: AddToWorkspaceRecommendationsAction.ADD_ID,
title: { value: `${ExtensionsLabel}: ${AddToWorkspaceRecommendationsAction.ADD_LABEL}`, original: 'Add to Recommended Extensions (Workspace)' },
title: { value: AddToWorkspaceRecommendationsAction.ADD_LABEL, original: 'Add to Recommended Extensions (Workspace)' },
category: localize('extensions', "Extensions")
},
when: this.addToWorkspaceRecommendationsContextKey
@@ -2000,7 +2000,7 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: AddToWorkspaceFolderRecommendationsAction.ADD_ID,
title: { value: `${ExtensionsLabel}: ${AddToWorkspaceFolderRecommendationsAction.ADD_LABEL}`, original: 'Extensions: Add to Recommended Extensions (Workspace Folder)' },
title: { value: AddToWorkspaceFolderRecommendationsAction.ADD_LABEL, original: 'Extensions: Add to Recommended Extensions (Workspace Folder)' },
category: localize('extensions', "Extensions")
},
when: this.addToWorkspaceFolderRecommendationsContextKey
@@ -2014,7 +2014,7 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: AddToWorkspaceRecommendationsAction.IGNORE_ID,
title: { value: `${ExtensionsLabel}: ${AddToWorkspaceRecommendationsAction.IGNORE_LABEL}`, original: 'Extensions: Ignore Recommended Extension (Workspace)' },
title: { value: AddToWorkspaceRecommendationsAction.IGNORE_LABEL, original: 'Extensions: Ignore Recommended Extension (Workspace)' },
category: localize('extensions', "Extensions")
},
when: this.addToWorkspaceRecommendationsContextKey
@@ -2028,7 +2028,7 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: AddToWorkspaceFolderRecommendationsAction.IGNORE_ID,
title: { value: `${ExtensionsLabel}: ${AddToWorkspaceFolderRecommendationsAction.IGNORE_LABEL}`, original: 'Extensions: Ignore Recommended Extension (Workspace Folder)' },
title: { value: AddToWorkspaceFolderRecommendationsAction.IGNORE_LABEL, original: 'Extensions: Ignore Recommended Extension (Workspace Folder)' },
category: localize('extensions', "Extensions")
},
when: this.addToWorkspaceFolderRecommendationsContextKey

View File

@@ -12,6 +12,7 @@ import { VIEWLET_ID, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/e
import { IExtensionGalleryService, IExtensionManagementService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ILogService } from 'vs/platform/log/common/log';
import { DisposableStore } from 'vs/base/common/lifecycle';
export class InstallExtensionQuickAccessProvider extends PickerQuickAccessProvider<IPickerQuickAccessItem> {
@@ -27,7 +28,7 @@ export class InstallExtensionQuickAccessProvider extends PickerQuickAccessProvid
super(InstallExtensionQuickAccessProvider.PREFIX);
}
protected getPicks(filter: string, token: CancellationToken): Array<IPickerQuickAccessItem | IQuickPickSeparator> | Promise<Array<IPickerQuickAccessItem | IQuickPickSeparator>> {
protected getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Array<IPickerQuickAccessItem | IQuickPickSeparator> | Promise<Array<IPickerQuickAccessItem | IQuickPickSeparator>> {
// Nothing typed
if (!filter) {

View File

@@ -64,24 +64,6 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor
}
private registerViews(): void {
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
this._register(viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
content: localize('noWorkspaceHelp', "You have not yet added a folder to the workspace.\n[Add Folder](command:{0})", AddRootFolderAction.ID),
when: WorkbenchStateContext.isEqualTo('workspace')
}));
const commandId = isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID;
this._register(viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
content: localize('remoteNoFolderHelp', "Connected to remote.\n[Open Folder](command:{0})", commandId),
when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.notEqualsTo(''), IsWebContext.toNegated())
}));
this._register(viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
content: localize('noFolderHelp', "You have not yet opened a folder.\n[Open Folder](command:{0})", commandId),
when: ContextKeyExpr.or(ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.isEqualTo('')), ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), IsWebContext))
}));
const viewDescriptors = viewsRegistry.getViews(VIEW_CONTAINER);
let viewDescriptorsToRegister: IViewDescriptor[] = [];
@@ -275,3 +257,23 @@ export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry
icon: 'codicon-files',
order: 10 // {{SQL CARBON EDIT}}
}, ViewContainerLocation.Sidebar);
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
content: localize({ key: 'noWorkspaceHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] },
"You have not yet added a folder to the workspace.\n[Add Folder](command:{0})", AddRootFolderAction.ID),
when: WorkbenchStateContext.isEqualTo('workspace')
});
const commandId = isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID;
viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
content: localize({ key: 'remoteNoFolderHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] },
"Connected to remote.\n[Open Folder](command:{0})", commandId),
when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.notEqualsTo(''), IsWebContext.toNegated())
});
viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
content: localize({ key: 'noFolderHelp', comment: ['Please do not translate the word "commmand", it is part of our internal syntax which must not change'] },
"You have not yet opened a folder.\n[Open Folder](command:{0})", commandId),
when: ContextKeyExpr.or(ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.isEqualTo('')), ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), IsWebContext))
});

View File

@@ -7,7 +7,7 @@ import * as assert from 'assert';
import { Event } from 'vs/base/common/event';
import { toResource } from 'vs/base/test/common/utils';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { TestFilesConfigurationService, TestEnvironmentService, workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices';
import { TestFilesConfigurationService, workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices';
import { IResolvedTextFileEditorModel, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
@@ -56,8 +56,7 @@ suite('EditorAutoSave', () => {
instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService(
<IContextKeyService>instantiationService.createInstance(MockContextKeyService),
configurationService,
TestEnvironmentService
configurationService
));
const part = instantiationService.createInstance(EditorPart);

View File

@@ -6,7 +6,7 @@
import * as assert from 'assert';
import { toResource } from 'vs/base/test/common/utils';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { workbenchInstantiationService, TestServiceAccessor, TestFilesConfigurationService, TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices';
import { workbenchInstantiationService, TestServiceAccessor, TestFilesConfigurationService } from 'vs/workbench/test/browser/workbenchTestServices';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
@@ -56,8 +56,7 @@ suite('Files - TextFileEditor', () => {
instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService(
<IContextKeyService>instantiationService.createInstance(MockContextKeyService),
configurationService,
TestEnvironmentService
configurationService
));
const part = instantiationService.createInstance(EditorPart);

View File

@@ -143,39 +143,26 @@ registerAction2(class extends Action2 {
registerAction2(class extends Action2 {
constructor() {
super({
id: `workbench.output.action.turnOffAutoScroll`,
title: nls.localize('outputScrollOff', "Turn Auto Scrolling Off"),
id: `workbench.output.action.toggleAutoScroll`,
title: { value: nls.localize('toggleAutoScroll', "Toggle Auto Scrolling"), original: 'Toggle Auto Scrolling' },
tooltip: { value: nls.localize('outputScrollOff', "Turn Auto Scrolling Off"), original: 'Turn Auto Scrolling Off' },
menu: {
id: MenuId.ViewTitle,
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', OUTPUT_VIEW_ID), CONTEXT_OUTPUT_SCROLL_LOCK.negate()),
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', OUTPUT_VIEW_ID)),
group: 'navigation',
order: 3,
},
icon: { id: 'codicon/unlock' }
icon: { id: 'codicon/unlock' },
toggled: {
condition: CONTEXT_OUTPUT_SCROLL_LOCK,
icon: { id: 'codicon/lock' },
tooltip: { value: nls.localize('outputScrollOn', "Turn Auto Scrolling On"), original: 'Turn Auto Scrolling On' }
}
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const outputView = accessor.get(IViewsService).getActiveViewWithId<OutputViewPane>(OUTPUT_VIEW_ID)!;
outputView.scrollLock = true;
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: `workbench.output.action.turnOnAutoScroll`,
title: nls.localize('outputScrollOn', "Turn Auto Scrolling On"),
menu: {
id: MenuId.ViewTitle,
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', OUTPUT_VIEW_ID), CONTEXT_OUTPUT_SCROLL_LOCK),
group: 'navigation',
order: 3,
},
icon: { id: 'codicon/lock' },
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const outputView = accessor.get(IViewsService).getActiveViewWithId<OutputViewPane>(OUTPUT_VIEW_ID)!;
outputView.scrollLock = false;
outputView.scrollLock = !outputView.scrollLock;
}
});
registerAction2(class extends Action2 {

View File

@@ -116,22 +116,6 @@ export class OutputViewPane extends ViewPane {
this.editor.layout({ height, width });
}
getActions(): IAction[] {
if (!this.actions) {
this.actions = [
// this._register(this.instantiationService.createInstance(SwitchOutputAction)),
// this._register(this.instantiationService.createInstance(ClearOutputAction, ClearOutputAction.ID, ClearOutputAction.LABEL)),
// this._register(this.instantiationService.createInstance(ToggleOrSetOutputScrollLockAction, ToggleOrSetOutputScrollLockAction.ID, ToggleOrSetOutputScrollLockAction.LABEL)),
// this._register(this.instantiationService.createInstance(OpenLogOutputFile))
];
}
return [...super.getActions(), ...this.actions];
}
getSecondaryActions(): IAction[] {
return [...super.getSecondaryActions(), ...this.editor.getSecondaryActions()];
}
getActionViewItem(action: IAction): IActionViewItem | undefined {
if (action.id === 'workbench.output.action.switchBetweenOutputs') {
return this.instantiationService.createInstance(SwitchOutputActionViewItem, action);

View File

@@ -0,0 +1,109 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { ICommandQuickPick } from 'vs/platform/quickinput/browser/commandsQuickAccess';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { CancellationToken } from 'vs/base/common/cancellation';
import { timeout } from 'vs/base/common/async';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { DisposableStore, toDisposable, dispose } from 'vs/base/common/lifecycle';
import { AbstractEditorCommandsQuickAccessProvider } from 'vs/editor/contrib/quickAccess/commandsQuickAccess';
import { IEditor } from 'vs/editor/common/editorCommon';
import { Language } from 'vs/base/common/platform';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { INotificationService } from 'vs/platform/notification/common/notification';
export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider {
// If extensions are not yet registered, we wait for a little moment to give them
// a chance to register so that the complete set of commands shows up as result
// We do not want to delay functionality beyond that time though to keep the commands
// functional.
private readonly extensionRegistrationRace = Promise.race([
timeout(800),
this.extensionService.whenInstalledExtensionsRegistered()
]);
get activeTextEditorControl(): IEditor | undefined { return this.editorService.activeTextEditorControl; }
constructor(
@IEditorService private readonly editorService: IEditorService,
@IMenuService private readonly menuService: IMenuService,
@IExtensionService private readonly extensionService: IExtensionService,
@IEnvironmentService environmentService: IEnvironmentService,
@IInstantiationService instantiationService: IInstantiationService,
@IKeybindingService keybindingService: IKeybindingService,
@ICommandService commandService: ICommandService,
@ITelemetryService telemetryService: ITelemetryService,
@INotificationService notificationService: INotificationService
) {
super({ showAlias: !Language.isDefaultVariant() }, instantiationService, keybindingService, commandService, telemetryService, notificationService);
}
protected async getCommandPicks(disposables: DisposableStore, token: CancellationToken): Promise<Array<ICommandQuickPick>> {
// wait for extensions registration or 800ms once
await this.extensionRegistrationRace;
if (token.isCancellationRequested) {
return [];
}
return [
...this.getCodeEditorCommandPicks(),
...this.getGlobalCommandPicks(disposables)
];
}
private getGlobalCommandPicks(disposables: DisposableStore): ICommandQuickPick[] {
const globalCommandPicks: ICommandQuickPick[] = [];
const globalCommandsMenu = this.editorService.invokeWithinEditorContext(accessor =>
this.menuService.createMenu(MenuId.CommandPalette, accessor.get(IContextKeyService))
);
const globalCommandsMenuActions = globalCommandsMenu.getActions()
.reduce((r, [, actions]) => [...r, ...actions], <Array<MenuItemAction | SubmenuItemAction | string>>[])
.filter(action => action instanceof MenuItemAction) as MenuItemAction[];
for (const action of globalCommandsMenuActions) {
// Label
let label = (typeof action.item.title === 'string' ? action.item.title : action.item.title.value) || action.item.id;
// Category
const category = typeof action.item.category === 'string' ? action.item.category : action.item.category?.value;
if (category) {
label = localize('commandWithCategory', "{0}: {1}", category, label);
}
// Alias
const aliasLabel = typeof action.item.title !== 'string' ? action.item.title.original : undefined;
const aliasCategory = (category && action.item.category && typeof action.item.category !== 'string') ? action.item.category.original : undefined;
const commandAlias = (aliasLabel && category) ?
aliasCategory ? `${aliasCategory}: ${aliasLabel}` : `${category}: ${aliasLabel}` :
aliasLabel;
globalCommandPicks.push({
commandId: action.item.id,
commandAlias,
label
});
}
// Cleanup
globalCommandsMenu.dispose();
disposables.add(toDisposable(() => dispose(globalCommandsMenuActions)));
return globalCommandPicks;
}
}

View File

@@ -8,8 +8,10 @@ import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/
import { Registry } from 'vs/platform/registry/common/platform';
import { HelpQuickAccessProvider } from 'vs/platform/quickinput/browser/helpQuickAccess';
import { ViewQuickAccessProvider } from 'vs/workbench/contrib/quickaccess/browser/viewQuickAccess';
import { QUICK_ACCESS_COMMAND_ID } from 'vs/workbench/contrib/quickaccess/browser/quickAccessCommands';
import { QUICK_ACCESS_COMMAND_ID, quickAccessCommand } from 'vs/workbench/contrib/quickaccess/browser/quickAccessCommands';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { CommandsQuickAccessProvider } from 'vs/workbench/contrib/quickaccess/browser/commandsQuickAccess';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
const registry = Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess);
@@ -34,6 +36,13 @@ registry.registerQuickAccessProvider({
helpEntries: [{ description: localize('viewQuickAccess', "Open View"), needsEditor: false }]
});
registry.registerQuickAccessProvider({
ctor: CommandsQuickAccessProvider,
prefix: CommandsQuickAccessProvider.PREFIX,
placeholder: localize('commandsQuickAccessPlaceholder', "Type the name of a command to run."),
helpEntries: [{ description: localize('commandsQuickAccess', "Show and Run Commands"), needsEditor: false }]
});
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: QUICK_ACCESS_COMMAND_ID, title: {
@@ -42,3 +51,10 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
category: localize('quickAccess', "Quick Access")
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: QUICK_ACCESS_COMMAND_ID,
weight: KeybindingWeight.WorkbenchContrib,
when: undefined,
handler: quickAccessCommand.handler
});

View File

@@ -4,12 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ICommand } from 'vs/platform/commands/common/commands';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
export const QUICK_ACCESS_COMMAND_ID = 'workbench.action.openQuickAccess';
CommandsRegistry.registerCommand({
export const quickAccessCommand: ICommand = {
id: QUICK_ACCESS_COMMAND_ID,
handler: async function (accessor: ServicesAccessor, prefix: string | null = null) {
const quickInputService = accessor.get(IQuickInputService);
@@ -25,4 +25,4 @@ CommandsRegistry.registerCommand({
}
}]
}
});
};

View File

@@ -213,7 +213,7 @@ class CommandPaletteEditorAction extends EditorAction {
super({
id: ShowAllCommandsAction.ID,
label: localize('showCommands.label', "Command Palette..."),
alias: 'Command Palette',
alias: 'Command Palette...',
precondition: EditorContextKeys.editorSimpleInput.toNegated(),
contextMenuOpts: {
group: 'z_commands',

View File

@@ -153,6 +153,10 @@
background-repeat: no-repeat;
}
.scm-viewlet .monaco-list-row .resource > .name > .monaco-icon-label > .actions .action-label.codicon {
height: 22px;
}
.scm-viewlet .scm-editor {
box-sizing: border-box;
padding: 5px 12px 5px 16px;

View File

@@ -207,7 +207,7 @@ registry.registerWorkbenchAction(
registry.registerWorkbenchAction(SyncActionDescriptor.create(RerunSearchEditorSearchAction, RerunSearchEditorSearchAction.ID, RerunSearchEditorSearchAction.LABEL,
{ mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_R } }, ContextKeyExpr.and(SearchEditorConstants.InSearchEditor)),
'Search Editor: Rerun', category);
'Search Editor: Search Again', category);
//#endregion

View File

@@ -12,6 +12,7 @@ import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService';
import { CustomTask, ContributedTask } from 'vs/workbench/contrib/tasks/common/tasks';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IStringDictionary } from 'vs/base/common/collections';
import { DisposableStore } from 'vs/base/common/lifecycle';
export class TasksQuickAccessProvider extends PickerQuickAccessProvider<IPickerQuickAccessItem> {
@@ -28,7 +29,7 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider<IPickerQ
this.activationPromise = extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask');
}
protected async getPicks(filter: string, token: CancellationToken): Promise<Array<IPickerQuickAccessItem | IQuickPickSeparator>> {
protected async getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Promise<Array<IPickerQuickAccessItem | IQuickPickSeparator>> {
// always await extensions
await this.activationPromise;

View File

@@ -30,7 +30,7 @@ export class UserDataSyncViewContribution implements IWorkbenchContribution {
@IUserDataSyncBackupStoreService private readonly userDataSyncBackupStoreService: IUserDataSyncBackupStoreService,
) {
const container = this.registerSyncViewContainer();
// Disable remote backup view until server is upgraded.
// Disable until server returns the correct timestamp
// this.registerBackupView(container, true);
this.registerBackupView(container, false);
}

View File

@@ -5,7 +5,6 @@
import * as fs from 'fs';
import * as gracefulFs from 'graceful-fs';
import { createHash } from 'crypto';
import { webFrame } from 'electron';
import { importEntries, mark } from 'vs/base/common/performance';
import { Workbench } from 'vs/workbench/browser/workbench';
@@ -13,13 +12,11 @@ import { NativeWindow } from 'vs/workbench/electron-browser/window';
import { setZoomLevel, setZoomFactor, setFullscreen } from 'vs/base/browser/browser';
import { domContentLoaded, addDisposableListener, EventType, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
import { onUnexpectedError } from 'vs/base/common/errors';
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService';
import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { stat } from 'vs/base/node/pfs';
import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService';
import { INativeWindowConfiguration } from 'vs/platform/windows/node/window';
import { ISingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
@@ -51,6 +48,8 @@ import { FileUserDataProvider } from 'vs/workbench/services/userData/common/file
import { basename } from 'vs/base/common/resources';
import { IProductService } from 'vs/platform/product/common/productService';
import product from 'vs/platform/product/common/product';
import { NativeResourceIdentityService } from 'vs/platform/resource/node/resourceIdentityServiceImpl';
import { IResourceIdentityService } from 'vs/platform/resource/common/resourceIdentityService';
class DesktopMain extends Disposable {
@@ -214,7 +213,10 @@ class DesktopMain extends Disposable {
fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider);
}
const payload = await this.resolveWorkspaceInitializationPayload();
const resourceIdentityService = this._register(new NativeResourceIdentityService());
serviceCollection.set(IResourceIdentityService, resourceIdentityService);
const payload = await this.resolveWorkspaceInitializationPayload(resourceIdentityService);
const services = await Promise.all([
this.createWorkspaceService(payload, fileService, remoteAgentService, logService).then(service => {
@@ -240,7 +242,7 @@ class DesktopMain extends Disposable {
return { serviceCollection, logService, storageService: services[1] };
}
private async resolveWorkspaceInitializationPayload(): Promise<IWorkspaceInitializationPayload> {
private async resolveWorkspaceInitializationPayload(resourceIdentityService: IResourceIdentityService): Promise<IWorkspaceInitializationPayload> {
// Multi-root workspace
if (this.environmentService.configuration.workspace) {
@@ -250,7 +252,7 @@ class DesktopMain extends Disposable {
// Single-folder workspace
let workspaceInitializationPayload: IWorkspaceInitializationPayload | undefined;
if (this.environmentService.configuration.folderUri) {
workspaceInitializationPayload = await this.resolveSingleFolderWorkspaceInitializationPayload(this.environmentService.configuration.folderUri);
workspaceInitializationPayload = await this.resolveSingleFolderWorkspaceInitializationPayload(this.environmentService.configuration.folderUri, resourceIdentityService);
}
// Fallback to empty workspace if we have no payload yet.
@@ -270,46 +272,16 @@ class DesktopMain extends Disposable {
return workspaceInitializationPayload;
}
private async resolveSingleFolderWorkspaceInitializationPayload(folderUri: ISingleFolderWorkspaceIdentifier): Promise<ISingleFolderWorkspaceInitializationPayload | undefined> {
// Return early the folder is not local
if (folderUri.scheme !== Schemas.file) {
return { id: createHash('md5').update(folderUri.toString()).digest('hex'), folder: folderUri };
}
function computeLocalDiskFolderId(folder: URI, stat: fs.Stats): string {
let ctime: number | undefined;
if (isLinux) {
ctime = stat.ino; // Linux: birthtime is ctime, so we cannot use it! We use the ino instead!
} else if (isMacintosh) {
ctime = stat.birthtime.getTime(); // macOS: birthtime is fine to use as is
} else if (isWindows) {
if (typeof stat.birthtimeMs === 'number') {
ctime = Math.floor(stat.birthtimeMs); // Windows: fix precision issue in node.js 8.x to get 7.x results (see https://github.com/nodejs/node/issues/19897)
} else {
ctime = stat.birthtime.getTime();
}
}
// we use the ctime as extra salt to the ID so that we catch the case of a folder getting
// deleted and recreated. in that case we do not want to carry over previous state
return createHash('md5').update(folder.fsPath).update(ctime ? String(ctime) : '').digest('hex');
}
// For local: ensure path is absolute and exists
private async resolveSingleFolderWorkspaceInitializationPayload(folderUri: ISingleFolderWorkspaceIdentifier, resourceIdentityService: IResourceIdentityService): Promise<ISingleFolderWorkspaceInitializationPayload | undefined> {
try {
const sanitizedFolderPath = sanitizeFilePath(folderUri.fsPath, process.env['VSCODE_CWD'] || process.cwd());
const fileStat = await stat(sanitizedFolderPath);
const sanitizedFolderUri = URI.file(sanitizedFolderPath);
return {
id: computeLocalDiskFolderId(sanitizedFolderUri, fileStat),
folder: sanitizedFolderUri
};
const folder = folderUri.scheme === Schemas.file
? URI.file(sanitizeFilePath(folderUri.fsPath, process.env['VSCODE_CWD'] || process.cwd())) // For local: ensure path is absolute
: folderUri;
const id = await resourceIdentityService.resolveResourceIdentity(folderUri);
return { id, folder };
} catch (error) {
onUnexpectedError(error);
}
return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check
}

View File

@@ -17,7 +17,7 @@ import { IStoredWorkspaceFolder, IWorkspaceIdentifier } from 'vs/platform/worksp
import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService';
import { WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { extname, join } from 'vs/base/common/path';
import { join } from 'vs/base/common/path';
import { equals } from 'vs/base/common/objects';
import { Schemas } from 'vs/base/common/network';
import { IConfigurationModel } from 'vs/platform/configuration/common/configuration';
@@ -30,7 +30,7 @@ export class UserConfiguration extends Disposable {
private readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());
readonly onDidChangeConfiguration: Event<ConfigurationModel> = this._onDidChangeConfiguration.event;
private readonly userConfiguration: MutableDisposable<UserSettings | FileServiceBasedConfigurationWithNames> = this._register(new MutableDisposable<UserSettings | FileServiceBasedConfigurationWithNames>());
private readonly userConfiguration: MutableDisposable<UserSettings | FileServiceBasedConfiguration> = this._register(new MutableDisposable<UserSettings | FileServiceBasedConfiguration>());
private readonly reloadConfigurationScheduler: RunOnceScheduler;
constructor(
@@ -52,8 +52,10 @@ export class UserConfiguration extends Disposable {
}
async reload(): Promise<ConfigurationModel> {
if (!(this.userConfiguration.value instanceof FileServiceBasedConfigurationWithNames)) {
this.userConfiguration.value = new FileServiceBasedConfigurationWithNames(resources.dirname(this.userSettingsResource), [FOLDER_SETTINGS_NAME, TASKS_CONFIGURATION_KEY], this.scopes, this.fileService);
if (!(this.userConfiguration.value instanceof FileServiceBasedConfiguration)) {
const folder = resources.dirname(this.userSettingsResource);
const standAloneConfigurationResources: [string, URI][] = [TASKS_CONFIGURATION_KEY].map(name => ([name, resources.joinPath(folder, `${name}.json`)]));
this.userConfiguration.value = new FileServiceBasedConfiguration(folder.toString(), [this.userSettingsResource], standAloneConfigurationResources, this.scopes, this.fileService);
this._register(this.userConfiguration.value.onDidChange(() => this.reloadConfigurationScheduler.schedule()));
}
return this.userConfiguration.value!.loadConfiguration();
@@ -64,24 +66,27 @@ export class UserConfiguration extends Disposable {
}
}
class FileServiceBasedConfigurationWithNames extends Disposable {
class FileServiceBasedConfiguration extends Disposable {
private readonly allResources: URI[];
private _folderSettingsModelParser: ConfigurationModelParser;
private _standAloneConfigurations: ConfigurationModel[];
private _cache: ConfigurationModel;
protected readonly configurationResources: URI[];
protected changeEventTriggerScheduler: RunOnceScheduler;
protected readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
private readonly changeEventTriggerScheduler: RunOnceScheduler;
private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChange: Event<void> = this._onDidChange.event;
constructor(protected readonly configurationFolder: URI,
private readonly configurationNames: string[],
constructor(
name: string,
private readonly settingsResources: URI[],
private readonly standAloneConfigurationResources: [string, URI][],
private readonly scopes: ConfigurationScope[] | undefined,
private fileService: IFileService) {
private fileService: IFileService
) {
super();
this.configurationResources = this.configurationNames.map(name => resources.joinPath(this.configurationFolder, `${name}.json`));
this._folderSettingsModelParser = new ConfigurationModelParser(this.configurationFolder.toString(), this.scopes);
this.allResources = [...this.settingsResources, ...this.standAloneConfigurationResources.map(([, resource]) => resource)];
this._folderSettingsModelParser = new ConfigurationModelParser(name, this.scopes);
this._standAloneConfigurations = [];
this._cache = new ConfigurationModel();
@@ -90,30 +95,37 @@ class FileServiceBasedConfigurationWithNames extends Disposable {
}
async loadConfiguration(): Promise<ConfigurationModel> {
const configurationContents = await Promise.all(this.configurationResources.map(async resource => {
try {
const content = await this.fileService.readFile(resource);
return content.value.toString();
} catch (error) {
if ((<FileOperationError>error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) {
errors.onUnexpectedError(error);
const resolveContents = async (resources: URI[]): Promise<(string | undefined)[]> => {
return Promise.all(resources.map(async resource => {
try {
const content = await this.fileService.readFile(resource);
return content.value.toString();
} catch (error) {
if ((<FileOperationError>error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) {
errors.onUnexpectedError(error);
}
}
}
return undefined;
}));
return undefined;
}));
};
const [settingsContents, standAloneConfigurationContents] = await Promise.all([
resolveContents(this.settingsResources),
resolveContents(this.standAloneConfigurationResources.map(([, resource]) => resource)),
]);
// reset
this._standAloneConfigurations = [];
this._folderSettingsModelParser.parseContent('');
// parse
if (configurationContents[0]) {
this._folderSettingsModelParser.parseContent(configurationContents[0]);
if (settingsContents[0]) {
this._folderSettingsModelParser.parseContent(settingsContents[0]);
}
for (let index = 1; index < configurationContents.length; index++) {
const contents = configurationContents[index];
for (let index = 0; index < standAloneConfigurationContents.length; index++) {
const contents = standAloneConfigurationContents[index];
if (contents) {
const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(this.configurationResources[index].toString(), this.configurationNames[index]);
const standAloneConfigurationModelParser = new StandaloneConfigurationModelParser(this.standAloneConfigurationResources[index][1].toString(), this.standAloneConfigurationResources[index][0]);
standAloneConfigurationModelParser.parseContent(contents);
this._standAloneConfigurations.push(standAloneConfigurationModelParser.configurationModel);
}
@@ -139,49 +151,22 @@ class FileServiceBasedConfigurationWithNames extends Disposable {
}
protected async handleFileEvents(event: FileChangesEvent): Promise<void> {
const events = event.changes;
let affectedByChanges = false;
// Find changes that affect workspace configuration files
for (let i = 0, len = events.length; i < len; i++) {
const resource = events[i].resource;
const basename = resources.basename(resource);
const isJson = extname(basename) === '.json';
const isConfigurationFolderDeleted = (events[i].type === FileChangeType.DELETED && resources.isEqual(resource, this.configurationFolder));
if (!isJson && !isConfigurationFolderDeleted) {
continue; // only JSON files or the actual settings folder
const isAffectedByChanges = (): boolean => {
// One of the resources has changed
if (this.allResources.some(resource => event.contains(resource))) {
return true;
}
const folderRelativePath = this.toFolderRelativePath(resource);
if (!folderRelativePath) {
continue; // event is not inside folder
// One of the resource's parent got deleted
if (this.allResources.some(resource => event.contains(resources.dirname(resource), FileChangeType.DELETED))) {
return true;
}
// Handle case where ".vscode" got deleted
if (isConfigurationFolderDeleted) {
affectedByChanges = true;
break;
}
// only valid workspace config files
if (this.configurationResources.some(configurationResource => resources.isEqual(configurationResource, resource))) {
affectedByChanges = true;
break;
}
}
if (affectedByChanges) {
return false;
};
if (isAffectedByChanges()) {
this.changeEventTriggerScheduler.schedule();
}
}
private toFolderRelativePath(resource: URI): string | undefined {
if (resources.isEqualOrParent(resource, this.configurationFolder)) {
return resources.relativePath(this.configurationFolder, resource);
}
return undefined;
}
}
export class RemoteUserConfiguration extends Disposable {
@@ -667,14 +652,6 @@ export interface IFolderConfiguration extends IDisposable {
reprocess(): ConfigurationModel;
}
class FileServiceBasedFolderConfiguration extends FileServiceBasedConfigurationWithNames implements IFolderConfiguration {
constructor(configurationFolder: URI, workbenchState: WorkbenchState, fileService: IFileService) {
super(configurationFolder, [FOLDER_SETTINGS_NAME /*First one should be settings */, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY], WorkbenchState.WORKSPACE === workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES, fileService);
}
}
class CachedFolderConfiguration extends Disposable implements IFolderConfiguration {
private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
@@ -742,13 +719,13 @@ export class FolderConfiguration extends Disposable implements IFolderConfigurat
this.configurationFolder = resources.joinPath(workspaceFolder.uri, configFolderRelativePath);
this.folderConfiguration = this.cachedFolderConfiguration = new CachedFolderConfiguration(workspaceFolder.uri, configFolderRelativePath, configurationCache);
if (workspaceFolder.uri.scheme === Schemas.file) {
this.folderConfiguration = new FileServiceBasedFolderConfiguration(this.configurationFolder, this.workbenchState, fileService);
this.folderConfiguration = this.createFileServiceBasedConfiguration(fileService);
} else {
whenProviderRegistered(workspaceFolder.uri, fileService)
.then(() => {
this.folderConfiguration.dispose();
this.folderConfigurationDisposable.dispose();
this.folderConfiguration = new FileServiceBasedFolderConfiguration(this.configurationFolder, this.workbenchState, fileService);
this.folderConfiguration = this.createFileServiceBasedConfiguration(fileService);
this._register(this.folderConfiguration.onDidChange(e => this.onDidFolderConfigurationChange()));
this.onDidFolderConfigurationChange();
});
@@ -769,8 +746,14 @@ export class FolderConfiguration extends Disposable implements IFolderConfigurat
this._onDidChange.fire();
}
private createFileServiceBasedConfiguration(fileService: IFileService) {
const settingsResources = [resources.joinPath(this.configurationFolder, `${FOLDER_SETTINGS_NAME}.json`)];
const standAloneConfigurationResources: [string, URI][] = [TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY].map(name => ([name, resources.joinPath(this.configurationFolder, `${name}.json`)]));
return new FileServiceBasedConfiguration(this.configurationFolder.toString(), settingsResources, standAloneConfigurationResources, WorkbenchState.WORKSPACE === this.workbenchState ? FOLDER_SCOPES : WORKSPACE_SCOPES, fileService);
}
private updateCache(): Promise<void> {
if (this.configurationFolder.scheme !== Schemas.file && this.folderConfiguration instanceof FileServiceBasedFolderConfiguration) {
if (this.configurationFolder.scheme !== Schemas.file && this.folderConfiguration instanceof FileServiceBasedConfiguration) {
return this.folderConfiguration.loadConfiguration()
.then(configurationModel => this.cachedFolderConfiguration.updateConfiguration(configurationModel));
}

View File

@@ -88,8 +88,8 @@ export abstract class AbstractFileDialogService implements IFileDialogService {
}
async showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise<ConfirmResult> {
if (this.environmentService.isExtensionDevelopment) {
return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests)
if (this.environmentService.isExtensionDevelopment && this.environmentService.extensionTestsLocationURI) {
return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev testing mode because we cannot assume we run interactive
}
return this.doShowSaveConfirm(fileNamesOrResources);

View File

@@ -6,7 +6,7 @@
import { SaveDialogOptions, OpenDialogOptions } from 'electron';
import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, IDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs';
import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
@@ -190,16 +190,6 @@ export class FileDialogService extends AbstractFileDialogService implements IFil
// Don't allow untitled schema through.
return schema === Schemas.untitled ? [Schemas.file] : (schema !== Schemas.file ? [schema, Schemas.file] : [schema]);
}
async showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise<ConfirmResult> {
if (this.environmentService.isExtensionDevelopment) {
if (!this.environmentService.args['extension-development-confirm-save']) {
return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests)
}
}
return super.doShowSaveConfirm(fileNamesOrResources);
}
}
registerSingleton(IFileDialogService, FileDialogService, true);

View File

@@ -146,6 +146,10 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
return false;
}
}
if (extensionKind === 'web') {
// Web extensions are not yet supported to be disabled by kind
return false;
}
}
return true;
}

View File

@@ -490,6 +490,30 @@ suite('ExtensionEnablementService Test', () => {
assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), true);
});
test('test web extension on local server is not disabled by kind', async () => {
instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService));
const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`) });
testObject = new TestExtensionEnablementService(instantiationService);
assert.ok(testObject.isEnabled(localWorkspaceExtension));
assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally);
});
test('test web extension on remote server is not disabled by kind', async () => {
instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService));
const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) });
testObject = new TestExtensionEnablementService(instantiationService);
assert.ok(testObject.isEnabled(localWorkspaceExtension));
assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally);
});
test('test web extension with no server is not disabled by kind', async () => {
instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService));
const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.https }) });
testObject = new TestExtensionEnablementService(instantiationService);
assert.ok(testObject.isEnabled(localWorkspaceExtension));
assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally);
});
});
function anExtensionManagementServer(authority: string, instantiationService: TestInstantiationService): IExtensionManagementServer {

View File

@@ -289,6 +289,11 @@ export const schema: IJSONSchema = {
body: 'onUri',
description: nls.localize('vscode.extension.activationEvents.onUri', 'An activation event emitted whenever a system-wide Uri directed towards this extension is open.'),
},
{
label: 'onCustomEditor',
body: 'onCustomEditor:${9:viewType}',
description: nls.localize('vscode.extension.activationEvents.onCustomEditor', 'An activation event emitted whenever the specified custom editor becomes visible.'),
},
{
label: '*',
description: nls.localize('vscode.extension.activationEvents.star', 'An activation event emitted on VS Code startup. To ensure a great end user experience, please use this activation event in your extension only when no other activation events combination works in your use-case.'),

View File

@@ -671,7 +671,7 @@ registerSingleton(IExtensionService, ExtensionService);
class RestartExtensionHostAction extends Action {
public static readonly ID = 'workbench.action.restartExtensionHost';
public static readonly LABEL = nls.localize('restartExtensionHost', "Developer: Restart Extension Host");
public static readonly LABEL = nls.localize('restartExtensionHost', "Restart Extension Host");
constructor(
id: string,
@@ -687,4 +687,4 @@ class RestartExtensionHostAction extends Action {
}
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(SyncActionDescriptor.create(RestartExtensionHostAction, RestartExtensionHostAction.ID, RestartExtensionHostAction.LABEL), 'Developer: Restart Extension Host');
registry.registerWorkbenchAction(SyncActionDescriptor.create(RestartExtensionHostAction, RestartExtensionHostAction.ID, RestartExtensionHostAction.LABEL), 'Developer: Restart Extension Host', nls.localize('developer', "Developer"));

View File

@@ -11,7 +11,6 @@ import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/cont
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { IFilesConfiguration, AutoSaveConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { equals } from 'vs/base/common/objects';
import { URI } from 'vs/base/common/uri';
import { isWeb } from 'vs/base/common/platform';
@@ -83,8 +82,7 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi
constructor(
@IContextKeyService contextKeyService: IContextKeyService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super();
@@ -203,7 +201,7 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi
}
get isHotExitEnabled(): boolean {
return !this.environmentService.isExtensionDevelopment && this.currentHotExitConfig !== HotExitConfiguration.OFF;
return this.currentHotExitConfig !== HotExitConfiguration.OFF;
}
get hotExitConfiguration(): string {

View File

@@ -293,9 +293,11 @@ export class ColorThemeData implements IWorkbenchColorTheme {
const settings = tokenColors[i].settings;
if (score >= foregroundScore && settings.foreground) {
foreground = settings.foreground;
foregroundScore = score;
}
if (score >= fontStyleScore && types.isString(settings.fontStyle)) {
fontStyle = settings.fontStyle;
fontStyleScore = score;
}
}
}
@@ -657,7 +659,7 @@ function nameMatcher(identifers: string[], scope: ProbeScope): number {
let lastScopeIndex = scope.length - 1;
let lastIdentifierIndex = findInIdents(scope[lastScopeIndex--], identifers.length);
if (lastIdentifierIndex >= 0) {
const score = (lastIdentifierIndex + 1) * 0x10000 + scope.length;
const score = (lastIdentifierIndex + 1) * 0x10000 + identifers[lastIdentifierIndex].length;
while (lastScopeIndex >= 0) {
lastIdentifierIndex = findInIdents(scope[lastScopeIndex--], lastIdentifierIndex);
if (lastIdentifierIndex === -1) {

View File

@@ -289,6 +289,41 @@ suite('Themes - TokenStyleResolving', () => {
});
test('resolveScopes - match most specific', async () => {
const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test');
const customTokenColors: ITokenColorCustomizations = {
textMateRules: [
{
scope: 'entity.name.type',
settings: {
fontStyle: 'underline',
foreground: '#A6E22E'
}
},
{
scope: 'entity.name.type.class',
settings: {
foreground: '#FF00FF'
}
},
{
scope: 'entity.name',
settings: {
foreground: '#FFFFFF'
}
},
]
};
themeData.setCustomTokenColors(customTokenColors);
const tokenStyle = themeData.resolveScopes([['entity.name.type.class']]);
assertTokenStyle(tokenStyle, ts('#FF00FF', { underline: true }), 'entity.name.type.class');
});
test('rule matching', async () => {
const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test');
themeData.setCustomColors({ 'editor.foreground': '#000000' });

View File

@@ -126,7 +126,7 @@ export function workbenchInstantiationService(overrides?: { textFileService?: (i
instantiationService.stub(IWorkspaceContextService, workspaceContextService);
const configService = new TestConfigurationService();
instantiationService.stub(IConfigurationService, configService);
instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService(contextKeyService, configService, TestEnvironmentService));
instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService(contextKeyService, configService));
instantiationService.stub(ITextResourceConfigurationService, new TestTextResourceConfigurationService(configService));
instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService));
instantiationService.stub(IStorageService, new TestStorageService());