Merge from vscode 79a1f5a5ca0c6c53db617aa1fa5a2396d2caebe2

This commit is contained in:
ADS Merger
2020-05-31 19:47:51 +00:00
parent 84492049e8
commit 28be33cfea
913 changed files with 28242 additions and 15549 deletions

View File

@@ -42,6 +42,9 @@ app.setPath('userData', userDataPath);
// Set temp directory based on crash-reporter-directory CLI argument
// The crash reporter will store crashes in temp folder so we need
// to change that location accordingly.
// If a crash-reporter-directory is specified we setup the crash reporter
// right from the beginning as early as possible to monitor all processes.
let crashReporterDirectory = args['crash-reporter-directory'];
if (crashReporterDirectory) {
crashReporterDirectory = path.normalize(crashReporterDirectory);
@@ -54,8 +57,20 @@ if (crashReporterDirectory) {
app.exit(1);
}
}
// Crashes are stored in the temp directory by default, so we
// need to change that directory to the provided one
console.log(`Found --crash-reporter-directory argument. Setting temp directory to be '${crashReporterDirectory}'`);
app.setPath('temp', crashReporterDirectory);
// Start crash reporter
const { crashReporter } = require('electron');
crashReporter.start({
companyName: 'Microsoft',
productName: product.nameShort,
submitURL: '',
uploadToServer: false
});
}
// Set logs path before app 'ready' event if running portable
@@ -71,7 +86,15 @@ setCurrentWorkingDirectory();
// Register custom schemes with privileges
protocol.registerSchemesAsPrivileged([
{ scheme: 'vscode-resource', privileges: { secure: true, supportFetchAPI: true, corsEnabled: true } }
{
scheme: 'vscode-webview-resource',
privileges: {
secure: true,
standard: true,
supportFetchAPI: true,
corsEnabled: true,
}
},
]);
// Global app listeners

View File

@@ -444,5 +444,10 @@ declare module 'azdata' {
getAssessmentItems(ownerUri: string, targetType: sqlAssessment.SqlAssessmentTargetType): Promise<SqlAssessmentResult>;
generateAssessmentScript(items: SqlAssessmentResultItem[]): Promise<ResultStatus>;
}
}
export interface TreeItem2 extends vscode.TreeItem2 {
payload?: IConnectionProfile;
childProvider?: string;
type?: ExtensionNodeType;
}
}

View File

@@ -272,13 +272,17 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
// Add sash
if (this.options.enableResizing && this.viewItems.length > 1) {
const orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL;
const layoutProvider = this.orientation === Orientation.VERTICAL ? { getHorizontalSashTop: (sash: Sash) => this.getSashPosition(sash) } : { getVerticalSashLeft: (sash: Sash) => this.getSashPosition(sash) };
const sash = new Sash(this.sashContainer, layoutProvider, {
orientation,
orthogonalStartSash: this.orthogonalStartSash,
orthogonalEndSash: this.orthogonalEndSash
});
const sash = this.orientation === Orientation.HORIZONTAL
? new Sash(this.sashContainer, { getHorizontalSashTop: (sash: Sash) => this.getSashPosition(sash) }, {
orientation: Orientation.HORIZONTAL,
orthogonalStartSash: this.orthogonalStartSash,
orthogonalEndSash: this.orthogonalEndSash
})
: new Sash(this.sashContainer, { getVerticalSashLeft: (sash: Sash) => this.getSashPosition(sash) }, {
orientation: Orientation.VERTICAL,
orthogonalStartSash: this.orthogonalStartSash,
orthogonalEndSash: this.orthogonalEndSash
});
const sashEventMapper = this.orientation === Orientation.VERTICAL
? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY, alt: e.altKey } as ISashEvent)
@@ -375,13 +379,17 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
// Add sash
if (this.options.enableResizing && this.viewItems.length > 1) {
const orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL;
const layoutProvider = this.orientation === Orientation.VERTICAL ? { getHorizontalSashTop: (sash: Sash) => this.getSashPosition(sash) } : { getVerticalSashLeft: (sash: Sash) => this.getSashPosition(sash) };
const sash = new Sash(this.sashContainer, layoutProvider, {
orientation,
orthogonalStartSash: this.orthogonalStartSash,
orthogonalEndSash: this.orthogonalEndSash
});
const sash = this.orientation === Orientation.HORIZONTAL
? new Sash(this.sashContainer, { getHorizontalSashTop: (sash: Sash) => this.getSashPosition(sash) }, {
orientation: Orientation.HORIZONTAL,
orthogonalStartSash: this.orthogonalStartSash,
orthogonalEndSash: this.orthogonalEndSash
})
: new Sash(this.sashContainer, { getVerticalSashLeft: (sash: Sash) => this.getSashPosition(sash) }, {
orientation: Orientation.VERTICAL,
orthogonalStartSash: this.orthogonalStartSash,
orthogonalEndSash: this.orthogonalEndSash
});
const sashEventMapper = this.orientation === Orientation.VERTICAL
? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY, alt: e.altKey } as ISashEvent)

View File

@@ -125,11 +125,11 @@
background: transparent;
}
.vs .monaco-workbench input {
.monaco-workbench.vs input {
color: var(--color-content);
}
.vs .monaco-workbench .input {
.monaco-workbench.vs .input {
background-color: white;
}
@@ -248,7 +248,7 @@
background: transparent;
}
.vs-dark .monaco-workbench input, .vs-dark .monaco-workbench .input {
.monaco-workbench.vs-dark input, .monaco-workbench.vs-dark .input {
color: var(--color-content);
background-color: #3C3C3C;
}
@@ -299,7 +299,7 @@
background: transparent;
}
.hc-black .monaco-workbench input {
.monaco-workbench.hc-black input {
color: #000;
background-color: #FFF;
}

View File

@@ -7,11 +7,11 @@
background-size: 10px;
}
.vs .monaco-workbench .tab > .tab-close .action-label.close-editor-action {
.monaco-workbench.vs .tab > .tab-close .action-label.close-editor-action {
background: url('icons/close.svg') center center no-repeat;
}
.vs-dark .monaco-workbench .tab > .tab-close .action-label.close-editor-action,
.hc-black .monaco-workbench .tab > .tab-close .action-label.close-editor-action {
.monaco-workbench.vs-dark .tab > .tab-close .action-label.close-editor-action,
.monaco-workbench.hc-black .tab > .tab-close .action-label.close-editor-action {
background: url('icons/close_inverse.svg') center center no-repeat;
}
}

View File

@@ -38,35 +38,35 @@ export class BrowserClipboardService implements IClipboardService {
/**
* Reads text from the system find pasteboard.
*/
readFindText(): string {
readFindText(): Promise<string> {
return this._vsClipboardService.readFindText();
}
/**
* Writes text to the system find pasteboard.
*/
writeFindText(text: string): void {
this._vsClipboardService.writeFindText(text);
writeFindText(text: string): Promise<void> {
return this._vsClipboardService.writeFindText(text);
}
/**
* Writes resources to the system clipboard.
*/
writeResources(resources: URI[]): void {
this._vsClipboardService.writeResources(resources);
writeResources(resources: URI[]): Promise<void> {
return this._vsClipboardService.writeResources(resources);
}
/**
* Reads resources from the system clipboard.
*/
readResources(): URI[] {
readResources(): Promise<URI[]> {
return this._vsClipboardService.readResources();
}
/**
* Find out if resources are copied to the clipboard.
*/
hasResources(): boolean {
hasResources(): Promise<boolean> {
return this._vsClipboardService.hasResources();
}
@@ -74,4 +74,14 @@ export class BrowserClipboardService implements IClipboardService {
// eslint-disable-next-line no-sync
return this._vsClipboardService.readTextSync();
}
readFindTextSync(): string {
// eslint-disable-next-line no-sync
return this._vsClipboardService.readFindTextSync();
}
writeFindTextSync(text: string): void {
// eslint-disable-next-line no-sync
return this._vsClipboardService.writeFindTextSync(text);
}
}

View File

@@ -68,18 +68,18 @@ modelview-dom-component .showEditorSelection .code-line .code-line:hover:before
border-left: none;
}
.vs-dark .monaco-workbench modelview-dom-component .showEditorSelection .code-active-line:before
.hc-black .monaco-workbench modelview-dom-component .showEditorSelection .code-active-line:before {
.monaco-workbench.vs-dark modelview-dom-component .showEditorSelection .code-active-line:before
.monaco-workbench.hc-black modelview-dom-component .showEditorSelection .code-active-line:before {
border-left: 3px solid rgba(255, 255, 255, 0.4);
}
.vs-dark .monaco-workbench modelview-dom-component .showEditorSelection .code-line:hover:before
.hc-black .monaco-workbench modelview-dom-component .showEditorSelection .code-line:hover:before {
.monaco-workbench.vs-dark modelview-dom-component .showEditorSelection .code-line:hover:before
.monaco-workbench.hc-black modelview-dom-component .showEditorSelection .code-line:hover:before {
border-left: 3px solid rgba(255, 255, 255, 0.60);
}
.vs-dark .monaco-workbench modelview-dom-component .showEditorSelection .code-line .code-line:hover:before
.hc-black .monaco-workbench modelview-dom-component .showEditorSelection .code-line .code-line:hover:before {
.monaco-workbench.vs-dark modelview-dom-component .showEditorSelection .code-line .code-line:hover:before
.monaco-workbench.hc-black modelview-dom-component .showEditorSelection .code-line .code-line:hover:before {
border-left: none;
}
@@ -205,15 +205,15 @@ modelview-dom-component pre {
background-color: rgba(220, 220, 220, 0.4);
}
.vs-dark .monaco-workbench modelview-dom-component pre {
.monaco-workbench.vs-dark modelview-dom-component pre {
background-color: rgba(10, 10, 10, 0.4);
}
.hc-black .monaco-workbench modelview-dom-component pre {
.monaco-workbench.hc-black modelview-dom-component pre {
background-color: rgb(0, 0, 0);
}
.hc-black .monaco-workbench modelview-dom-component h1 {
.monaco-workbench.hc-black modelview-dom-component h1 {
border-color: rgb(0, 0, 0);
}
@@ -221,7 +221,7 @@ modelview-dom-component table > thead > tr > th {
border-color: rgba(0, 0, 0, 0.69);
}
.vs-dark .monaco-workbench modelview-dom-component table > thead > tr > th {
.monaco-workbench.vs-dark modelview-dom-component table > thead > tr > th {
border-color: rgba(255, 255, 255, 0.69);
}
@@ -231,8 +231,8 @@ modelview-dom-component table > tbody > tr + tr > td {
border-color: rgba(0, 0, 0, 0.18);
}
.vs-dark .monaco-workbench modelview-dom-component h1,
.vs-dark .monaco-workbench modelview-dom-component hr,
.vs-dark .monaco-workbench modelview-dom-component table > tbody > tr + tr > td {
.monaco-workbench.vs-dark modelview-dom-component h1,
.monaco-workbench.vs-dark modelview-dom-component hr,
.monaco-workbench.vs-dark modelview-dom-component table > tbody > tr + tr > td {
border-color: rgba(255, 255, 255, 0.18);
}

View File

@@ -7,7 +7,7 @@ import { localize } from 'vs/nls';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { EditorInput, GroupIdentifier, IRevertOptions, ISaveOptions, IEditorInput, TextResourceEditorInput } from 'vs/workbench/common/editor';
import { EditorInput, GroupIdentifier, IRevertOptions, ISaveOptions, IEditorInput } from 'vs/workbench/common/editor';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConnectionManagementService, IConnectableInput, INewConnectionParams, RunQueryOnConnectionMode } from 'sql/platform/connection/common/connectionManagement';
@@ -17,6 +17,7 @@ import { IQueryModelService } from 'sql/workbench/services/query/common/queryMod
import { ExecutionPlanOptions } from 'azdata';
import { startsWith } from 'vs/base/common/strings';
import { IRange } from 'vs/editor/common/core/range';
import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput';
const MAX_SIZE = 13;
@@ -119,7 +120,7 @@ export abstract class QueryEditorInput extends EditorInput implements IConnectab
constructor(
private _description: string,
protected _text: TextResourceEditorInput,
protected _text: AbstractTextResourceEditorInput,
protected _results: QueryResultsInput,
@IConnectionManagementService private readonly connectionManagementService: IConnectionManagementService,
@IQueryModelService private readonly queryModelService: IQueryModelService,
@@ -168,7 +169,7 @@ export abstract class QueryEditorInput extends EditorInput implements IConnectab
// Getters for private properties
public get uri(): string { return this.resource!.toString(true); }
public get text(): TextResourceEditorInput { return this._text; }
public get text(): AbstractTextResourceEditorInput { return this._text; }
public get results(): QueryResultsInput { return this._results; }
// Description is shown beside the tab name in the combobox of open editors
public getDescription(): string { return this._description; }

View File

@@ -5,9 +5,9 @@
import { Action } from 'vs/base/common/actions';
import { IOpenerService } from 'vs/platform/opener/common/opener';
// eslint-disable-next-line code-layering,code-import-patterns
import { IElectronService } from 'vs/platform/electron/node/electron';
import { URI } from 'vs/base/common/uri';
// eslint-disable-next-line code-layering,code-import-patterns
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
export class ShowFileInFolderAction extends Action {

View File

@@ -18,7 +18,7 @@
.server-explorer-viewlet:lang(ko) .monaco-tree .monaco-tree-row .server-group { font-weight: normal; }
/* High Contrast Theming */
.hc-black .monaco-workbench .server-explorer-viewlet .server-group {
.monaco-workbench.hc-black .server-explorer-viewlet .server-group {
line-height: 20px;
}

View File

@@ -102,7 +102,7 @@ export class WebviewContent extends AngularDisposable implements OnInit, IDashbo
{},
{
allowScripts: true
});
}, undefined);
this._webview.mountTo(this._el.nativeElement);

View File

@@ -102,7 +102,7 @@ export class WebviewWidget extends DashboardWidget implements IDashboardWidget,
{},
{
allowScripts: true,
});
}, undefined);
this._webview.mountTo(this._el.nativeElement);
this._onMessageDisposable = this._webview.onMessage(e => {

View File

@@ -18,7 +18,7 @@
.server-explorer-viewlet:lang(ko) .monaco-tree .monaco-tree-row .server-group { font-weight: normal; }
/* High Contrast Theming */
.hc-black .monaco-workbench .server-explorer-viewlet .server-group {
.monaco-workbench.hc-black .server-explorer-viewlet .server-group {
line-height: 20px;
}

View File

@@ -15,7 +15,7 @@ import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { IEditorInput, EditorInput, IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor';
import { ITextEditorOptions, IEditorOptions } from 'vs/platform/editor/common/editor';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorGroup, OpenEditorContext } from 'vs/workbench/services/editor/common/editorGroupsService';
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
import { QueryEditorInput } from 'sql/workbench/common/editor/query/queryEditorInput';
import { Registry } from 'vs/platform/registry/common/platform';
@@ -72,7 +72,7 @@ suite('Editor Replacer Contribution', () => {
instantiationService.stub(IEditorService, editorService);
const contrib = instantiationService.createInstance(EditorReplacementContribution);
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.sql'), undefined, undefined);
const response = editorService.fireOpenEditor(input, undefined, undefined as IEditorGroup);
const response = editorService.fireOpenEditor(input, undefined, undefined as IEditorGroup, OpenEditorContext.NEW_EDITOR);
assert(response?.override);
const newinput = <any>(await response.override) as EditorInput; // our test service returns this so we are fine to cast this
@@ -87,7 +87,7 @@ suite('Editor Replacer Contribution', () => {
instantiationService.stub(IEditorService, editorService);
const contrib = instantiationService.createInstance(EditorReplacementContribution);
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.other'), undefined, 'sql');
const response = editorService.fireOpenEditor(input, undefined, undefined as IEditorGroup);
const response = editorService.fireOpenEditor(input, undefined, undefined as IEditorGroup, OpenEditorContext.NEW_EDITOR);
assert(response?.override);
const newinput = <any>(await response.override) as EditorInput; // our test service returns this so we are fine to cast this
@@ -102,7 +102,7 @@ suite('Editor Replacer Contribution', () => {
instantiationService.stub(IEditorService, editorService);
const contrib = instantiationService.createInstance(EditorReplacementContribution);
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.notebook'), undefined, undefined);
const response = editorService.fireOpenEditor(input, undefined, undefined as IEditorGroup);
const response = editorService.fireOpenEditor(input, undefined, undefined as IEditorGroup, OpenEditorContext.NEW_EDITOR);
assert(response?.override);
const newinput = <any>(await response.override) as EditorInput; // our test service returns this so we are fine to cast this
@@ -117,7 +117,7 @@ suite('Editor Replacer Contribution', () => {
instantiationService.stub(IEditorService, editorService);
const contrib = instantiationService.createInstance(EditorReplacementContribution);
const input = instantiationService.createInstance(FileEditorInput, URI.file('/test/file.iynb'), undefined, 'notebook');
const response = editorService.fireOpenEditor(input, undefined, undefined as IEditorGroup);
const response = editorService.fireOpenEditor(input, undefined, undefined as IEditorGroup, OpenEditorContext.NEW_EDITOR);
assert(response?.override);
const newinput = <any>(await response.override) as EditorInput; // our test service returns this so we are fine to cast this
@@ -135,7 +135,7 @@ suite('Editor Replacer Contribution', () => {
const service = accessor.untitledTextEditorService;
const input = instantiationService.createInstance(UntitledTextEditorInput, service.create());
const response = editorService.fireOpenEditor(input, undefined, undefined as IEditorGroup);
const response = editorService.fireOpenEditor(input, undefined, undefined as IEditorGroup, OpenEditorContext.NEW_EDITOR);
assert(response?.override);
const newinput = <any>(await response.override) as EditorInput; // our test service returns this so we are fine to cast this
@@ -153,7 +153,7 @@ suite('Editor Replacer Contribution', () => {
const service = accessor.untitledTextEditorService;
const untitled = instantiationService.createInstance(UntitledTextEditorInput, service.create());
const input = instantiationService.createInstance(UntitledQueryEditorInput, '', untitled, undefined);
const response = editorService.fireOpenEditor(input, undefined, undefined as IEditorGroup);
const response = editorService.fireOpenEditor(input, undefined, undefined as IEditorGroup, OpenEditorContext.NEW_EDITOR);
assert(response === undefined);
contrib.dispose();
@@ -167,7 +167,7 @@ suite('Editor Replacer Contribution', () => {
const accessor = instantiationService.createInstance(ServiceAccessor);
const service = accessor.untitledTextEditorService;
const input = instantiationService.createInstance(UntitledTextEditorInput, service.create({ associatedResource: URI.file('/test/file.unknown') }));
const response = editorService.fireOpenEditor(input, undefined, undefined as IEditorGroup);
const response = editorService.fireOpenEditor(input, undefined, undefined as IEditorGroup, OpenEditorContext.NEW_EDITOR);
assert(response === undefined);
contrib.dispose();
@@ -191,10 +191,10 @@ class MockEditorService extends TestEditorService {
});
}
fireOpenEditor(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup) {
fireOpenEditor(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup, context: OpenEditorContext, id?: string) {
for (const handler of this.overridenOpens) {
let response: IOpenEditorOverride | undefined;
if (response = handler.open(editor, options, group)) {
if (response = handler.open(editor, options, group, context, id)) {
return response;
}
}

View File

@@ -71,7 +71,7 @@ export default class WebViewComponent extends ComponentBase implements IComponen
{},
{
allowScripts: true
});
}, undefined);
this._webview.mountTo(this._el.nativeElement);

View File

@@ -219,8 +219,8 @@
background: url("./media/light/new_notebook.svg") center center no-repeat;
}
.vs-dark .monaco-workbench .notebook-action.new-notebook,
.hc-black .monaco-workbench .notebook-action.new-notebook {
.monaco-workbench.vs-dark .notebook-action.new-notebook,
.monaco-workbench.hc-black .notebook-action.new-notebook {
background: url("./media/dark/new_notebook_inverse.svg") center center
no-repeat;
}

View File

@@ -8,8 +8,8 @@
background-image: url('add_server.svg');
}
.vs-dark .monaco-workbench .add-server-action,
.hc-black .monaco-workbench .add-server-action {
.monaco-workbench.vs-dark .add-server-action,
.monaco-workbench.hc-black .add-server-action {
background-image: url('add_server_inverse.svg');
}
@@ -17,8 +17,8 @@
background-image: url('new_servergroup.svg');
}
.vs-dark .monaco-workbench .add-server-group-action,
.hc-black .monaco-workbench .add-server-group-action {
.monaco-workbench.vs-dark .add-server-group-action,
.monaco-workbench.hc-black .add-server-group-action {
background-image: url('new_servergroup_inverse.svg');
}
@@ -26,7 +26,7 @@
background-image: url('connected_active_server.svg');
}
.vs-dark .monaco-workbench .active-connections-action,
.hc-black .monaco-workbench .active-connections-action{
.monaco-workbench.vs-dark .active-connections-action,
.monaco-workbench.hc-black .active-connections-action{
background-image: url('connected_active_server_inverse.svg');
}

View File

@@ -11,10 +11,10 @@
border-left: 3px solid #DDD;
}
.vs-dark .monaco-workbench .binarydiff-right {
.monaco-workbench.vs-dark .binarydiff-right {
border-left: 3px solid rgb(20, 20, 20);
}
.hc-black .monaco-workbench .binarydiff-right {
.monaco-workbench.hc-black .binarydiff-right {
border-left: 3px solid #6FC3DF;
}
}

View File

@@ -3,11 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.vs .monaco-workbench > .editor > .content.drag {
.monaco-workbench.vs > .editor > .content.drag {
background-color: #ECECEC;
}
.vs-dark .monaco-workbench > .editor > .content.drag {
.monaco-workbench.vs-dark > .editor > .content.drag {
background-color: #2D2D2D;
}
@@ -28,17 +28,17 @@
}
.vs #monaco-workbench-editor-drop-overlay,
.vs .monaco-workbench > .editor.empty > .content.dropfeedback {
.monaco-workbench.vs > .editor.empty > .content.dropfeedback {
background-color: rgba(51,153,255, 0.18);
}
.vs-dark #monaco-workbench-editor-drop-overlay,
.vs-dark .monaco-workbench > .editor.empty > .content.dropfeedback {
.monaco-workbench.vs-dark > .editor.empty > .content.dropfeedback {
background-color: rgba(83, 89, 93, 0.5);
}
.hc-black #monaco-workbench-editor-drop-overlay,
.hc-black .monaco-workbench > .editor.empty > .content.dropfeedback {
.monaco-workbench.hc-black > .editor.empty > .content.dropfeedback {
background: none !important;
outline: 2px dashed #f38518;
outline-offset: -2px;
@@ -67,63 +67,63 @@
box-sizing: content-box;
}
.vs .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.dragging {
.monaco-workbench.vs > .editor > .content.vertical-layout > .one-editor-silo.dragging {
border-left: 1px solid #E7E7E7;
border-right: 1px solid #E7E7E7;
}
.vs .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.dragging {
.monaco-workbench.vs > .editor > .content.horizontal-layout > .one-editor-silo.dragging {
border-top: 1px solid #E7E7E7;
border-bottom: 1px solid #E7E7E7;
}
.vs .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-two,
.vs .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-three {
.monaco-workbench.vs > .editor > .content.vertical-layout > .one-editor-silo.editor-two,
.monaco-workbench.vs > .editor > .content.vertical-layout > .one-editor-silo.editor-three {
border-left: 1px solid #E7E7E7;
}
.vs .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-two,
.vs .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-three {
.monaco-workbench.vs > .editor > .content.horizontal-layout > .one-editor-silo.editor-two,
.monaco-workbench.vs > .editor > .content.horizontal-layout > .one-editor-silo.editor-three {
border-top: 1px solid #E7E7E7;
}
.vs-dark .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.dragging {
.monaco-workbench.vs-dark > .editor > .content.vertical-layout > .one-editor-silo.dragging {
border-left: 1px solid #444;
border-right: 1px solid #444;
}
.vs-dark .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.dragging {
.monaco-workbench.vs-dark > .editor > .content.horizontal-layout > .one-editor-silo.dragging {
border-top: 1px solid #444;
border-bottom: 1px solid #444;
}
.vs-dark .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-two,
.vs-dark .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-three {
.monaco-workbench.vs-dark > .editor > .content.vertical-layout > .one-editor-silo.editor-two,
.monaco-workbench.vs-dark > .editor > .content.vertical-layout > .one-editor-silo.editor-three {
border-left: 1px solid #444;
}
.vs-dark .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-two,
.vs-dark .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-three {
.monaco-workbench.vs-dark > .editor > .content.horizontal-layout > .one-editor-silo.editor-two,
.monaco-workbench.vs-dark > .editor > .content.horizontal-layout > .one-editor-silo.editor-three {
border-top: 1px solid #444;
}
.hc-black .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.dragging {
.monaco-workbench.hc-black > .editor > .content.vertical-layout > .one-editor-silo.dragging {
border-left: 1px solid #6FC3DF;
border-right: 1px solid #6FC3DF;
}
.hc-black .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.dragging {
.monaco-workbench.hc-black > .editor > .content.horizontal-layout > .one-editor-silo.dragging {
border-top: 1px solid #6FC3DF;
border-bottom: 1px solid #6FC3DF;
}
.hc-black .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-two,
.hc-black .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-three {
.monaco-workbench.hc-black > .editor > .content.vertical-layout > .one-editor-silo.editor-two,
.monaco-workbench.hc-black > .editor > .content.vertical-layout > .one-editor-silo.editor-three {
border-left: 1px solid #6FC3DF;
}
.hc-black .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-two,
.hc-black .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-three {
.monaco-workbench.hc-black > .editor > .content.horizontal-layout > .one-editor-silo.editor-two,
.monaco-workbench.hc-black > .editor > .content.horizontal-layout > .one-editor-silo.editor-three {
border-top: 1px solid #6FC3DF;
}
@@ -149,4 +149,4 @@
.monaco-workbench > .editor > .content > .one-editor-silo > .container > .editor-container {
height: calc(100% - 35px); /* Editor is below editor title */
}
}

View File

@@ -3,15 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.vs .monaco-workbench .monaco-editor-background {
.monaco-workbench.vs .monaco-editor-background {
background-color: white;
}
.vs-dark .monaco-workbench .monaco-editor-background {
.monaco-workbench.vs-dark .monaco-editor-background {
background-color: #1E1E1E;
}
.hc-black .monaco-workbench .monaco-editor-background {
.monaco-workbench.hc-black .monaco-editor-background {
background-color: #000;
}
@@ -24,11 +24,11 @@
background-image: url('letterpress.svg');
}
.vs-dark .monaco-workbench .part.editor.empty {
.monaco-workbench.vs-dark .part.editor.empty {
background-image: url('letterpress-dark.svg');
}
.hc-black .monaco-workbench .part.editor.empty {
.monaco-workbench.hc-black .part.editor.empty {
background-image: url('letterpress-hc.svg');
}
@@ -38,4 +38,4 @@
.monaco-workbench .part.editor {
background-size: 260px 260px;
}
}
}

View File

@@ -28,11 +28,11 @@
opacity: 1;
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action {
.monaco-workbench.vs > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action {
background: url('close-dirty.svg') center center no-repeat;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action,
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action {
.monaco-workbench.vs-dark > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action,
.monaco-workbench.hc-black > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action {
background: url('close-dirty-inverse.svg') center center no-repeat;
}

View File

@@ -5,11 +5,11 @@
/* Title Container */
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.tabs {
.monaco-workbench.vs > .part.editor > .content > .one-editor-silo > .container > .title.tabs {
background: #F3F3F3;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.tabs {
.monaco-workbench.vs-dark > .part.editor > .content > .one-editor-silo > .container > .title.tabs {
background: #252526;
}
@@ -51,56 +51,56 @@
padding-left: 10px;
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab:not(.active) {
.monaco-workbench.vs > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab:not(.active) {
background-color: #ECECEC;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab:not(.active) {
.monaco-workbench.vs-dark > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab:not(.active) {
background-color: #2D2D2D;
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab {
.monaco-workbench.vs > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab {
border-left-color: #F3F3F3;
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active:last-child {
.monaco-workbench.vs > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active:last-child {
border-right-color: #F3F3F3;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab {
.monaco-workbench.vs-dark > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab {
border-left-color: #252526;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active:last-child {
.monaco-workbench.vs-dark > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active:last-child {
border-right-color: #252526;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab:first-child,
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab:first-child {
.monaco-workbench.vs-dark > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab:first-child,
.monaco-workbench.vs > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab:first-child {
border-left-color: transparent;
}
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab {
.monaco-workbench.hc-black > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab {
border-left-color: #6FC3DF;
}
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active {
.monaco-workbench.hc-black > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active {
outline: 2px solid #f38518;
outline-offset: -1px;
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container.dropfeedback,
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dropfeedback {
.monaco-workbench.vs > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container.dropfeedback,
.monaco-workbench.vs > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dropfeedback {
background-color: #DDECFF;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container.dropfeedback,
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dropfeedback {
.monaco-workbench.vs-dark > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container.dropfeedback,
.monaco-workbench.vs-dark > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dropfeedback {
background-color: #383B3D;
}
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container.dropfeedback,
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dropfeedback {
.monaco-workbench.hc-black > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container.dropfeedback,
.monaco-workbench.hc-black > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dropfeedback {
background: none !important;
outline: 2px dashed #f38518;
outline-offset: -2px;
@@ -117,20 +117,20 @@
height: 16px; /* tweak the icon size of the editor labels when icons are enabled */
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label {
.monaco-workbench.vs > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label {
opacity: 0.7 !important;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label {
.monaco-workbench.vs-dark > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label {
opacity: 0.5 !important;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active .tab-label,
.monaco-workbench.vs-dark > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active .tab-label,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dropfeedback .tab-label {
opacity: 1 !important;
}
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label {
.monaco-workbench.hc-black > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label {
opacity: 1 !important;
}
@@ -172,21 +172,21 @@
margin-right: 0.5em;
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action {
.monaco-workbench.vs > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action {
background: url('close-dirty.svg') center center no-repeat;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action,
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action {
.monaco-workbench.vs-dark > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action,
.monaco-workbench.hc-black > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action {
background: url('close-dirty-inverse.svg') center center no-repeat;
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action:hover {
.monaco-workbench.vs > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action:hover {
background: url('close.svg') center center no-repeat;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action:hover,
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action:hover {
.monaco-workbench.vs-dark > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action:hover,
.monaco-workbench.hc-black > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action:hover {
background: url('close-inverse.svg') center center no-repeat;
}
@@ -202,12 +202,12 @@
background-position-x: calc(100% - 6px); /* to the right of the tab label */
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.no-close-button.dirty {
.monaco-workbench.vs > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.no-close-button.dirty {
background-image: url('close-dirty.svg');
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.no-close-button.dirty,
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.no-close-button.dirty {
.monaco-workbench.vs-dark > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.no-close-button.dirty,
.monaco-workbench.hc-black > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.no-close-button.dirty {
background-image: url('close-dirty-inverse.svg');
}
@@ -217,4 +217,4 @@
cursor: default;
flex: initial;
padding-left: 4px;
}
}

View File

@@ -3,20 +3,20 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.vs .monaco-workbench .textdiff-editor-action.next {
.monaco-workbench.vs .textdiff-editor-action.next {
background: url('next-diff.svg') center center no-repeat;
}
.vs .monaco-workbench .textdiff-editor-action.previous {
.monaco-workbench.vs .textdiff-editor-action.previous {
background: url('previous-diff.svg') center center no-repeat;
}
.vs-dark .monaco-workbench .textdiff-editor-action.next,
.hc-black .monaco-workbench .textdiff-editor-action.next {
.monaco-workbench.vs-dark .textdiff-editor-action.next,
.monaco-workbench.hc-black .textdiff-editor-action.next {
background: url('next-diff-inverse.svg') center center no-repeat;
}
.vs-dark .monaco-workbench .textdiff-editor-action.previous,
.hc-black .monaco-workbench .textdiff-editor-action.previous {
.monaco-workbench.vs-dark .textdiff-editor-action.previous,
.monaco-workbench.hc-black .textdiff-editor-action.previous {
background: url('previous-diff-inverse.svg') center center no-repeat;
}
}

View File

@@ -26,23 +26,23 @@
cursor: pointer;
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label a,
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label a {
.monaco-workbench.vs > .part.editor > .content > .one-editor-silo > .container > .title .title-label a,
.monaco-workbench.vs > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label a {
color: rgba(51, 51, 51, 0.5);
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .title-label a,
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab .tab-label a {
.monaco-workbench.vs > .part.editor > .content > .one-editor-silo > .container > .title.active .title-label a,
.monaco-workbench.vs > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab .tab-label a {
color: #333333;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label a,
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label a {
.monaco-workbench.vs-dark > .part.editor > .content > .one-editor-silo > .container > .title .title-label a,
.monaco-workbench.vs-dark > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label a {
color: rgba(255, 255, 255, 0.5);
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .title-label a,
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab .tab-label a {
.monaco-workbench.vs-dark > .part.editor > .content > .one-editor-silo > .container > .title.active .title-label a,
.monaco-workbench.vs-dark > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab .tab-label a {
color: white;
}
@@ -59,8 +59,8 @@
background-repeat: no-repeat;
}
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-actions .action-label,
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .editor-actions .action-label {
.monaco-workbench.hc-black > .part.editor > .content > .one-editor-silo > .container > .title .title-actions .action-label,
.monaco-workbench.hc-black > .part.editor > .content > .one-editor-silo > .container > .title .editor-actions .action-label {
line-height: initial;
}
@@ -94,8 +94,8 @@
background: url('close.svg') center center no-repeat;
}
.vs-dark .monaco-workbench .close-editor-action,
.hc-black .monaco-workbench .close-editor-action {
.monaco-workbench.vs-dark .close-editor-action,
.monaco-workbench.hc-black .close-editor-action {
background: url('close-inverse.svg') center center no-repeat;
}
@@ -103,8 +103,8 @@
background: url('split-editor-vertical.svg') center center no-repeat;
}
.vs-dark .monaco-workbench > .part.editor > .content.vertical-layout > .one-editor-silo > .container > .title .split-editor-action,
.hc-black .monaco-workbench > .part.editor > .content.vertical-layout > .one-editor-silo > .container > .title .split-editor-action {
.monaco-workbench.vs-dark > .part.editor > .content.vertical-layout > .one-editor-silo > .container > .title .split-editor-action,
.monaco-workbench.hc-black > .part.editor > .content.vertical-layout > .one-editor-silo > .container > .title .split-editor-action {
background: url('split-editor-vertical-inverse.svg') center center no-repeat;
}
@@ -112,8 +112,8 @@
background: url('split-editor-horizontal.svg') center center no-repeat;
}
.vs-dark .monaco-workbench > .part.editor > .content.horizontal-layout > .one-editor-silo > .container > .title .split-editor-action,
.hc-black .monaco-workbench > .part.editor > .content.horizontal-layout > .one-editor-silo > .container > .title .split-editor-action {
.monaco-workbench.vs-dark > .part.editor > .content.horizontal-layout > .one-editor-silo > .container > .title .split-editor-action,
.monaco-workbench.hc-black > .part.editor > .content.horizontal-layout > .one-editor-silo > .container > .title .split-editor-action {
background: url('split-editor-horizontal-inverse.svg') center center no-repeat;
}
@@ -121,7 +121,7 @@
background: url('stackview.svg') center center no-repeat;
}
.vs-dark .monaco-workbench .show-group-editors-action,
.hc-black .monaco-workbench .show-group-editors-action {
.monaco-workbench.vs-dark .show-group-editors-action,
.monaco-workbench.hc-black .show-group-editors-action {
background: url('stackview-inverse.svg') center center no-repeat;
}
}

View File

@@ -94,7 +94,7 @@ export class WebViewDialog extends Modal {
{},
{
allowScripts: true
});
}, undefined);
this._webview.mountTo(this._body);

View File

@@ -8,7 +8,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IElectronService } from 'vs/platform/electron/node/electron';
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
export class NativeEnablePreviewFeatures extends AbstractEnablePreviewFeatures {

View File

@@ -8,7 +8,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IElectronService } from 'vs/platform/electron/node/electron';
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
export class NativeEnablePreviewFeatures extends AbstractEnablePreviewFeatures {

View File

@@ -34,4 +34,5 @@ export interface IContextMenuDelegate {
actionRunner?: IActionRunner;
autoSelectFirstItem?: boolean;
anchorAlignment?: AnchorAlignment;
anchorAsContainer?: boolean;
}

View File

@@ -16,16 +16,22 @@ import { escape } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { renderCodicons, markdownEscapeEscapedCodicons } from 'vs/base/common/codicons';
import { resolvePath } from 'vs/base/common/resources';
export interface MarkedOptions extends marked.MarkedOptions {
baseUrl?: never;
}
export interface MarkdownRenderOptions extends FormattedTextRenderOptions {
codeBlockRenderer?: (modeId: string, value: string) => Promise<string>;
codeBlockRenderCallback?: () => void;
baseUrl?: URI;
}
/**
* Create html nodes for the given content element.
*/
export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}, markedOptions: marked.MarkedOptions = {}): HTMLElement {
export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}, markedOptions: MarkedOptions = {}): HTMLElement {
const element = createElement(options);
const _uriMassage = function (part: string): string {
@@ -82,6 +88,9 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
if (href) {
({ href, dimensions } = parseHrefAndDimensions(href));
href = _href(href, true);
if (options.baseUrl) {
href = resolvePath(options.baseUrl, href).toString();
}
attributes.push(`src="${href}"`);
}
if (text) {
@@ -101,6 +110,12 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
text = removeMarkdownEscapes(text);
}
href = _href(href, false);
if (options.baseUrl) {
const hasScheme = /^\w[\w\d+.-]*:/.test(href);
if (!hasScheme) {
href = resolvePath(options.baseUrl, href).toString();
}
}
title = removeMarkdownEscapes(title);
href = removeMarkdownEscapes(href);
if (
@@ -187,6 +202,13 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
}));
}
// Use our own sanitizer so that we can let through only spans.
// Otherwise, we'd be letting all html be rendered.
// If we want to allow markdown permitted tags, then we can delete sanitizer and sanitize.
markedOptions.sanitizer = (html: string): string => {
const match = markdown.isTrusted ? html.match(/^(<span[^<]+>)|(<\/\s*span>)$/) : undefined;
return match ? html : '';
};
markedOptions.sanitize = true;
markedOptions.renderer = renderer;
@@ -202,18 +224,32 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
markedOptions
);
function filter(token: { tag: string, attrs: { readonly [key: string]: string } }): boolean {
if (token.tag === 'span' && markdown.isTrusted) {
if (token.attrs['style'] && Object.keys(token.attrs).length === 1) {
return !!token.attrs['style'].match(/^(color\:#[0-9a-fA-F]+;)?(background-color\:#[0-9a-fA-F]+;)?$/);
}
return false;
}
return true;
}
element.innerHTML = insane(renderedMarkdown, {
allowedSchemes,
// allowedTags should included everything that markdown renders to.
// Since we have our own sanitize function for marked, it's possible we missed some tag so let insane make sure.
// HTML tags that can result from markdown are from reading https://spec.commonmark.org/0.29/
allowedTags: ['ul', 'li', 'p', 'code', 'blockquote', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'em', 'pre', 'table', 'tr', 'td', 'div', 'del', 'a', 'strong', 'br', 'img', 'span'],
allowedAttributes: {
'a': ['href', 'name', 'target', 'data-href'],
'iframe': ['allowfullscreen', 'frameborder', 'src'],
'img': ['src', 'title', 'alt', 'width', 'height'],
'div': ['class', 'data-code'],
'span': ['class'],
'span': ['class', 'style'],
// https://github.com/microsoft/vscode/issues/95937
'th': ['align'],
'td': ['align']
}
},
filter
});
signalInnerHTML!();

View File

@@ -31,6 +31,7 @@ export interface IActionViewItem extends IDisposable {
export interface IBaseActionViewItemOptions {
draggable?: boolean;
isMenu?: boolean;
useEventAsContext?: boolean;
}
export class BaseActionViewItem extends Disposable implements IActionViewItem {
@@ -178,7 +179,7 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem {
onClick(event: DOM.EventLike): void {
DOM.EventHelper.stop(event, true);
const context = types.isUndefinedOrNull(this._context) ? undefined : this._context;
const context = types.isUndefinedOrNull(this._context) ? this.options?.useEventAsContext ? event : undefined : this._context;
this.actionRunner.run(this._action, context);
}
@@ -404,6 +405,7 @@ export interface IActionBarOptions {
ariaLabel?: string;
animated?: boolean;
triggerKeys?: ActionTrigger;
allowContextMenu?: boolean;
}
const defaultOptions: IActionBarOptions = {
@@ -633,9 +635,11 @@ export class ActionBar extends Disposable implements IActionRunner {
actionViewItemElement.setAttribute('role', 'presentation');
// Prevent native context menu on actions
this._register(DOM.addDisposableListener(actionViewItemElement, DOM.EventType.CONTEXT_MENU, (e: DOM.EventLike) => {
DOM.EventHelper.stop(e, true);
}));
if (!this.options.allowContextMenu) {
this._register(DOM.addDisposableListener(actionViewItemElement, DOM.EventType.CONTEXT_MENU, (e: DOM.EventLike) => {
DOM.EventHelper.stop(e, true);
}));
}
let item: IActionViewItem | undefined;

View File

@@ -7,3 +7,12 @@
position: absolute;
z-index: 2500;
}
.context-view.fixed {
all: initial;
font-family: inherit;
font-size: 13px;
position: fixed;
z-index: 2500;
color: inherit;
}

View File

@@ -38,7 +38,7 @@ export interface IDelegate {
}
export interface IContextViewProvider {
showContextView(delegate: IDelegate): void;
showContextView(delegate: IDelegate, container?: HTMLElement): void;
hideContextView(): void;
layout(): void;
}
@@ -104,23 +104,25 @@ export class ContextView extends Disposable {
private container: HTMLElement | null = null;
private view: HTMLElement;
private useFixedPosition: boolean;
private delegate: IDelegate | null = null;
private toDisposeOnClean: IDisposable = Disposable.None;
private toDisposeOnSetContainer: IDisposable = Disposable.None;
constructor(container: HTMLElement) {
constructor(container: HTMLElement, useFixedPosition: boolean) {
super();
this.view = DOM.$('.context-view');
this.useFixedPosition = false;
DOM.hide(this.view);
this.setContainer(container);
this.setContainer(container, useFixedPosition);
this._register(toDisposable(() => this.setContainer(null)));
this._register(toDisposable(() => this.setContainer(null, false)));
}
setContainer(container: HTMLElement | null): void {
setContainer(container: HTMLElement | null, useFixedPosition: boolean): void {
if (this.container) {
this.toDisposeOnSetContainer.dispose();
this.container.removeChild(this.view);
@@ -146,6 +148,8 @@ export class ContextView extends Disposable {
this.toDisposeOnSetContainer = toDisposeOnSetContainer;
}
this.useFixedPosition = useFixedPosition;
}
show(delegate: IDelegate): void {
@@ -254,10 +258,11 @@ export class ContextView extends Disposable {
DOM.removeClasses(this.view, 'top', 'bottom', 'left', 'right');
DOM.addClass(this.view, anchorPosition === AnchorPosition.BELOW ? 'bottom' : 'top');
DOM.addClass(this.view, anchorAlignment === AnchorAlignment.LEFT ? 'left' : 'right');
DOM.toggleClass(this.view, 'fixed', this.useFixedPosition);
const containerPosition = DOM.getDomNodePagePosition(this.container!);
this.view.style.top = `${top - containerPosition.top}px`;
this.view.style.left = `${left - containerPosition.left}px`;
this.view.style.top = `${top - (this.useFixedPosition ? DOM.getDomNodePagePosition(this.view).top : containerPosition.top)}px`;
this.view.style.left = `${left - (this.useFixedPosition ? DOM.getDomNodePagePosition(this.view).left : containerPosition.left)}px`;
this.view.style.width = 'initial';
}

View File

@@ -14,6 +14,7 @@ import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes';
import { EventHelper, EventType, removeClass, addClass, append, $, addDisposableListener, addClasses } from 'vs/base/browser/dom';
import { IContextMenuDelegate } from 'vs/base/browser/contextmenu';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { Emitter } from 'vs/base/common/event';
export interface ILabelRenderer {
(container: HTMLElement): IDisposable | null;
@@ -29,7 +30,10 @@ export class BaseDropdown extends ActionRunner {
private boxContainer?: HTMLElement;
private _label?: HTMLElement;
private contents?: HTMLElement;
private visible: boolean | undefined;
private _onDidChangeVisibility = new Emitter<boolean>();
readonly onDidChangeVisibility = this._onDidChangeVisibility.event;
constructor(container: HTMLElement, options: IBaseDropdownOptions) {
super();
@@ -48,7 +52,7 @@ export class BaseDropdown extends ActionRunner {
}
for (const event of [EventType.CLICK, EventType.MOUSE_DOWN, GestureEventType.Tap]) {
this._register(addDisposableListener(this._label, event, e => EventHelper.stop(e, true))); // prevent default click behaviour to trigger
this._register(addDisposableListener(this.element, event, e => EventHelper.stop(e, true))); // prevent default click behaviour to trigger
}
for (const event of [EventType.MOUSE_DOWN, GestureEventType.Tap]) {
@@ -101,11 +105,17 @@ export class BaseDropdown extends ActionRunner {
}
show(): void {
this.visible = true;
if (!this.visible) {
this.visible = true;
this._onDidChangeVisibility.fire(true);
}
}
hide(): void {
this.visible = false;
if (this.visible) {
this.visible = false;
this._onDidChangeVisibility.fire(false);
}
}
isVisible(): boolean {
@@ -256,7 +266,8 @@ export class DropdownMenu extends BaseDropdown {
getMenuClassName: () => this.menuClassName,
onHide: () => this.onHide(),
actionRunner: this.menuOptions ? this.menuOptions.actionRunner : undefined,
anchorAlignment: this.menuOptions ? this.menuOptions.anchorAlignment : AnchorAlignment.LEFT
anchorAlignment: this.menuOptions ? this.menuOptions.anchorAlignment : AnchorAlignment.LEFT,
anchorAsContainer: true
});
}
@@ -303,6 +314,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
this.element.tabIndex = 0;
this.element.setAttribute('role', 'button');
this.element.setAttribute('aria-haspopup', 'true');
this.element.setAttribute('aria-expanded', 'false');
this.element.title = this._action.label || '';
return null;
@@ -321,6 +333,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
}
this.dropdownMenu = this._register(new DropdownMenu(container, options));
this._register(this.dropdownMenu.onDidChangeVisibility(visible => this.element?.setAttribute('aria-expanded', `${visible}`)));
this.dropdownMenu.menuOptions = {
actionViewItemProvider: this.actionViewItemProvider,

View File

@@ -380,7 +380,7 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
throw new Error('Invalid index');
}
this.splitview.addView(node, size, index);
this.splitview.addView(node, size, index, skipLayout);
this._addChild(node, index);
this.onDidChildrenChange();
}
@@ -791,7 +791,7 @@ function flipNode<T extends Node>(node: T, size: number, orthogonalSize: number)
newSize += size - totalSize;
}
result.addChild(flipNode(child, orthogonalSize, newSize), newSize, 0);
result.addChild(flipNode(child, orthogonalSize, newSize), newSize, 0, true);
}
return result as T;

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor-hover {
.monaco-hover {
cursor: default;
position: absolute;
overflow: hidden;
@@ -16,34 +16,34 @@
line-height: 1.5em;
}
.monaco-editor-hover.hidden {
.monaco-hover.hidden {
display: none;
}
.monaco-editor-hover .hover-contents {
.monaco-hover .hover-contents {
padding: 4px 8px;
}
.monaco-editor-hover .markdown-hover > .hover-contents:not(.code-hover-contents) {
.monaco-hover .markdown-hover > .hover-contents:not(.code-hover-contents) {
max-width: 500px;
word-wrap: break-word;
}
.monaco-editor-hover .markdown-hover > .hover-contents:not(.code-hover-contents) hr {
.monaco-hover .markdown-hover > .hover-contents:not(.code-hover-contents) hr {
/* This is a strange rule but it avoids https://github.com/microsoft/vscode/issues/96795, just 100vw on its own caused the actual hover width to increase */
min-width: calc(100% + 100vw);
}
.monaco-editor-hover p,
.monaco-editor-hover ul {
.monaco-hover p,
.monaco-hover ul {
margin: 8px 0;
}
.monaco-editor-hover code {
.monaco-hover code {
font-family: var(--monaco-monospace-font);
}
.monaco-editor-hover hr {
.monaco-hover hr {
margin-top: 4px;
margin-bottom: -6px;
margin-left: -10px;
@@ -51,78 +51,78 @@
height: 1px;
}
.monaco-editor-hover p:first-child,
.monaco-editor-hover ul:first-child {
.monaco-hover p:first-child,
.monaco-hover ul:first-child {
margin-top: 0;
}
.monaco-editor-hover p:last-child,
.monaco-editor-hover ul:last-child {
.monaco-hover p:last-child,
.monaco-hover ul:last-child {
margin-bottom: 0;
}
/* MarkupContent Layout */
.monaco-editor-hover ul {
.monaco-hover ul {
padding-left: 20px;
}
.monaco-editor-hover ol {
.monaco-hover ol {
padding-left: 20px;
}
.monaco-editor-hover li > p {
.monaco-hover li > p {
margin-bottom: 0;
}
.monaco-editor-hover li > ul {
.monaco-hover li > ul {
margin-top: 0;
}
.monaco-editor-hover code {
.monaco-hover code {
border-radius: 3px;
padding: 0 0.4em;
}
.monaco-editor-hover .monaco-tokenized-source {
.monaco-hover .monaco-tokenized-source {
white-space: pre-wrap;
word-break: break-all;
}
.monaco-editor-hover .hover-row.status-bar {
.monaco-hover .hover-row.status-bar {
font-size: 12px;
line-height: 22px;
}
.monaco-editor-hover .hover-row.status-bar .actions {
.monaco-hover .hover-row.status-bar .actions {
display: flex;
padding: 0px 8px;
}
.monaco-editor-hover .hover-row.status-bar .actions .action-container {
.monaco-hover .hover-row.status-bar .actions .action-container {
margin-right: 16px;
cursor: pointer;
}
.monaco-editor-hover .hover-row.status-bar .actions .action-container .action .icon {
.monaco-hover .hover-row.status-bar .actions .action-container .action .icon {
padding-right: 4px;
}
.monaco-editor-hover .markdown-hover .hover-contents .codicon {
.monaco-hover .markdown-hover .hover-contents .codicon {
color: inherit;
font-size: inherit;
vertical-align: middle;
}
.monaco-editor-hover .hover-contents a.code-link:before {
.monaco-hover .hover-contents a.code-link:before {
content: '(';
}
.monaco-editor-hover .hover-contents a.code-link:after {
.monaco-hover .hover-contents a.code-link:after {
content: ')';
}
.monaco-editor-hover .hover-contents a.code-link {
.monaco-hover .hover-contents a.code-link {
color: inherit;
}
.monaco-editor-hover .hover-contents a.code-link > span {
.monaco-hover .hover-contents a.code-link > span {
text-decoration: underline;
/** Hack to force underline to show **/
border-bottom: 1px solid transparent;

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 'vs/css!./hover';
import * as dom from 'vs/base/browser/dom';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
const $ = dom.$;
export class HoverWidget extends Disposable {
public readonly containerDomNode: HTMLElement;
public readonly contentsDomNode: HTMLElement;
private readonly _scrollbar: DomScrollableElement;
constructor() {
super();
this.containerDomNode = document.createElement('div');
this.containerDomNode.className = 'monaco-hover';
this.containerDomNode.tabIndex = 0;
this.containerDomNode.setAttribute('role', 'tooltip');
this.contentsDomNode = document.createElement('div');
this.contentsDomNode.className = 'monaco-hover-content';
this._scrollbar = this._register(new DomScrollableElement(this.contentsDomNode, {}));
this.containerDomNode.appendChild(this._scrollbar.getDomNode());
}
public onContentsChanged(): void {
this._scrollbar.scanDomNode();
}
}
export function renderHoverAction(parent: HTMLElement, actionOptions: { label: string, iconClass?: string, run: (target: HTMLElement) => void, commandId: string }, keybindingLabel: string | null): IDisposable {
const actionContainer = dom.append(parent, $('div.action-container'));
const action = dom.append(actionContainer, $('a.action'));
action.setAttribute('href', '#');
action.setAttribute('role', 'button');
if (actionOptions.iconClass) {
dom.append(action, $(`span.icon.${actionOptions.iconClass}`));
}
const label = dom.append(action, $('span'));
label.textContent = keybindingLabel ? `${actionOptions.label} (${keybindingLabel})` : actionOptions.label;
return dom.addDisposableListener(actionContainer, dom.EventType.CLICK, e => {
e.stopPropagation();
e.preventDefault();
actionOptions.run(actionContainer);
});
}

View File

@@ -165,7 +165,7 @@ export class PagedList<T> implements IDisposable {
}
get onDidChangeFocus(): Event<IListEvent<T>> {
return Event.map(this.list.onDidChangeFocus, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
return Event.map(this.list.onDidChangeFocus, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
}
get onDidOpen(): Event<IListEvent<T>> {
@@ -173,11 +173,11 @@ export class PagedList<T> implements IDisposable {
}
get onDidChangeSelection(): Event<IListEvent<T>> {
return Event.map(this.list.onDidChangeSelection, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
return Event.map(this.list.onDidChangeSelection, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
}
get onPin(): Event<IListEvent<T>> {
return Event.map(this.list.onDidPin, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
return Event.map(this.list.onDidPin, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
}
get onContextMenu(): Event<IListContextMenuEvent<T>> {

View File

@@ -9,8 +9,8 @@ import { Gesture, EventType as TouchEventType, GestureEvent } from 'vs/base/brow
import * as DOM from 'vs/base/browser/dom';
import { Event, Emitter } from 'vs/base/common/event';
import { domEvent } from 'vs/base/browser/event';
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { ScrollEvent, ScrollbarVisibility, INewScrollDimensions } from 'vs/base/common/scrollable';
import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { ScrollEvent, ScrollbarVisibility, INewScrollDimensions, Scrollable } from 'vs/base/common/scrollable';
import { RangeMap, shift } from './rangeMap';
import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListTouchEvent, IListGestureEvent, IListDragEvent, IListDragAndDrop, ListDragOverEffect } from './list';
import { RowCache, IRow } from './rowCache';
@@ -44,11 +44,16 @@ export interface IListViewDragAndDrop<T> extends IListDragAndDrop<T> {
export interface IListViewAccessibilityProvider<T> {
getSetSize?(element: T, index: number, listLength: number): number;
getPosInSet?(element: T, index: number): number;
getRole?(element: T): string;
getRole?(element: T): string | undefined;
isChecked?(element: T): boolean | undefined;
}
export interface IListViewOptions<T> {
export interface IListViewOptionsUpdate {
readonly additionalScrollHeight?: number;
readonly smoothScrolling?: boolean;
}
export interface IListViewOptions<T> extends IListViewOptionsUpdate {
readonly dnd?: IListViewDragAndDrop<T>;
readonly useShadows?: boolean;
readonly verticalScrollMode?: ScrollbarVisibility;
@@ -58,7 +63,6 @@ export interface IListViewOptions<T> {
readonly mouseSupport?: boolean;
readonly horizontalScrolling?: boolean;
readonly accessibilityProvider?: IListViewAccessibilityProvider<T>;
readonly additionalScrollHeight?: number;
readonly transformOptimization?: boolean;
}
@@ -158,7 +162,7 @@ class ListViewAccessibilityProvider<T> implements Required<IListViewAccessibilit
readonly getSetSize: (element: any, index: number, listLength: number) => number;
readonly getPosInSet: (element: any, index: number) => number;
readonly getRole: (element: T) => string;
readonly getRole: (element: T) => string | undefined;
readonly isChecked: (element: T) => boolean | undefined;
constructor(accessibilityProvider?: IListViewAccessibilityProvider<T>) {
@@ -204,7 +208,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
private lastRenderHeight: number;
private renderWidth = 0;
private rowsContainer: HTMLElement;
private scrollableElement: ScrollableElement;
private scrollable: Scrollable;
private scrollableElement: SmoothScrollableElement;
private _scrollHeight: number = 0;
private scrollableElementUpdateDisposable: IDisposable | null = null;
private scrollableElementWidthDelayer = new Delayer<void>(50);
@@ -278,18 +283,20 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.rowsContainer = document.createElement('div');
this.rowsContainer.className = 'monaco-list-rows';
if (options.transformOptimization) {
const transformOptimization = getOrDefault(options, o => o.transformOptimization, DefaultOptions.transformOptimization);
if (transformOptimization) {
this.rowsContainer.style.transform = 'translate3d(0px, 0px, 0px)';
}
this.disposables.add(Gesture.addTarget(this.rowsContainer));
this.scrollableElement = this.disposables.add(new ScrollableElement(this.rowsContainer, {
this.scrollable = new Scrollable(getOrDefault(options, o => o.smoothScrolling, false) ? 125 : 0, cb => DOM.scheduleAtNextAnimationFrame(cb));
this.scrollableElement = this.disposables.add(new SmoothScrollableElement(this.rowsContainer, {
alwaysConsumeMouseWheel: true,
horizontal: this.horizontalScrolling ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden,
vertical: getOrDefault(options, o => o.verticalScrollMode, DefaultOptions.verticalScrollMode),
useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows)
}));
useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows),
}, this.scrollable));
this.domNode.appendChild(this.scrollableElement.getDomNode());
container.appendChild(this.domNode);
@@ -319,6 +326,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
if (options.additionalScrollHeight !== undefined) {
this.additionalScrollHeight = options.additionalScrollHeight;
}
if (options.smoothScrolling !== undefined) {
this.scrollable.setSmoothScrollDuration(options.smoothScrolling ? 125 : 0);
}
}
triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) {
@@ -651,7 +662,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
if (!item.row) {
item.row = this.cache.alloc(item.templateId);
const role = this.accessibilityProvider.getRole(item.element);
const role = this.accessibilityProvider.getRole(item.element) || 'listitem';
item.row!.domNode!.setAttribute('role', role);
const checked = this.accessibilityProvider.isChecked(item.element);
if (typeof checked !== 'undefined') {

View File

@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./list';
import { localize } from 'vs/nls';
import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
import { isNumber } from 'vs/base/common/types';
import { range, firstIndex, binarySearch } from 'vs/base/common/arrays';
@@ -17,7 +16,7 @@ import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardE
import { Event, Emitter, EventBufferer } from 'vs/base/common/event';
import { domEvent } from 'vs/base/browser/event';
import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, IKeyboardNavigationLabelProvider, IListDragAndDrop, IListDragOverReaction, ListError, IKeyboardNavigationDelegate } from './list';
import { ListView, IListViewOptions, IListViewDragAndDrop, IListViewAccessibilityProvider } from './listView';
import { ListView, IListViewOptions, IListViewDragAndDrop, IListViewAccessibilityProvider, IListViewOptionsUpdate } from './listView';
import { Color } from 'vs/base/common/color';
import { mixin } from 'vs/base/common/objects';
import { ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable';
@@ -26,6 +25,7 @@ import { CombinedSpliceable } from 'vs/base/browser/ui/list/splice';
import { clamp } from 'vs/base/common/numbers';
import { matchesPrefix } from 'vs/base/common/filters';
import { IDragAndDropData } from 'vs/base/browser/dnd';
import { alert } from 'vs/base/browser/ui/aria/aria';
interface ITraitChangeEvent {
indexes: number[];
@@ -344,6 +344,7 @@ class TypeLabelController<T> implements IDisposable {
private automaticKeyboardNavigation = true;
private triggered = false;
private previouslyFocused = -1;
private readonly enabledDisposables = new DisposableStore();
private readonly disposables = new DisposableStore();
@@ -393,6 +394,7 @@ class TypeLabelController<T> implements IDisposable {
const onInput = Event.reduce<string | null, string | null>(Event.any(onChar, onClear), (r, i) => i === null ? null : ((r || '') + i));
onInput(this.onInput, this, this.enabledDisposables);
onClear(this.onClear, this, this.enabledDisposables);
this.enabled = true;
this.triggered = false;
@@ -408,6 +410,19 @@ class TypeLabelController<T> implements IDisposable {
this.triggered = false;
}
private onClear(): void {
const focus = this.list.getFocus();
if (focus.length > 0 && focus[0] === this.previouslyFocused) {
// List: re-anounce element on typing end since typed keys will interupt aria label of focused element
// Do not announce if there was a focus change at the end to prevent duplication https://github.com/microsoft/vscode/issues/95961
const ariaLabel = this.list.options.accessibilityProvider?.getAriaLabel(this.list.element(focus[0]));
if (ariaLabel) {
alert(ariaLabel);
}
}
this.previouslyFocused = -1;
}
private onInput(word: string | null): void {
if (!word) {
this.state = TypeLabelControllerState.Idle;
@@ -426,6 +441,7 @@ class TypeLabelController<T> implements IDisposable {
const labelStr = label && label.toString();
if (typeof labelStr === 'undefined' || matchesPrefix(word, labelStr)) {
this.previouslyFocused = start;
this.list.setFocus([index]);
this.list.reveal(index);
return;
@@ -1091,10 +1107,9 @@ class ListViewDragAndDrop<T> implements IListViewDragAndDrop<T> {
}
}
export interface IListOptionsUpdate {
export interface IListOptionsUpdate extends IListViewOptionsUpdate {
readonly enableKeyboardNavigation?: boolean;
readonly automaticKeyboardNavigation?: boolean;
readonly additionalScrollHeight?: number;
}
export class List<T> implements ISpliceable<T>, IDisposable {
@@ -1272,9 +1287,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
this.typeLabelController.updateOptions(this._options);
}
if (optionsUpdate.additionalScrollHeight !== undefined) {
this.view.updateOptions(optionsUpdate);
}
this.view.updateOptions(optionsUpdate);
}
get options(): IListOptions<T> {
@@ -1363,7 +1376,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
set ariaLabel(value: string) {
this._ariaLabel = value;
this.view.domNode.setAttribute('aria-label', localize('aria list', "{0}. Use the navigation keys to navigate.", value));
this.view.domNode.setAttribute('aria-label', value);
}
domFocus(): void {

View File

@@ -88,6 +88,9 @@
padding: 0.5em 0 0 0;
margin-bottom: 0.5em;
width: 100%;
height: 0px !important;
margin-left: .8em !important;
margin-right: .8em !important;
}
.monaco-menu .monaco-action-bar.vertical .action-label.separator.text {

View File

@@ -41,6 +41,7 @@ export interface IMenuOptions {
enableMnemonics?: boolean;
anchorAlignment?: AnchorAlignment;
expandDirection?: Direction;
useEventAsContext?: boolean;
}
export interface IMenuStyles {
@@ -316,7 +317,7 @@ export class Menu extends ActionBar {
return menuActionViewItem;
} else {
const menuItemOptions: IMenuItemOptions = { enableMnemonics: options.enableMnemonics };
const menuItemOptions: IMenuItemOptions = { enableMnemonics: options.enableMnemonics, useEventAsContext: options.useEventAsContext };
if (options.getKeyBinding) {
const keybinding = options.getKeyBinding(action);
if (keybinding) {

View File

@@ -140,8 +140,8 @@ export class MenuBar extends Disposable {
eventHandled = false;
}
// Never allow default tab behavior
if (event.equals(KeyCode.Tab | KeyMod.Shift) || event.equals(KeyCode.Tab)) {
// Never allow default tab behavior when not compact
if (this.options.compactMode === undefined && (event.equals(KeyCode.Tab | KeyMod.Shift) || event.equals(KeyCode.Tab))) {
event.preventDefault();
}
@@ -315,7 +315,7 @@ export class MenuBar extends Disposable {
createOverflowMenu(): void {
const label = this.options.compactMode !== undefined ? nls.localize('mAppMenu', 'Application Menu') : nls.localize('mMore', 'More');
const title = this.options.compactMode !== undefined ? label : undefined;
const buttonElement = $('div.menubar-menu-button', { 'role': 'menuitem', 'tabindex': -1, 'aria-label': label, 'title': title, 'aria-haspopup': true });
const buttonElement = $('div.menubar-menu-button', { 'role': 'menuitem', 'tabindex': this.options.compactMode !== undefined ? 0 : -1, 'aria-label': label, 'title': title, 'aria-haspopup': true });
const titleElement = $('div.menubar-menu-title.toolbar-toggle-more' + menuBarMoreIcon.cssSelector, { 'role': 'none', 'aria-hidden': true });
buttonElement.appendChild(titleElement);
@@ -326,7 +326,7 @@ export class MenuBar extends Disposable {
let event = new StandardKeyboardEvent(e as KeyboardEvent);
let eventHandled = true;
if ((event.equals(KeyCode.DownArrow) || event.equals(KeyCode.Enter)) && !this.isOpen) {
if ((event.equals(KeyCode.DownArrow) || event.equals(KeyCode.Enter) || (this.options.compactMode !== undefined && event.equals(KeyCode.Space))) && !this.isOpen) {
this.focusedMenu = { index: MenuBar.OVERFLOW_INDEX };
this.openedViaKeyboard = true;
this.focusState = MenubarState.OPEN;
@@ -945,7 +945,8 @@ export class MenuBar extends Disposable {
actionRunner: this.actionRunner,
enableMnemonics: this.options.alwaysOnMnemonics || (this.mnemonicsInUse && this.options.enableMnemonics),
ariaLabel: withNullAsUndefined(customMenu.buttonElement.getAttribute('aria-label')),
expandDirection: this.options.compactMode !== undefined ? this.options.compactMode : Direction.Right
expandDirection: this.options.compactMode !== undefined ? this.options.compactMode : Direction.Right,
useEventAsContext: true
};
let menuWidget = this._register(new Menu(menuHolder, customMenu.actions, menuOptions));

View File

@@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-mouse-cursor-text {
cursor: text;
}
/* The following selector looks a bit funny, but that is needed to cover all the workbench and the editor!! */
.vs-dark .mac .monaco-mouse-cursor-text, .hc-black .mac .monaco-mouse-cursor-text,
.vs-dark.mac .monaco-mouse-cursor-text, .hc-black.mac .monaco-mouse-cursor-text {
cursor: -webkit-image-set(url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAL0lEQVQoz2NgCD3x//9/BhBYBWdhgFVAiVW4JBFKGIa4AqD0//9D3pt4I4tAdAMAHTQ/j5Zom30AAAAASUVORK5CYII=') 1x, url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVRIx2NgYGBY/R8I/vx5eelX3n82IJ9FxGf6tksvf/8FiTMQAcAGQMDvSwu09abffY8QYSAScNk45G198eX//yev73/4///701eh//kZSARckrNBRvz//+8+6ZohwCzjGNjdgQxkAg7B9WADeBjIBqtJCbhRA0YNoIkBSNmaPEMoNmA0FkYNoFKhapJ6FGyAH3nauaSmPfwI0v/3OukVi0CIZ+F25KrtYcx/CTIy0e+rC7R1Z4KMICVTQQ14feVXIbR695u14+Ir4gwAAD49E54wc1kWAAAAAElFTkSuQmCC') 2x) 5 8, text;
}

View File

@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./mouseCursor';
export const MOUSE_CURSOR_TEXT_CSS_CLASS_NAME = `monaco-mouse-cursor-text`;

View File

@@ -13,13 +13,6 @@
pointer-events: none;
}
.monaco-sash.vertical {
cursor: ew-resize;
top: 0;
width: 4px;
height: 100%;
}
.monaco-sash.mac.vertical {
cursor: col-resize;
}
@@ -32,13 +25,6 @@
cursor: w-resize;
}
.monaco-sash.horizontal {
cursor: ns-resize;
left: 0;
width: 100%;
height: 4px;
}
.monaco-sash.mac.horizontal {
cursor: row-resize;
}
@@ -51,52 +37,11 @@
cursor: n-resize;
}
.monaco-sash:not(.disabled).orthogonal-start::before,
.monaco-sash:not(.disabled).orthogonal-end::after {
content: ' ';
height: 8px;
width: 8px;
z-index: 100;
display: block;
cursor: all-scroll;
position: absolute;
}
.monaco-sash.orthogonal-start.vertical::before {
left: -2px;
top: -4px;
}
.monaco-sash.orthogonal-end.vertical::after {
left: -2px;
bottom: -4px;
}
.monaco-sash.orthogonal-start.horizontal::before {
top: -2px;
left: -4px;
}
.monaco-sash.orthogonal-end.horizontal::after {
top: -2px;
right: -4px;
}
.monaco-sash.disabled {
cursor: default !important;
pointer-events: none !important;
}
/** Touch **/
.monaco-sash.touch.vertical {
width: 20px;
}
.monaco-sash.touch.horizontal {
height: 20px;
}
/** Debug **/
.monaco-sash.debug {
@@ -110,4 +55,4 @@
.monaco-sash.debug:not(.disabled).orthogonal-start::before,
.monaco-sash.debug:not(.disabled).orthogonal-end::after {
background: red;
}
}

View File

@@ -5,7 +5,6 @@
import 'vs/css!./sash';
import { IDisposable, dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { isIPad } from 'vs/base/browser/browser';
import { isMacintosh } from 'vs/base/common/platform';
import * as types from 'vs/base/common/types';
import { EventType, GestureEvent, Gesture } from 'vs/base/browser/touch';
@@ -39,9 +38,18 @@ export interface ISashEvent {
}
export interface ISashOptions {
orientation?: Orientation;
orthogonalStartSash?: Sash;
orthogonalEndSash?: Sash;
readonly orientation: Orientation;
readonly orthogonalStartSash?: Sash;
readonly orthogonalEndSash?: Sash;
readonly size?: number;
}
export interface IVerticalSashOptions extends ISashOptions {
readonly orientation: Orientation.VERTICAL;
}
export interface IHorizontalSashOptions extends ISashOptions {
readonly orientation: Orientation.HORIZONTAL;
}
export const enum Orientation {
@@ -56,12 +64,20 @@ export const enum SashState {
Enabled
}
let globalSize = 4;
const onDidChangeGlobalSize = new Emitter<number>();
export function setGlobalSashSize(size: number): void {
globalSize = size;
onDidChangeGlobalSize.fire(size);
}
export class Sash extends Disposable {
private el: HTMLElement;
private layoutProvider: ISashLayoutProvider;
private hidden: boolean;
private orientation!: Orientation;
private size: number;
private _state: SashState = SashState.Enabled;
get state(): SashState { return this._state; }
@@ -127,7 +143,9 @@ export class Sash extends Disposable {
this._orthogonalEndSash = sash;
}
constructor(container: HTMLElement, layoutProvider: ISashLayoutProvider, options: ISashOptions = {}) {
constructor(container: HTMLElement, layoutProvider: IVerticalSashLayoutProvider, options: ISashOptions);
constructor(container: HTMLElement, layoutProvider: IHorizontalSashLayoutProvider, options: ISashOptions);
constructor(container: HTMLElement, layoutProvider: ISashLayoutProvider, options: ISashOptions) {
super();
this.el = append(container, $('.monaco-sash'));
@@ -142,12 +160,21 @@ export class Sash extends Disposable {
this._register(Gesture.addTarget(this.el));
this._register(domEvent(this.el, EventType.Start)(this.onTouchStart, this));
if (isIPad) {
// see also https://ux.stackexchange.com/questions/39023/what-is-the-optimum-button-size-of-touch-screen-applications
addClass(this.el, 'touch');
}
if (typeof options.size === 'number') {
this.size = options.size;
this.setOrientation(options.orientation || Orientation.VERTICAL);
if (options.orientation === Orientation.VERTICAL) {
this.el.style.width = `${this.size}px`;
} else {
this.el.style.height = `${this.size}px`;
}
} else {
this.size = globalSize;
this._register(onDidChangeGlobalSize.event(size => {
this.size = size;
this.layout();
}));
}
this.hidden = false;
this.layoutProvider = layoutProvider;
@@ -155,11 +182,7 @@ export class Sash extends Disposable {
this.orthogonalStartSash = options.orthogonalStartSash;
this.orthogonalEndSash = options.orthogonalEndSash;
toggleClass(this.el, 'debug', DEBUG);
}
setOrientation(orientation: Orientation): void {
this.orientation = orientation;
this.orientation = options.orientation || Orientation.VERTICAL;
if (this.orientation === Orientation.HORIZONTAL) {
addClass(this.el, 'horizontal');
@@ -169,9 +192,9 @@ export class Sash extends Disposable {
addClass(this.el, 'vertical');
}
if (this.layoutProvider) {
this.layout();
}
toggleClass(this.el, 'debug', DEBUG);
this.layout();
}
private onMouseDown(e: MouseEvent): void {
@@ -331,11 +354,9 @@ export class Sash extends Disposable {
}
layout(): void {
const size = isIPad ? 20 : 4;
if (this.orientation === Orientation.VERTICAL) {
const verticalProvider = (<IVerticalSashLayoutProvider>this.layoutProvider);
this.el.style.left = verticalProvider.getVerticalSashLeft(this) - (size / 2) + 'px';
this.el.style.left = verticalProvider.getVerticalSashLeft(this) - (this.size / 2) + 'px';
if (verticalProvider.getVerticalSashTop) {
this.el.style.top = verticalProvider.getVerticalSashTop(this) + 'px';
@@ -346,7 +367,7 @@ export class Sash extends Disposable {
}
} else {
const horizontalProvider = (<IHorizontalSashLayoutProvider>this.layoutProvider);
this.el.style.top = horizontalProvider.getHorizontalSashTop(this) - (size / 2) + 'px';
this.el.style.top = horizontalProvider.getHorizontalSashTop(this) - (this.size / 2) + 'px';
if (horizontalProvider.getHorizontalSashLeft) {
this.el.style.left = horizontalProvider.getHorizontalSashLeft(this) + 'px';
@@ -384,15 +405,15 @@ export class Sash extends Disposable {
private getOrthogonalSash(e: MouseEvent): Sash | undefined {
if (this.orientation === Orientation.VERTICAL) {
if (e.offsetY <= 4) {
if (e.offsetY <= this.size) {
return this.orthogonalStartSash;
} else if (e.offsetY >= this.el.clientHeight - 4) {
} else if (e.offsetY >= this.el.clientHeight - this.size) {
return this.orthogonalEndSash;
}
} else {
if (e.offsetX <= 4) {
if (e.offsetX <= this.size) {
return this.orthogonalStartSash;
} else if (e.offsetX >= this.el.clientWidth - 4) {
} else if (e.offsetX >= this.el.clientWidth - this.size) {
return this.orthogonalEndSash;
}
}

View File

@@ -533,6 +533,14 @@ export class SmoothScrollableElement extends AbstractScrollableElement {
super(element, options, scrollable);
}
public setScrollPosition(update: INewScrollPosition): void {
this._scrollable.setScrollPositionNow(update);
}
public getScrollPosition(): IScrollPosition {
return this._scrollable.getCurrentScrollPosition();
}
}
export class DomScrollableElement extends ScrollableElement {

View File

@@ -6,3 +6,9 @@
.monaco-select-box {
width: 100%;
}
.monaco-select-box-dropdown-container {
font-size: 13px;
font-weight: normal;
text-transform: none;
}

View File

@@ -40,6 +40,7 @@ export interface ISelectBoxOptions {
useCustomDrawn?: boolean;
ariaLabel?: string;
minBottomMargin?: number;
optionsAsChildren?: boolean;
}
// Utilize optionItem interface to capture all option parameters

View File

@@ -90,8 +90,8 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
private _isVisible: boolean;
private selectBoxOptions: ISelectBoxOptions;
public selectElement: HTMLSelectElement; // {{SQL CARBON EDIT}}
public selectElement: HTMLSelectElement; // {{SQL CARBON EDIT}}
private container?: HTMLElement;
private options: ISelectOptionItem[] = [];
private selected: number;
private readonly _onDidSelect: Emitter<ISelectData>;
@@ -310,6 +310,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
}
public render(container: HTMLElement): void {
this.container = container;
dom.addClass(container, 'select-container');
container.appendChild(this.selectElement);
this.applyStyles();
@@ -454,7 +455,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
dom.toggleClass(this.selectElement, 'synthetic-focus', false);
},
anchorPosition: this._dropDownPosition
});
}, this.selectBoxOptions.optionsAsChildren ? this.container : undefined);
// Hide so we can relay out
this._isVisible = true;
@@ -469,7 +470,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
dom.toggleClass(this.selectElement, 'synthetic-focus', false);
},
anchorPosition: this._dropDownPosition
});
}, this.selectBoxOptions.optionsAsChildren ? this.container : undefined);
// Track initial selection the case user escape, blur
this._currentSelection = this.selected;
@@ -739,7 +740,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
mouseSupport: false,
accessibilityProvider: {
getAriaLabel: (element) => element.text,
getWidgetAriaLabel: () => localize('selectBox', "Select Box"),
getWidgetAriaLabel: () => localize({ key: 'selectBox', comment: ['Behave like native select dropdown element.'] }, "Select Box"),
getRole: () => 'option',
getWidgetRole: () => 'listbox'
}

View File

@@ -16,7 +16,12 @@
flex-direction: column;
}
.monaco-pane-view .pane.horizontal:not(.expanded) {
flex-direction: row;
}
.monaco-pane-view .pane > .pane-header {
height: 22px;
font-size: 11px;
font-weight: bold;
text-transform: uppercase;
@@ -26,6 +31,12 @@
align-items: center;
}
.monaco-pane-view .pane.horizontal:not(.expanded) > .pane-header {
flex-direction: column;
height: 100%;
width: 22px;
}
.monaco-pane-view .pane > .pane-header > .twisties {
width: 20px;
display: flex;
@@ -36,6 +47,11 @@
flex-shrink: 0;
}
.monaco-pane-view .pane.horizontal:not(.expanded) > .pane-header > .twisties {
margin-top: 2px;
margin-bottom: 2px;
}
.monaco-pane-view .pane > .pane-header.expanded > .twisties::before {
transform: rotate(90deg);
}
@@ -132,6 +148,7 @@
width: 100%;
height: 100%;
min-height: 22px;
min-width: 19px;
pointer-events: none; /* very important to not take events away from the parent */
transition: opacity 150ms ease-out;

View File

@@ -106,7 +106,7 @@ export abstract class Pane extends Disposable implements IView {
get minimumSize(): number {
const headerSize = this.headerSize;
const expanded = !this.headerVisible || this.isExpanded();
const minimumBodySize = expanded ? this.minimumBodySize : this._orientation === Orientation.HORIZONTAL ? 50 : 0;
const minimumBodySize = expanded ? this.minimumBodySize : 0;
return headerSize + minimumBodySize;
}
@@ -114,7 +114,7 @@ export abstract class Pane extends Disposable implements IView {
get maximumSize(): number {
const headerSize = this.headerSize;
const expanded = !this.headerVisible || this.isExpanded();
const maximumBodySize = expanded ? this.maximumBodySize : this._orientation === Orientation.HORIZONTAL ? 50 : 0;
const maximumBodySize = expanded ? this.maximumBodySize : 0;
return headerSize + maximumBodySize;
}
@@ -126,7 +126,7 @@ export abstract class Pane extends Disposable implements IView {
this._expanded = typeof options.expanded === 'undefined' ? true : !!options.expanded;
this._orientation = typeof options.orientation === 'undefined' ? Orientation.VERTICAL : options.orientation;
this.ariaHeaderLabel = localize('viewSection', "{0} Section", options.title);
this._minimumBodySize = typeof options.minimumBodySize === 'number' ? options.minimumBodySize : 120;
this._minimumBodySize = typeof options.minimumBodySize === 'number' ? options.minimumBodySize : this._orientation === Orientation.HORIZONTAL ? 200 : 120;
this._maximumBodySize = typeof options.maximumBodySize === 'number' ? options.maximumBodySize : Number.POSITIVE_INFINITY;
this.element = $('.pane');
@@ -141,6 +141,10 @@ export abstract class Pane extends Disposable implements IView {
return false;
}
if (this.element) {
toggleClass(this.element, 'expanded', expanded);
}
this._expanded = !!expanded;
this.updateHeader();
@@ -185,12 +189,21 @@ export abstract class Pane extends Disposable implements IView {
this._orientation = orientation;
if (this.element) {
toggleClass(this.element, 'horizontal', this.orientation === Orientation.HORIZONTAL);
toggleClass(this.element, 'vertical', this.orientation === Orientation.VERTICAL);
}
if (this.header) {
this.updateHeader();
}
}
render(): void {
toggleClass(this.element, 'expanded', this.isExpanded());
toggleClass(this.element, 'horizontal', this.orientation === Orientation.HORIZONTAL);
toggleClass(this.element, 'vertical', this.orientation === Orientation.VERTICAL);
this.header = $('.pane-header');
append(this.element, this.header);
this.header.setAttribute('tabindex', '0');
@@ -250,8 +263,6 @@ export abstract class Pane extends Disposable implements IView {
style(styles: IPaneStyles): void {
this.styles = styles;
this.element.style.borderLeft = this.styles.leftBorder && this.orientation === Orientation.HORIZONTAL ? `1px solid ${this.styles.leftBorder}` : '';
if (!this.header) {
return;
}
@@ -262,7 +273,6 @@ export abstract class Pane extends Disposable implements IView {
protected updateHeader(): void {
const expanded = !this.headerVisible || this.isExpanded();
this.header.style.height = `${this.headerSize}px`;
this.header.style.lineHeight = `${this.headerSize}px`;
toggleClass(this.header, 'hidden', !this.headerVisible);
toggleClass(this.header, 'expanded', expanded);
@@ -272,6 +282,7 @@ export abstract class Pane extends Disposable implements IView {
this.header.style.backgroundColor = this.styles.headerBackground ? this.styles.headerBackground.toString() : '';
this.header.style.borderTop = this.styles.headerBorder && this.orientation === Orientation.VERTICAL ? `1px solid ${this.styles.headerBorder}` : '';
this._dropBackground = this.styles.dropBackground;
this.element.style.borderLeft = this.styles.leftBorder && this.orientation === Orientation.HORIZONTAL ? `1px solid ${this.styles.leftBorder}` : '';
}
protected abstract renderHeader(container: HTMLElement): void;

View File

@@ -331,8 +331,8 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
}
}
addView(view: IView<TLayoutContext>, size: number | Sizing, index = this.viewItems.length): void {
this.doAddView(view, size, index, false);
addView(view: IView<TLayoutContext>, size: number | Sizing, index = this.viewItems.length, skipLayout?: boolean): void {
this.doAddView(view, size, index, skipLayout);
}
removeView(index: number, sizing?: Sizing): IView<TLayoutContext> {
@@ -689,13 +689,17 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
// Add sash
if (this.viewItems.length > 1) {
const orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL;
const layoutProvider = this.orientation === Orientation.VERTICAL ? { getHorizontalSashTop: (sash: Sash) => this.getSashPosition(sash) } : { getVerticalSashLeft: (sash: Sash) => this.getSashPosition(sash) };
const sash = new Sash(this.sashContainer, layoutProvider, {
orientation,
orthogonalStartSash: this.orthogonalStartSash,
orthogonalEndSash: this.orthogonalEndSash
});
const sash = this.orientation === Orientation.VERTICAL
? new Sash(this.sashContainer, { getHorizontalSashTop: (sash: Sash) => this.getSashPosition(sash) }, {
orientation: Orientation.HORIZONTAL,
orthogonalStartSash: this.orthogonalStartSash,
orthogonalEndSash: this.orthogonalEndSash
})
: new Sash(this.sashContainer, { getVerticalSashLeft: (sash: Sash) => this.getSashPosition(sash) }, {
orientation: Orientation.VERTICAL,
orthogonalStartSash: this.orthogonalStartSash,
orthogonalEndSash: this.orthogonalEndSash
});
const sashEventMapper = this.orientation === Orientation.VERTICAL
? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY, alt: e.altKey })
@@ -951,7 +955,7 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
position += this.viewItems[i].size;
if (this.sashItems[i].sash === sash) {
return Math.min(position, this.contentSize - 2);
return position;
}
}

View File

@@ -707,7 +707,7 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
.map(e => new StandardKeyboardEvent(e))
.filter(this.keyboardNavigationEventFilter || (() => true))
.filter(() => this.automaticKeyboardNavigation || this.triggered)
.filter(e => this.keyboardNavigationDelegate.mightProducePrintableCharacter(e) || ((this.pattern.length > 0 || this.triggered) && ((e.keyCode === KeyCode.Escape || e.keyCode === KeyCode.Backspace) && !e.altKey && !e.ctrlKey && !e.metaKey) || (e.keyCode === KeyCode.Backspace && (isMacintosh ? (e.altKey && !e.metaKey) : e.ctrlKey) && !e.shiftKey)))
.filter(e => (this.keyboardNavigationDelegate.mightProducePrintableCharacter(e) && !(e.keyCode === KeyCode.DownArrow || e.keyCode === KeyCode.UpArrow || e.keyCode === KeyCode.LeftArrow || e.keyCode === KeyCode.RightArrow)) || ((this.pattern.length > 0 || this.triggered) && ((e.keyCode === KeyCode.Escape || e.keyCode === KeyCode.Backspace) && !e.altKey && !e.ctrlKey && !e.metaKey) || (e.keyCode === KeyCode.Backspace && (isMacintosh ? (e.altKey && !e.metaKey) : e.ctrlKey) && !e.shiftKey)))
.forEach(e => { e.stopPropagation(); e.preventDefault(); })
.event;
@@ -962,6 +962,7 @@ export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions {
readonly simpleKeyboardNavigation?: boolean;
readonly filterOnType?: boolean;
readonly openOnSingleClick?: boolean;
readonly smoothScrolling?: boolean;
}
export interface IAbstractTreeOptions<T, TFilterData = void> extends IAbstractTreeOptionsUpdate, IListOptions<T> {
@@ -1360,7 +1361,8 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
this.view.updateOptions({
enableKeyboardNavigation: this._options.simpleKeyboardNavigation,
automaticKeyboardNavigation: this._options.automaticKeyboardNavigation
automaticKeyboardNavigation: this._options.automaticKeyboardNavigation,
smoothScrolling: this._options.smoothScrolling
});
if (this.typeFilterController) {

View File

@@ -13,7 +13,7 @@ export class CollapseAllAction<TInput, T, TFilterData = void> extends Action {
super('vs.tree.collapse', nls.localize('collapse all', "Collapse All"), 'collapse-all', enabled);
}
async run(context?: any): Promise<any> {
async run(): Promise<any> {
this.viewer.collapseAll();
this.viewer.setSelection([]);
this.viewer.setFocus([]);

View File

@@ -764,7 +764,7 @@ export class IdleValue<T> {
this._handle.dispose();
}
getValue(): T {
get value(): T {
if (!this._didRun) {
this._handle.dispose();
this._executor();

View File

@@ -342,7 +342,17 @@ export const enum CharCode {
* Unicode Character 'LINE SEPARATOR' (U+2028)
* http://www.fileformat.info/info/unicode/char/2028/index.htm
*/
LINE_SEPARATOR_2028 = 8232,
LINE_SEPARATOR = 0x2028,
/**
* Unicode Character 'PARAGRAPH SEPARATOR' (U+2029)
* http://www.fileformat.info/info/unicode/char/2029/index.htm
*/
PARAGRAPH_SEPARATOR = 0x2029,
/**
* Unicode Character 'NEXT LINE' (U+0085)
* http://www.fileformat.info/info/unicode/char/0085/index.htm
*/
NEXT_LINE = 0x0085,
// http://www.fileformat.info/info/unicode/category/Sk/list.htm
U_CIRCUMFLEX = 0x005E, // U+005E CIRCUMFLEX
@@ -422,4 +432,4 @@ export const enum CharCode {
* http://www.fileformat.info/info/unicode/char/feff/index.htm
*/
UTF8_BOM = 65279
}
}

View File

@@ -244,7 +244,7 @@ export namespace Codicon {
export const collapseAll = new Codicon('collapse-all', { character: '\\eac5' });
export const colorMode = new Codicon('color-mode', { character: '\\eac6' });
export const commentDiscussion = new Codicon('comment-discussion', { character: '\\eac7' });
export const compareChanges = new Codicon('compare-changes', { character: '\\eac8' });
export const compareChanges = new Codicon('compare-changes', { character: '\\eafd' });
export const creditCard = new Codicon('credit-card', { character: '\\eac9' });
export const dash = new Codicon('dash', { character: '\\eacc' });
export const dashboard = new Codicon('dashboard', { character: '\\eacd' });
@@ -448,7 +448,6 @@ export namespace Codicon {
export const debugReverseContinue = new Codicon('debug-reverse-continue', { character: '\\eb8e' });
export const debugStepBack = new Codicon('debug-step-back', { character: '\\eb8f' });
export const debugRestartFrame = new Codicon('debug-restart-frame', { character: '\\eb90' });
export const debugAlternate = new Codicon('debug-alternate', { character: '\\eb91' });
export const callIncoming = new Codicon('call-incoming', { character: '\\eb92' });
export const callOutgoing = new Codicon('call-outgoing', { character: '\\eb93' });
export const menu = new Codicon('menu', { character: '\\eb94' });
@@ -465,8 +464,13 @@ export namespace Codicon {
export const syncIgnored = new Codicon('sync-ignored', { character: '\\eb9f' });
export const pinned = new Codicon('pinned', { character: '\\eba0' });
export const githubInverted = new Codicon('github-inverted', { character: '\\eba1' });
export const debugAlt2 = new Codicon('debug-alt-2', { character: '\\f101' });
export const debugAlt = new Codicon('debug-alt', { character: '\\f102' });
export const debugAlt = new Codicon('debug-alt', { character: '\\eb91' });
export const serverProcess = new Codicon('server-process', { character: '\\eba2' });
export const serverEnvironment = new Codicon('server-environment', { character: '\\eba3' });
export const pass = new Codicon('pass', { character: '\\eba4' });
export const stopCircle = new Codicon('stop-circle', { character: '\\eba5' });
export const playCircle = new Codicon('play-circle', { character: '\\eba6' });
export const record = new Codicon('record', { character: '\\eba7' });
}

View File

@@ -6,7 +6,12 @@
import { sep } from 'vs/base/common/path';
import { IdleValue } from 'vs/base/common/async';
const intlFileNameCollator: IdleValue<{ collator: Intl.Collator, collatorIsNumeric: boolean }> = new IdleValue(() => {
// When comparing large numbers of strings, such as in sorting large arrays, is better for
// performance to create an Intl.Collator object and use the function provided by its compare
// property than it is to use String.prototype.localeCompare()
// A collator with numeric sorting enabled, and no sensitivity to case or to accents
const intlFileNameCollatorBaseNumeric: IdleValue<{ collator: Intl.Collator, collatorIsNumeric: boolean }> = new IdleValue(() => {
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
return {
collator: collator,
@@ -14,20 +19,64 @@ const intlFileNameCollator: IdleValue<{ collator: Intl.Collator, collatorIsNumer
};
});
// A collator with numeric sorting enabled.
const intlFileNameCollatorNumeric: IdleValue<{ collator: Intl.Collator }> = new IdleValue(() => {
const collator = new Intl.Collator(undefined, { numeric: true });
return {
collator: collator
};
});
// A collator with numeric sorting enabled, and sensitivity to accents and diacritics but not case.
const intlFileNameCollatorNumericCaseInsenstive: IdleValue<{ collator: Intl.Collator }> = new IdleValue(() => {
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'accent' });
return {
collator: collator
};
});
export function compareFileNames(one: string | null, other: string | null, caseSensitive = false): number {
const a = one || '';
const b = other || '';
const result = intlFileNameCollator.getValue().collator.compare(a, b);
const result = intlFileNameCollatorBaseNumeric.value.collator.compare(a, b);
// Using the numeric option in the collator will
// make compare(`foo1`, `foo01`) === 0. We must disambiguate.
if (intlFileNameCollator.getValue().collatorIsNumeric && result === 0 && a !== b) {
if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && result === 0 && a !== b) {
return a < b ? -1 : 1;
}
return result;
}
/** Compares filenames by name then extension, sorting numbers numerically instead of alphabetically. */
export function compareFileNamesNumeric(one: string | null, other: string | null): number {
const [oneName, oneExtension] = extractNameAndExtension(one, true);
const [otherName, otherExtension] = extractNameAndExtension(other, true);
const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsenstive.value.collator;
let result;
// Check for name differences, comparing numbers numerically instead of alphabetically.
result = compareAndDisambiguateByLength(collatorNumeric, oneName, otherName);
if (result !== 0) {
return result;
}
// Check for case insensitive extension differences, comparing numbers numerically instead of alphabetically.
result = compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension);
if (result !== 0) {
return result;
}
// Disambiguate the extension case if needed.
if (oneExtension !== otherExtension) {
return collatorNumeric.compare(oneExtension, otherExtension);
}
return 0;
}
const FileNameMatch = /^(.*?)(\.([^.]*))?$/;
export function noIntlCompareFileNames(one: string | null, other: string | null, caseSensitive = false): number {
@@ -54,19 +103,19 @@ export function compareFileExtensions(one: string | null, other: string | null):
const [oneName, oneExtension] = extractNameAndExtension(one);
const [otherName, otherExtension] = extractNameAndExtension(other);
let result = intlFileNameCollator.getValue().collator.compare(oneExtension, otherExtension);
let result = intlFileNameCollatorBaseNumeric.value.collator.compare(oneExtension, otherExtension);
if (result === 0) {
// Using the numeric option in the collator will
// make compare(`foo1`, `foo01`) === 0. We must disambiguate.
if (intlFileNameCollator.getValue().collatorIsNumeric && oneExtension !== otherExtension) {
if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && oneExtension !== otherExtension) {
return oneExtension < otherExtension ? -1 : 1;
}
// Extensions are equal, compare filenames
result = intlFileNameCollator.getValue().collator.compare(oneName, otherName);
result = intlFileNameCollatorBaseNumeric.value.collator.compare(oneName, otherName);
if (intlFileNameCollator.getValue().collatorIsNumeric && result === 0 && oneName !== otherName) {
if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && result === 0 && oneName !== otherName) {
return oneName < otherName ? -1 : 1;
}
}
@@ -74,10 +123,63 @@ export function compareFileExtensions(one: string | null, other: string | null):
return result;
}
function extractNameAndExtension(str?: string | null): [string, string] {
/** Compares filenames by extenson, then by name. Sorts numbers numerically, not alphabetically. */
export function compareFileExtensionsNumeric(one: string | null, other: string | null): number {
const [oneName, oneExtension] = extractNameAndExtension(one, true);
const [otherName, otherExtension] = extractNameAndExtension(other, true);
const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsenstive.value.collator;
let result;
// Check for extension differences, ignoring differences in case and comparing numbers numerically.
result = compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension);
if (result !== 0) {
return result;
}
// Compare names.
result = compareAndDisambiguateByLength(collatorNumeric, oneName, otherName);
if (result !== 0) {
return result;
}
// Disambiguate extension case if needed.
if (oneExtension !== otherExtension) {
return collatorNumeric.compare(oneExtension, otherExtension);
}
return 0;
}
/** Extracts the name and extension from a full filename, with optional special handling for dotfiles */
function extractNameAndExtension(str?: string | null, dotfilesAsNames = false): [string, string] {
const match = str ? FileNameMatch.exec(str) as Array<string> : ([] as Array<string>);
return [(match && match[1]) || '', (match && match[3]) || ''];
let result: [string, string] = [(match && match[1]) || '', (match && match[3]) || ''];
// if the dotfilesAsNames option is selected, treat an empty filename with an extension,
// or a filename that starts with a dot, as a dotfile name
if (dotfilesAsNames && (!result[0] && result[1] || result[0] && result[0].charAt(0) === '.')) {
result = [result[0] + '.' + result[1], ''];
}
return result;
}
function compareAndDisambiguateByLength(collator: Intl.Collator, one: string, other: string) {
// Check for differences
let result = collator.compare(one, other);
if (result !== 0) {
return result;
}
// In a numeric comparison, `foo1` and `foo01` will compare as equivalent.
// Disambiguate by sorting the shorter string first.
if (one.length !== other.length) {
return one.length < other.length ? -1 : 1;
}
return 0;
}
function comparePathComponents(one: string, other: string, caseSensitive = false): number {

View File

@@ -328,8 +328,8 @@ export namespace Event {
}
export interface NodeEventEmitter {
on(event: string | symbol, listener: Function): this;
removeListener(event: string | symbol, listener: Function): this;
on(event: string | symbol, listener: Function): unknown;
removeListener(event: string | symbol, listener: Function): unknown;
}
export function fromNodeEventEmitter<T>(emitter: NodeEventEmitter, eventName: string, map: (...args: any[]) => T = id => id): Event<T> {

View File

@@ -7,6 +7,7 @@ import { isWindows } from 'vs/base/common/platform';
import { startsWithIgnoreCase, equalsIgnoreCase, rtrim } from 'vs/base/common/strings';
import { CharCode } from 'vs/base/common/charCode';
import { sep, posix, isAbsolute, join, normalize } from 'vs/base/common/path';
import { isNumber } from 'vs/base/common/types';
export function isPathSeparator(code: number) {
return code === CharCode.Slash || code === CharCode.Backslash;
@@ -300,3 +301,38 @@ export function indexOfPath(path: string, candidate: string, ignoreCase: boolean
return path.indexOf(candidate);
}
export interface IPathWithLineAndColumn {
path: string;
line?: number;
column?: number;
}
export function parseLineAndColumnAware(rawPath: string): IPathWithLineAndColumn {
const segments = rawPath.split(':'); // C:\file.txt:<line>:<column>
let path: string | undefined = undefined;
let line: number | undefined = undefined;
let column: number | undefined = undefined;
segments.forEach(segment => {
const segmentAsNumber = Number(segment);
if (!isNumber(segmentAsNumber)) {
path = !!path ? [path, segment].join(':') : segment; // a colon can well be part of a path (e.g. C:\...)
} else if (line === undefined) {
line = segmentAsNumber;
} else if (column === undefined) {
column = segmentAsNumber;
}
});
if (!path) {
throw new Error('Format for `--goto` should be: `FILE:LINE(:COLUMN)`');
}
return {
path,
line: line !== undefined ? line : undefined,
column: column !== undefined ? column : line !== undefined ? 1 : undefined // if we have a line, make sure column is also set
};
}

View File

@@ -833,7 +833,7 @@ export interface IPreparedQuery extends IPreparedQueryPiece {
values: IPreparedQueryPiece[] | undefined;
/**
* Wether the query contains path separator(s) or not.
* Whether the query contains path separator(s) or not.
*/
containsPathSeparator: boolean;
}

View File

@@ -9,6 +9,7 @@ export function insane(
readonly allowedSchemes?: readonly string[],
readonly allowedTags?: readonly string[],
readonly allowedAttributes?: { readonly [key: string]: string[] },
readonly filter?: (token: { tag: string, attrs: { readonly [key: string]: string } }) => boolean,
},
strict?: boolean,
): string;

View File

@@ -5,6 +5,10 @@
export namespace Iterable {
export function is<T = any>(thing: any): thing is IterableIterator<T> {
return thing && typeof thing === 'object' && typeof thing[Symbol.iterator] === 'function';
}
const _empty: Iterable<any> = Object.freeze([]);
export function empty<T = any>(): Iterable<T> {
return _empty;

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { once } from 'vs/base/common/functional';
import { Iterable } from 'vs/base/common/iterator';
/**
* Enables logging of potentially leaked disposables.
@@ -54,26 +55,26 @@ export function isDisposable<E extends object>(thing: E): thing is E & IDisposab
export function dispose<T extends IDisposable>(disposable: T): T;
export function dispose<T extends IDisposable>(disposable: T | undefined): T | undefined;
export function dispose<T extends IDisposable, A extends IterableIterator<T> = IterableIterator<T>>(disposables: IterableIterator<T>): A;
export function dispose<T extends IDisposable>(disposables: Array<T>): Array<T>;
export function dispose<T extends IDisposable>(disposables: ReadonlyArray<T>): ReadonlyArray<T>;
export function dispose<T extends IDisposable>(disposables: T | T[] | undefined): T | T[] | undefined {
if (Array.isArray(disposables)) {
disposables.forEach(d => {
export function dispose<T extends IDisposable>(arg: T | IterableIterator<T> | undefined): any {
if (Iterable.is(arg)) {
for (let d of arg) {
if (d) {
markTracked(d);
d.dispose();
}
});
return [];
} else if (disposables) {
markTracked(disposables);
disposables.dispose();
return disposables;
} else {
return undefined;
}
return arg;
} else if (arg) {
markTracked(arg);
arg.dispose();
return arg;
}
}
export function combinedDisposable(...disposables: IDisposable[]): IDisposable {
disposables.forEach(markTracked);
return trackDisposable({ dispose: () => dispose(disposables) });

View File

@@ -206,7 +206,7 @@ export class UriIterator implements IKeyIterator<URI> {
cmp(a: string): number {
if (this._states[this._stateIdx] === UriIteratorState.Scheme) {
return compareSubstringIgnoreCase(a, this._value.scheme);
return compare(a, this._value.scheme);
} else if (this._states[this._stateIdx] === UriIteratorState.Authority) {
return compareSubstringIgnoreCase(a, this._value.authority);
} else if (this._states[this._stateIdx] === UriIteratorState.Path) {
@@ -486,11 +486,9 @@ export class ResourceMap<T> implements Map<URI, T> {
readonly [Symbol.toStringTag] = 'ResourceMap';
protected readonly map: Map<string, T>;
protected readonly ignoreCase?: boolean;
constructor() {
this.map = new Map<string, T>();
this.ignoreCase = false; // in the future this should be an uri-comparator
constructor(other?: ResourceMap<T>) {
this.map = other ? new Map(other.map) : new Map();
}
set(resource: URI, value: T): this {
@@ -550,20 +548,7 @@ export class ResourceMap<T> implements Map<URI, T> {
}
private toKey(resource: URI): string {
let key = resource.toString();
if (this.ignoreCase) {
key = key.toLowerCase();
}
return key;
}
clone(): ResourceMap<T> {
const resourceMap = new ResourceMap<T>();
this.map.forEach((value, key) => resourceMap.map.set(key, value));
return resourceMap;
return resource.toString();
}
}
@@ -580,18 +565,23 @@ export const enum Touch {
AsNew = 2
}
export class LinkedMap<K, V> {
export class LinkedMap<K, V> implements Map<K, V> {
readonly [Symbol.toStringTag] = 'LinkedMap';
private _map: Map<K, Item<K, V>>;
private _head: Item<K, V> | undefined;
private _tail: Item<K, V> | undefined;
private _size: number;
private _state: number;
constructor() {
this._map = new Map<K, Item<K, V>>();
this._head = undefined;
this._tail = undefined;
this._size = 0;
this._state = 0;
}
clear(): void {
@@ -599,6 +589,7 @@ export class LinkedMap<K, V> {
this._head = undefined;
this._tail = undefined;
this._size = 0;
this._state++;
}
isEmpty(): boolean {
@@ -632,7 +623,7 @@ export class LinkedMap<K, V> {
return item.value;
}
set(key: K, value: V, touch: Touch = Touch.None): void {
set(key: K, value: V, touch: Touch = Touch.None): this {
let item = this._map.get(key);
if (item) {
item.value = value;
@@ -658,6 +649,7 @@ export class LinkedMap<K, V> {
this._map.set(key, item);
this._size++;
}
return this;
}
delete(key: K): boolean {
@@ -690,6 +682,7 @@ export class LinkedMap<K, V> {
}
forEach(callbackfn: (value: V, key: K, map: LinkedMap<K, V>) => void, thisArg?: any): void {
const state = this._state;
let current = this._head;
while (current) {
if (thisArg) {
@@ -697,38 +690,25 @@ export class LinkedMap<K, V> {
} else {
callbackfn(current.value, current.key, this);
}
if (this._state !== state) {
throw new Error(`LinkedMap got modified during iteration.`);
}
current = current.next;
}
}
values(): V[] {
const result: V[] = [];
let current = this._head;
while (current) {
result.push(current.value);
current = current.next;
}
return result;
}
keys(): K[] {
const result: K[] = [];
let current = this._head;
while (current) {
result.push(current.key);
current = current.next;
}
return result;
}
/* VS Code / Monaco editor runs on es5 which has no Symbol.iterator
keys(): IterableIterator<K> {
const current = this._head;
const map = this;
const state = this._state;
let current = this._head;
const iterator: IterableIterator<K> = {
[Symbol.iterator]() {
return iterator;
},
next():IteratorResult<K> {
next(): IteratorResult<K> {
if (map._state !== state) {
throw new Error(`LinkedMap got modified during iteration.`);
}
if (current) {
const result = { value: current.key, done: false };
current = current.next;
@@ -742,12 +722,17 @@ export class LinkedMap<K, V> {
}
values(): IterableIterator<V> {
const current = this._head;
const map = this;
const state = this._state;
let current = this._head;
const iterator: IterableIterator<V> = {
[Symbol.iterator]() {
return iterator;
},
next():IteratorResult<V> {
next(): IteratorResult<V> {
if (map._state !== state) {
throw new Error(`LinkedMap got modified during iteration.`);
}
if (current) {
const result = { value: current.value, done: false };
current = current.next;
@@ -759,7 +744,34 @@ export class LinkedMap<K, V> {
};
return iterator;
}
*/
entries(): IterableIterator<[K, V]> {
const map = this;
const state = this._state;
let current = this._head;
const iterator: IterableIterator<[K, V]> = {
[Symbol.iterator]() {
return iterator;
},
next(): IteratorResult<[K, V]> {
if (map._state !== state) {
throw new Error(`LinkedMap got modified during iteration.`);
}
if (current) {
const result: IteratorResult<[K, V]> = { value: [current.key, current.value], done: false };
current = current.next;
return result;
} else {
return { value: undefined, done: true };
}
}
};
return iterator;
}
[Symbol.iterator](): IterableIterator<[K, V]> {
return this.entries();
}
protected trimOld(newSize: number) {
if (newSize >= this.size) {
@@ -781,6 +793,7 @@ export class LinkedMap<K, V> {
if (current) {
current.previous = undefined;
}
this._state++;
}
private addItemFirst(item: Item<K, V>): void {
@@ -794,6 +807,7 @@ export class LinkedMap<K, V> {
this._head.previous = item;
}
this._head = item;
this._state++;
}
private addItemLast(item: Item<K, V>): void {
@@ -807,6 +821,7 @@ export class LinkedMap<K, V> {
this._tail.next = item;
}
this._tail = item;
this._state++;
}
private removeItem(item: Item<K, V>): void {
@@ -843,6 +858,7 @@ export class LinkedMap<K, V> {
}
item.next = undefined;
item.previous = undefined;
this._state++;
}
private touch(item: Item<K, V>, touch: Touch): void {
@@ -879,6 +895,7 @@ export class LinkedMap<K, V> {
item.next = this._head;
this._head.previous = item;
this._head = item;
this._state++;
} else if (touch === Touch.AsNew) {
if (item === this._tail) {
return;
@@ -902,6 +919,7 @@ export class LinkedMap<K, V> {
item.previous = this._tail;
this._tail.next = item;
this._tail = item;
this._state++;
}
}
@@ -953,17 +971,18 @@ export class LRUCache<K, V> extends LinkedMap<K, V> {
this.checkTrim();
}
get(key: K): V | undefined {
return super.get(key, Touch.AsNew);
get(key: K, touch: Touch = Touch.AsNew): V | undefined {
return super.get(key, touch);
}
peek(key: K): V | undefined {
return super.get(key, Touch.None);
}
set(key: K, value: V): void {
set(key: K, value: V): this {
super.set(key, value, Touch.AsNew);
this.checkTrim();
return this;
}
private checkTrim() {

View File

@@ -335,3 +335,13 @@ export function getMediaMime(path: string): string | undefined {
const ext = extname(path);
return mapExtToMediaMimes[ext.toLowerCase()];
}
export function getExtensionForMimeType(mimeType: string): string | undefined {
for (const extension in mapExtToMediaMimes) {
if (mapExtToMediaMimes[extension] === mimeType) {
return extension;
}
}
return undefined;
}

View File

@@ -59,6 +59,8 @@ export namespace Schemas {
export const vscodeSettings = 'vscode-settings';
export const webviewPanel = 'webview-panel';
export const vscodeWebviewResource = 'vscode-webview-resource';
}
class RemoteAuthoritiesImpl {

View File

@@ -6,7 +6,7 @@
import * as extpath from 'vs/base/common/extpath';
import * as paths from 'vs/base/common/path';
import { URI, uriToFsPath } from 'vs/base/common/uri';
import { equalsIgnoreCase } from 'vs/base/common/strings';
import { equalsIgnoreCase, compare as strCompare, compareIgnoreCase } from 'vs/base/common/strings';
import { Schemas } from 'vs/base/common/network';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { CharCode } from 'vs/base/common/charCode';
@@ -17,248 +17,363 @@ export function originalFSPath(uri: URI): string {
return uriToFsPath(uri, true);
}
/**
* Creates a key from a resource URI to be used to resource comparison and for resource maps.
* URI queries are included, fragments are ignored.
*/
export function getComparisonKey(resource: URI, caseInsensitivePath = hasToIgnoreCase(resource)): string {
let path = resource.path || '/';
if (caseInsensitivePath) {
path = path.toLowerCase();
}
return resource.with({ authority: resource.authority.toLowerCase(), path: path, fragment: null }).toString();
//#region IExtUri
export interface IExtUri {
// --- identity
/**
* Compares two uris.
*
* @param uri1 Uri
* @param uri2 Uri
* @param ignoreFragment Ignore the fragment (defaults to `false`)
*/
compare(uri1: URI, uri2: URI, ignoreFragment?: boolean): number;
/**
* Tests whether two uris are equal
*
* @param uri1 Uri
* @param uri2 Uri
* @param ignoreFragment Ignore the fragment (defaults to `false`)
*/
isEqual(uri1: URI | undefined, uri2: URI | undefined, ignoreFragment?: boolean): boolean;
/**
* Tests whether a `candidate` URI is a parent or equal of a given `base` URI.
*
* @param base A uri which is "longer"
* @param parentCandidate A uri which is "shorter" then `base`
* @param ignoreFragment Ignore the fragment (defaults to `false`)
*/
isEqualOrParent(base: URI, parentCandidate: URI, ignoreFragment?: boolean): boolean;
/**
* Creates a key from a resource URI to be used to resource comparison and for resource maps.
* @see ResourceMap
* @param uri Uri
* @param ignoreFragment Ignore the fragment (defaults to `false`)
*/
getComparisonKey(uri: URI, ignoreFragment?: boolean): string;
// --- path math
basenameOrAuthority(resource: URI): string;
/**
* Returns the basename of the path component of an uri.
* @param resource
*/
basename(resource: URI): string;
/**
* Returns the extension of the path component of an uri.
* @param resource
*/
extname(resource: URI): string;
/**
* Return a URI representing the directory of a URI path.
*
* @param resource The input URI.
* @returns The URI representing the directory of the input URI.
*/
dirname(resource: URI): URI;
/**
* Join a URI path with path fragments and normalizes the resulting path.
*
* @param resource The input URI.
* @param pathFragment The path fragment to add to the URI path.
* @returns The resulting URI.
*/
joinPath(resource: URI, ...pathFragment: string[]): URI
/**
* Normalizes the path part of a URI: Resolves `.` and `..` elements with directory names.
*
* @param resource The URI to normalize the path.
* @returns The URI with the normalized path.
*/
normalizePath(resource: URI): URI;
/**
*
* @param from
* @param to
*/
relativePath(from: URI, to: URI): string | undefined;
/**
* Resolves an absolute or relative path against a base URI.
* The path can be relative or absolute posix or a Windows path
*/
resolvePath(base: URI, path: string): URI;
// --- misc
/**
* Returns true if the URI path is absolute.
*/
isAbsolutePath(resource: URI): boolean;
/**
* Tests whether the two authorities are the same
*/
isEqualAuthority(a1: string, a2: string): boolean;
/**
* Returns true if the URI path has a trailing path separator
*/
hasTrailingPathSeparator(resource: URI, sep?: string): boolean;
/**
* Removes a trailing path separator, if there's one.
* Important: Doesn't remove the first slash, it would make the URI invalid
*/
removeTrailingPathSeparator(resource: URI, sep?: string): URI;
/**
* Adds a trailing path separator to the URI if there isn't one already.
* For example, c:\ would be unchanged, but c:\users would become c:\users\
*/
addTrailingPathSeparator(resource: URI, sep?: string): URI;
}
export function hasToIgnoreCase(resource: URI | undefined): boolean {
// A file scheme resource is in the same platform as code, so ignore case for non linux platforms
// Resource can be from another platform. Lowering the case as an hack. Should come from File system provider
return resource && resource.scheme === Schemas.file ? !isLinux : true;
}
export class ExtUri implements IExtUri {
export function basenameOrAuthority(resource: URI): string {
return basename(resource) || resource.authority;
}
constructor(private _ignorePathCasing: (uri: URI) => boolean) { }
/**
* Tests whether a `candidate` URI is a parent or equal of a given `base` URI.
* URI queries must match, fragments are ignored.
* @param base A uri which is "longer"
* @param parentCandidate A uri which is "shorter" then `base`
*/
export function isEqualOrParent(base: URI, parentCandidate: URI, ignoreCase = hasToIgnoreCase(base)): boolean {
if (base.scheme === parentCandidate.scheme) {
if (base.scheme === Schemas.file) {
return extpath.isEqualOrParent(originalFSPath(base), originalFSPath(parentCandidate), ignoreCase) && base.query === parentCandidate.query;
}
if (isEqualAuthority(base.authority, parentCandidate.authority)) {
return extpath.isEqualOrParent(base.path || '/', parentCandidate.path || '/', ignoreCase, '/') && base.query === parentCandidate.query;
}
}
return false;
}
/**
* Tests whether the two authorities are the same
*/
export function isEqualAuthority(a1: string, a2: string) {
return a1 === a2 || equalsIgnoreCase(a1, a2);
}
/**
* Tests whether two resources are the same. URI queries must match, fragments are ignored unless requested.
*/
export function isEqual(first: URI | undefined, second: URI | undefined, caseInsensitivePath = hasToIgnoreCase(first), ignoreFragment = true): boolean {
if (first === second) {
return true;
}
if (!first || !second) {
return false;
}
if (first.scheme !== second.scheme || !isEqualAuthority(first.authority, second.authority)) {
return false;
}
const p1 = first.path || '/', p2 = second.path || '/';
return (p1 === p2 || caseInsensitivePath && equalsIgnoreCase(p1, p2)) && first.query === second.query && (ignoreFragment || first.fragment === second.fragment);
}
export function basename(resource: URI): string {
return paths.posix.basename(resource.path);
}
export function extname(resource: URI): string {
return paths.posix.extname(resource.path);
}
/**
* Return a URI representing the directory of a URI path.
*
* @param resource The input URI.
* @returns The URI representing the directory of the input URI.
*/
export function dirname(resource: URI): URI {
if (resource.path.length === 0) {
return resource;
}
let dirname;
if (resource.scheme === Schemas.file) {
dirname = URI.file(paths.dirname(originalFSPath(resource))).path;
} else {
dirname = paths.posix.dirname(resource.path);
if (resource.authority && dirname.length && dirname.charCodeAt(0) !== CharCode.Slash) {
console.error(`dirname("${resource.toString})) resulted in a relative path`);
dirname = '/'; // If a URI contains an authority component, then the path component must either be empty or begin with a CharCode.Slash ("/") character
}
}
return resource.with({
path: dirname
});
}
/**
* Join a URI path with path fragments and normalizes the resulting path.
*
* @param resource The input URI.
* @param pathFragment The path fragment to add to the URI path.
* @returns The resulting URI.
*/
export function joinPath(resource: URI, ...pathFragment: string[]): URI {
let joinedPath: string;
if (resource.scheme === 'file') {
joinedPath = URI.file(paths.join(originalFSPath(resource), ...pathFragment)).path;
} else {
joinedPath = paths.posix.join(resource.path || '/', ...pathFragment);
}
return resource.with({
path: joinedPath
});
}
/**
* Normalizes the path part of a URI: Resolves `.` and `..` elements with directory names.
*
* @param resource The URI to normalize the path.
* @returns The URI with the normalized path.
*/
export function normalizePath(resource: URI): URI {
if (!resource.path.length) {
return resource;
}
let normalizedPath: string;
if (resource.scheme === Schemas.file) {
normalizedPath = URI.file(paths.normalize(originalFSPath(resource))).path;
} else {
normalizedPath = paths.posix.normalize(resource.path);
}
return resource.with({
path: normalizedPath
});
}
/**
* Returns true if the URI path is absolute.
*/
export function isAbsolutePath(resource: URI): boolean {
return !!resource.path && resource.path[0] === '/';
}
/**
* Returns true if the URI path has a trailing path separator
*/
export function hasTrailingPathSeparator(resource: URI, sep: string = paths.sep): boolean {
if (resource.scheme === Schemas.file) {
const fsp = originalFSPath(resource);
return fsp.length > extpath.getRoot(fsp).length && fsp[fsp.length - 1] === sep;
} else {
const p = resource.path;
return (p.length > 1 && p.charCodeAt(p.length - 1) === CharCode.Slash) && !(/^[a-zA-Z]:(\/$|\\$)/.test(resource.fsPath)); // ignore the slash at offset 0
}
}
/**
* Removes a trailing path separator, if there's one.
* Important: Doesn't remove the first slash, it would make the URI invalid
*/
export function removeTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {
// Make sure that the path isn't a drive letter. A trailing separator there is not removable.
if (hasTrailingPathSeparator(resource, sep)) {
return resource.with({ path: resource.path.substr(0, resource.path.length - 1) });
}
return resource;
}
/**
* Adds a trailing path separator to the URI if there isn't one already.
* For example, c:\ would be unchanged, but c:\users would become c:\users\
*/
export function addTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {
let isRootSep: boolean = false;
if (resource.scheme === Schemas.file) {
const fsp = originalFSPath(resource);
isRootSep = ((fsp !== undefined) && (fsp.length === extpath.getRoot(fsp).length) && (fsp[fsp.length - 1] === sep));
} else {
sep = '/';
const p = resource.path;
isRootSep = p.length === 1 && p.charCodeAt(p.length - 1) === CharCode.Slash;
}
if (!isRootSep && !hasTrailingPathSeparator(resource, sep)) {
return resource.with({ path: resource.path + '/' });
}
return resource;
}
/**
* Returns a relative path between two URIs. If the URIs don't have the same schema or authority, `undefined` is returned.
* The returned relative path always uses forward slashes.
*/
export function relativePath(from: URI, to: URI, caseInsensitivePath = hasToIgnoreCase(from)): string | undefined {
if (from.scheme !== to.scheme || !isEqualAuthority(from.authority, to.authority)) {
return undefined;
}
if (from.scheme === Schemas.file) {
const relativePath = paths.relative(originalFSPath(from), originalFSPath(to));
return isWindows ? extpath.toSlashes(relativePath) : relativePath;
}
let fromPath = from.path || '/', toPath = to.path || '/';
if (caseInsensitivePath) {
// make casing of fromPath match toPath
let i = 0;
for (const len = Math.min(fromPath.length, toPath.length); i < len; i++) {
if (fromPath.charCodeAt(i) !== toPath.charCodeAt(i)) {
if (fromPath.charAt(i).toLowerCase() !== toPath.charAt(i).toLowerCase()) {
break;
compare(uri1: URI, uri2: URI, ignoreFragment: boolean = false): number {
// scheme
let ret = strCompare(uri1.scheme, uri2.scheme);
if (ret === 0) {
// authority
ret = compareIgnoreCase(uri1.authority, uri2.authority);
if (ret === 0) {
// path
ret = this._ignorePathCasing(uri1) ? compareIgnoreCase(uri1.path, uri2.path) : strCompare(uri1.path, uri2.path);
// query
if (ret === 0) {
ret = strCompare(uri1.query, uri2.query);
// fragment
if (ret === 0 && !ignoreFragment) {
ret = strCompare(uri1.fragment, uri2.fragment);
}
}
}
}
fromPath = toPath.substr(0, i) + fromPath.substr(i);
return ret;
}
getComparisonKey(uri: URI, ignoreFragment: boolean = false): string {
return uri.with({
path: this._ignorePathCasing(uri) ? uri.path.toLowerCase() : undefined,
fragment: ignoreFragment ? null : undefined
}).toString();
}
isEqual(uri1: URI | undefined, uri2: URI | undefined, ignoreFragment: boolean = false): boolean {
if (uri1 === uri2) {
return true;
}
if (!uri1 || !uri2) {
return false;
}
if (uri1.scheme !== uri2.scheme || !isEqualAuthority(uri1.authority, uri2.authority)) {
return false;
}
const p1 = uri1.path, p2 = uri2.path;
return (p1 === p2 || this._ignorePathCasing(uri1) && equalsIgnoreCase(p1, p2)) && uri1.query === uri2.query && (ignoreFragment || uri1.fragment === uri2.fragment);
}
isEqualOrParent(base: URI, parentCandidate: URI, ignoreFragment: boolean = false): boolean {
if (base.scheme === parentCandidate.scheme) {
if (base.scheme === Schemas.file) {
return extpath.isEqualOrParent(originalFSPath(base), originalFSPath(parentCandidate), this._ignorePathCasing(base)) && base.query === parentCandidate.query && (ignoreFragment || base.fragment === parentCandidate.fragment);
}
if (isEqualAuthority(base.authority, parentCandidate.authority)) {
return extpath.isEqualOrParent(base.path, parentCandidate.path, this._ignorePathCasing(base), '/') && base.query === parentCandidate.query && (ignoreFragment || base.fragment === parentCandidate.fragment);
}
}
return false;
}
// --- path math
joinPath(resource: URI, ...pathFragment: string[]): URI {
return URI.joinPath(resource, ...pathFragment);
}
basenameOrAuthority(resource: URI): string {
return basename(resource) || resource.authority;
}
basename(resource: URI): string {
return paths.posix.basename(resource.path);
}
extname(resource: URI): string {
return paths.posix.extname(resource.path);
}
dirname(resource: URI): URI {
if (resource.path.length === 0) {
return resource;
}
let dirname;
if (resource.scheme === Schemas.file) {
dirname = URI.file(paths.dirname(originalFSPath(resource))).path;
} else {
dirname = paths.posix.dirname(resource.path);
if (resource.authority && dirname.length && dirname.charCodeAt(0) !== CharCode.Slash) {
console.error(`dirname("${resource.toString})) resulted in a relative path`);
dirname = '/'; // If a URI contains an authority component, then the path component must either be empty or begin with a CharCode.Slash ("/") character
}
}
return resource.with({
path: dirname
});
}
normalizePath(resource: URI): URI {
if (!resource.path.length) {
return resource;
}
let normalizedPath: string;
if (resource.scheme === Schemas.file) {
normalizedPath = URI.file(paths.normalize(originalFSPath(resource))).path;
} else {
normalizedPath = paths.posix.normalize(resource.path);
}
return resource.with({
path: normalizedPath
});
}
relativePath(from: URI, to: URI): string | undefined {
if (from.scheme !== to.scheme || !isEqualAuthority(from.authority, to.authority)) {
return undefined;
}
if (from.scheme === Schemas.file) {
const relativePath = paths.relative(originalFSPath(from), originalFSPath(to));
return isWindows ? extpath.toSlashes(relativePath) : relativePath;
}
let fromPath = from.path || '/', toPath = to.path || '/';
if (this._ignorePathCasing(from)) {
// make casing of fromPath match toPath
let i = 0;
for (const len = Math.min(fromPath.length, toPath.length); i < len; i++) {
if (fromPath.charCodeAt(i) !== toPath.charCodeAt(i)) {
if (fromPath.charAt(i).toLowerCase() !== toPath.charAt(i).toLowerCase()) {
break;
}
}
}
fromPath = toPath.substr(0, i) + fromPath.substr(i);
}
return paths.posix.relative(fromPath, toPath);
}
resolvePath(base: URI, path: string): URI {
if (base.scheme === Schemas.file) {
const newURI = URI.file(paths.resolve(originalFSPath(base), path));
return base.with({
authority: newURI.authority,
path: newURI.path
});
}
if (path.indexOf('/') === -1) { // no slashes? it's likely a Windows path
path = extpath.toSlashes(path);
if (/^[a-zA-Z]:(\/|$)/.test(path)) { // starts with a drive letter
path = '/' + path;
}
}
return base.with({
path: paths.posix.resolve(base.path, path)
});
}
// --- misc
isAbsolutePath(resource: URI): boolean {
return !!resource.path && resource.path[0] === '/';
}
isEqualAuthority(a1: string, a2: string) {
return a1 === a2 || equalsIgnoreCase(a1, a2);
}
hasTrailingPathSeparator(resource: URI, sep: string = paths.sep): boolean {
if (resource.scheme === Schemas.file) {
const fsp = originalFSPath(resource);
return fsp.length > extpath.getRoot(fsp).length && fsp[fsp.length - 1] === sep;
} else {
const p = resource.path;
return (p.length > 1 && p.charCodeAt(p.length - 1) === CharCode.Slash) && !(/^[a-zA-Z]:(\/$|\\$)/.test(resource.fsPath)); // ignore the slash at offset 0
}
}
removeTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {
// Make sure that the path isn't a drive letter. A trailing separator there is not removable.
if (hasTrailingPathSeparator(resource, sep)) {
return resource.with({ path: resource.path.substr(0, resource.path.length - 1) });
}
return resource;
}
addTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {
let isRootSep: boolean = false;
if (resource.scheme === Schemas.file) {
const fsp = originalFSPath(resource);
isRootSep = ((fsp !== undefined) && (fsp.length === extpath.getRoot(fsp).length) && (fsp[fsp.length - 1] === sep));
} else {
sep = '/';
const p = resource.path;
isRootSep = p.length === 1 && p.charCodeAt(p.length - 1) === CharCode.Slash;
}
if (!isRootSep && !hasTrailingPathSeparator(resource, sep)) {
return resource.with({ path: resource.path + '/' });
}
return resource;
}
return paths.posix.relative(fromPath, toPath);
}
/**
* Resolves an absolute or relative path against a base URI.
* The path can be relative or absolute posix or a Windows path
* Unbiased utility that takes uris "as they are". This means it can be interchanged with
* uri#toString() usages. The following is true
* ```
* assertEqual(aUri.toString() === bUri.toString(), exturi.isEqual(aUri, bUri))
* ```
*/
export function resolvePath(base: URI, path: string): URI {
if (base.scheme === Schemas.file) {
const newURI = URI.file(paths.resolve(originalFSPath(base), path));
return base.with({
authority: newURI.authority,
path: newURI.path
});
}
if (path.indexOf('/') === -1) { // no slashes? it's likely a Windows path
path = extpath.toSlashes(path);
if (/^[a-zA-Z]:(\/|$)/.test(path)) { // starts with a drive letter
path = '/' + path;
}
}
return base.with({
path: paths.posix.resolve(base.path, path)
});
}
export const extUri = new ExtUri(() => false);
/**
* BIASED utility that always ignores the casing of uris path. ONLY use these util if you
* understand what you are doing.
*
* Note that `IUriIdentityService#extUri` is a better replacement for this because that utility
* knows when path casing matters and when not.
*/
export const extUriIgnorePathCase = new ExtUri(_ => true);
const exturiBiasedIgnorePathCase = new ExtUri(uri => {
// A file scheme resource is in the same platform as code, so ignore case for non linux platforms
// Resource can be from another platform. Lowering the case as an hack. Should come from File system provider
return uri && uri.scheme === Schemas.file ? !isLinux : true;
});
export const isEqual = exturiBiasedIgnorePathCase.isEqual.bind(exturiBiasedIgnorePathCase);
export const isEqualOrParent = exturiBiasedIgnorePathCase.isEqualOrParent.bind(exturiBiasedIgnorePathCase);
export const getComparisonKey = exturiBiasedIgnorePathCase.getComparisonKey.bind(exturiBiasedIgnorePathCase);
export const basenameOrAuthority = exturiBiasedIgnorePathCase.basenameOrAuthority.bind(exturiBiasedIgnorePathCase);
export const basename = exturiBiasedIgnorePathCase.basename.bind(exturiBiasedIgnorePathCase);
export const extname = exturiBiasedIgnorePathCase.extname.bind(exturiBiasedIgnorePathCase);
export const dirname = exturiBiasedIgnorePathCase.dirname.bind(exturiBiasedIgnorePathCase);
export const joinPath = extUri.joinPath.bind(extUri);
export const normalizePath = exturiBiasedIgnorePathCase.normalizePath.bind(exturiBiasedIgnorePathCase);
export const relativePath = exturiBiasedIgnorePathCase.relativePath.bind(exturiBiasedIgnorePathCase);
export const resolvePath = exturiBiasedIgnorePathCase.resolvePath.bind(exturiBiasedIgnorePathCase);
export const isAbsolutePath = exturiBiasedIgnorePathCase.isAbsolutePath.bind(exturiBiasedIgnorePathCase);
export const isEqualAuthority = exturiBiasedIgnorePathCase.isEqualAuthority.bind(exturiBiasedIgnorePathCase);
export const hasTrailingPathSeparator = exturiBiasedIgnorePathCase.hasTrailingPathSeparator.bind(exturiBiasedIgnorePathCase);
export const removeTrailingPathSeparator = exturiBiasedIgnorePathCase.removeTrailingPathSeparator.bind(exturiBiasedIgnorePathCase);
export const addTrailingPathSeparator = exturiBiasedIgnorePathCase.addTrailingPathSeparator.bind(exturiBiasedIgnorePathCase);
//#endregion
export function distinctParents<T>(items: T[], resourceAccessor: (item: T) => URI): T[] {
const distinctParents: T[] = [];

View File

@@ -13,10 +13,18 @@ export const enum ScrollbarVisibility {
}
export interface ScrollEvent {
oldWidth: number;
oldScrollWidth: number;
oldScrollLeft: number;
width: number;
scrollWidth: number;
scrollLeft: number;
oldHeight: number;
oldScrollHeight: number;
oldScrollTop: number;
height: number;
scrollHeight: number;
scrollTop: number;
@@ -134,10 +142,18 @@ export class ScrollState implements IScrollDimensions, IScrollPosition {
const scrollTopChanged = (this.scrollTop !== previous.scrollTop);
return {
oldWidth: previous.width,
oldScrollWidth: previous.scrollWidth,
oldScrollLeft: previous.scrollLeft,
width: this.width,
scrollWidth: this.scrollWidth,
scrollLeft: this.scrollLeft,
oldHeight: previous.height,
oldScrollHeight: previous.scrollHeight,
oldScrollTop: previous.scrollTop,
height: this.height,
scrollHeight: this.scrollHeight,
scrollTop: this.scrollTop,

View File

@@ -0,0 +1,203 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
class Node<K, V> {
readonly forward: Node<K, V>[];
constructor(readonly level: number, readonly key: K, public value: V) {
this.forward = [];
}
}
const NIL: undefined = undefined;
interface Comparator<K> {
(a: K, b: K): number;
}
export class SkipList<K, V> implements Map<K, V> {
readonly [Symbol.toStringTag] = 'SkipList';
private _maxLevel: number;
private _level: number = 1;
private _header: Node<K, V>;
private _size: number = 0;
/**
*
* @param capacity Capacity at which the list performs best
*/
constructor(
readonly comparator: (a: K, b: K) => number,
capacity: number = 2 ** 16
) {
this._maxLevel = Math.max(1, Math.log2(capacity) | 0);
this._header = <any>new Node(this._maxLevel, NIL, NIL);
}
get size(): number {
return this._size;
}
clear(): void {
this._header = <any>new Node(this._maxLevel, NIL, NIL);
}
has(key: K): boolean {
return Boolean(SkipList._search(this, key, this.comparator));
}
get(key: K): V | undefined {
return SkipList._search(this, key, this.comparator)?.value;
}
set(key: K, value: V): this {
if (SkipList._insert(this, key, value, this.comparator)) {
this._size += 1;
}
return this;
}
delete(key: K): boolean {
const didDelete = SkipList._delete(this, key, this.comparator);
if (didDelete) {
this._size -= 1;
}
return didDelete;
}
// --- iteration
forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void {
let node = this._header.forward[0];
while (node) {
callbackfn.call(thisArg, node.value, node.key, this);
node = node.forward[0];
}
}
[Symbol.iterator](): IterableIterator<[K, V]> {
return this.entries();
}
*entries(): IterableIterator<[K, V]> {
let node = this._header.forward[0];
while (node) {
yield [node.key, node.value];
node = node.forward[0];
}
}
*keys(): IterableIterator<K> {
let node = this._header.forward[0];
while (node) {
yield node.key;
node = node.forward[0];
}
}
*values(): IterableIterator<V> {
let node = this._header.forward[0];
while (node) {
yield node.value;
node = node.forward[0];
}
}
toString(): string {
// debug string...
let result = '[SkipList]:';
let node = this._header.forward[0];
while (node) {
result += `node(${node.key}, ${node.value}, lvl:${node.level})`;
node = node.forward[0];
}
return result;
}
// from https://www.epaperpress.com/sortsearch/download/skiplist.pdf
private static _search<K, V>(list: SkipList<K, V>, searchKey: K, comparator: Comparator<K>) {
let x = list._header;
for (let i = list._level; i >= 0; i--) {
while (x.forward[i] && comparator(x.forward[i].key, searchKey) < 0) {
x = x.forward[i];
}
}
x = x.forward[0];
if (x && comparator(x.key, searchKey) === 0) {
return x;
}
return undefined;
}
private static _insert<K, V>(list: SkipList<K, V>, searchKey: K, value: V, comparator: Comparator<K>) {
let update: Node<K, V>[] = [];
let x = list._header;
for (let i = list._level; i >= 0; i--) {
while (x.forward[i] && comparator(x.forward[i].key, searchKey) < 0) {
x = x.forward[i];
}
update[i] = x;
}
x = x.forward[0];
if (x && comparator(x.key, searchKey) === 0) {
// update
x.value = value;
return false;
} else {
// insert
let lvl = SkipList._randomLevel(list);
if (lvl > list._level) {
for (let i = list._level + 1; i <= lvl; i++) {
update[i] = list._header;
}
list._level = lvl;
}
x = new Node<K, V>(lvl, searchKey, value);
for (let i = 0; i <= lvl; i++) {
x.forward[i] = update[i].forward[i];
update[i].forward[i] = x;
}
return true;
}
}
private static _randomLevel(list: SkipList<any, any>, p: number = 0.5): number {
let lvl = 1;
while (Math.random() < p && lvl < list._maxLevel) {
lvl += 1;
}
return lvl;
}
private static _delete<K, V>(list: SkipList<K, V>, searchKey: K, comparator: Comparator<K>) {
let update: Node<K, V>[] = [];
let x = list._header;
for (let i = list._level; i >= 0; i--) {
while (x.forward[i] && comparator(x.forward[i].key, searchKey) < 0) {
x = x.forward[i];
}
update[i] = x;
}
x = x.forward[0];
if (!x || comparator(x.key, searchKey) !== 0) {
// not found
return false;
}
for (let i = 0; i < list._level; i++) {
if (update[i].forward[i] !== x) {
break;
}
update[i].forward[i] = x.forward[i];
}
while (list._level >= 1 && list._header.forward[list._level] === NIL) {
list._level -= 1;
}
return true;
}
}

View File

@@ -728,6 +728,14 @@ export function isBasicASCII(str: string): boolean {
return IS_BASIC_ASCII.test(str);
}
export const UNUSUAL_LINE_TERMINATORS = /[\u2028\u2029\u0085]/; // LINE SEPARATOR (LS), PARAGRAPH SEPARATOR (PS), NEXT LINE (NEL)
/**
* Returns true if `str` contains unusual line terminators, like LS, PS or NEL
*/
export function containsUnusualLineTerminators(str: string): boolean {
return UNUSUAL_LINE_TERMINATORS.test(str);
}
export function containsFullWidthCharacter(str: string): boolean {
for (let i = 0, len = str.length; i < len; i++) {
if (isFullWidthCharacter(str.charCodeAt(i))) {

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ipcRenderer, Event } from 'electron';
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import { IContextMenuItem, ISerializableContextMenuItem, CONTEXT_MENU_CLOSE_CHANNEL, CONTEXT_MENU_CHANNEL, IPopupOptions, IContextMenuEvent } from 'vs/base/parts/contextmenu/common/contextmenu';
let contextMenuIdPool = 0;
@@ -13,7 +13,7 @@ export function popup(items: IContextMenuItem[], options?: IPopupOptions): void
const contextMenuId = contextMenuIdPool++;
const onClickChannel = `vscode:onContextMenu${contextMenuId}`;
const onClickChannelHandler = (_event: Event, itemId: number, context: IContextMenuEvent) => {
const onClickChannelHandler = (event: unknown, itemId: number, context: IContextMenuEvent) => {
const item = processedItems[itemId];
if (item.click) {
item.click(context);
@@ -21,7 +21,7 @@ export function popup(items: IContextMenuItem[], options?: IPopupOptions): void
};
ipcRenderer.once(onClickChannel, onClickChannelHandler);
ipcRenderer.once(CONTEXT_MENU_CLOSE_CHANNEL, (_event: Event, closedContextMenuId: number) => {
ipcRenderer.once(CONTEXT_MENU_CLOSE_CHANNEL, (event: unknown, closedContextMenuId: number) => {
if (closedContextMenuId !== contextMenuId) {
return;
}

View File

@@ -8,7 +8,7 @@ import { Event } from 'vs/base/common/event';
import { VSBuffer } from 'vs/base/common/buffer';
export interface Sender {
send(channel: string, msg: Buffer | null): void;
send(channel: string, msg: unknown): void;
}
export class Protocol implements IMessagePassingProtocol {
@@ -17,13 +17,13 @@ export class Protocol implements IMessagePassingProtocol {
send(message: VSBuffer): void {
try {
this.sender.send('ipc:message', (<Buffer>message.buffer));
this.sender.send('vscode:message', message.buffer);
} catch (e) {
// systems are going down
}
}
dispose(): void {
this.sender.send('ipc:disconnect', null);
this.sender.send('vscode:disconnect', null);
}
}
}

View File

@@ -10,7 +10,9 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance
import * as errors from 'vs/base/common/errors';
import { VSBuffer } from 'vs/base/common/buffer';
import { getRandomElement } from 'vs/base/common/arrays';
import { isFunction } from 'vs/base/common/types';
import { isFunction, isUndefinedOrNull } from 'vs/base/common/types';
import { revive } from 'vs/base/common/marshalling';
import { isUpperAsciiLetter } from 'vs/base/common/strings';
/**
* An `IChannel` is an abstraction over a collection of commands.
@@ -919,3 +921,142 @@ export class StaticRouter<TContext = string> implements IClientRouter<TContext>
return await this.route(hub);
}
}
//#region createChannelReceiver / createChannelSender
/**
* Use both `createChannelReceiver` and `createChannelSender`
* for automated process <=> process communication over methods
* and events. You do not need to spell out each method on both
* sides, a proxy will take care of this.
*
* Rules:
* - if marshalling is enabled, only `URI` and `RegExp` is converted
* automatically for you
* - events must follow the naming convention `onUppercase`
* - `CancellationToken` is currently not supported
* - if a context is provided, you can use `AddFirstParameterToFunctions`
* utility to signal this in the receiving side type
*/
export interface IBaseChannelOptions {
/**
* Disables automatic marshalling of `URI`.
* If marshalling is disabled, `UriComponents`
* must be used instead.
*/
disableMarshalling?: boolean;
}
export interface IChannelReceiverOptions extends IBaseChannelOptions { }
export function createChannelReceiver(service: unknown, options?: IChannelReceiverOptions): IServerChannel {
const handler = service as { [key: string]: unknown };
const disableMarshalling = options && options.disableMarshalling;
// Buffer any event that should be supported by
// iterating over all property keys and finding them
const mapEventNameToEvent = new Map<string, Event<unknown>>();
for (const key in handler) {
if (propertyIsEvent(key)) {
mapEventNameToEvent.set(key, Event.buffer(handler[key] as Event<unknown>, true));
}
}
return new class implements IServerChannel {
listen<T>(_: unknown, event: string): Event<T> {
const eventImpl = mapEventNameToEvent.get(event);
if (eventImpl) {
return eventImpl as Event<T>;
}
throw new Error(`Event not found: ${event}`);
}
call(_: unknown, command: string, args?: any[]): Promise<any> {
const target = handler[command];
if (typeof target === 'function') {
// Revive unless marshalling disabled
if (!disableMarshalling && Array.isArray(args)) {
for (let i = 0; i < args.length; i++) {
args[i] = revive(args[i]);
}
}
return target.apply(handler, args);
}
throw new Error(`Method not found: ${command}`);
}
};
}
export interface IChannelSenderOptions extends IBaseChannelOptions {
/**
* If provided, will add the value of `context`
* to each method call to the target.
*/
context?: unknown;
/**
* If provided, will not proxy any of the properties
* that are part of the Map but rather return that value.
*/
properties?: Map<string, unknown>;
}
export function createChannelSender<T>(channel: IChannel, options?: IChannelSenderOptions): T {
const disableMarshalling = options && options.disableMarshalling;
return new Proxy({}, {
get(_target: T, propKey: PropertyKey) {
if (typeof propKey === 'string') {
// Check for predefined values
if (options?.properties?.has(propKey)) {
return options.properties.get(propKey);
}
// Event
if (propertyIsEvent(propKey)) {
return channel.listen(propKey);
}
// Function
return async function (...args: any[]) {
// Add context if any
let methodArgs: any[];
if (options && !isUndefinedOrNull(options.context)) {
methodArgs = [options.context, ...args];
} else {
methodArgs = args;
}
const result = await channel.call(propKey, methodArgs);
// Revive unless marshalling disabled
if (!disableMarshalling) {
return revive(result);
}
return result;
};
}
throw new Error(`Property not found: ${String(propKey)}`);
}
}) as T;
}
function propertyIsEvent(name: string): boolean {
// Assume a property is an event if it has a form of "onSomething"
return name[0] === 'o' && name[1] === 'n' && isUpperAsciiLetter(name.charCodeAt(2));
}
//#endregion

View File

@@ -5,7 +5,7 @@
import { Event, Emitter } from 'vs/base/common/event';
import { IPCServer, ClientConnectionEvent } from 'vs/base/parts/ipc/common/ipc';
import { Protocol } from 'vs/base/parts/ipc/node/ipc.electron';
import { Protocol } from 'vs/base/parts/ipc/common/ipc.electron';
import { ipcMain, WebContents } from 'electron';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { VSBuffer } from 'vs/base/common/buffer';
@@ -27,7 +27,7 @@ export class Server extends IPCServer {
private static readonly Clients = new Map<number, IDisposable>();
private static getOnDidClientConnect(): Event<ClientConnectionEvent> {
const onHello = Event.fromNodeEventEmitter<WebContents>(ipcMain, 'ipc:hello', ({ sender }) => sender);
const onHello = Event.fromNodeEventEmitter<WebContents>(ipcMain, 'vscode:hello', ({ sender }) => sender);
return Event.map(onHello, webContents => {
const id = webContents.id;
@@ -40,8 +40,8 @@ export class Server extends IPCServer {
const onDidClientReconnect = new Emitter<void>();
Server.Clients.set(id, toDisposable(() => onDidClientReconnect.fire()));
const onMessage = createScopedOnMessageEvent(id, 'ipc:message') as Event<VSBuffer>;
const onDidClientDisconnect = Event.any(Event.signal(createScopedOnMessageEvent(id, 'ipc:disconnect')), onDidClientReconnect.event);
const onMessage = createScopedOnMessageEvent(id, 'vscode:message') as Event<VSBuffer>;
const onDidClientDisconnect = Event.any(Event.signal(createScopedOnMessageEvent(id, 'vscode:disconnect')), onDidClientReconnect.event);
const protocol = new Protocol(webContents, onMessage);
return { protocol, onDidClientDisconnect };

View File

@@ -5,18 +5,18 @@
import { Event } from 'vs/base/common/event';
import { IPCClient } from 'vs/base/parts/ipc/common/ipc';
import { Protocol } from 'vs/base/parts/ipc/node/ipc.electron';
import { ipcRenderer } from 'electron';
import { Protocol } from 'vs/base/parts/ipc/common/ipc.electron';
import { IDisposable } from 'vs/base/common/lifecycle';
import { VSBuffer } from 'vs/base/common/buffer';
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
export class Client extends IPCClient implements IDisposable {
private protocol: Protocol;
private static createProtocol(): Protocol {
const onMessage = Event.fromNodeEventEmitter<VSBuffer>(ipcRenderer, 'ipc:message', (_, message: Buffer) => VSBuffer.wrap(message));
ipcRenderer.send('ipc:hello');
const onMessage = Event.fromNodeEventEmitter<VSBuffer>(ipcRenderer, 'vscode:message', (_, message) => VSBuffer.wrap(message));
ipcRenderer.send('vscode:hello');
return new Protocol(ipcRenderer, onMessage);
}
@@ -29,4 +29,4 @@ export class Client extends IPCClient implements IDisposable {
dispose(): void {
this.protocol.dispose();
}
}
}

View File

@@ -1,133 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc';
import { revive } from 'vs/base/common/marshalling';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { isUpperAsciiLetter } from 'vs/base/common/strings';
/**
* Use both `createChannelReceiver` and `createChannelSender`
* for automated process <=> process communication over methods
* and events. You do not need to spell out each method on both
* sides, a proxy will take care of this.
*
* Rules:
* - if marshalling is enabled, only `URI` and `RegExp` is converted
* automatically for you
* - events must follow the naming convention `onUppercase`
* - `CancellationToken` is currently not supported
* - if a context is provided, you can use `AddFirstParameterToFunctions`
* utility to signal this in the receiving side type
*/
export interface IBaseChannelOptions {
/**
* Disables automatic marshalling of `URI`.
* If marshalling is disabled, `UriComponents`
* must be used instead.
*/
disableMarshalling?: boolean;
}
export interface IChannelReceiverOptions extends IBaseChannelOptions { }
export function createChannelReceiver(service: unknown, options?: IChannelReceiverOptions): IServerChannel {
const handler = service as { [key: string]: unknown };
const disableMarshalling = options && options.disableMarshalling;
// Buffer any event that should be supported by
// iterating over all property keys and finding them
const mapEventNameToEvent = new Map<string, Event<unknown>>();
for (const key in handler) {
if (propertyIsEvent(key)) {
mapEventNameToEvent.set(key, Event.buffer(handler[key] as Event<unknown>, true));
}
}
return new class implements IServerChannel {
listen<T>(_: unknown, event: string): Event<T> {
const eventImpl = mapEventNameToEvent.get(event);
if (eventImpl) {
return eventImpl as Event<T>;
}
throw new Error(`Event not found: ${event}`);
}
call(_: unknown, command: string, args?: any[]): Promise<any> {
const target = handler[command];
if (typeof target === 'function') {
// Revive unless marshalling disabled
if (!disableMarshalling && Array.isArray(args)) {
for (let i = 0; i < args.length; i++) {
args[i] = revive(args[i]);
}
}
return target.apply(handler, args);
}
throw new Error(`Method not found: ${command}`);
}
};
}
export interface IChannelSenderOptions extends IBaseChannelOptions {
/**
* If provided, will add the value of `context`
* to each method call to the target.
*/
context?: unknown;
}
export function createChannelSender<T>(channel: IChannel, options?: IChannelSenderOptions): T {
const disableMarshalling = options && options.disableMarshalling;
return new Proxy({}, {
get(_target: T, propKey: PropertyKey) {
if (typeof propKey === 'string') {
// Event
if (propertyIsEvent(propKey)) {
return channel.listen(propKey);
}
// Function
return async function (...args: any[]) {
// Add context if any
let methodArgs: any[];
if (options && !isUndefinedOrNull(options.context)) {
methodArgs = [options.context, ...args];
} else {
methodArgs = args;
}
const result = await channel.call(propKey, methodArgs);
// Revive unless marshalling disabled
if (!disableMarshalling) {
return revive(result);
}
return result;
};
}
throw new Error(`Property not found: ${String(propKey)}`);
}
}) as T;
}
function propertyIsEvent(name: string): boolean {
// Assume a property is an event if it has a form of "onSomething"
return name[0] === 'o' && name[1] === 'n' && isUpperAsciiLetter(name.charCodeAt(2));
}

View File

@@ -4,8 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { IChannel, IServerChannel, IMessagePassingProtocol, IPCServer, ClientConnectionEvent, IPCClient } from 'vs/base/parts/ipc/common/ipc';
import { createChannelReceiver, createChannelSender } from 'vs/base/parts/ipc/node/ipc';
import { IChannel, IServerChannel, IMessagePassingProtocol, IPCServer, ClientConnectionEvent, IPCClient, createChannelReceiver, createChannelSender } from 'vs/base/parts/ipc/common/ipc';
import { Emitter, Event } from 'vs/base/common/event';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { canceled } from 'vs/base/common/errors';
@@ -103,7 +102,7 @@ interface ITestService {
error(message: string): Promise<void>;
neverComplete(): Promise<void>;
neverCompleteCT(cancellationToken: CancellationToken): Promise<void>;
buffersLength(buffers: Buffer[]): Promise<number>;
buffersLength(buffers: VSBuffer[]): Promise<number>;
marshall(uri: URI): Promise<URI>;
context(): Promise<unknown>;
@@ -135,8 +134,8 @@ class TestService implements ITestService {
return new Promise((_, e) => cancellationToken.onCancellationRequested(() => e(canceled())));
}
buffersLength(buffers: Buffer[]): Promise<number> {
return Promise.resolve(buffers.reduce((r, b) => r + b.length, 0));
buffersLength(buffers: VSBuffer[]): Promise<number> {
return Promise.resolve(buffers.reduce((r, b) => r + b.buffer.length, 0));
}
ping(msg: string): void {
@@ -199,7 +198,7 @@ class TestChannelClient implements ITestService {
return this.channel.call('neverCompleteCT', undefined, cancellationToken);
}
buffersLength(buffers: Buffer[]): Promise<number> {
buffersLength(buffers: VSBuffer[]): Promise<number> {
return this.channel.call('buffersLength', buffers);
}
@@ -317,7 +316,7 @@ suite('Base IPC', function () {
});
test('buffers in arrays', async function () {
const r = await ipcService.buffersLength([Buffer.allocUnsafe(2), Buffer.allocUnsafe(3)]);
const r = await ipcService.buffersLength([VSBuffer.alloc(2), VSBuffer.alloc(3)]);
return assert.equal(r, 5);
});
});
@@ -383,7 +382,7 @@ suite('Base IPC', function () {
});
test('buffers in arrays', async function () {
const r = await ipcService.buffersLength([Buffer.allocUnsafe(2), Buffer.allocUnsafe(3)]);
const r = await ipcService.buffersLength([VSBuffer.alloc(2), VSBuffer.alloc(3)]);
return assert.equal(r, 5);
});
});

View File

@@ -307,7 +307,7 @@ export interface IQuickInputButton {
iconClass?: string;
tooltip?: string;
/**
* Wether to always show the button. By default buttons
* Whether to always show the button. By default buttons
* are only visible when hovering over them with the mouse
*/
alwaysVisible?: boolean;

View File

@@ -0,0 +1,249 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// #######################################################################
// ### ###
// ### electron.d.ts types we need in a common layer for reuse ###
// ### (copied from Electron 7.x) ###
// ### ###
// #######################################################################
export interface MessageBoxOptions {
/**
* Can be `"none"`, `"info"`, `"error"`, `"question"` or `"warning"`. On Windows,
* `"question"` displays the same icon as `"info"`, unless you set an icon using
* the `"icon"` option. On macOS, both `"warning"` and `"error"` display the same
* warning icon.
*/
type?: string;
/**
* Array of texts for buttons. On Windows, an empty array will result in one button
* labeled "OK".
*/
buttons?: string[];
/**
* Index of the button in the buttons array which will be selected by default when
* the message box opens.
*/
defaultId?: number;
/**
* Title of the message box, some platforms will not show it.
*/
title?: string;
/**
* Content of the message box.
*/
message: string;
/**
* Extra information of the message.
*/
detail?: string;
/**
* If provided, the message box will include a checkbox with the given label.
*/
checkboxLabel?: string;
/**
* Initial checked state of the checkbox. `false` by default.
*/
checkboxChecked?: boolean;
// icon?: NativeImage;
/**
* The index of the button to be used to cancel the dialog, via the `Esc` key. By
* default this is assigned to the first button with "cancel" or "no" as the label.
* If no such labeled buttons exist and this option is not set, `0` will be used as
* the return value.
*/
cancelId?: number;
/**
* On Windows Electron will try to figure out which one of the `buttons` are common
* buttons (like "Cancel" or "Yes"), and show the others as command links in the
* dialog. This can make the dialog appear in the style of modern Windows apps. If
* you don't like this behavior, you can set `noLink` to `true`.
*/
noLink?: boolean;
/**
* Normalize the keyboard access keys across platforms. Default is `false`.
* Enabling this assumes `&` is used in the button labels for the placement of the
* keyboard shortcut access key and labels will be converted so they work correctly
* on each platform, `&` characters are removed on macOS, converted to `_` on
* Linux, and left untouched on Windows. For example, a button label of `Vie&w`
* will be converted to `Vie_w` on Linux and `View` on macOS and can be selected
* via `Alt-W` on Windows and Linux.
*/
normalizeAccessKeys?: boolean;
}
export interface MessageBoxReturnValue {
/**
* The index of the clicked button.
*/
response: number;
/**
* The checked state of the checkbox if `checkboxLabel` was set. Otherwise `false`.
*/
checkboxChecked: boolean;
}
export interface OpenDevToolsOptions {
/**
* Opens the devtools with specified dock state, can be `right`, `bottom`,
* `undocked`, `detach`. Defaults to last used dock state. In `undocked` mode it's
* possible to dock back. In `detach` mode it's not.
*/
mode: ('right' | 'bottom' | 'undocked' | 'detach');
/**
* Whether to bring the opened devtools window to the foreground. The default is
* `true`.
*/
activate?: boolean;
}
export interface SaveDialogOptions {
title?: string;
/**
* Absolute directory path, absolute file path, or file name to use by default.
*/
defaultPath?: string;
/**
* Custom label for the confirmation button, when left empty the default label will
* be used.
*/
buttonLabel?: string;
filters?: FileFilter[];
/**
* Message to display above text fields.
*
* @platform darwin
*/
message?: string;
/**
* Custom label for the text displayed in front of the filename text field.
*
* @platform darwin
*/
nameFieldLabel?: string;
/**
* Show the tags input box, defaults to `true`.
*
* @platform darwin
*/
showsTagField?: boolean;
/**
* Create a security scoped bookmark when packaged for the Mac App Store. If this
* option is enabled and the file doesn't already exist a blank file will be
* created at the chosen path.
*
* @platform darwin,mas
*/
securityScopedBookmarks?: boolean;
}
export interface OpenDialogOptions {
title?: string;
defaultPath?: string;
/**
* Custom label for the confirmation button, when left empty the default label will
* be used.
*/
buttonLabel?: string;
filters?: FileFilter[];
/**
* Contains which features the dialog should use. The following values are
* supported:
*/
properties?: Array<'openFile' | 'openDirectory' | 'multiSelections' | 'showHiddenFiles' | 'createDirectory' | 'promptToCreate' | 'noResolveAliases' | 'treatPackageAsDirectory'>;
/**
* Message to display above input boxes.
*
* @platform darwin
*/
message?: string;
/**
* Create security scoped bookmarks when packaged for the Mac App Store.
*
* @platform darwin,mas
*/
securityScopedBookmarks?: boolean;
}
export interface OpenDialogReturnValue {
/**
* whether or not the dialog was canceled.
*/
canceled: boolean;
/**
* An array of file paths chosen by the user. If the dialog is cancelled this will
* be an empty array.
*/
filePaths: string[];
/**
* An array matching the `filePaths` array of base64 encoded strings which contains
* security scoped bookmark data. `securityScopedBookmarks` must be enabled for
* this to be populated. (For return values, see table here.)
*
* @platform darwin,mas
*/
bookmarks?: string[];
}
export interface SaveDialogReturnValue {
/**
* whether or not the dialog was canceled.
*/
canceled: boolean;
/**
* If the dialog is canceled, this will be `undefined`.
*/
filePath?: string;
/**
* Base64 encoded string which contains the security scoped bookmark data for the
* saved file. `securityScopedBookmarks` must be enabled for this to be present.
* (For return values, see table here.)
*
* @platform darwin,mas
*/
bookmark?: string;
}
export interface CrashReporterStartOptions {
companyName: string;
/**
* URL that crash reports will be sent to as POST.
*/
submitURL: string;
/**
* Defaults to `app.name`.
*/
productName?: string;
/**
* Whether crash reports should be sent to the server. Default is `true`.
*/
uploadToServer?: boolean;
/**
* Default is `false`.
*/
ignoreSystemCrashHandler?: boolean;
/**
* An object you can define that will be sent along with the report. Only string
* properties are sent correctly. Nested objects are not supported. When using
* Windows, the property names and values must be fewer than 64 characters.
*/
extra?: Record<string, string>;
/**
* Directory to store the crash reports temporarily (only used when the crash
* reporter is started via `process.crashReporter.start`).
*/
crashesDirectory?: string;
}
export interface FileFilter {
// Docs: http://electronjs.org/docs/api/structures/file-filter
extensions: string[];
name: string;
}

View File

@@ -0,0 +1,115 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// @ts-check
(function () {
'use strict';
const { ipcRenderer, webFrame, crashReporter } = require('electron');
// @ts-ignore
window.vscode = {
/**
* A minimal set of methods exposed from ipcRenderer
* to support communication to electron-main
*
* @type {typeof import('../electron-sandbox/globals').ipcRenderer}
*/
ipcRenderer: {
/**
* @param {string} channel
* @param {any[]} args
*/
send(channel, ...args) {
validateIPC(channel);
ipcRenderer.send(channel, ...args);
},
/**
* @param {string} channel
* @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener
*/
on(channel, listener) {
validateIPC(channel);
ipcRenderer.on(channel, listener);
},
/**
* @param {string} channel
* @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener
*/
once(channel, listener) {
validateIPC(channel);
ipcRenderer.once(channel, listener);
},
/**
* @param {string} channel
* @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener
*/
removeListener(channel, listener) {
validateIPC(channel);
ipcRenderer.removeListener(channel, listener);
}
},
/**
* Support for methods of webFrame type.
*
* @type {typeof import('../electron-sandbox/globals').webFrame}
*/
webFrame: {
getZoomFactor() {
return webFrame.getZoomFactor();
},
getZoomLevel() {
return webFrame.getZoomLevel();
},
/**
* @param {number} level
*/
setZoomLevel(level) {
webFrame.setZoomLevel(level);
}
},
/**
* Support for methods of crashReporter type.
*
* @type {typeof import('../electron-sandbox/globals').crashReporter}
*/
crashReporter: {
/**
* @param {Electron.CrashReporterStartOptions} options
*/
start(options) {
crashReporter.start(options);
}
}
};
//#region Utilities
/**
* @param {string} channel
*/
function validateIPC(channel) {
if (!channel || !channel.startsWith('vscode:')) {
throw new Error(`Unsupported event IPC channel '${channel}'`);
}
}
//#endregion
}());

View File

@@ -0,0 +1,93 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CrashReporterStartOptions } from 'vs/base/parts/sandbox/common/electronTypes';
export const ipcRenderer = (window as any).vscode.ipcRenderer as {
/**
* Listens to `channel`, when a new message arrives `listener` would be called with
* `listener(event, args...)`.
*/
on(channel: string, listener: (event: unknown, ...args: any[]) => void): void;
/**
* Adds a one time `listener` function for the event. This `listener` is invoked
* only the next time a message is sent to `channel`, after which it is removed.
*/
once(channel: string, listener: (event: unknown, ...args: any[]) => void): void;
/**
* Removes the specified `listener` from the listener array for the specified
* `channel`.
*/
removeListener(channel: string, listener: (event: unknown, ...args: any[]) => void): void;
/**
* Send an asynchronous message to the main process via `channel`, along with
* arguments. Arguments will be serialized with the Structured Clone Algorithm,
* just like `postMessage`, so prototype chains will not be included. Sending
* Functions, Promises, Symbols, WeakMaps, or WeakSets will throw an exception.
*
* > **NOTE**: Sending non-standard JavaScript types such as DOM objects or special
* Electron objects is deprecated, and will begin throwing an exception starting
* with Electron 9.
*
* The main process handles it by listening for `channel` with the `ipcMain`
* module.
*/
send(channel: string, ...args: any[]): void;
};
export const webFrame = (window as any).vscode.webFrame as {
/**
* The current zoom factor.
*/
getZoomFactor(): number;
/**
* The current zoom level.
*/
getZoomLevel(): number;
/**
* Changes the zoom level to the specified level. The original size is 0 and each
* increment above or below represents zooming 20% larger or smaller to default
* limits of 300% and 50% of original size, respectively.
*/
setZoomLevel(level: number): void;
};
export const crashReporter = (window as any).vscode.crashReporter as {
/**
* You are required to call this method before using any other `crashReporter` APIs
* and in each process (main/renderer) from which you want to collect crash
* reports. You can pass different options to `crashReporter.start` when calling
* from different processes.
*
* **Note** Child processes created via the `child_process` module will not have
* access to the Electron modules. Therefore, to collect crash reports from them,
* use `process.crashReporter.start` instead. Pass the same options as above along
* with an additional one called `crashesDirectory` that should point to a
* directory to store the crash reports temporarily. You can test this out by
* calling `process.crash()` to crash the child process.
*
* **Note:** If you need send additional/updated `extra` parameters after your
* first call `start` you can call `addExtraParameter` on macOS or call `start`
* again with the new/updated `extra` parameters on Linux and Windows.
*
* **Note:** On macOS and windows, Electron uses a new `crashpad` client for crash
* collection and reporting. If you want to enable crash reporting, initializing
* `crashpad` from the main process using `crashReporter.start` is required
* regardless of which process you want to collect crashes from. Once initialized
* this way, the crashpad handler collects crashes from all processes. You still
* have to call `crashReporter.start` from the renderer or child process, otherwise
* crashes from them will get reported without `companyName`, `productName` or any
* of the `extra` information.
*/
start(options: CrashReporterStartOptions): void;
};

View File

@@ -0,0 +1,15 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { ipcRenderer, crashReporter, webFrame } from 'vs/base/parts/sandbox/electron-sandbox/globals';
suite('Sandbox', () => {
test('globals', () => {
assert.ok(ipcRenderer);
assert.ok(crashReporter);
assert.ok(webFrame);
});
});

View File

@@ -3,48 +3,294 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { compareFileNames, compareFileExtensions } from 'vs/base/common/comparers';
import { compareFileNames, compareFileExtensions, compareFileNamesNumeric, compareFileExtensionsNumeric } from 'vs/base/common/comparers';
import * as assert from 'assert';
const compareLocale = (a: string, b: string) => a.localeCompare(b);
const compareLocaleNumeric = (a: string, b: string) => a.localeCompare(b, undefined, { numeric: true });
suite('Comparers', () => {
test('compareFileNames', () => {
//
// Comparisons with the same results as compareFileNamesNumeric
//
// name-only comparisons
assert(compareFileNames(null, null) === 0, 'null should be equal');
assert(compareFileNames(null, 'abc') < 0, 'null should be come before real values');
assert(compareFileNames('', '') === 0, 'empty should be equal');
assert(compareFileNames('abc', 'abc') === 0, 'equal names should be equal');
assert(compareFileNames('.abc', '.abc') === 0, 'equal full names should be equal');
assert(compareFileNames('.env', '.env.example') < 0, 'filenames with extensions should come after those without');
assert(compareFileNames('.env.example', '.gitattributes') < 0, 'filenames starting with dots and with extensions should still sort properly');
assert(compareFileNames('z', 'A') > 0, 'z comes is after A regardless of case');
assert(compareFileNames('Z', 'a') > 0, 'Z comes after a regardless of case');
// name plus extension comparisons
assert(compareFileNames('bbb.aaa', 'aaa.bbb') > 0, 'files with extensions are compared first by filename');
// dotfile comparisons
assert(compareFileNames('.abc', '.abc') === 0, 'equal dotfile names should be equal');
assert(compareFileNames('.env.', '.gitattributes') < 0, 'filenames starting with dots and with extensions should still sort properly');
assert(compareFileNames('.env', '.aaa.env') > 0, 'dotfiles sort alphabetically when they contain multiple dots');
assert(compareFileNames('.env', '.env.aaa') < 0, 'dotfiles with the same root sort shortest first');
assert(compareFileNames('.aaa_env', '.aaa.env') < 0, 'and underscore in a dotfile name will sort before a dot');
// dotfile vs non-dotfile comparisons
assert(compareFileNames(null, '.abc') < 0, 'null should come before dotfiles');
assert(compareFileNames('.env', 'aaa') < 0, 'dotfiles come before filenames without extensions');
assert(compareFileNames('.env', 'aaa.env') < 0, 'dotfiles come before filenames with extensions');
assert(compareFileNames('.md', 'A.MD') < 0, 'dotfiles sort before uppercase files');
assert(compareFileNames('.MD', 'a.md') < 0, 'dotfiles sort before lowercase files');
// numeric comparisons
assert(compareFileNames('1', '1') === 0, 'numerically equal full names should be equal');
assert(compareFileNames('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal');
assert(compareFileNames('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order');
assert(compareFileNames('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order even when they are multiple digits long');
assert(compareFileNames('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically');
assert(compareFileNames('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number');
//
// Comparisons with different results than compareFileNamesNumeric
//
// name-only comparisons
assert(compareFileNames('a', 'A') !== compareLocale('a', 'A'), 'the same letter does not sort by locale');
assert(compareFileNames('â', 'Â') !== compareLocale('â', 'Â'), 'the same accented letter does not sort by locale');
assert.notDeepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileNames), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases do not sort in locale order');
assert.notDeepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileNames), ['email', 'Email', 'émail', 'Émail'].sort(compareLocale), 'the same base characters with different case or accents do not sort in locale order');
// name plus extension comparisons
assert(compareFileNames('aggregate.go', 'aggregate_repo.go') > 0, 'compares the whole name all at once by locale');
// numeric comparisons
assert(compareFileNames('abc02.txt', 'abc002.txt') > 0, 'filenames with equivalent numbers and leading zeros sort in unicode order');
assert(compareFileNames('abc.txt1', 'abc.txt01') > 0, 'same name plus extensions with equal numbers sort in unicode order');
assert(compareFileNames('art01', 'Art01') !== 'art01'.localeCompare('Art01', undefined, { numeric: true }),
'a numerically equivalent word of a different case does not compare numerically based on locale');
});
test('compareFileExtensions', () => {
//
// Comparisons with the same results as compareFileExtensionsNumeric
//
// name-only comparisons
assert(compareFileExtensions(null, null) === 0, 'null should be equal');
assert(compareFileExtensions(null, '.abc') < 0, 'null should come before real files');
assert(compareFileExtensions(null, 'abc') < 0, 'null should come before real files without extension');
assert(compareFileExtensions('', '') === 0, 'empty should be equal');
assert(compareFileExtensions('abc', 'abc') === 0, 'equal names should be equal');
assert(compareFileExtensions('.abc', '.abc') === 0, 'equal full names should be equal');
assert(compareFileExtensions('z', 'A') > 0, 'z comes after A');
assert(compareFileExtensions('Z', 'a') > 0, 'Z comes after a');
// name plus extension comparisons
assert(compareFileExtensions('file.ext', 'file.ext') === 0, 'equal full names should be equal');
assert(compareFileExtensions('a.ext', 'b.ext') < 0, 'if equal extensions, filenames should be compared');
assert(compareFileExtensions('.ext', 'a.ext') < 0, 'if equal extensions, filenames should be compared, empty filename should come before others');
assert(compareFileExtensions('file.aaa', 'file.bbb') < 0, 'files should be compared by extensions');
assert(compareFileExtensions('file.aaa', 'file.bbb') < 0, 'files with equal names should be compared by extensions');
assert(compareFileExtensions('bbb.aaa', 'aaa.bbb') < 0, 'files should be compared by extensions even if filenames compare differently');
assert(compareFileExtensions('agg.go', 'aggrepo.go') < 0, 'shorter names sort before longer names');
assert(compareFileExtensions('agg.go', 'agg_repo.go') < 0, 'shorter names short before longer names even when the longer name contains an underscore');
assert(compareFileExtensions('a.MD', 'b.md') < 0, 'when extensions are the same except for case, the files sort by name');
// dotfile comparisons
assert(compareFileExtensions('.abc', '.abc') === 0, 'equal dotfiles should be equal');
assert(compareFileExtensions('.md', '.Gitattributes') > 0, 'dotfiles sort alphabetically regardless of case');
// dotfile vs non-dotfile comparisons
assert(compareFileExtensions(null, '.abc') < 0, 'null should come before dotfiles');
assert(compareFileExtensions('.env', 'aaa.env') < 0, 'if equal extensions, filenames should be compared, empty filename should come before others');
assert(compareFileExtensions('.MD', 'a.md') < 0, 'if extensions differ in case, files sort by extension in unicode order');
// numeric comparisons
assert(compareFileExtensions('1', '1') === 0, 'numerically equal full names should be equal');
assert(compareFileExtensions('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal');
assert(compareFileExtensions('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order');
assert(compareFileExtensions('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order even when they are multiple digits long');
assert(compareFileExtensions('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically');
assert(compareFileExtensions('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number');
assert(compareFileExtensions('abc2.txt2', 'abc1.txt10') < 0, 'extensions with numbers should be in numerical order, not alphabetical order');
assert(compareFileExtensions('txt.abc1', 'txt.abc1') === 0, 'equal extensions with numbers should be equal');
assert(compareFileExtensions('txt.abc1', 'txt.abc2') < 0, 'extensions with numbers should be in numerical order, not alphabetical order');
assert(compareFileExtensions('txt.abc2', 'txt.abc10') < 0, 'extensions with numbers should be in numerical order even when they are multiple digits long');
assert(compareFileExtensions('a.ext1', 'b.ext1') < 0, 'if equal extensions with numbers, filenames should be compared');
assert(compareFileExtensions('file2.ext2', 'file1.ext10') < 0, 'extensions with numbers should be in numerical order, not alphabetical order');
assert(compareFileExtensions('file.ext01', 'file.ext1') < 0, 'extensions with equal numbers should be in alphabetical order');
assert(compareFileExtensions('a10.txt', 'A2.txt') > 0, 'filenames with number and case differences compare numerically');
// Same extension comparison that has the same result as compareFileExtensionsNumeric, but a different result than compareFileNames
// This is an edge case caused by compareFileNames comparing the whole name all at once instead of the name and then the extension.
assert(compareFileExtensions('aggregate.go', 'aggregate_repo.go') < 0, 'when extensions are equal, names sort in dictionary order');
//
// Comparisons with different results from compareFileExtensionsNumeric
//
// name-only comparisions
assert(compareFileExtensions('a', 'A') !== compareLocale('a', 'A'), 'the same letter of different case does not sort by locale');
assert(compareFileExtensions('â', 'Â') !== compareLocale('â', 'Â'), 'the same accented letter of different case does not sort by locale');
assert.notDeepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileExtensions), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases do not sort in locale order');
assert.notDeepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileExtensions), ['email', 'Email', 'émail', 'Émail'].sort((a, b) => a.localeCompare(b)), 'the same base characters with different case or accents do not sort in locale order');
// name plus extension comparisons
assert(compareFileExtensions('a.MD', 'a.md') !== compareLocale('MD', 'md'), 'case differences in extensions do not sort by locale');
assert(compareFileExtensions('a.md', 'A.md') !== compareLocale('a', 'A'), 'case differences in names do not sort by locale');
// dotfile comparisons
assert(compareFileExtensions('.env', '.aaa.env') < 0, 'a dotfile with an extension is treated as a name plus an extension - equal extensions');
assert(compareFileExtensions('.env', '.env.aaa') > 0, 'a dotfile with an extension is treated as a name plus an extension - unequal extensions');
// dotfile vs non-dotfile comparisons
assert(compareFileExtensions('.env', 'aaa') > 0, 'filenames without extensions come before dotfiles');
assert(compareFileExtensions('.md', 'A.MD') > 0, 'a file with an uppercase extension sorts before a dotfile of the same lowercase extension');
// numeric comparisons
assert(compareFileExtensions('abc.txt01', 'abc.txt1') < 0, 'extensions with equal numbers sort in unicode order');
assert(compareFileExtensions('art01', 'Art01') !== compareLocaleNumeric('art01', 'Art01'), 'a numerically equivalent word of a different case does not compare by locale');
assert(compareFileExtensions('abc02.txt', 'abc002.txt') > 0, 'filenames with equivalent numbers and leading zeros sort in unicode order');
assert(compareFileExtensions('txt.abc01', 'txt.abc1') < 0, 'extensions with equivalent numbers sort in unicode order');
});
test('compareFileNamesNumeric', () => {
//
// Comparisons with the same results as compareFileNames
//
// name-only comparisons
assert(compareFileNamesNumeric(null, null) === 0, 'null should be equal');
assert(compareFileNamesNumeric(null, 'abc') < 0, 'null should be come before real values');
assert(compareFileNamesNumeric('', '') === 0, 'empty should be equal');
assert(compareFileNamesNumeric('abc', 'abc') === 0, 'equal names should be equal');
assert(compareFileNamesNumeric('z', 'A') > 0, 'z comes is after A regardless of case');
assert(compareFileNamesNumeric('Z', 'a') > 0, 'Z comes after a regardless of case');
// name plus extension comparisons
assert(compareFileNamesNumeric('file.ext', 'file.ext') === 0, 'equal full names should be equal');
assert(compareFileNamesNumeric('a.ext', 'b.ext') < 0, 'if equal extensions, filenames should be compared');
assert(compareFileNamesNumeric('file.aaa', 'file.bbb') < 0, 'files with equal names should be compared by extensions');
assert(compareFileNamesNumeric('bbb.aaa', 'aaa.bbb') > 0, 'files should be compared by names even if extensions compare differently');
// dotfile comparisons
assert(compareFileNamesNumeric('.abc', '.abc') === 0, 'equal dotfile names should be equal');
assert(compareFileNamesNumeric('.env.', '.gitattributes') < 0, 'filenames starting with dots and with extensions should still sort properly');
assert(compareFileNamesNumeric('.env', '.aaa.env') > 0, 'dotfiles sort alphabetically when they contain multiple dots');
assert(compareFileNamesNumeric('.env', '.env.aaa') < 0, 'dotfiles with the same root sort shortest first');
assert(compareFileNamesNumeric('.aaa_env', '.aaa.env') < 0, 'and underscore in a dotfile name will sort before a dot');
// dotfile vs non-dotfile comparisons
assert(compareFileNamesNumeric(null, '.abc') < 0, 'null should come before dotfiles');
assert(compareFileNamesNumeric('.env', 'aaa') < 0, 'dotfiles come before filenames without extensions');
assert(compareFileNamesNumeric('.env', 'aaa.env') < 0, 'dotfiles come before filenames with extensions');
assert(compareFileNamesNumeric('.md', 'A.MD') < 0, 'dotfiles sort before uppercase files');
assert(compareFileNamesNumeric('.MD', 'a.md') < 0, 'dotfiles sort before lowercase files');
// numeric comparisons
assert(compareFileNamesNumeric('1', '1') === 0, 'numerically equal full names should be equal');
assert(compareFileNamesNumeric('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal');
assert(compareFileNamesNumeric('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order');
assert(compareFileNamesNumeric('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order even when they are multiple digits long');
assert(compareFileNamesNumeric('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically');
assert(compareFileNamesNumeric('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number');
//
// Comparisons with different results than compareFileNames
//
// name-only comparisons
assert(compareFileNamesNumeric('a', 'A') === compareLocale('a', 'A'), 'the same letter sorts by locale');
assert(compareFileNamesNumeric('â', 'Â') === compareLocale('â', 'Â'), 'the same accented letter sorts by locale');
assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileNamesNumeric), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order');
assert.deepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileNamesNumeric), ['email', 'Email', 'émail', 'Émail'].sort(compareLocale), 'the same base characters with different case or accents sort in locale order');
// name plus extensions comparisons
assert(compareFileNamesNumeric('aggregate.go', 'aggregate_repo.go') < 0, 'compares the name first, then the extension');
// numeric comparisons
assert(compareFileNamesNumeric('abc02.txt', 'abc002.txt') < 0, 'filenames with equivalent numbers and leading zeros sort shortest number first');
assert(compareFileNamesNumeric('abc.txt1', 'abc.txt01') < 0, 'same name plus extensions with equal numbers sort shortest number first');
assert(compareFileNamesNumeric('art01', 'Art01') === compareLocaleNumeric('art01', 'Art01'), 'a numerically equivalent word of a different case compares numerically based on locale');
});
test('compareFileExtensionsNumeric', () => {
//
// Comparisons with the same result as compareFileExtensions
//
// name-only comparisons
assert(compareFileExtensionsNumeric(null, null) === 0, 'null should be equal');
assert(compareFileExtensionsNumeric(null, 'abc') < 0, 'null should come before real files without extensions');
assert(compareFileExtensionsNumeric('', '') === 0, 'empty should be equal');
assert(compareFileExtensionsNumeric('abc', 'abc') === 0, 'equal names should be equal');
assert(compareFileExtensionsNumeric('z', 'A') > 0, 'z comes after A');
assert(compareFileExtensionsNumeric('Z', 'a') > 0, 'Z comes after a');
// name plus extension comparisons
assert(compareFileExtensionsNumeric('file.ext', 'file.ext') === 0, 'equal full filenames should be equal');
assert(compareFileExtensionsNumeric('a.ext', 'b.ext') < 0, 'if equal extensions, filenames should be compared');
assert(compareFileExtensionsNumeric('file.aaa', 'file.bbb') < 0, 'files with equal names should be compared by extensions');
assert(compareFileExtensionsNumeric('bbb.aaa', 'aaa.bbb') < 0, 'files should be compared by extension first');
assert(compareFileExtensionsNumeric('agg.go', 'aggrepo.go') < 0, 'shorter names sort before longer names');
assert(compareFileExtensionsNumeric('agg.go', 'agg_repo.go') < 0, 'shorter names short before longer names even when the longer name contains an underscore');
assert(compareFileExtensionsNumeric('a.MD', 'b.md') < 0, 'when extensions are the same except for case, the files sort by name');
// dotfile comparisons
assert(compareFileExtensionsNumeric('.abc', '.abc') === 0, 'equal dotfiles should be equal');
assert(compareFileExtensionsNumeric('.md', '.Gitattributes') > 0, 'dotfiles sort alphabetically regardless of case');
// dotfile vs non-dotfile comparisons
assert(compareFileExtensionsNumeric(null, '.abc') < 0, 'null should come before dotfiles');
assert(compareFileExtensionsNumeric('.env', 'aaa.env') < 0, 'dotfiles come before filenames with extensions');
assert(compareFileExtensionsNumeric('.MD', 'a.md') < 0, 'dotfiles sort before lowercase files');
// numeric comparisons
assert(compareFileExtensionsNumeric('1', '1') === 0, 'numerically equal full names should be equal');
assert(compareFileExtensionsNumeric('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal');
assert(compareFileExtensionsNumeric('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order');
assert(compareFileExtensionsNumeric('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order');
assert(compareFileExtensionsNumeric('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically');
assert(compareFileExtensionsNumeric('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number');
assert(compareFileExtensionsNumeric('abc2.txt2', 'abc1.txt10') < 0, 'extensions with numbers should be in numerical order, not alphabetical order');
assert(compareFileExtensionsNumeric('txt.abc1', 'txt.abc1') === 0, 'equal extensions with numbers should be equal');
assert(compareFileExtensionsNumeric('txt.abc1', 'txt.abc2') < 0, 'extensions with numbers should be in numerical order, not alphabetical order');
assert(compareFileExtensionsNumeric('txt.abc2', 'txt.abc10') < 0, 'extensions with numbers should be in numerical order even when they are multiple digits long');
assert(compareFileExtensionsNumeric('a.ext1', 'b.ext1') < 0, 'if equal extensions with numbers, filenames should be compared');
assert(compareFileExtensionsNumeric('a10.txt', 'A2.txt') > 0, 'filenames with number and case differences compare numerically');
// Same extension comparison that has the same result as compareFileExtensions, but a different result than compareFileNames
// This is an edge case caused by compareFileNames comparing the whole name all at once instead of the name and then the extension.
assert(compareFileExtensionsNumeric('aggregate.go', 'aggregate_repo.go') < 0, 'when extensions are equal, names sort in dictionary order');
//
// Comparisons with different results than compareFileExtensions
//
// name-only comparisons
assert(compareFileExtensionsNumeric('a', 'A') === compareLocale('a', 'A'), 'the same letter of different case sorts by locale');
assert(compareFileExtensionsNumeric('â', 'Â') === compareLocale('â', 'Â'), 'the same accented letter of different case sorts by locale');
assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileExtensionsNumeric), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order');
assert.deepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileExtensionsNumeric), ['email', 'Email', 'émail', 'Émail'].sort((a, b) => a.localeCompare(b)), 'the same base characters with different case or accents sort in locale order');
// name plus extension comparisons
assert(compareFileExtensionsNumeric('a.MD', 'a.md') === compareLocale('MD', 'md'), 'case differences in extensions sort by locale');
assert(compareFileExtensionsNumeric('a.md', 'A.md') === compareLocale('a', 'A'), 'case differences in names sort by locale');
// dotfile comparisons
assert(compareFileExtensionsNumeric('.env', '.aaa.env') > 0, 'dotfiles sort alphabetically when they contain multiple dots');
assert(compareFileExtensionsNumeric('.env', '.env.aaa') < 0, 'dotfiles with the same root sort shortest first');
// dotfile vs non-dotfile comparisons
assert(compareFileExtensionsNumeric('.env', 'aaa') < 0, 'dotfiles come before filenames without extensions');
assert(compareFileExtensionsNumeric('.md', 'A.MD') < 0, 'dotfiles sort before uppercase files');
// numeric comparisons
assert(compareFileExtensionsNumeric('abc.txt01', 'abc.txt1') > 0, 'extensions with equal numbers should be in shortest-first order');
assert(compareFileExtensionsNumeric('art01', 'Art01') === compareLocaleNumeric('art01', 'Art01'), 'a numerically equivalent word of a different case compares numerically based on locale');
assert(compareFileExtensionsNumeric('abc02.txt', 'abc002.txt') < 0, 'filenames with equivalent numbers and leading zeros sort shortest string first');
assert(compareFileExtensionsNumeric('txt.abc01', 'txt.abc1') > 0, 'extensions with equivalent numbers sort shortest extension first');
});
});

View File

@@ -130,4 +130,41 @@ suite('Paths', () => {
assert.equal(extpath.indexOfPath('/some/long/path', '/some/long', false), 0);
assert.equal(extpath.indexOfPath('/some/long/path', '/PATH', true), 10);
});
test('parseLineAndColumnAware', () => {
let res = extpath.parseLineAndColumnAware('/foo/bar');
assert.equal(res.path, '/foo/bar');
assert.equal(res.line, undefined);
assert.equal(res.column, undefined);
res = extpath.parseLineAndColumnAware('/foo/bar:33');
assert.equal(res.path, '/foo/bar');
assert.equal(res.line, 33);
assert.equal(res.column, 1);
res = extpath.parseLineAndColumnAware('/foo/bar:33:34');
assert.equal(res.path, '/foo/bar');
assert.equal(res.line, 33);
assert.equal(res.column, 34);
res = extpath.parseLineAndColumnAware('C:\\foo\\bar');
assert.equal(res.path, 'C:\\foo\\bar');
assert.equal(res.line, undefined);
assert.equal(res.column, undefined);
res = extpath.parseLineAndColumnAware('C:\\foo\\bar:33');
assert.equal(res.path, 'C:\\foo\\bar');
assert.equal(res.line, 33);
assert.equal(res.column, 1);
res = extpath.parseLineAndColumnAware('C:\\foo\\bar:33:34');
assert.equal(res.path, 'C:\\foo\\bar');
assert.equal(res.line, 33);
assert.equal(res.column, 34);
res = extpath.parseLineAndColumnAware('/foo/bar:abb');
assert.equal(res.path, '/foo/bar:abb');
assert.equal(res.line, undefined);
assert.equal(res.column, undefined);
});
});

View File

@@ -13,8 +13,8 @@ suite('Map', () => {
let map = new LinkedMap<string, string>();
map.set('ak', 'av');
map.set('bk', 'bv');
assert.deepStrictEqual(map.keys(), ['ak', 'bk']);
assert.deepStrictEqual(map.values(), ['av', 'bv']);
assert.deepStrictEqual([...map.keys()], ['ak', 'bk']);
assert.deepStrictEqual([...map.values()], ['av', 'bv']);
assert.equal(map.first, 'av');
assert.equal(map.last, 'bv');
});
@@ -23,16 +23,16 @@ suite('Map', () => {
let map = new LinkedMap<string, string>();
map.set('ak', 'av');
map.set('ak', 'av', Touch.AsOld);
assert.deepStrictEqual(map.keys(), ['ak']);
assert.deepStrictEqual(map.values(), ['av']);
assert.deepStrictEqual([...map.keys()], ['ak']);
assert.deepStrictEqual([...map.values()], ['av']);
});
test('LinkedMap - Touch New one', () => {
let map = new LinkedMap<string, string>();
map.set('ak', 'av');
map.set('ak', 'av', Touch.AsNew);
assert.deepStrictEqual(map.keys(), ['ak']);
assert.deepStrictEqual(map.values(), ['av']);
assert.deepStrictEqual([...map.keys()], ['ak']);
assert.deepStrictEqual([...map.values()], ['av']);
});
test('LinkedMap - Touch Old two', () => {
@@ -40,8 +40,8 @@ suite('Map', () => {
map.set('ak', 'av');
map.set('bk', 'bv');
map.set('bk', 'bv', Touch.AsOld);
assert.deepStrictEqual(map.keys(), ['bk', 'ak']);
assert.deepStrictEqual(map.values(), ['bv', 'av']);
assert.deepStrictEqual([...map.keys()], ['bk', 'ak']);
assert.deepStrictEqual([...map.values()], ['bv', 'av']);
});
test('LinkedMap - Touch New two', () => {
@@ -49,8 +49,8 @@ suite('Map', () => {
map.set('ak', 'av');
map.set('bk', 'bv');
map.set('ak', 'av', Touch.AsNew);
assert.deepStrictEqual(map.keys(), ['bk', 'ak']);
assert.deepStrictEqual(map.values(), ['bv', 'av']);
assert.deepStrictEqual([...map.keys()], ['bk', 'ak']);
assert.deepStrictEqual([...map.values()], ['bv', 'av']);
});
test('LinkedMap - Touch Old from middle', () => {
@@ -59,8 +59,8 @@ suite('Map', () => {
map.set('bk', 'bv');
map.set('ck', 'cv');
map.set('bk', 'bv', Touch.AsOld);
assert.deepStrictEqual(map.keys(), ['bk', 'ak', 'ck']);
assert.deepStrictEqual(map.values(), ['bv', 'av', 'cv']);
assert.deepStrictEqual([...map.keys()], ['bk', 'ak', 'ck']);
assert.deepStrictEqual([...map.values()], ['bv', 'av', 'cv']);
});
test('LinkedMap - Touch New from middle', () => {
@@ -69,8 +69,8 @@ suite('Map', () => {
map.set('bk', 'bv');
map.set('ck', 'cv');
map.set('bk', 'bv', Touch.AsNew);
assert.deepStrictEqual(map.keys(), ['ak', 'ck', 'bk']);
assert.deepStrictEqual(map.values(), ['av', 'cv', 'bv']);
assert.deepStrictEqual([...map.keys()], ['ak', 'ck', 'bk']);
assert.deepStrictEqual([...map.values()], ['av', 'cv', 'bv']);
});
test('LinkedMap - basics', function () {
@@ -129,6 +129,61 @@ suite('Map', () => {
assert.ok(!map.has('1'));
});
test('LinkedMap - Iterators', () => {
const map = new LinkedMap<number, any>();
map.set(1, 1);
map.set(2, 2);
map.set(3, 3);
for (const elem of map.keys()) {
assert.ok(elem);
}
for (const elem of map.values()) {
assert.ok(elem);
}
for (const elem of map.entries()) {
assert.ok(elem);
}
{
const keys = map.keys();
const values = map.values();
const entries = map.entries();
map.get(1);
keys.next();
values.next();
entries.next();
}
{
const keys = map.keys();
const values = map.values();
const entries = map.entries();
map.get(1, Touch.AsNew);
let exceptions: number = 0;
try {
keys.next();
} catch (err) {
exceptions++;
}
try {
values.next();
} catch (err) {
exceptions++;
}
try {
entries.next();
} catch (err) {
exceptions++;
}
assert.strictEqual(exceptions, 3);
}
});
test('LinkedMap - LRU Cache simple', () => {
const cache = new LRUCache<number, number>(5);
@@ -136,10 +191,10 @@ suite('Map', () => {
assert.strictEqual(cache.size, 5);
cache.set(6, 6);
assert.strictEqual(cache.size, 5);
assert.deepStrictEqual(cache.keys(), [2, 3, 4, 5, 6]);
assert.deepStrictEqual([...cache.keys()], [2, 3, 4, 5, 6]);
cache.set(7, 7);
assert.strictEqual(cache.size, 5);
assert.deepStrictEqual(cache.keys(), [3, 4, 5, 6, 7]);
assert.deepStrictEqual([...cache.keys()], [3, 4, 5, 6, 7]);
let values: number[] = [];
[3, 4, 5, 6, 7].forEach(key => values.push(cache.get(key)!));
assert.deepStrictEqual(values, [3, 4, 5, 6, 7]);
@@ -150,11 +205,11 @@ suite('Map', () => {
[1, 2, 3, 4, 5].forEach(value => cache.set(value, value));
assert.strictEqual(cache.size, 5);
assert.deepStrictEqual(cache.keys(), [1, 2, 3, 4, 5]);
assert.deepStrictEqual([...cache.keys()], [1, 2, 3, 4, 5]);
cache.get(3);
assert.deepStrictEqual(cache.keys(), [1, 2, 4, 5, 3]);
assert.deepStrictEqual([...cache.keys()], [1, 2, 4, 5, 3]);
cache.peek(4);
assert.deepStrictEqual(cache.keys(), [1, 2, 4, 5, 3]);
assert.deepStrictEqual([...cache.keys()], [1, 2, 4, 5, 3]);
let values: number[] = [];
[1, 2, 3, 4, 5].forEach(key => values.push(cache.get(key)!));
assert.deepStrictEqual(values, [1, 2, 3, 4, 5]);
@@ -169,7 +224,7 @@ suite('Map', () => {
assert.strictEqual(cache.size, 10);
cache.limit = 5;
assert.strictEqual(cache.size, 5);
assert.deepStrictEqual(cache.keys(), [6, 7, 8, 9, 10]);
assert.deepStrictEqual([...cache.keys()], [6, 7, 8, 9, 10]);
cache.limit = 20;
assert.strictEqual(cache.size, 5);
for (let i = 11; i <= 20; i++) {
@@ -181,7 +236,7 @@ suite('Map', () => {
values.push(cache.get(i)!);
assert.strictEqual(cache.get(i), i);
}
assert.deepStrictEqual(cache.values(), values);
assert.deepStrictEqual([...cache.values()], values);
});
test('LinkedMap - LRU Cache limit with ratio', () => {
@@ -193,11 +248,11 @@ suite('Map', () => {
assert.strictEqual(cache.size, 10);
cache.set(11, 11);
assert.strictEqual(cache.size, 5);
assert.deepStrictEqual(cache.keys(), [7, 8, 9, 10, 11]);
assert.deepStrictEqual([...cache.keys()], [7, 8, 9, 10, 11]);
let values: number[] = [];
cache.keys().forEach(key => values.push(cache.get(key)!));
[...cache.keys()].forEach(key => values.push(cache.get(key)!));
assert.deepStrictEqual(values, [7, 8, 9, 10, 11]);
assert.deepStrictEqual(cache.values(), values);
assert.deepStrictEqual([...cache.values()], values);
});
test('LinkedMap - toJSON / fromJSON', () => {
@@ -222,7 +277,6 @@ suite('Map', () => {
assert.equal(key, 'ck');
assert.equal(value, 'cv');
}
i++;
});
});
@@ -237,7 +291,7 @@ suite('Map', () => {
map.delete('1');
assert.equal(map.get('1'), undefined);
assert.equal(map.size, 0);
assert.equal(map.keys().length, 0);
assert.equal([...map.keys()].length, 0);
});
test('LinkedMap - delete Head', function () {
@@ -251,8 +305,8 @@ suite('Map', () => {
map.delete('1');
assert.equal(map.get('2'), 2);
assert.equal(map.size, 1);
assert.equal(map.keys().length, 1);
assert.equal(map.keys()[0], 2);
assert.equal([...map.keys()].length, 1);
assert.equal([...map.keys()][0], 2);
});
test('LinkedMap - delete Tail', function () {
@@ -266,8 +320,8 @@ suite('Map', () => {
map.delete('2');
assert.equal(map.get('1'), 1);
assert.equal(map.size, 1);
assert.equal(map.keys().length, 1);
assert.equal(map.keys()[0], 1);
assert.equal([...map.keys()].length, 1);
assert.equal([...map.keys()][0], 1);
});
@@ -317,7 +371,8 @@ suite('Map', () => {
iter.reset(URI.parse('file:///usr/bin/file.txt'));
assert.equal(iter.value(), 'file');
assert.equal(iter.cmp('FILE'), 0);
// assert.equal(iter.cmp('FILE'), 0);
assert.equal(iter.cmp('file'), 0);
assert.equal(iter.hasNext(), true);
iter.next();
@@ -337,7 +392,8 @@ suite('Map', () => {
// scheme
assert.equal(iter.value(), 'file');
assert.equal(iter.cmp('FILE'), 0);
// assert.equal(iter.cmp('FILE'), 0);
assert.equal(iter.cmp('file'), 0);
assert.equal(iter.hasNext(), true);
iter.next();

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { dirname, basename, distinctParents, joinPath, isEqual, isEqualOrParent, hasToIgnoreCase, normalizePath, isAbsolutePath, relativePath, removeTrailingPathSeparator, hasTrailingPathSeparator, resolvePath, addTrailingPathSeparator, getComparisonKey } from 'vs/base/common/resources';
import { dirname, basename, distinctParents, joinPath, normalizePath, isAbsolutePath, relativePath, removeTrailingPathSeparator, hasTrailingPathSeparator, resolvePath, addTrailingPathSeparator, extUri, extUriIgnorePathCase } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { isWindows } from 'vs/base/common/platform';
import { toSlashes } from 'vs/base/common/extpath';
@@ -235,16 +235,19 @@ suite('Resources', () => {
}
});
function assertEqualURI(actual: URI, expected: URI, message?: string) {
if (!isEqual(expected, actual, hasToIgnoreCase(expected), false)) {
function assertEqualURI(actual: URI, expected: URI, message?: string, ignoreCase?: boolean) {
let util = ignoreCase ? extUriIgnorePathCase : extUri;
if (!util.isEqual(expected, actual)) {
assert.equal(actual.toString(), expected.toString(), message);
}
}
function assertRelativePath(u1: URI, u2: URI, expectedPath: string | undefined, ignoreJoin?: boolean, ignoreCase?: boolean) {
assert.equal(relativePath(u1, u2, ignoreCase), expectedPath, `from ${u1.toString()} to ${u2.toString()}`);
let util = ignoreCase ? extUriIgnorePathCase : extUri;
assert.equal(util.relativePath(u1, u2), expectedPath, `from ${u1.toString()} to ${u2.toString()}`);
if (expectedPath !== undefined && !ignoreJoin) {
assertEqualURI(removeTrailingPathSeparator(joinPath(u1, expectedPath)), removeTrailingPathSeparator(u2), 'joinPath on relativePath should be equal');
assertEqualURI(removeTrailingPathSeparator(joinPath(u1, expectedPath)), removeTrailingPathSeparator(u2), 'joinPath on relativePath should be equal', ignoreCase);
}
}
@@ -254,14 +257,14 @@ suite('Resources', () => {
assertRelativePath(URI.parse('foo://a/foo'), URI.parse('foo://a/foo/bar/goo'), 'bar/goo');
assertRelativePath(URI.parse('foo://a/'), URI.parse('foo://a/foo/bar/goo'), 'foo/bar/goo');
assertRelativePath(URI.parse('foo://a/foo/xoo'), URI.parse('foo://a/foo/bar'), '../bar');
assertRelativePath(URI.parse('foo://a/foo/xoo/yoo'), URI.parse('foo://a'), '../../..');
assertRelativePath(URI.parse('foo://a/foo/xoo/yoo'), URI.parse('foo://a'), '../../..', true);
assertRelativePath(URI.parse('foo://a/foo'), URI.parse('foo://a/foo/'), '');
assertRelativePath(URI.parse('foo://a/foo/'), URI.parse('foo://a/foo'), '');
assertRelativePath(URI.parse('foo://a/foo/'), URI.parse('foo://a/foo/'), '');
assertRelativePath(URI.parse('foo://a/foo'), URI.parse('foo://a/foo'), '');
assertRelativePath(URI.parse('foo://a'), URI.parse('foo://a'), '');
assertRelativePath(URI.parse('foo://a'), URI.parse('foo://a'), '', true);
assertRelativePath(URI.parse('foo://a/'), URI.parse('foo://a/'), '');
assertRelativePath(URI.parse('foo://a/'), URI.parse('foo://a'), '');
assertRelativePath(URI.parse('foo://a/'), URI.parse('foo://a'), '', true);
assertRelativePath(URI.parse('foo://a/foo?q'), URI.parse('foo://a/foo/bar#h'), 'bar', true);
assertRelativePath(URI.parse('foo://'), URI.parse('foo://a/b'), undefined);
assertRelativePath(URI.parse('foo://a2/b'), URI.parse('foo://a/b'), undefined);
@@ -346,10 +349,17 @@ suite('Resources', () => {
});
function assertIsEqual(u1: URI, u2: URI, ignoreCase: boolean, expected: boolean) {
assert.equal(isEqual(u1, u2, ignoreCase), expected, `${u1.toString()}${expected ? '===' : '!=='}${u2.toString()}`);
assert.equal(getComparisonKey(u1, ignoreCase) === getComparisonKey(u2, ignoreCase), expected, `comparison keys ${u1.toString()}, ${u2.toString()}`);
assert.equal(isEqualOrParent(u1, u2, ignoreCase), expected, `isEqualOrParent ${u1.toString()}, ${u2.toString()}`);
function assertIsEqual(u1: URI, u2: URI, ignoreCase: boolean | undefined, expected: boolean) {
let util = ignoreCase ? extUriIgnorePathCase : extUri;
assert.equal(util.isEqual(u1, u2), expected, `${u1.toString()}${expected ? '===' : '!=='}${u2.toString()}`);
assert.equal(util.compare(u1, u2) === 0, expected);
assert.equal(util.getComparisonKey(u1) === util.getComparisonKey(u2), expected, `comparison keys ${u1.toString()}, ${u2.toString()}`);
assert.equal(util.isEqualOrParent(u1, u2), expected, `isEqualOrParent ${u1.toString()}, ${u2.toString()}`);
if (!ignoreCase) {
assert.equal(u1.toString() === u2.toString(), expected);
}
}
@@ -358,7 +368,7 @@ suite('Resources', () => {
let fileURI2 = isWindows ? URI.file('C:\\foo\\Bar') : URI.file('/foo/Bar');
assertIsEqual(fileURI, fileURI, true, true);
assertIsEqual(fileURI, fileURI, false, true);
assertIsEqual(fileURI, fileURI, hasToIgnoreCase(fileURI), true);
assertIsEqual(fileURI, fileURI, undefined, true);
assertIsEqual(fileURI, fileURI2, true, true);
assertIsEqual(fileURI, fileURI2, false, false);
@@ -366,13 +376,15 @@ suite('Resources', () => {
let fileURI4 = URI.parse('foo://server:453/foo/Bar');
assertIsEqual(fileURI3, fileURI3, true, true);
assertIsEqual(fileURI3, fileURI3, false, true);
assertIsEqual(fileURI3, fileURI3, hasToIgnoreCase(fileURI3), true);
assertIsEqual(fileURI3, fileURI3, undefined, true);
assertIsEqual(fileURI3, fileURI4, true, true);
assertIsEqual(fileURI3, fileURI4, false, false);
assertIsEqual(fileURI, fileURI3, true, false);
assertIsEqual(URI.parse('foo://server'), URI.parse('foo://server/'), true, true);
assertIsEqual(URI.parse('file://server'), URI.parse('file://server/'), true, true);
assertIsEqual(URI.parse('http://server'), URI.parse('http://server/'), true, true);
assertIsEqual(URI.parse('foo://server'), URI.parse('foo://server/'), true, false); // only selected scheme have / as the default path
assertIsEqual(URI.parse('foo://server/foo'), URI.parse('foo://server/foo/'), true, false);
assertIsEqual(URI.parse('foo://server/foo'), URI.parse('foo://server/foo?'), true, true);
@@ -383,38 +395,39 @@ suite('Resources', () => {
assertIsEqual(fileURI5, fileURI3, true, false);
assertIsEqual(fileURI6, fileURI6, true, true);
assertIsEqual(fileURI6, fileURI5, true, false);
assertIsEqual(fileURI6, fileURI3, true, true);
assertIsEqual(fileURI6, fileURI3, true, false);
});
test('isEqualOrParent', () => {
let fileURI = isWindows ? URI.file('c:\\foo\\bar') : URI.file('/foo/bar');
let fileURI2 = isWindows ? URI.file('c:\\foo') : URI.file('/foo');
let fileURI2b = isWindows ? URI.file('C:\\Foo\\') : URI.file('/Foo/');
assert.equal(isEqualOrParent(fileURI, fileURI, true), true, '1');
assert.equal(isEqualOrParent(fileURI, fileURI, false), true, '2');
assert.equal(isEqualOrParent(fileURI, fileURI2, true), true, '3');
assert.equal(isEqualOrParent(fileURI, fileURI2, false), true, '4');
assert.equal(isEqualOrParent(fileURI, fileURI2b, true), true, '5');
assert.equal(isEqualOrParent(fileURI, fileURI2b, false), false, '6');
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI, fileURI), true, '1');
assert.equal(extUri.isEqualOrParent(fileURI, fileURI), true, '2');
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI, fileURI2), true, '3');
assert.equal(extUri.isEqualOrParent(fileURI, fileURI2), true, '4');
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI, fileURI2b), true, '5');
assert.equal(extUri.isEqualOrParent(fileURI, fileURI2b), false, '6');
assert.equal(isEqualOrParent(fileURI2, fileURI, false), false, '7');
assert.equal(isEqualOrParent(fileURI2b, fileURI2, true), true, '8');
assert.equal(extUri.isEqualOrParent(fileURI2, fileURI), false, '7');
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI2b, fileURI2), true, '8');
let fileURI3 = URI.parse('foo://server:453/foo/bar/goo');
let fileURI4 = URI.parse('foo://server:453/foo/');
let fileURI5 = URI.parse('foo://server:453/foo');
assert.equal(isEqualOrParent(fileURI3, fileURI3, true), true, '11');
assert.equal(isEqualOrParent(fileURI3, fileURI3, false), true, '12');
assert.equal(isEqualOrParent(fileURI3, fileURI4, true), true, '13');
assert.equal(isEqualOrParent(fileURI3, fileURI4, false), true, '14');
assert.equal(isEqualOrParent(fileURI3, fileURI, true), false, '15');
assert.equal(isEqualOrParent(fileURI5, fileURI5, true), true, '16');
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI3, fileURI3, true), true, '11');
assert.equal(extUri.isEqualOrParent(fileURI3, fileURI3), true, '12');
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI3, fileURI4, true), true, '13');
assert.equal(extUri.isEqualOrParent(fileURI3, fileURI4), true, '14');
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI3, fileURI, true), false, '15');
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI5, fileURI5, true), true, '16');
let fileURI6 = URI.parse('foo://server:453/foo?q=1');
let fileURI7 = URI.parse('foo://server:453/foo/bar?q=1');
assert.equal(isEqualOrParent(fileURI6, fileURI5, true), false, '17');
assert.equal(isEqualOrParent(fileURI6, fileURI6, true), true, '18');
assert.equal(isEqualOrParent(fileURI7, fileURI6, true), true, '19');
assert.equal(isEqualOrParent(fileURI7, fileURI5, true), false, '20');
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI6, fileURI5), false, '17');
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI6, fileURI6), true, '18');
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI7, fileURI6), true, '19');
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI7, fileURI5), false, '20');
});
});

View File

@@ -0,0 +1,218 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { SkipList } from 'vs/base/common/skipList';
import { StopWatch } from 'vs/base/common/stopwatch';
import { binarySearch } from 'vs/base/common/arrays';
suite('SkipList', function () {
function assertValues<V>(list: SkipList<any, V>, expected: V[]) {
assert.equal(list.size, expected.length);
assert.deepEqual([...list.values()], expected);
let valuesFromEntries = [...list.entries()].map(entry => entry[1]);
assert.deepEqual(valuesFromEntries, expected);
let valuesFromIter = [...list].map(entry => entry[1]);
assert.deepEqual(valuesFromIter, expected);
let i = 0;
list.forEach((value, _key, map) => {
assert.ok(map === list);
assert.deepEqual(value, expected[i++]);
});
}
function assertKeys<K>(list: SkipList<K, any>, expected: K[]) {
assert.equal(list.size, expected.length);
assert.deepEqual([...list.keys()], expected);
let keysFromEntries = [...list.entries()].map(entry => entry[0]);
assert.deepEqual(keysFromEntries, expected);
let keysFromIter = [...list].map(entry => entry[0]);
assert.deepEqual(keysFromIter, expected);
let i = 0;
list.forEach((_value, key, map) => {
assert.ok(map === list);
assert.deepEqual(key, expected[i++]);
});
}
test('set/get/delete', function () {
let list = new SkipList<number, number>((a, b) => a - b);
assert.equal(list.get(3), undefined);
list.set(3, 1);
assert.equal(list.get(3), 1);
assertValues(list, [1]);
list.set(3, 3);
assertValues(list, [3]);
list.set(1, 1);
list.set(4, 4);
assert.equal(list.get(3), 3);
assert.equal(list.get(1), 1);
assert.equal(list.get(4), 4);
assertValues(list, [1, 3, 4]);
assert.equal(list.delete(17), false);
assert.equal(list.delete(1), true);
assert.equal(list.get(1), undefined);
assert.equal(list.get(3), 3);
assert.equal(list.get(4), 4);
assertValues(list, [3, 4]);
});
test('Figure 3', function () {
let list = new SkipList<number, boolean>((a, b) => a - b);
list.set(3, true);
list.set(6, true);
list.set(7, true);
list.set(9, true);
list.set(12, true);
list.set(19, true);
list.set(21, true);
list.set(25, true);
assertKeys(list, [3, 6, 7, 9, 12, 19, 21, 25]);
list.set(17, true);
assert.deepEqual(list.size, 9);
assertKeys(list, [3, 6, 7, 9, 12, 17, 19, 21, 25]);
});
test('capacity max', function () {
let list = new SkipList<number, boolean>((a, b) => a - b, 10);
list.set(1, true);
list.set(2, true);
list.set(3, true);
list.set(4, true);
list.set(5, true);
list.set(6, true);
list.set(7, true);
list.set(8, true);
list.set(9, true);
list.set(10, true);
list.set(11, true);
list.set(12, true);
assertKeys(list, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
});
const cmp = (a: number, b: number): number => {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
};
function insertArraySorted(array: number[], element: number) {
let idx = binarySearch(array, element, cmp);
if (idx >= 0) {
array[idx] = element;
} else {
idx = ~idx;
// array = array.slice(0, idx).concat(element, array.slice(idx));
array.splice(idx, 0, element);
}
return array;
}
function delArraySorted(array: number[], element: number) {
let idx = binarySearch(array, element, cmp);
if (idx >= 0) {
// array = array.slice(0, idx).concat(array.slice(idx));
array.splice(idx, 1);
}
return array;
}
test('perf', function () {
this.skip();
// data
const max = 2 ** 16;
const values = new Set<number>();
for (let i = 0; i < max; i++) {
let value = Math.floor(Math.random() * max);
values.add(value);
}
console.log(values.size);
// init
let list = new SkipList<number, boolean>(cmp, max);
let sw = new StopWatch(true);
values.forEach(value => list.set(value, true));
sw.stop();
console.log(`[LIST] ${list.size} elements after ${sw.elapsed()}ms`);
let array: number[] = [];
sw = new StopWatch(true);
values.forEach(value => array = insertArraySorted(array, value));
sw.stop();
console.log(`[ARRAY] ${array.length} elements after ${sw.elapsed()}ms`);
// get
sw = new StopWatch(true);
let someValues = [...values].slice(0, values.size / 4);
someValues.forEach(key => {
let value = list.get(key); // find
console.assert(value, '[LIST] must have ' + key);
list.get(-key); // miss
});
sw.stop();
console.log(`[LIST] retrieve ${sw.elapsed()}ms (${(sw.elapsed() / (someValues.length * 2)).toPrecision(4)}ms/op)`);
sw = new StopWatch(true);
someValues.forEach(key => {
let idx = binarySearch(array, key, cmp); // find
console.assert(idx >= 0, '[ARRAY] must have ' + key);
binarySearch(array, -key, cmp); // miss
});
sw.stop();
console.log(`[ARRAY] retrieve ${sw.elapsed()}ms (${(sw.elapsed() / (someValues.length * 2)).toPrecision(4)}ms/op)`);
// insert
sw = new StopWatch(true);
someValues.forEach(key => {
list.set(-key, false);
});
sw.stop();
console.log(`[LIST] insert ${sw.elapsed()}ms (${(sw.elapsed() / someValues.length).toPrecision(4)}ms/op)`);
sw = new StopWatch(true);
someValues.forEach(key => {
array = insertArraySorted(array, -key);
});
sw.stop();
console.log(`[ARRAY] insert ${sw.elapsed()}ms (${(sw.elapsed() / someValues.length).toPrecision(4)}ms/op)`);
// delete
sw = new StopWatch(true);
someValues.forEach(key => {
list.delete(key); // find
list.delete(-key); // miss
});
sw.stop();
console.log(`[LIST] delete ${sw.elapsed()}ms (${(sw.elapsed() / (someValues.length * 2)).toPrecision(4)}ms/op)`);
sw = new StopWatch(true);
someValues.forEach(key => {
array = delArraySorted(array, key); // find
array = delArraySorted(array, -key); // miss
});
sw.stop();
console.log(`[ARRAY] delete ${sw.elapsed()}ms (${(sw.elapsed() / (someValues.length * 2)).toPrecision(4)}ms/op)`);
});
});

View File

@@ -32,7 +32,6 @@
'xterm': `${window.location.origin}/static/remote/web/node_modules/xterm/lib/xterm.js`,
'xterm-addon-search': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
'xterm-addon-unicode11': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`,
'xterm-addon-web-links': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
'xterm-addon-webgl': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
'semver-umd': `${window.location.origin}/static/remote/web/node_modules/semver-umd/lib/semver-umd.js`,
'@angular/core': `${window.location.origin}/static/remote/web/node_modules/@angular/core/bundles/core.umd.js`,

View File

@@ -36,7 +36,6 @@
'xterm': `${window.location.origin}/static/node_modules/xterm/lib/xterm.js`,
'xterm-addon-search': `${window.location.origin}/static/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
'xterm-addon-unicode11': `${window.location.origin}/static/node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`,
'xterm-addon-web-links': `${window.location.origin}/static/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
'xterm-addon-webgl': `${window.location.origin}/static/node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
'semver-umd': `${window.location.origin}/static/node_modules/semver-umd/lib/semver-umd.js`,
'@angular/core': `${window.location.origin}/static/node_modules/@angular/core/bundles/core.umd.js`,

View File

@@ -3,10 +3,12 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { clipboard, ipcRenderer, shell, webFrame } from 'electron';
import 'vs/css!./media/issueReporter';
import { ElectronService, IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
import { ipcRenderer, webFrame } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import * as os from 'os';
import * as browser from 'vs/base/browser/browser';
import { $ } from 'vs/base/browser/dom';
import { $, windowOpenNoOpener } from 'vs/base/browser/dom';
import { Button } from 'vs/base/browser/ui/button/button';
import 'vs/base/browser/ui/codicons/codiconStyles'; // make sure codicon css is loaded
import { CodiconLabel } from 'vs/base/browser/ui/codicons/codiconLabel';
@@ -15,21 +17,19 @@ import { debounce } from 'vs/base/common/decorators';
import { Disposable } from 'vs/base/common/lifecycle';
import * as platform from 'vs/base/common/platform';
import { escape } from 'vs/base/common/strings';
import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc';
import { createChannelSender } from 'vs/base/parts/ipc/node/ipc';
import { getDelayedChannel, createChannelSender } from 'vs/base/parts/ipc/common/ipc';
import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net';
import { normalizeGitHubUrl } from 'vs/platform/issue/common/issueReporterUtil';
import { IssueReporterData as IssueReporterModelData, IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel';
import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage';
import 'vs/css!./media/issueReporter';
import { localize } from 'vs/nls';
import { isRemoteDiagnosticError, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics';
import { EnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IMainProcessService, MainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
import { IMainProcessService, MainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { ISettingsSearchIssueReporterData, IssueReporterData, IssueReporterExtensionData, IssueReporterFeatures, IssueReporterStyles, IssueType } from 'vs/platform/issue/node/issue';
import { ISettingsSearchIssueReporterData, IssueReporterData, IssueReporterExtensionData, IssueReporterFeatures, IssueReporterStyles, IssueType } from 'vs/platform/issue/common/issue';
import { getLogLevel, ILogService } from 'vs/platform/log/common/log';
import { FollowerLogService, LoggerChannelClient } from 'vs/platform/log/common/logIpc';
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
@@ -64,6 +64,7 @@ export function startup(configuration: IssueReporterConfiguration) {
export class IssueReporter extends Disposable {
private environmentService!: INativeEnvironmentService;
private electronService!: IElectronService;
private telemetryService!: ITelemetryService;
private logService!: ILogService;
private readonly issueReporterModel: IssueReporterModel;
@@ -324,6 +325,9 @@ export class IssueReporter extends Disposable {
const mainProcessService = new MainProcessService(configuration.windowId);
serviceCollection.set(IMainProcessService, mainProcessService);
this.electronService = new ElectronService(configuration.windowId, mainProcessService) as IElectronService;
serviceCollection.set(IElectronService, this.electronService);
this.environmentService = new EnvironmentService(configuration, configuration.execPath);
const logService = new SpdLogService(`issuereporter${configuration.windowId}`, this.environmentService.logsPath, getLogLevel(this.environmentService));
@@ -462,7 +466,7 @@ export class IssueReporter extends Disposable {
this.addEventListener('extensionBugsLink', 'click', (e: Event) => {
const url = (<HTMLElement>e.target).innerText;
shell.openExternal(url);
windowOpenNoOpener(url);
});
this.addEventListener('disableExtensions', 'keydown', (e: Event) => {
@@ -941,9 +945,9 @@ export class IssueReporter extends Disposable {
private async writeToClipboard(baseUrl: string, issueBody: string): Promise<string> {
return new Promise((resolve, reject) => {
ipcRenderer.once('vscode:issueReporterClipboardResponse', (_: unknown, shouldWrite: boolean) => {
ipcRenderer.once('vscode:issueReporterClipboardResponse', async (event: unknown, shouldWrite: boolean) => {
if (shouldWrite) {
clipboard.writeText(issueBody);
await this.electronService.writeClipboardText(issueBody);
resolve(baseUrl + `&body=${encodeURIComponent(localize('pasteData', "We have written the needed data into your clipboard because it was too large to send. Please paste."))}`);
} else {
reject();
@@ -1194,7 +1198,7 @@ export class IssueReporter extends Disposable {
event.stopPropagation();
// Exclude right click
if (event.which < 3) {
shell.openExternal((<HTMLAnchorElement>event.target).href);
windowOpenNoOpener((<HTMLAnchorElement>event.target).href);
this.telemetryService.publicLog2('issueReporterViewSimilarIssue');
}
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { assign } from 'vs/base/common/objects';
import { IssueType, ISettingSearchResult, IssueReporterExtensionData } from 'vs/platform/issue/node/issue';
import { IssueType, ISettingSearchResult, IssueReporterExtensionData } from 'vs/platform/issue/common/issue';
import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics';
export interface IssueReporterData {

View File

@@ -95,25 +95,25 @@ textarea, input, select {
}
html {
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
color: #CCCCCC;
height: 100%;
}
html:lang(zh-Hans) {
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif;
}
html:lang(zh-Hant) {
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft Jhenghei", "PingFang TC", "Source Han Sans TC", "Source Han Sans", "Source Han Sans TW", sans-serif;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft Jhenghei", "PingFang TC", "Source Han Sans TC", "Source Han Sans", "Source Han Sans TW", sans-serif;
}
html:lang(ja) {
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Yu Gothic UI", "Meiryo UI", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Yu Gothic UI", "Meiryo UI", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif;
}
html:lang(ko) {
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Malgun Gothic", "Nanum Gothic", "Dotom", "Apple SD Gothic Neo", "AppleGothic", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Malgun Gothic", "Nanum Gothic", "Dotom", "Apple SD Gothic Neo", "AppleGothic", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif;
}
body {

View File

@@ -6,7 +6,7 @@
import * as assert from 'assert';
import { IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel';
import { normalizeGitHubUrl } from 'vs/platform/issue/common/issueReporterUtil';
import { IssueType } from 'vs/platform/issue/node/issue';
import { IssueType } from 'vs/platform/issue/common/issue';
suite('IssueReporter', () => {

View File

@@ -4,24 +4,24 @@
*--------------------------------------------------------------------------------------------*/
html {
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
font-size: 13px;
}
html:lang(zh-Hans) {
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif;
}
html:lang(zh-Hant) {
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft Jhenghei", "PingFang TC", "Source Han Sans TC", "Source Han Sans", "Source Han Sans TW", sans-serif;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft Jhenghei", "PingFang TC", "Source Han Sans TC", "Source Han Sans", "Source Han Sans TW", sans-serif;
}
html:lang(ja) {
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Yu Gothic UI", "Meiryo UI", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Yu Gothic UI", "Meiryo UI", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif;
}
html:lang(ko) {
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Malgun Gothic", "Nanum Gothic", "Dotom", "Apple SD Gothic Neo", "AppleGothic", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Malgun Gothic", "Nanum Gothic", "Dotom", "Apple SD Gothic Neo", "AppleGothic", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif;
}
body {

View File

@@ -4,16 +4,17 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/processExplorer';
import { webFrame, ipcRenderer, clipboard } from 'electron';
import { clipboard } from 'electron';
import { webFrame, ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import { repeat } from 'vs/base/common/strings';
import { totalmem } from 'os';
import product from 'vs/platform/product/common/product';
import { localize } from 'vs/nls';
import { ProcessExplorerStyles, ProcessExplorerData } from 'vs/platform/issue/node/issue';
import { ProcessExplorerStyles, ProcessExplorerData } from 'vs/platform/issue/common/issue';
import * as browser from 'vs/base/browser/browser';
import * as platform from 'vs/base/common/platform';
import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu';
import { popup } from 'vs/base/parts/contextmenu/electron-browser/contextmenu';
import { popup } from 'vs/base/parts/contextmenu/electron-sandbox/contextmenu';
import { ProcessItem } from 'vs/base/common/processes';
import { addDisposableListener } from 'vs/base/browser/dom';
import { DisposableStore } from 'vs/base/common/lifecycle';
@@ -369,7 +370,7 @@ function requestProcessList(totalWaitTime: number): void {
// Wait at least a second between requests.
if (waited > 1000) {
ipcRenderer.send('windowsInfoRequest');
ipcRenderer.send('vscode:windowsInfoRequest');
ipcRenderer.send('vscode:listProcesses');
} else {
requestProcessList(waited);
@@ -393,18 +394,18 @@ export function startup(data: ProcessExplorerData): void {
createCloseListener();
// Map window process pids to titles, annotate process names with this when rendering to distinguish between them
ipcRenderer.on('vscode:windowsInfoResponse', (_event: unknown, windows: any[]) => {
ipcRenderer.on('vscode:windowsInfoResponse', (event: unknown, windows: any[]) => {
mapPidToWindowTitle = new Map<number, string>();
windows.forEach(window => mapPidToWindowTitle.set(window.pid, window.title));
});
ipcRenderer.on('vscode:listProcessesResponse', (_event: Event, processRoots: [{ name: string, rootProcess: ProcessItem | IRemoteDiagnosticError }]) => {
ipcRenderer.on('vscode:listProcessesResponse', (event: unknown, processRoots: [{ name: string, rootProcess: ProcessItem | IRemoteDiagnosticError }]) => {
updateProcessInfo(processRoots);
requestProcessList(0);
});
lastRequestTime = Date.now();
ipcRenderer.send('windowsInfoRequest');
ipcRenderer.send('vscode:windowsInfoRequest');
ipcRenderer.send('vscode:listProcesses');
document.onkeydown = (e: KeyboardEvent) => {

View File

@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta http-equiv="Content-Security-Policy"
content="default-src 'none'; img-src 'self' https: data:; media-src 'none'; child-src 'self'; object-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' https:; font-src 'self' https:;">
content="default-src 'none'; img-src 'self' https: data:; media-src 'none'; child-src 'self'; object-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self' https:; font-src 'self' https:;">
<style>
html,
body {
@@ -18,7 +18,7 @@
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
font-size: 10pt;
background-color: #F3F3F3;
}
@@ -58,7 +58,7 @@
}
input {
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif !important;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif !important;
}
</style>
</head>

Some files were not shown because too many files have changed in this diff Show More