Archived
Merge from vscode 8aa90d444f5d051984e8055f547c4252d53479b3 (#5587)
* Merge from vscode 8aa90d444f5d051984e8055f547c4252d53479b3 * pipeline errors * fix build
This commit is contained in:
Vendored
+113
-56
@@ -1,5 +1,6 @@
|
||||
/**
|
||||
* Copyright (c) 2017, Daniel Imms (Source EULA).
|
||||
* Copyright (c) 2018, Microsoft Corporation (Source EULA).
|
||||
*/
|
||||
|
||||
declare module 'node-pty' {
|
||||
@@ -11,76 +12,132 @@ declare module 'node-pty' {
|
||||
* escaped properly.
|
||||
* @param options The options of the terminal.
|
||||
* @see CommandLineToArgvW https://msdn.microsoft.com/en-us/library/windows/desktop/bb776391(v=vs.85).aspx
|
||||
* @see Parsing C++ Command-Line Arguments https://msdn.microsoft.com/en-us/library/17w5ykft.aspx
|
||||
* @see Parsing C++ Comamnd-Line Arguments https://msdn.microsoft.com/en-us/library/17w5ykft.aspx
|
||||
* @see GetCommandLine https://msdn.microsoft.com/en-us/library/windows/desktop/ms683156.aspx
|
||||
*/
|
||||
export function spawn(file: string, args: string[] | string, options: IPtyForkOptions): IPty;
|
||||
export function spawn(file: string, args: string[] | string, options: IPtyForkOptions | IWindowsPtyForkOptions): IPty;
|
||||
|
||||
export interface IPtyForkOptions {
|
||||
name?: string;
|
||||
cols?: number;
|
||||
rows?: number;
|
||||
cwd?: string;
|
||||
env?: { [key: string]: string };
|
||||
uid?: number;
|
||||
gid?: number;
|
||||
encoding?: string;
|
||||
/**
|
||||
* Whether to use the experimental ConPTY system on Windows. This setting will be ignored on
|
||||
* non-Windows.
|
||||
*/
|
||||
experimentalUseConpty?: boolean;
|
||||
name?: string;
|
||||
cols?: number;
|
||||
rows?: number;
|
||||
cwd?: string;
|
||||
env?: { [key: string]: string };
|
||||
uid?: number;
|
||||
gid?: number;
|
||||
encoding?: string;
|
||||
}
|
||||
|
||||
export interface IWindowsPtyForkOptions {
|
||||
name?: string;
|
||||
cols?: number;
|
||||
rows?: number;
|
||||
cwd?: string;
|
||||
env?: { [key: string]: string };
|
||||
encoding?: string;
|
||||
/**
|
||||
* Whether to use the experimental ConPTY system on Windows. When this is not set, ConPTY will
|
||||
* be used when the Windows build number is >= 18309 (it's available in 17134 and 17692 but is
|
||||
* too unstable to enable by default).
|
||||
*
|
||||
* This setting does nothing on non-Windows.
|
||||
*/
|
||||
experimentalUseConpty?: boolean;
|
||||
/**
|
||||
* Whether to use PSEUDOCONSOLE_INHERIT_CURSOR in conpty.
|
||||
* @see https://docs.microsoft.com/en-us/windows/console/createpseudoconsole
|
||||
*/
|
||||
conptyInheritCursor?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface representing a pseudoterminal, on Windows this is emulated via the winpty library.
|
||||
*/
|
||||
export interface IPty {
|
||||
/**
|
||||
* The process ID of the outer process.
|
||||
*/
|
||||
pid: number;
|
||||
/**
|
||||
* The process ID of the outer process.
|
||||
*/
|
||||
readonly pid: number;
|
||||
|
||||
/**
|
||||
* The title of the active process.
|
||||
*/
|
||||
process: string;
|
||||
/**
|
||||
* The column size in characters.
|
||||
*/
|
||||
readonly cols: number;
|
||||
|
||||
/**
|
||||
* Adds a listener to the data event, fired when data is returned from the pty.
|
||||
* @param event The name of the event.
|
||||
* @param listener The callback function.
|
||||
*/
|
||||
on(event: 'data', listener: (data: string) => void): void;
|
||||
/**
|
||||
* The row size in characters.
|
||||
*/
|
||||
readonly rows: number;
|
||||
|
||||
/**
|
||||
* Adds a listener to the exit event, fired when the pty exits.
|
||||
* @param event The name of the event.
|
||||
* @param listener The callback function, exitCode is the exit code of the process and signal is
|
||||
* the signal that triggered the exit. signal is not supported on Windows.
|
||||
*/
|
||||
on(event: 'exit', listener: (exitCode: number, signal?: number) => void): void;
|
||||
/**
|
||||
* The title of the active process.
|
||||
*/
|
||||
readonly process: string;
|
||||
|
||||
/**
|
||||
* Resizes the dimensions of the pty.
|
||||
* @param columns The number of columns to use.
|
||||
* @param rows The number of rows to use.
|
||||
*/
|
||||
resize(columns: number, rows: number): void;
|
||||
/**
|
||||
* Adds an event listener for when a data event fires. This happens when data is returned from
|
||||
* the pty.
|
||||
* @returns an `IDisposable` to stop listening.
|
||||
*/
|
||||
readonly onData: IEvent<string>;
|
||||
|
||||
/**
|
||||
* Writes data to the pty.
|
||||
* @param data The data to write.
|
||||
*/
|
||||
write(data: string): void;
|
||||
/**
|
||||
* Adds an event listener for when an exit event fires. This happens when the pty exits.
|
||||
* @returns an `IDisposable` to stop listening.
|
||||
*/
|
||||
readonly onExit: IEvent<{ exitCode: number, signal?: number }>;
|
||||
|
||||
/**
|
||||
* Kills the pty.
|
||||
* @param signal The signal to use, defaults to SIGHUP. If the TIOCSIG/TIOCSIGNAL ioctl is not
|
||||
* supported then the process will be killed instead. This parameter is not supported on
|
||||
* Windows.
|
||||
* @throws Will throw when signal is used on Windows.
|
||||
*/
|
||||
kill(signal?: string): void;
|
||||
/**
|
||||
* Adds a listener to the data event, fired when data is returned from the pty.
|
||||
* @param event The name of the event.
|
||||
* @param listener The callback function.
|
||||
* @deprecated Use IPty.onData
|
||||
*/
|
||||
on(event: 'data', listener: (data: string) => void): void;
|
||||
|
||||
/**
|
||||
* Adds a listener to the exit event, fired when the pty exits.
|
||||
* @param event The name of the event.
|
||||
* @param listener The callback function, exitCode is the exit code of the process and signal is
|
||||
* the signal that triggered the exit. signal is not supported on Windows.
|
||||
* @deprecated Use IPty.onExit
|
||||
*/
|
||||
on(event: 'exit', listener: (exitCode: number, signal?: number) => void): void;
|
||||
|
||||
/**
|
||||
* Resizes the dimensions of the pty.
|
||||
* @param columns THe number of columns to use.
|
||||
* @param rows The number of rows to use.
|
||||
*/
|
||||
resize(columns: number, rows: number): void;
|
||||
|
||||
/**
|
||||
* Writes data to the pty.
|
||||
* @param data The data to write.
|
||||
*/
|
||||
write(data: string): void;
|
||||
|
||||
/**
|
||||
* Kills the pty.
|
||||
* @param signal The signal to use, defaults to SIGHUP. This parameter is not supported on
|
||||
* Windows.
|
||||
* @throws Will throw when signal is used on Windows.
|
||||
*/
|
||||
kill(signal?: string): void;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An object that can be disposed via a dispose function.
|
||||
*/
|
||||
export interface IDisposable {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* An event that can be listened to.
|
||||
* @returns an `IDisposable` to stop listening.
|
||||
*/
|
||||
export interface IEvent<T> {
|
||||
(listener: (e: T) => any): IDisposable;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+5
-3
@@ -1025,9 +1025,11 @@ declare module 'vscode-xterm' {
|
||||
|
||||
charMeasure?: { height: number, width: number };
|
||||
|
||||
renderer: {
|
||||
_renderLayers: any[];
|
||||
onIntersectionChange: any;
|
||||
_renderCoordinator: {
|
||||
_renderer: {
|
||||
_renderLayers: any[];
|
||||
};
|
||||
_onIntersectionChange: any;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import { ButtonGroup, IButtonStyles } from 'vs/base/browser/ui/button/button';
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
|
||||
export interface IDialogOptions {
|
||||
cancelId?: number;
|
||||
@@ -30,6 +31,11 @@ export interface IDialogStyles extends IButtonStyles {
|
||||
dialogBorder?: Color;
|
||||
}
|
||||
|
||||
interface ButtonMapEntry {
|
||||
label: string;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export class Dialog extends Disposable {
|
||||
private element: HTMLElement | undefined;
|
||||
private modal: HTMLElement | undefined;
|
||||
@@ -92,12 +98,13 @@ export class Dialog extends Disposable {
|
||||
|
||||
let focusedButton = 0;
|
||||
this.buttonGroup = new ButtonGroup(this.buttonsContainer, this.buttons.length, { title: true });
|
||||
const buttonMap = this.rearrangeButtons(this.buttons, this.options.cancelId);
|
||||
this.buttonGroup.buttons.forEach((button, index) => {
|
||||
button.label = mnemonicButtonLabel(this.buttons[index], true);
|
||||
button.label = mnemonicButtonLabel(buttonMap[index].label, true);
|
||||
|
||||
this._register(button.onDidClick(e => {
|
||||
EventHelper.stop(e);
|
||||
resolve(index);
|
||||
resolve(buttonMap[index].index);
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -228,4 +235,22 @@ export class Dialog extends Disposable {
|
||||
this.focusToReturn = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private rearrangeButtons(buttons: Array<string>, cancelId: number | undefined): ButtonMapEntry[] {
|
||||
const buttonMap: ButtonMapEntry[] = [];
|
||||
// Maps each button to its current label and old index so that when we move them around it's not a problem
|
||||
buttons.forEach((button, index) => {
|
||||
buttonMap.push({ label: button, index: index });
|
||||
});
|
||||
|
||||
if (isMacintosh) {
|
||||
if (cancelId !== undefined) {
|
||||
const cancelButton = buttonMap.splice(cancelId, 1)[0];
|
||||
buttonMap.reverse();
|
||||
buttonMap.splice(buttonMap.length - 1, 0, cancelButton);
|
||||
}
|
||||
}
|
||||
|
||||
return buttonMap;
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,8 @@ export interface IListVirtualDelegate<T> {
|
||||
export interface IListRenderer<T, TTemplateData> {
|
||||
templateId: string;
|
||||
renderTemplate(container: HTMLElement): TTemplateData;
|
||||
renderElement(element: T, index: number, templateData: TTemplateData, dynamicHeightProbing?: boolean): void;
|
||||
disposeElement?(element: T, index: number, templateData: TTemplateData, dynamicHeightProbing?: boolean): void;
|
||||
renderElement(element: T, index: number, templateData: TTemplateData, height: number | undefined): void;
|
||||
disposeElement?(element: T, index: number, templateData: TTemplateData, height: number | undefined): void;
|
||||
disposeTemplate(templateData: TTemplateData): void;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ class PagedRenderer<TElement, TTemplateData> implements IListRenderer<number, IT
|
||||
return { data, disposable: { dispose: () => { } } };
|
||||
}
|
||||
|
||||
renderElement(index: number, _: number, data: ITemplateData<TTemplateData>, dynamicHeightProbing?: boolean): void {
|
||||
renderElement(index: number, _: number, data: ITemplateData<TTemplateData>, height: number | undefined): void {
|
||||
if (data.disposable) {
|
||||
data.disposable.dispose();
|
||||
}
|
||||
@@ -47,7 +47,7 @@ class PagedRenderer<TElement, TTemplateData> implements IListRenderer<number, IT
|
||||
const model = this.modelProvider();
|
||||
|
||||
if (model.isResolved(index)) {
|
||||
return this.renderer.renderElement(model.get(index), index, data.data, dynamicHeightProbing);
|
||||
return this.renderer.renderElement(model.get(index), index, data.data, height);
|
||||
}
|
||||
|
||||
const cts = new CancellationTokenSource();
|
||||
@@ -55,7 +55,7 @@ class PagedRenderer<TElement, TTemplateData> implements IListRenderer<number, IT
|
||||
data.disposable = { dispose: () => cts.cancel() };
|
||||
|
||||
this.renderer.renderPlaceholder(index, data.data);
|
||||
promise.then(entry => this.renderer.renderElement(entry, index, data.data!, dynamicHeightProbing));
|
||||
promise.then(entry => this.renderer.renderElement(entry, index, data.data!, height));
|
||||
}
|
||||
|
||||
disposeTemplate(data: ITemplateData<TTemplateData>): void {
|
||||
|
||||
@@ -586,7 +586,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
}
|
||||
|
||||
if (renderer) {
|
||||
renderer.renderElement(item.element, index, item.row.templateData);
|
||||
renderer.renderElement(item.element, index, item.row.templateData, item.size);
|
||||
}
|
||||
|
||||
const uri = this.dnd.getDragURI(item.element);
|
||||
@@ -647,7 +647,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
const renderer = this.renderers.get(item.templateId);
|
||||
if (renderer && renderer.disposeElement) {
|
||||
renderer.disposeElement(item.element, index, item.row!.templateData);
|
||||
renderer.disposeElement(item.element, index, item.row!.templateData, item.size);
|
||||
}
|
||||
|
||||
this.cache.release(item.row!);
|
||||
@@ -1088,10 +1088,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
const renderer = this.renderers.get(item.templateId);
|
||||
if (renderer) {
|
||||
renderer.renderElement(item.element, index, row.templateData, true);
|
||||
renderer.renderElement(item.element, index, row.templateData, undefined);
|
||||
|
||||
if (renderer.disposeElement) {
|
||||
renderer.disposeElement(item.element, index, row.templateData, true);
|
||||
renderer.disposeElement(item.element, index, row.templateData, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -979,20 +979,20 @@ class PipelineRenderer<T> implements IListRenderer<T, any> {
|
||||
return this.renderers.map(r => r.renderTemplate(container));
|
||||
}
|
||||
|
||||
renderElement(element: T, index: number, templateData: any[], dynamicHeightProbing?: boolean): void {
|
||||
renderElement(element: T, index: number, templateData: any[], height: number | undefined): void {
|
||||
let i = 0;
|
||||
|
||||
for (const renderer of this.renderers) {
|
||||
renderer.renderElement(element, index, templateData[i++], dynamicHeightProbing);
|
||||
renderer.renderElement(element, index, templateData[i++], height);
|
||||
}
|
||||
}
|
||||
|
||||
disposeElement(element: T, index: number, templateData: any[], dynamicHeightProbing?: boolean): void {
|
||||
disposeElement(element: T, index: number, templateData: any[], height: number | undefined): void {
|
||||
let i = 0;
|
||||
|
||||
for (const renderer of this.renderers) {
|
||||
if (renderer.disposeElement) {
|
||||
renderer.disposeElement(element, index, templateData[i], dynamicHeightProbing);
|
||||
renderer.disposeElement(element, index, templateData[i], height);
|
||||
}
|
||||
|
||||
i += 1;
|
||||
|
||||
@@ -105,6 +105,15 @@ export class Menu extends ActionBar {
|
||||
|
||||
this.menuDisposables = [];
|
||||
|
||||
addDisposableListener(menuElement, EventType.KEY_DOWN, (e) => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
|
||||
// Stop tab navigation of menus
|
||||
if (event.equals(KeyCode.Tab)) {
|
||||
EventHelper.stop(e, true);
|
||||
}
|
||||
});
|
||||
|
||||
if (options.enableMnemonics) {
|
||||
this.menuDisposables.push(addDisposableListener(menuElement, EventType.KEY_DOWN, (e) => {
|
||||
const key = e.key.toLocaleLowerCase();
|
||||
|
||||
@@ -240,8 +240,8 @@ class TreeRenderer<T, TFilterData, TTemplateData> implements IListRenderer<ITree
|
||||
return { container, twistie, templateData };
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<T, TFilterData>, index: number, templateData: ITreeListTemplateData<TTemplateData>, dynamicHeightProbing?: boolean): void {
|
||||
if (!dynamicHeightProbing) {
|
||||
renderElement(node: ITreeNode<T, TFilterData>, index: number, templateData: ITreeListTemplateData<TTemplateData>, height: number | undefined): void {
|
||||
if (typeof height === 'number') {
|
||||
this.renderedNodes.set(node, templateData);
|
||||
this.renderedElements.set(node.element, node);
|
||||
}
|
||||
@@ -250,15 +250,15 @@ class TreeRenderer<T, TFilterData, TTemplateData> implements IListRenderer<ITree
|
||||
templateData.twistie.style.marginLeft = `${indent}px`;
|
||||
this.update(node, templateData);
|
||||
|
||||
this.renderer.renderElement(node, index, templateData.templateData, dynamicHeightProbing);
|
||||
this.renderer.renderElement(node, index, templateData.templateData, height);
|
||||
}
|
||||
|
||||
disposeElement(node: ITreeNode<T, TFilterData>, index: number, templateData: ITreeListTemplateData<TTemplateData>, dynamicHeightProbing?: boolean): void {
|
||||
disposeElement(node: ITreeNode<T, TFilterData>, index: number, templateData: ITreeListTemplateData<TTemplateData>, height: number | undefined): void {
|
||||
if (this.renderer.disposeElement) {
|
||||
this.renderer.disposeElement(node, index, templateData.templateData, dynamicHeightProbing);
|
||||
this.renderer.disposeElement(node, index, templateData.templateData, height);
|
||||
}
|
||||
|
||||
if (!dynamicHeightProbing) {
|
||||
if (typeof height === 'number') {
|
||||
this.renderedNodes.delete(node);
|
||||
this.renderedElements.delete(node.element);
|
||||
}
|
||||
|
||||
@@ -98,8 +98,8 @@ class DataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ITreeRe
|
||||
return { templateData };
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, dynamicHeightProbing?: boolean): void {
|
||||
this.renderer.renderElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData, dynamicHeightProbing);
|
||||
renderElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
|
||||
this.renderer.renderElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData, height);
|
||||
}
|
||||
|
||||
renderTwistie(element: IAsyncDataTreeNode<TInput, T>, twistieElement: HTMLElement): boolean {
|
||||
@@ -107,9 +107,9 @@ class DataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements ITreeRe
|
||||
return false;
|
||||
}
|
||||
|
||||
disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, dynamicHeightProbing?: boolean): void {
|
||||
disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
|
||||
if (this.renderer.disposeElement) {
|
||||
this.renderer.disposeElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData, dynamicHeightProbing);
|
||||
this.renderer.disposeElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData, height);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,12 @@ export function createCancelablePromise<T>(callback: (token: CancellationToken)
|
||||
};
|
||||
}
|
||||
|
||||
export function raceCancellation<T>(promise: Promise<T>, token: CancellationToken): Promise<T | undefined>;
|
||||
export function raceCancellation<T>(promise: Promise<T>, token: CancellationToken, defaultValue: T): Promise<T>;
|
||||
export function raceCancellation<T>(promise: Promise<T>, token: CancellationToken, defaultValue?: T): Promise<T> {
|
||||
return Promise.race([promise, new Promise<T>(resolve => token.onCancellationRequested(() => resolve(defaultValue)))]);
|
||||
}
|
||||
|
||||
export function asPromise<T>(callback: () => T | Thenable<T>): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const item = callback();
|
||||
@@ -767,4 +773,4 @@ export async function retry<T>(task: ITask<Promise<T>>, delay: number, retries:
|
||||
}
|
||||
|
||||
return Promise.reject(lastError);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,25 +362,6 @@ export function matchesFuzzy2(pattern: string, word: string): IMatch[] | null {
|
||||
return score ? createMatches(score) : null;
|
||||
}
|
||||
|
||||
export function anyScore(pattern: string, lowPattern: string, _patternPos: number, word: string, lowWord: string, _wordPos: number): FuzzyScore {
|
||||
const result = fuzzyScore(pattern, lowPattern, 0, word, lowWord, 0, true);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
let matches = 0;
|
||||
let score = 0;
|
||||
let idx = _wordPos;
|
||||
for (let patternPos = 0; patternPos < lowPattern.length && patternPos < _maxLen; ++patternPos) {
|
||||
const wordPos = lowWord.indexOf(lowPattern.charAt(patternPos), idx);
|
||||
if (wordPos >= 0) {
|
||||
score += 1;
|
||||
matches += 2 ** wordPos;
|
||||
idx = wordPos + 1;
|
||||
}
|
||||
}
|
||||
return [score, matches, _wordPos];
|
||||
}
|
||||
|
||||
//#region --- fuzzyScore ---
|
||||
|
||||
export function createMatches(score: undefined | FuzzyScore): IMatch[] {
|
||||
|
||||
@@ -11,5 +11,11 @@
|
||||
</body>
|
||||
|
||||
<!-- Startup via workbench.js -->
|
||||
<script src="../../../../../out/vs/code/browser/workbench/workbench.js"></script>
|
||||
<script>
|
||||
self.CONNECTION_AUTH_TOKEN = '{{CONNECTION_AUTH_TOKEN}}';
|
||||
self.USER_HOME_DIR = '{{USER_HOME_DIR}}';
|
||||
</script>
|
||||
|
||||
<!-- Startup via workbench.js -->
|
||||
<script src="./out/vs/code/browser/workbench/workbench.js"></script>
|
||||
</html>
|
||||
@@ -17,7 +17,7 @@
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
loadScript('../../../../../out/vs/loader.js', function () {
|
||||
loadScript('./out/vs/loader.js', function () {
|
||||
|
||||
// @ts-ignore
|
||||
require.config({
|
||||
|
||||
@@ -33,7 +33,7 @@ import { EnvironmentService } from 'vs/platform/environment/node/environmentServ
|
||||
import { IssueReporterModel, IssueReporterData as IssueReporterModelData } from 'vs/code/electron-browser/issue/issueReporterModel';
|
||||
import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData, IssueReporterFeatures, IssueReporterExtensionData } from 'vs/platform/issue/common/issue';
|
||||
import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage';
|
||||
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { createBufferSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/node/logIpc';
|
||||
import { ILogService, getLogLevel } from 'vs/platform/log/common/log';
|
||||
import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
|
||||
@@ -300,7 +300,7 @@ export class IssueReporter extends Disposable {
|
||||
serviceCollection.set(IWindowsService, new WindowsService(mainProcessService));
|
||||
this.environmentService = new EnvironmentService(configuration, configuration.execPath);
|
||||
|
||||
const logService = createSpdLogService(`issuereporter${configuration.windowId}`, getLogLevel(this.environmentService), this.environmentService.logsPath);
|
||||
const logService = createBufferSpdLogService(`issuereporter${configuration.windowId}`, getLogLevel(this.environmentService), this.environmentService.logsPath);
|
||||
const logLevelClient = new LogLevelSetterChannelClient(mainProcessService.getChannel('loglevel'));
|
||||
this.logService = new FollowerLogService(logLevelClient, logService);
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppen
|
||||
import { IWindowsService, ActiveWindowManager } from 'vs/platform/windows/common/windows';
|
||||
import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { createBufferSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
|
||||
import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/node/logIpc';
|
||||
import { LocalizationsService } from 'vs/platform/localizations/node/localizations';
|
||||
@@ -94,7 +94,7 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
|
||||
|
||||
const mainRouter = new StaticRouter(ctx => ctx === 'main');
|
||||
const logLevelClient = new LogLevelSetterChannelClient(server.getChannel('loglevel', mainRouter));
|
||||
const logService = new FollowerLogService(logLevelClient, createSpdLogService('sharedprocess', initData.logLevel, environmentService.logsPath));
|
||||
const logService = new FollowerLogService(logLevelClient, createBufferSpdLogService('sharedprocess', initData.logLevel, environmentService.logsPath));
|
||||
disposables.push(logService);
|
||||
|
||||
logService.info('main', JSON.stringify(configuration));
|
||||
@@ -122,7 +122,7 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
|
||||
const services = new ServiceCollection();
|
||||
const environmentService = accessor.get(IEnvironmentService);
|
||||
const { appRoot, extensionsPath, extensionDevelopmentLocationURI: extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService;
|
||||
const telemetryLogService = new FollowerLogService(logLevelClient, createSpdLogService('telemetry', initData.logLevel, environmentService.logsPath));
|
||||
const telemetryLogService = new FollowerLogService(logLevelClient, createBufferSpdLogService('telemetry', initData.logLevel, environmentService.logsPath));
|
||||
telemetryLogService.info('The below are logs for every telemetry event sent from VS Code once the log level is set to trace.');
|
||||
telemetryLogService.info('===========================================================');
|
||||
|
||||
|
||||
@@ -288,7 +288,7 @@ function startup(args: ParsedArgs): void {
|
||||
return Promise.reject(error);
|
||||
})
|
||||
.then(mainIpcServer => {
|
||||
bufferLogService.logger = createSpdLogService('main', bufferLogService.getLevel(), environmentService.logsPath);
|
||||
createSpdLogService('main', bufferLogService.getLevel(), environmentService.logsPath).then(logger => bufferLogService.logger = logger);
|
||||
|
||||
return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup();
|
||||
});
|
||||
|
||||
@@ -31,7 +31,7 @@ import { mkdirp, writeFile } from 'vs/base/node/pfs';
|
||||
import { getBaseLabel } from 'vs/base/common/labels';
|
||||
import { IStateService } from 'vs/platform/state/common/state';
|
||||
import { StateService } from 'vs/platform/state/node/stateService';
|
||||
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { createBufferSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { ILogService, getLogLevel } from 'vs/platform/log/common/log';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { areSameExtensions, adoptToGalleryExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
@@ -291,7 +291,7 @@ export function main(argv: ParsedArgs): Promise<void> {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
const environmentService = new EnvironmentService(argv, process.execPath);
|
||||
const logService = createSpdLogService('cli', getLogLevel(environmentService), environmentService.logsPath);
|
||||
const logService = createBufferSpdLogService('cli', getLogLevel(environmentService), environmentService.logsPath);
|
||||
process.once('exit', () => logService.dispose());
|
||||
|
||||
logService.info('main', argv);
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Range } from 'vs/editor/common/core/range';
|
||||
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { EditorKeybindingCancellationTokenSource } from 'vs/editor/browser/core/keybindingCancellation';
|
||||
|
||||
export const enum CodeEditorStateFlag {
|
||||
Value = 1,
|
||||
@@ -78,12 +79,12 @@ export class EditorState {
|
||||
* A cancellation token source that cancels when the editor changes as expressed
|
||||
* by the provided flags
|
||||
*/
|
||||
export class EditorStateCancellationTokenSource extends CancellationTokenSource {
|
||||
export class EditorStateCancellationTokenSource extends EditorKeybindingCancellationTokenSource {
|
||||
|
||||
private readonly _listener: IDisposable[] = [];
|
||||
|
||||
constructor(readonly editor: IActiveCodeEditor, flags: CodeEditorStateFlag, parent?: CancellationToken) {
|
||||
super(parent);
|
||||
super(editor, parent);
|
||||
|
||||
if (flags & CodeEditorStateFlag.Position) {
|
||||
this._listener.push(editor.onDidChangeCursorPosition(_ => this.cancel()));
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { LinkedList } from 'vs/base/common/linkedList';
|
||||
import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
|
||||
|
||||
const IEditorCancellationTokens = createDecorator<IEditorCancellationTokens>('IEditorCancelService');
|
||||
|
||||
interface IEditorCancellationTokens {
|
||||
_serviceBrand: any;
|
||||
add(editor: ICodeEditor, cts: CancellationTokenSource): () => void;
|
||||
cancel(editor: ICodeEditor): void;
|
||||
}
|
||||
|
||||
const ctxCancellableOperation = new RawContextKey('cancellableOperation', false);
|
||||
|
||||
registerSingleton(IEditorCancellationTokens, class implements IEditorCancellationTokens {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private readonly _tokens = new WeakMap<ICodeEditor, { key: IContextKey<boolean>, tokens: LinkedList<CancellationTokenSource> }>();
|
||||
|
||||
add(editor: ICodeEditor, cts: CancellationTokenSource): () => void {
|
||||
let data = this._tokens.get(editor);
|
||||
if (!data) {
|
||||
data = editor.invokeWithinContext(accessor => {
|
||||
const key = ctxCancellableOperation.bindTo(accessor.get(IContextKeyService));
|
||||
const tokens = new LinkedList<CancellationTokenSource>();
|
||||
return { key, tokens };
|
||||
});
|
||||
this._tokens.set(editor, data);
|
||||
}
|
||||
|
||||
let removeFn: Function | undefined;
|
||||
|
||||
data.key.set(true);
|
||||
removeFn = data.tokens.push(cts);
|
||||
|
||||
return () => {
|
||||
// remove w/o cancellation
|
||||
if (removeFn) {
|
||||
removeFn();
|
||||
data!.key.set(!data!.tokens.isEmpty());
|
||||
removeFn = undefined;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
cancel(editor: ICodeEditor): void {
|
||||
const data = this._tokens.get(editor);
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
// remove with cancellation
|
||||
const cts = data.tokens.pop();
|
||||
if (cts) {
|
||||
cts.cancel();
|
||||
data.key.set(!data.tokens.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
}, true);
|
||||
|
||||
export class EditorKeybindingCancellationTokenSource extends CancellationTokenSource {
|
||||
|
||||
private readonly _unregister: Function;
|
||||
|
||||
constructor(readonly editor: ICodeEditor, parent?: CancellationToken) {
|
||||
super(parent);
|
||||
this._unregister = editor.invokeWithinContext(accessor => accessor.get(IEditorCancellationTokens).add(editor, this));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._unregister();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorCommand(new class extends EditorCommand {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.cancelOperation',
|
||||
kbOpts: {
|
||||
weight: KeybindingWeight.EditorContrib,
|
||||
primary: KeyCode.Escape
|
||||
},
|
||||
precondition: ctxCancellableOperation
|
||||
});
|
||||
}
|
||||
|
||||
runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
accessor.get(IEditorCancellationTokens).cancel(editor);
|
||||
}
|
||||
});
|
||||
@@ -323,9 +323,9 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
|
||||
}
|
||||
|
||||
if (this._renderSideBySide) {
|
||||
this._setStrategy(new DiffEdtorWidgetSideBySide(this._createDataSource(), this._enableSplitViewResizing));
|
||||
this._setStrategy(new DiffEditorWidgetSideBySide(this._createDataSource(), this._enableSplitViewResizing));
|
||||
} else {
|
||||
this._setStrategy(new DiffEdtorWidgetInline(this._createDataSource(), this._enableSplitViewResizing));
|
||||
this._setStrategy(new DiffEditorWidgetInline(this._createDataSource(), this._enableSplitViewResizing));
|
||||
}
|
||||
|
||||
this._register(themeService.onThemeChange(t => {
|
||||
@@ -597,9 +597,9 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
|
||||
// renderSideBySide
|
||||
if (renderSideBySideChanged) {
|
||||
if (this._renderSideBySide) {
|
||||
this._setStrategy(new DiffEdtorWidgetSideBySide(this._createDataSource(), this._enableSplitViewResizing));
|
||||
this._setStrategy(new DiffEditorWidgetSideBySide(this._createDataSource(), this._enableSplitViewResizing));
|
||||
} else {
|
||||
this._setStrategy(new DiffEdtorWidgetInline(this._createDataSource(), this._enableSplitViewResizing));
|
||||
this._setStrategy(new DiffEditorWidgetInline(this._createDataSource(), this._enableSplitViewResizing));
|
||||
}
|
||||
// Update class name
|
||||
this._containerDomElement.className = DiffEditorWidget._getClassName(this._themeService.getTheme(), this._renderSideBySide);
|
||||
@@ -1547,7 +1547,7 @@ const DECORATIONS = {
|
||||
|
||||
};
|
||||
|
||||
class DiffEdtorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffEditorWidgetStyle, IVerticalSashLayoutProvider {
|
||||
class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffEditorWidgetStyle, IVerticalSashLayoutProvider {
|
||||
|
||||
static MINIMUM_EDITOR_WIDTH = 100;
|
||||
|
||||
@@ -1592,13 +1592,13 @@ class DiffEdtorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffEd
|
||||
|
||||
sashPosition = this._disableSash ? midPoint : sashPosition || midPoint;
|
||||
|
||||
if (contentWidth > DiffEdtorWidgetSideBySide.MINIMUM_EDITOR_WIDTH * 2) {
|
||||
if (sashPosition < DiffEdtorWidgetSideBySide.MINIMUM_EDITOR_WIDTH) {
|
||||
sashPosition = DiffEdtorWidgetSideBySide.MINIMUM_EDITOR_WIDTH;
|
||||
if (contentWidth > DiffEditorWidgetSideBySide.MINIMUM_EDITOR_WIDTH * 2) {
|
||||
if (sashPosition < DiffEditorWidgetSideBySide.MINIMUM_EDITOR_WIDTH) {
|
||||
sashPosition = DiffEditorWidgetSideBySide.MINIMUM_EDITOR_WIDTH;
|
||||
}
|
||||
|
||||
if (sashPosition > contentWidth - DiffEdtorWidgetSideBySide.MINIMUM_EDITOR_WIDTH) {
|
||||
sashPosition = contentWidth - DiffEdtorWidgetSideBySide.MINIMUM_EDITOR_WIDTH;
|
||||
if (sashPosition > contentWidth - DiffEditorWidgetSideBySide.MINIMUM_EDITOR_WIDTH) {
|
||||
sashPosition = contentWidth - DiffEditorWidgetSideBySide.MINIMUM_EDITOR_WIDTH;
|
||||
}
|
||||
} else {
|
||||
sashPosition = midPoint;
|
||||
@@ -1807,7 +1807,7 @@ class SideBySideViewZonesComputer extends ViewZonesComputer {
|
||||
}
|
||||
}
|
||||
|
||||
class DiffEdtorWidgetInline extends DiffEditorWidgetStyle implements IDiffEditorWidgetStyle {
|
||||
class DiffEditorWidgetInline extends DiffEditorWidgetStyle implements IDiffEditorWidgetStyle {
|
||||
|
||||
private decorationsLeft: number;
|
||||
|
||||
|
||||
@@ -248,7 +248,7 @@ class EditorModelManager extends Disposable {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public esureSyncedResources(resources: URI[]): void {
|
||||
public ensureSyncedResources(resources: URI[]): void {
|
||||
for (const resource of resources) {
|
||||
let resourceStr = resource.toString();
|
||||
|
||||
@@ -387,7 +387,7 @@ export class EditorWorkerClient extends Disposable {
|
||||
|
||||
protected _withSyncedResources(resources: URI[]): Promise<EditorSimpleWorkerImpl> {
|
||||
return this._getProxy().then((proxy) => {
|
||||
this._getOrCreateModelManager(proxy).esureSyncedResources(resources);
|
||||
this._getOrCreateModelManager(proxy).ensureSyncedResources(resources);
|
||||
return proxy;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { illegalArgument, onUnexpectedExternalError } from 'vs/base/common/errors';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { CodeEditorStateFlag, EditorState, EditorStateCancellationTokenSource, TextModelCancellationTokenSource } from 'vs/editor/browser/core/editorState';
|
||||
import { CodeEditorStateFlag, EditorStateCancellationTokenSource, TextModelCancellationTokenSource } from 'vs/editor/browser/core/editorState';
|
||||
import { IActiveCodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { registerLanguageCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
@@ -143,27 +143,25 @@ export async function formatDocumentRangeWithProvider(
|
||||
const workerService = accessor.get(IEditorWorkerService);
|
||||
|
||||
let model: ITextModel;
|
||||
let validate: () => boolean;
|
||||
let cts: CancellationTokenSource;
|
||||
if (isCodeEditor(editorOrModel)) {
|
||||
model = editorOrModel.getModel();
|
||||
const state = new EditorState(editorOrModel, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position);
|
||||
validate = () => state.validate(editorOrModel);
|
||||
cts = new EditorStateCancellationTokenSource(editorOrModel, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position, token);
|
||||
} else {
|
||||
model = editorOrModel;
|
||||
const versionNow = editorOrModel.getVersionId();
|
||||
validate = () => versionNow === editorOrModel.getVersionId();
|
||||
cts = new TextModelCancellationTokenSource(editorOrModel, token);
|
||||
}
|
||||
|
||||
const rawEdits = await provider.provideDocumentRangeFormattingEdits(
|
||||
model,
|
||||
range,
|
||||
model.getFormattingOptions(),
|
||||
token
|
||||
cts.token
|
||||
);
|
||||
|
||||
const edits = await workerService.computeMoreMinimalEdits(model.uri, rawEdits);
|
||||
|
||||
if (!validate()) {
|
||||
if (cts.token.isCancellationRequested) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { alert } from 'vs/base/browser/ui/aria/aria';
|
||||
import { createCancelablePromise } from 'vs/base/common/async';
|
||||
import { createCancelablePromise, raceCancellation } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
@@ -64,9 +64,9 @@ export class DefinitionAction extends EditorAction {
|
||||
|
||||
const cts = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position);
|
||||
|
||||
const definitionPromise = this._getTargetLocationForPosition(model, pos, cts.token).then(async references => {
|
||||
const definitionPromise = raceCancellation(this._getTargetLocationForPosition(model, pos, cts.token), cts.token).then(async references => {
|
||||
|
||||
if (cts.token.isCancellationRequested || model.isDisposed() || editor.getModel() !== model) {
|
||||
if (!references || model.isDisposed()) {
|
||||
// new model, no more model
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { fuzzyScore, fuzzyScoreGracefulAggressive, anyScore, FuzzyScorer, FuzzyScore } from 'vs/base/common/filters';
|
||||
import { fuzzyScore, fuzzyScoreGracefulAggressive, FuzzyScorer, FuzzyScore } from 'vs/base/common/filters';
|
||||
import { isDisposable } from 'vs/base/common/lifecycle';
|
||||
import { CompletionList, CompletionItemProvider, CompletionItemKind } from 'vs/editor/common/modes';
|
||||
import { CompletionItem } from './suggest';
|
||||
import { InternalSuggestOptions, EDITOR_DEFAULTS } from 'vs/editor/common/config/editorOptions';
|
||||
import { WordDistance } from 'vs/editor/contrib/suggest/wordDistance';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { compareIgnoreCase } from 'vs/base/common/strings';
|
||||
|
||||
type StrictCompletionItem = Required<CompletionItem>;
|
||||
|
||||
@@ -215,8 +216,15 @@ export class CompletionModel {
|
||||
if (!match) {
|
||||
continue; // NO match
|
||||
}
|
||||
item.score = anyScore(word, wordLow, 0, item.completion.label, item.labelLow, 0);
|
||||
item.score[0] = match[0]; // use score from filterText
|
||||
if (compareIgnoreCase(item.completion.filterText, item.completion.label) === 0) {
|
||||
// filterText and label are actually the same -> use good highlights
|
||||
item.score = match;
|
||||
} else {
|
||||
// re-run the scorer on the label in the hope of a result BUT use the rank
|
||||
// of the filterText-match
|
||||
item.score = scoreFn(word, wordLow, wordPos, item.completion.label, item.labelLow, 0, false) || FuzzyScore.Default;
|
||||
item.score[0] = match[0]; // use score from filterText
|
||||
}
|
||||
|
||||
} else {
|
||||
// by default match `word` against the `label`
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ISelectedSuggestion, SuggestWidget } from './suggestWidget';
|
||||
import { CharacterSet } from 'vs/editor/common/core/characterClassifier';
|
||||
|
||||
export class CommitCharacterController {
|
||||
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
private _active?: {
|
||||
readonly acceptCharacters: CharacterSet;
|
||||
readonly item: ISelectedSuggestion;
|
||||
};
|
||||
|
||||
constructor(editor: ICodeEditor, widget: SuggestWidget, accept: (selected: ISelectedSuggestion) => any) {
|
||||
|
||||
this._disposables.push(widget.onDidShow(() => this._onItem(widget.getFocusedItem())));
|
||||
this._disposables.push(widget.onDidFocus(this._onItem, this));
|
||||
this._disposables.push(widget.onDidHide(this.reset, this));
|
||||
|
||||
this._disposables.push(editor.onWillType(text => {
|
||||
if (this._active) {
|
||||
const ch = text.charCodeAt(text.length - 1);
|
||||
if (this._active.acceptCharacters.has(ch) && editor.getConfiguration().contribInfo.acceptSuggestionOnCommitCharacter) {
|
||||
accept(this._active.item);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private _onItem(selected: ISelectedSuggestion | undefined): void {
|
||||
if (!selected || !isNonEmptyArray(selected.item.completion.commitCharacters)) {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
const acceptCharacters = new CharacterSet();
|
||||
for (const ch of selected.item.completion.commitCharacters) {
|
||||
if (ch.length > 0) {
|
||||
acceptCharacters.add(ch.charCodeAt(0));
|
||||
}
|
||||
}
|
||||
this._active = { acceptCharacters, item: selected };
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this._active = undefined;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
dispose(this._disposables);
|
||||
}
|
||||
}
|
||||
@@ -31,57 +31,8 @@ import { WordContextKey } from 'vs/editor/contrib/suggest/wordContextKey';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
|
||||
import { IdleValue } from 'vs/base/common/async';
|
||||
import { CharacterSet } from 'vs/editor/common/core/characterClassifier';
|
||||
import { isObject } from 'vs/base/common/types';
|
||||
|
||||
class AcceptOnCharacterOracle {
|
||||
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
private _active?: {
|
||||
readonly acceptCharacters: CharacterSet;
|
||||
readonly item: ISelectedSuggestion;
|
||||
};
|
||||
|
||||
constructor(editor: ICodeEditor, widget: SuggestWidget, accept: (selected: ISelectedSuggestion) => any) {
|
||||
|
||||
this._disposables.push(widget.onDidShow(() => this._onItem(widget.getFocusedItem())));
|
||||
this._disposables.push(widget.onDidFocus(this._onItem, this));
|
||||
this._disposables.push(widget.onDidHide(this.reset, this));
|
||||
|
||||
this._disposables.push(editor.onWillType(text => {
|
||||
if (this._active) {
|
||||
const ch = text.charCodeAt(text.length - 1);
|
||||
if (this._active.acceptCharacters.has(ch) && editor.getConfiguration().contribInfo.acceptSuggestionOnCommitCharacter) {
|
||||
accept(this._active.item);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private _onItem(selected: ISelectedSuggestion | undefined): void {
|
||||
if (!selected || !isNonEmptyArray(selected.item.completion.commitCharacters)) {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
const acceptCharacters = new CharacterSet();
|
||||
for (const ch of selected.item.completion.commitCharacters) {
|
||||
if (ch.length > 0) {
|
||||
acceptCharacters.add(ch.charCodeAt(0));
|
||||
}
|
||||
}
|
||||
this._active = { acceptCharacters, item: selected };
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this._active = undefined;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
dispose(this._disposables);
|
||||
}
|
||||
}
|
||||
import { CommitCharacterController } from './suggestCommitCharacters';
|
||||
|
||||
export class SuggestController implements IEditorContribution {
|
||||
|
||||
@@ -113,15 +64,15 @@ export class SuggestController implements IEditorContribution {
|
||||
const widget = this._instantiationService.createInstance(SuggestWidget, this._editor);
|
||||
|
||||
this._toDispose.push(widget);
|
||||
this._toDispose.push(widget.onDidSelect(item => this._onDidSelectItem(item, false, true), this));
|
||||
this._toDispose.push(widget.onDidSelect(item => this._insertSuggestion(item, false, true), this));
|
||||
|
||||
// Wire up logic to accept a suggestion on certain characters
|
||||
const autoAcceptOracle = new AcceptOnCharacterOracle(this._editor, widget, item => this._onDidSelectItem(item, false, true));
|
||||
const commitCharacterController = new CommitCharacterController(this._editor, widget, item => this._insertSuggestion(item, false, true));
|
||||
this._toDispose.push(
|
||||
autoAcceptOracle,
|
||||
commitCharacterController,
|
||||
this._model.onDidSuggest(e => {
|
||||
if (e.completionModel.items.length === 0) {
|
||||
autoAcceptOracle.reset();
|
||||
commitCharacterController.reset();
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -210,7 +161,7 @@ export class SuggestController implements IEditorContribution {
|
||||
}
|
||||
}
|
||||
|
||||
protected _onDidSelectItem(event: ISelectedSuggestion | undefined, keepAlternativeSuggestions: boolean, undoStops: boolean): void {
|
||||
protected _insertSuggestion(event: ISelectedSuggestion | undefined, keepAlternativeSuggestions: boolean, undoStops: boolean): void {
|
||||
if (!event || !event.item) {
|
||||
this._alternatives.getValue().reset();
|
||||
this._model.cancel();
|
||||
@@ -282,7 +233,7 @@ export class SuggestController implements IEditorContribution {
|
||||
if (modelVersionNow !== model.getAlternativeVersionId()) {
|
||||
model.undo();
|
||||
}
|
||||
this._onDidSelectItem(next, false, false);
|
||||
this._insertSuggestion(next, false, false);
|
||||
break;
|
||||
}
|
||||
});
|
||||
@@ -364,7 +315,7 @@ export class SuggestController implements IEditorContribution {
|
||||
return;
|
||||
}
|
||||
this._editor.pushUndoStop();
|
||||
this._onDidSelectItem({ index, item, model: completionModel }, true, false);
|
||||
this._insertSuggestion({ index, item, model: completionModel }, true, false);
|
||||
|
||||
}, undefined, listener);
|
||||
});
|
||||
@@ -375,10 +326,8 @@ export class SuggestController implements IEditorContribution {
|
||||
}
|
||||
|
||||
acceptSelectedSuggestion(keepAlternativeSuggestions?: boolean): void {
|
||||
if (this._widget) {
|
||||
const item = this._widget.getValue().getFocusedItem();
|
||||
this._onDidSelectItem(item, !!keepAlternativeSuggestions, true);
|
||||
}
|
||||
const item = this._widget.getValue().getFocusedItem();
|
||||
this._insertSuggestion(item, !!keepAlternativeSuggestions, true);
|
||||
}
|
||||
|
||||
acceptNextSuggestion() {
|
||||
@@ -390,58 +339,40 @@ export class SuggestController implements IEditorContribution {
|
||||
}
|
||||
|
||||
cancelSuggestWidget(): void {
|
||||
if (this._widget) {
|
||||
this._model.cancel();
|
||||
this._widget.getValue().hideWidget();
|
||||
}
|
||||
this._model.cancel();
|
||||
this._widget.getValue().hideWidget();
|
||||
}
|
||||
|
||||
selectNextSuggestion(): void {
|
||||
if (this._widget) {
|
||||
this._widget.getValue().selectNext();
|
||||
}
|
||||
this._widget.getValue().selectNext();
|
||||
}
|
||||
|
||||
selectNextPageSuggestion(): void {
|
||||
if (this._widget) {
|
||||
this._widget.getValue().selectNextPage();
|
||||
}
|
||||
this._widget.getValue().selectNextPage();
|
||||
}
|
||||
|
||||
selectLastSuggestion(): void {
|
||||
if (this._widget) {
|
||||
this._widget.getValue().selectLast();
|
||||
}
|
||||
this._widget.getValue().selectLast();
|
||||
}
|
||||
|
||||
selectPrevSuggestion(): void {
|
||||
if (this._widget) {
|
||||
this._widget.getValue().selectPrevious();
|
||||
}
|
||||
this._widget.getValue().selectPrevious();
|
||||
}
|
||||
|
||||
selectPrevPageSuggestion(): void {
|
||||
if (this._widget) {
|
||||
this._widget.getValue().selectPreviousPage();
|
||||
}
|
||||
this._widget.getValue().selectPreviousPage();
|
||||
}
|
||||
|
||||
selectFirstSuggestion(): void {
|
||||
if (this._widget) {
|
||||
this._widget.getValue().selectFirst();
|
||||
}
|
||||
this._widget.getValue().selectFirst();
|
||||
}
|
||||
|
||||
toggleSuggestionDetails(): void {
|
||||
if (this._widget) {
|
||||
this._widget.getValue().toggleDetails();
|
||||
}
|
||||
this._widget.getValue().toggleDetails();
|
||||
}
|
||||
|
||||
toggleSuggestionFocus(): void {
|
||||
if (this._widget) {
|
||||
this._widget.getValue().toggleDetailsFocus();
|
||||
}
|
||||
this._widget.getValue().toggleDetailsFocus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,6 @@ export class SuggestModel implements IDisposable {
|
||||
private _quickSuggestDelay: number;
|
||||
private _triggerCharacterListener: IDisposable;
|
||||
private readonly _triggerQuickSuggest = new TimeoutTimer();
|
||||
private readonly _triggerRefilter = new TimeoutTimer();
|
||||
private _state: State = State.Idle;
|
||||
|
||||
private _requestToken?: CancellationTokenSource;
|
||||
@@ -161,7 +160,7 @@ export class SuggestModel implements IDisposable {
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose([this._onDidCancel, this._onDidSuggest, this._onDidTrigger, this._triggerCharacterListener, this._triggerQuickSuggest, this._triggerRefilter]);
|
||||
dispose([this._onDidCancel, this._onDidSuggest, this._onDidTrigger, this._triggerCharacterListener, this._triggerQuickSuggest]);
|
||||
this._toDispose = dispose(this._toDispose);
|
||||
dispose(this._completionModel);
|
||||
this.cancel();
|
||||
@@ -221,7 +220,6 @@ export class SuggestModel implements IDisposable {
|
||||
|
||||
cancel(retrigger: boolean = false): void {
|
||||
if (this._state !== State.Idle) {
|
||||
this._triggerRefilter.cancel();
|
||||
this._triggerQuickSuggest.cancel();
|
||||
if (this._requestToken) {
|
||||
this._requestToken.cancel();
|
||||
@@ -328,14 +326,15 @@ export class SuggestModel implements IDisposable {
|
||||
}
|
||||
|
||||
private _refilterCompletionItems(): void {
|
||||
if (this._state === State.Idle) {
|
||||
return;
|
||||
}
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
// refine active suggestion
|
||||
this._triggerRefilter.cancelAndSet(() => {
|
||||
// Re-filter suggestions. This MUST run async because filtering/scoring
|
||||
// uses the model content AND the cursor position. The latter is NOT
|
||||
// updated when the document has changed (the event which drives this method)
|
||||
// and therefore a little pause (next mirco task) is needed. See:
|
||||
// https://stackoverflow.com/questions/25915634/difference-between-microtask-and-macrotask-within-an-event-loop-context#25933985
|
||||
Promise.resolve().then(() => {
|
||||
if (this._state === State.Idle) {
|
||||
return;
|
||||
}
|
||||
if (!this._editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
@@ -343,7 +342,7 @@ export class SuggestModel implements IDisposable {
|
||||
const position = this._editor.getPosition();
|
||||
const ctx = new LineContext(model, position, this._state === State.Auto, false);
|
||||
this._onNewContext(ctx);
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
|
||||
trigger(context: SuggestTriggerContext, retrigger: boolean = false, onlyFrom?: Set<CompletionItemProvider>, existingItems?: CompletionItem[]): void {
|
||||
|
||||
@@ -30,7 +30,6 @@ import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { TimeoutTimer, CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { CompletionItemKind, completionKindToCssClass } from 'vs/editor/common/modes';
|
||||
import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
|
||||
@@ -545,10 +544,8 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate<Compl
|
||||
return;
|
||||
}
|
||||
|
||||
item.resolve(CancellationToken.None).then(() => {
|
||||
this.onDidSelectEmitter.fire({ item, index, model: completionModel });
|
||||
this.editor.focus();
|
||||
});
|
||||
this.onDidSelectEmitter.fire({ item, index, model: completionModel });
|
||||
this.editor.focus();
|
||||
}
|
||||
|
||||
private _getSuggestionAriaAlertLabel(item: CompletionItem): string {
|
||||
|
||||
@@ -671,8 +671,8 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
|
||||
return withOracle(async (sugget, editor) => {
|
||||
class TestCtrl extends SuggestController {
|
||||
_onDidSelectItem(item: ISelectedSuggestion) {
|
||||
super._onDidSelectItem(item, false, true);
|
||||
_insertSuggestion(item: ISelectedSuggestion) {
|
||||
super._insertSuggestion(item, false, true);
|
||||
}
|
||||
}
|
||||
const ctrl = <TestCtrl>editor.registerAndInstantiateContribution(TestCtrl);
|
||||
@@ -687,7 +687,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () {
|
||||
const [first] = event.completionModel.items;
|
||||
assert.equal(first.completion.label, 'bar');
|
||||
|
||||
ctrl._onDidSelectItem({ item: first, index: 0, model: event.completionModel });
|
||||
ctrl._insertSuggestion({ item: first, index: 0, model: event.completionModel });
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
|
||||
@@ -162,7 +162,6 @@ export interface IExtensionGalleryService {
|
||||
getChangelog(extension: IGalleryExtension, token: CancellationToken): Promise<string>;
|
||||
getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise<ITranslation | null>;
|
||||
getAllVersions(extension: IGalleryExtension, compatible: boolean): Promise<IGalleryExtensionVersion[]>;
|
||||
loadAllDependencies(dependencies: IExtensionIdentifier[], token: CancellationToken): Promise<IGalleryExtension[]>;
|
||||
getExtensionsReport(): Promise<IReportedExtension[]>;
|
||||
getCompatibleExtension(extension: IGalleryExtension): Promise<IGalleryExtension | null>;
|
||||
getCompatibleExtension(id: IExtensionIdentifier, version?: string): Promise<IGalleryExtension | null>;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { tmpdir } from 'os';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import { distinct } from 'vs/base/common/arrays';
|
||||
import { getErrorMessage, isPromiseCanceledError, canceled } from 'vs/base/common/errors';
|
||||
import { StatisticType, IGalleryExtension, IExtensionGalleryService, IGalleryExtensionAsset, IQueryOptions, SortBy, SortOrder, IExtensionIdentifier, IReportedExtension, InstallOperation, ITranslation, IGalleryExtensionVersion, IGalleryExtensionAssets, isIExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { getGalleryExtensionId, getGalleryExtensionTelemetryData, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
@@ -750,10 +749,6 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
return Promise.resolve('');
|
||||
}
|
||||
|
||||
loadAllDependencies(extensions: IExtensionIdentifier[], token: CancellationToken): Promise<IGalleryExtension[]> {
|
||||
return this.getDependenciesRecursively(extensions.map(e => e.id), [], token);
|
||||
}
|
||||
|
||||
getAllVersions(extension: IGalleryExtension, compatible: boolean): Promise<IGalleryExtensionVersion[]> {
|
||||
let query = new Query()
|
||||
.withFlags(Flags.IncludeVersions, Flags.IncludeFiles, Flags.IncludeVersionProperties, Flags.ExcludeNonValidated)
|
||||
@@ -782,58 +777,6 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
});
|
||||
}
|
||||
|
||||
private loadDependencies(extensionNames: string[], token: CancellationToken): Promise<IGalleryExtension[]> {
|
||||
if (!extensionNames || extensionNames.length === 0) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
let query = new Query()
|
||||
.withFlags(Flags.IncludeLatestVersionOnly, Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeFiles, Flags.IncludeVersionProperties)
|
||||
.withPage(1, extensionNames.length)
|
||||
.withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code')
|
||||
.withFilter(FilterType.ExcludeWithFlags, flagsToString(Flags.Unpublished))
|
||||
.withAssetTypes(AssetType.Icon, AssetType.License, AssetType.Details, AssetType.Manifest, AssetType.VSIX)
|
||||
.withFilter(FilterType.ExtensionName, ...extensionNames);
|
||||
|
||||
return this.queryGallery(query, token).then(result => {
|
||||
const dependencies: IGalleryExtension[] = [];
|
||||
const ids: string[] = [];
|
||||
|
||||
for (let index = 0; index < result.galleryExtensions.length; index++) {
|
||||
const rawExtension = result.galleryExtensions[index];
|
||||
if (ids.indexOf(rawExtension.extensionId) === -1) {
|
||||
dependencies.push(toExtension(rawExtension, rawExtension.versions[0], index, query, 'dependencies'));
|
||||
ids.push(rawExtension.extensionId);
|
||||
}
|
||||
}
|
||||
return dependencies;
|
||||
});
|
||||
}
|
||||
|
||||
private getDependenciesRecursively(toGet: string[], result: IGalleryExtension[], token: CancellationToken): Promise<IGalleryExtension[]> {
|
||||
if (!toGet || !toGet.length) {
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
toGet = result.length ? toGet.filter(e => !ExtensionGalleryService.hasExtensionByName(result, e)) : toGet;
|
||||
if (!toGet.length) {
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
return this.loadDependencies(toGet, token)
|
||||
.then(loadedDependencies => {
|
||||
const dependenciesSet = new Set<string>();
|
||||
for (const dep of loadedDependencies) {
|
||||
if (dep.properties.dependencies) {
|
||||
dep.properties.dependencies.forEach(d => dependenciesSet.add(d));
|
||||
}
|
||||
}
|
||||
result = distinct(result.concat(loadedDependencies), d => d.identifier.uuid);
|
||||
const dependencies: string[] = [];
|
||||
dependenciesSet.forEach(d => !ExtensionGalleryService.hasExtensionByName(result, d) && dependencies.push(d));
|
||||
return this.getDependenciesRecursively(dependencies, result, token);
|
||||
});
|
||||
}
|
||||
|
||||
private getAsset(asset: IGalleryExtensionAsset, options: IRequestOptions = {}, token: CancellationToken = CancellationToken.None): Promise<IRequestContext> {
|
||||
return this.commonHeadersPromise.then(commonHeaders => {
|
||||
const baseOptions = { type: 'GET' };
|
||||
@@ -952,15 +895,6 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
});
|
||||
}
|
||||
|
||||
private static hasExtensionByName(extensions: IGalleryExtension[], name: string): boolean {
|
||||
for (const extension of extensions) {
|
||||
if (`${extension.publisher}.${extension.name}` === name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getExtensionsReport(): Promise<IReportedExtension[]> {
|
||||
if (!this.isEnabled()) {
|
||||
return Promise.reject(new Error('No extension gallery service configured.'));
|
||||
|
||||
@@ -16,18 +16,24 @@ export function isUIExtension(manifest: IExtensionManifest, uiContributions: str
|
||||
case 'ui': return true;
|
||||
case 'workspace': return false;
|
||||
default: {
|
||||
// Tagged as UI extension in product
|
||||
if (isNonEmptyArray(product.uiExtensions) && product.uiExtensions.some(id => areSameExtensions({ id }, { id: extensionId }))) {
|
||||
return true;
|
||||
}
|
||||
// Not an UI extension if it has main
|
||||
if (manifest.main) {
|
||||
return false;
|
||||
}
|
||||
// Not an UI extension if it has dependencies or an extension pack
|
||||
if (isNonEmptyArray(manifest.extensionDependencies) || isNonEmptyArray(manifest.extensionPack)) {
|
||||
return false;
|
||||
}
|
||||
if (manifest.contributes) {
|
||||
// Not an UI extension if it has no ui contributions
|
||||
if (!uiContributions.length || Object.keys(manifest.contributes).some(contribution => uiContributions.indexOf(contribution) === -1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Default is UI Extension
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,15 @@
|
||||
import * as path from 'vs/base/common/path';
|
||||
import { ILogService, LogLevel, NullLogService, AbstractLogService } from 'vs/platform/log/common/log';
|
||||
import * as spdlog from 'spdlog';
|
||||
import { BufferLogService } from 'vs/platform/log/common/bufferLog';
|
||||
|
||||
export function createSpdLogService(processName: string, logLevel: LogLevel, logsFolder: string): ILogService {
|
||||
export async function createSpdLogService(processName: string, logLevel: LogLevel, logsFolder: string): Promise<ILogService> {
|
||||
// Do not crash if spdlog cannot be loaded
|
||||
try {
|
||||
const _spdlog: typeof spdlog = require.__$__nodeRequire('spdlog');
|
||||
_spdlog.setAsyncMode(8192, 500);
|
||||
const logfilePath = path.join(logsFolder, `${processName}.log`);
|
||||
const logger = new _spdlog.RotatingLogger(processName, logfilePath, 1024 * 1024 * 5, 6);
|
||||
const logger = await _spdlog.createRotatingLoggerAsync(processName, logfilePath, 1024 * 1024 * 5, 6);
|
||||
logger.setLevel(0);
|
||||
|
||||
return new SpdLogService(logger, logLevel);
|
||||
@@ -28,6 +29,12 @@ export function createRotatingLogger(name: string, filename: string, filesize: n
|
||||
return _spdlog.createRotatingLogger(name, filename, filesize, filecount);
|
||||
}
|
||||
|
||||
export function createBufferSpdLogService(processName: string, logLevel: LogLevel, logsFolder: string): ILogService {
|
||||
const bufferLogService = new BufferLogService();
|
||||
createSpdLogService(processName, logLevel, logsFolder).then(logger => bufferLogService.logger = logger);
|
||||
return bufferLogService;
|
||||
}
|
||||
|
||||
class SpdLogService extends AbstractLogService implements ILogService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
@@ -67,6 +67,7 @@ export interface IProductConfiguration {
|
||||
keyboardShortcutsUrlWin: string;
|
||||
introductoryVideosUrl: string;
|
||||
tipsAndTricksUrl: string;
|
||||
newsletterSignupUrl: string;
|
||||
twitterUrl: string;
|
||||
requestFeatureUrl: string;
|
||||
reportIssueUrl: string;
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IWebSocketFactory, IConnectCallback } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { ISocket } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
|
||||
class BrowserSocket implements ISocket {
|
||||
public readonly socket: WebSocket;
|
||||
|
||||
constructor(socket: WebSocket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.socket.close();
|
||||
}
|
||||
|
||||
public onData(_listener: (e: VSBuffer) => void): IDisposable {
|
||||
const fileReader = new FileReader();
|
||||
const queue: Blob[] = [];
|
||||
let isReading = false;
|
||||
fileReader.onload = function (event) {
|
||||
isReading = false;
|
||||
const buff = <ArrayBuffer>(<any>event.target).result;
|
||||
|
||||
try {
|
||||
_listener(VSBuffer.wrap(new Uint8Array(buff)));
|
||||
} catch (err) {
|
||||
onUnexpectedError(err);
|
||||
}
|
||||
|
||||
if (queue.length > 0) {
|
||||
enqueue(queue.shift()!);
|
||||
}
|
||||
};
|
||||
const enqueue = (blob: Blob) => {
|
||||
if (isReading) {
|
||||
queue.push(blob);
|
||||
return;
|
||||
}
|
||||
isReading = true;
|
||||
fileReader.readAsArrayBuffer(blob);
|
||||
};
|
||||
const listener = (e: MessageEvent) => {
|
||||
enqueue(<Blob>e.data);
|
||||
};
|
||||
this.socket.addEventListener('message', listener);
|
||||
return {
|
||||
dispose: () => this.socket.removeEventListener('message', listener)
|
||||
};
|
||||
}
|
||||
|
||||
public onClose(listener: () => void): IDisposable {
|
||||
this.socket.addEventListener('close', listener);
|
||||
return {
|
||||
dispose: () => this.socket.removeEventListener('close', listener)
|
||||
};
|
||||
}
|
||||
|
||||
public onEnd(listener: () => void): IDisposable {
|
||||
return Disposable.None;
|
||||
}
|
||||
|
||||
public write(buffer: VSBuffer): void {
|
||||
this.socket.send(buffer.buffer);
|
||||
}
|
||||
|
||||
public end(): void {
|
||||
this.socket.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const browserWebSocketFactory = new class implements IWebSocketFactory {
|
||||
connect(host: string, port: number, query: string, callback: IConnectCallback): void {
|
||||
const errorListener = (err: any) => callback(err, undefined);
|
||||
const socket = new WebSocket(`ws://${host}:${port}/?${query}&skipWebSocketFrames=false`);
|
||||
socket.onopen = function (event) {
|
||||
socket.removeEventListener('error', errorListener);
|
||||
callback(undefined, new BrowserSocket(socket));
|
||||
};
|
||||
socket.addEventListener('error', errorListener);
|
||||
}
|
||||
};
|
||||
@@ -3,11 +3,15 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Client, PersistentProtocol, ISocket } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { Client, PersistentProtocol, ISocket, ProtocolConstants } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
|
||||
export const enum ConnectionType {
|
||||
Management = 1,
|
||||
@@ -15,6 +19,37 @@ export const enum ConnectionType {
|
||||
Tunnel = 3,
|
||||
}
|
||||
|
||||
export interface AuthRequest {
|
||||
type: 'auth';
|
||||
auth: string;
|
||||
}
|
||||
|
||||
export interface SignRequest {
|
||||
type: 'sign';
|
||||
data: string;
|
||||
}
|
||||
|
||||
export interface ConnectionTypeRequest {
|
||||
type: 'connectionType';
|
||||
commit?: string;
|
||||
signedData?: string;
|
||||
desiredConnectionType?: ConnectionType;
|
||||
args?: any;
|
||||
isBuilt: boolean;
|
||||
}
|
||||
|
||||
export interface ErrorMessage {
|
||||
type: 'error';
|
||||
reason: string;
|
||||
}
|
||||
|
||||
export interface OKMessage {
|
||||
type: 'ok';
|
||||
}
|
||||
|
||||
export type HandshakeMessage = AuthRequest | SignRequest | ConnectionTypeRequest | ErrorMessage | OKMessage;
|
||||
|
||||
|
||||
interface ISimpleConnectionOptions {
|
||||
isBuilt: boolean;
|
||||
commit: string | undefined;
|
||||
@@ -34,7 +69,84 @@ export interface IWebSocketFactory {
|
||||
}
|
||||
|
||||
async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptions, connectionType: ConnectionType, args: any | undefined): Promise<PersistentProtocol> {
|
||||
throw new Error(`Not implemented`);
|
||||
const protocol = await new Promise<PersistentProtocol>((c, e) => {
|
||||
options.webSocketFactory.connect(
|
||||
options.host,
|
||||
options.port,
|
||||
`reconnectionToken=${options.reconnectionToken}&reconnection=${options.reconnectionProtocol ? 'true' : 'false'}`,
|
||||
(err: any, socket: ISocket) => {
|
||||
if (err) {
|
||||
e(err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.reconnectionProtocol) {
|
||||
options.reconnectionProtocol.beginAcceptReconnection(socket, null);
|
||||
c(options.reconnectionProtocol);
|
||||
} else {
|
||||
c(new PersistentProtocol(socket, null));
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return new Promise<PersistentProtocol>((c, e) => {
|
||||
|
||||
const messageRegistration = protocol.onControlMessage(raw => {
|
||||
const msg = <HandshakeMessage>JSON.parse(raw.toString());
|
||||
// Stop listening for further events
|
||||
messageRegistration.dispose();
|
||||
|
||||
const error = getErrorFromMessage(msg);
|
||||
if (error) {
|
||||
return e(error);
|
||||
}
|
||||
|
||||
if (msg.type === 'sign') {
|
||||
|
||||
let signed = msg.data;
|
||||
if (platform.isNative) {
|
||||
try {
|
||||
const vsda = <any>require.__$__nodeRequire('vsda');
|
||||
const signer = new vsda.signer();
|
||||
if (signer) {
|
||||
signed = signer.sign(msg.data);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('signer.sign: ' + e);
|
||||
}
|
||||
} else {
|
||||
signed = (<any>self).CONNECTION_AUTH_TOKEN;
|
||||
}
|
||||
|
||||
const connTypeRequest: ConnectionTypeRequest = {
|
||||
type: 'connectionType',
|
||||
commit: options.commit,
|
||||
signedData: signed,
|
||||
desiredConnectionType: connectionType,
|
||||
isBuilt: options.isBuilt
|
||||
};
|
||||
if (args) {
|
||||
connTypeRequest.args = args;
|
||||
}
|
||||
protocol.sendControl(VSBuffer.fromString(JSON.stringify(connTypeRequest)));
|
||||
c(protocol);
|
||||
} else {
|
||||
e(new Error('handshake error'));
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
e(new Error('handshake timeout'));
|
||||
}, 2000);
|
||||
|
||||
// TODO@vs-remote: use real nonce here
|
||||
const authRequest: AuthRequest = {
|
||||
type: 'auth',
|
||||
auth: '00000000000000000000'
|
||||
};
|
||||
protocol.sendControl(VSBuffer.fromString(JSON.stringify(authRequest)));
|
||||
});
|
||||
}
|
||||
|
||||
interface IManagementConnectionResult {
|
||||
@@ -148,6 +260,12 @@ export async function connectRemoteAgentTunnel(options: IConnectionOptions, tunn
|
||||
return protocol;
|
||||
}
|
||||
|
||||
function sleep(seconds: number): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
setTimeout(resolve, seconds * 1000);
|
||||
});
|
||||
}
|
||||
|
||||
export const enum PersistenConnectionEventType {
|
||||
ConnectionLost,
|
||||
ReconnectionWait,
|
||||
@@ -184,11 +302,99 @@ abstract class PersistentConnection extends Disposable {
|
||||
public readonly reconnectionToken: string;
|
||||
public readonly protocol: PersistentProtocol;
|
||||
|
||||
private _isReconnecting: boolean;
|
||||
private _permanentFailure: boolean;
|
||||
|
||||
constructor(options: IConnectionOptions, reconnectionToken: string, protocol: PersistentProtocol) {
|
||||
super();
|
||||
this._options = options;
|
||||
this.reconnectionToken = reconnectionToken;
|
||||
this.protocol = protocol;
|
||||
this._isReconnecting = false;
|
||||
this._permanentFailure = false;
|
||||
|
||||
this._register(protocol.onSocketClose(() => this._beginReconnecting()));
|
||||
this._register(protocol.onSocketTimeout(() => this._beginReconnecting()));
|
||||
}
|
||||
|
||||
private async _beginReconnecting(): Promise<void> {
|
||||
// Only have one reconnection loop active at a time.
|
||||
if (this._isReconnecting) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this._isReconnecting = true;
|
||||
await this._runReconnectingLoop();
|
||||
} finally {
|
||||
this._isReconnecting = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async _runReconnectingLoop(): Promise<void> {
|
||||
if (this._permanentFailure) {
|
||||
// no more attempts!
|
||||
return;
|
||||
}
|
||||
this._onDidStateChange.fire(new ConnectionLostEvent());
|
||||
const TIMES = [5, 5, 10, 10, 10, 10, 10, 30];
|
||||
const disconnectStartTime = Date.now();
|
||||
let attempt = -1;
|
||||
do {
|
||||
attempt++;
|
||||
const waitTime = (attempt < TIMES.length ? TIMES[attempt] : TIMES[TIMES.length - 1]);
|
||||
try {
|
||||
this._onDidStateChange.fire(new ReconnectionWaitEvent(waitTime));
|
||||
await sleep(waitTime);
|
||||
|
||||
// connection was lost, let's try to re-establish it
|
||||
this._onDidStateChange.fire(new ReconnectionRunningEvent());
|
||||
const simpleOptions = await resolveConnectionOptions(this._options, this.reconnectionToken, this.protocol);
|
||||
await connectWithTimeLimit(this._reconnect(simpleOptions), 30 * 1000 /*30s*/);
|
||||
this._onDidStateChange.fire(new ConnectionGainEvent());
|
||||
|
||||
break;
|
||||
} catch (err) {
|
||||
if (err.code === 'VSCODE_CONNECTION_ERROR') {
|
||||
console.error(`A permanent connection error occurred`);
|
||||
console.error(err);
|
||||
this._permanentFailure = true;
|
||||
this._onDidStateChange.fire(new ReconnectionPermanentFailureEvent());
|
||||
this.protocol.acceptDisconnect();
|
||||
break;
|
||||
}
|
||||
if (Date.now() - disconnectStartTime > ProtocolConstants.ReconnectionGraceTime) {
|
||||
console.error(`Giving up after reconnection grace time has expired!`);
|
||||
this._permanentFailure = true;
|
||||
this._onDidStateChange.fire(new ReconnectionPermanentFailureEvent());
|
||||
this.protocol.acceptDisconnect();
|
||||
break;
|
||||
}
|
||||
if (RemoteAuthorityResolverError.isTemporarilyNotAvailable(err)) {
|
||||
console.warn(`A temporarily not available error occured while trying to reconnect:`);
|
||||
console.warn(err);
|
||||
// try again!
|
||||
continue;
|
||||
}
|
||||
if ((err.code === 'ETIMEDOUT' || err.code === 'ENETUNREACH' || err.code === 'ECONNREFUSED' || err.code === 'ECONNRESET') && err.syscall === 'connect') {
|
||||
console.warn(`A connect error occured while trying to reconnect:`);
|
||||
console.warn(err);
|
||||
// try again!
|
||||
continue;
|
||||
}
|
||||
if (isPromiseCanceledError(err)) {
|
||||
console.warn(`A cancel error occured while trying to reconnect:`);
|
||||
console.warn(err);
|
||||
// try again!
|
||||
continue;
|
||||
}
|
||||
console.error(`An error occured while trying to reconnect:`);
|
||||
console.error(err);
|
||||
this._permanentFailure = true;
|
||||
this._onDidStateChange.fire(new ReconnectionPermanentFailureEvent());
|
||||
this.protocol.acceptDisconnect();
|
||||
break;
|
||||
}
|
||||
} while (!this._permanentFailure);
|
||||
}
|
||||
|
||||
protected abstract _reconnect(options: ISimpleConnectionOptions): Promise<void>;
|
||||
@@ -227,6 +433,24 @@ export class ExtensionHostPersistentConnection extends PersistentConnection {
|
||||
}
|
||||
}
|
||||
|
||||
function connectWithTimeLimit(p: Promise<void>, timeLimit: number): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
let timeout = setTimeout(() => {
|
||||
const err: any = new Error('Time limit reached');
|
||||
err.code = 'ETIMEDOUT';
|
||||
err.syscall = 'connect';
|
||||
reject(err);
|
||||
}, timeLimit);
|
||||
p.then(() => {
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
}, (err) => {
|
||||
clearTimeout(timeout);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getErrorFromMessage(msg: any): Error | null {
|
||||
if (msg && msg.type === 'error') {
|
||||
const error = new Error(`Connection error: ${msg.reason}`);
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors';
|
||||
import BaseErrorTelemetry from '../common/errorTelemetry';
|
||||
|
||||
export default class ErrorTelemetry extends BaseErrorTelemetry {
|
||||
protected installErrorListeners(): void {
|
||||
setUnexpectedErrorHandler(err => console.error(err));
|
||||
|
||||
// Print a console message when rejection isn't handled within N seconds. For details:
|
||||
// see https://nodejs.org/api/process.html#process_event_unhandledrejection
|
||||
// and https://nodejs.org/api/process.html#process_event_rejectionhandled
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { URI as uri } from 'vs/base/common/uri';
|
||||
import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, ITerminalSettings, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory, IDebugAdapterTrackerFactory } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, ITerminalSettings, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import {
|
||||
ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext,
|
||||
IExtHostContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto
|
||||
@@ -26,7 +26,6 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
|
||||
private _debugAdaptersHandleCounter = 1;
|
||||
private readonly _debugConfigurationProviders: Map<number, IDebugConfigurationProvider>;
|
||||
private readonly _debugAdapterDescriptorFactories: Map<number, IDebugAdapterDescriptorFactory>;
|
||||
private readonly _debugAdapterTrackerFactories: Map<number, IDebugAdapterTrackerFactory>;
|
||||
private readonly _sessions: Set<DebugSessionUUID>;
|
||||
|
||||
constructor(
|
||||
@@ -53,7 +52,6 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
|
||||
this._debugAdapters = new Map();
|
||||
this._debugConfigurationProviders = new Map();
|
||||
this._debugAdapterDescriptorFactories = new Map();
|
||||
this._debugAdapterTrackerFactories = new Map();
|
||||
this._sessions = new Set();
|
||||
}
|
||||
|
||||
@@ -207,24 +205,6 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb
|
||||
}
|
||||
}
|
||||
|
||||
public $registerDebugAdapterTrackerFactory(debugType: string, handle: number) {
|
||||
const factory = <IDebugAdapterTrackerFactory>{
|
||||
type: debugType,
|
||||
};
|
||||
this._debugAdapterTrackerFactories.set(handle, factory);
|
||||
this._toDispose.push(this.debugService.getConfigurationManager().registerDebugAdapterTrackerFactory(factory));
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
public $unregisterDebugAdapterTrackerFactory(handle: number) {
|
||||
const factory = this._debugAdapterTrackerFactories.get(handle);
|
||||
if (factory) {
|
||||
this._debugAdapterTrackerFactories.delete(handle);
|
||||
this.debugService.getConfigurationManager().unregisterDebugAdapterTrackerFactory(factory);
|
||||
}
|
||||
}
|
||||
|
||||
private getSession(sessionId: DebugSessionUUID | undefined): IDebugSession | undefined {
|
||||
if (sessionId) {
|
||||
return this.debugService.getModel().getSession(sessionId, true);
|
||||
|
||||
@@ -686,10 +686,8 @@ export interface MainThreadDebugServiceShape extends IDisposable {
|
||||
$acceptDAExit(handle: number, code: number | undefined, signal: string | undefined): void;
|
||||
$registerDebugConfigurationProvider(type: string, hasProvideMethod: boolean, hasResolveMethod: boolean, hasProvideDaMethod: boolean, handle: number): Promise<void>;
|
||||
$registerDebugAdapterDescriptorFactory(type: string, handle: number): Promise<void>;
|
||||
$registerDebugAdapterTrackerFactory(type: string, handle: number): Promise<any>;
|
||||
$unregisterDebugConfigurationProvider(handle: number): void;
|
||||
$unregisterDebugAdapterDescriptorFactory(handle: number): void;
|
||||
$unregisterDebugAdapterTrackerFactory(handle: number): void;
|
||||
$startDebugging(folder: UriComponents | undefined, nameOrConfig: string | IDebugConfiguration, parentSessionID: string | undefined): Promise<boolean>;
|
||||
$customDebugAdapterRequest(id: DebugSessionUUID, command: string, args: any): Promise<any>;
|
||||
$appendDebugConsole(value: string): void;
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace schema {
|
||||
export interface IUserFriendlyMenuItem {
|
||||
command: string;
|
||||
alt?: string;
|
||||
precondition?: string;
|
||||
when?: string;
|
||||
group?: string;
|
||||
}
|
||||
@@ -79,6 +80,10 @@ namespace schema {
|
||||
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'alt'));
|
||||
return false;
|
||||
}
|
||||
if (item.precondition && typeof item.precondition !== 'string') {
|
||||
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'precondition'));
|
||||
return false;
|
||||
}
|
||||
if (item.when && typeof item.when !== 'string') {
|
||||
collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when'));
|
||||
return false;
|
||||
@@ -103,6 +108,10 @@ namespace schema {
|
||||
description: localize('vscode.extension.contributes.menuItem.alt', 'Identifier of an alternative command to execute. The command must be declared in the \'commands\'-section'),
|
||||
type: 'string'
|
||||
},
|
||||
precondition: {
|
||||
description: localize('vscode.extension.contributes.menuItem.precondition', 'Condition which must be true to enable this item'),
|
||||
type: 'string'
|
||||
},
|
||||
when: {
|
||||
description: localize('vscode.extension.contributes.menuItem.when', 'Condition which must be true to show this item'),
|
||||
type: 'string'
|
||||
@@ -114,7 +123,7 @@ namespace schema {
|
||||
}
|
||||
};
|
||||
|
||||
export const menusContribtion: IJSONSchema = {
|
||||
export const menusContribution: IJSONSchema = {
|
||||
description: localize('vscode.extension.contributes.menus', "Contributes menu items to the editor"),
|
||||
type: 'object',
|
||||
properties: {
|
||||
@@ -354,7 +363,7 @@ let _menuRegistrations: IDisposable[] = [];
|
||||
|
||||
ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyMenuItem[] }>({
|
||||
extensionPoint: 'menus',
|
||||
jsonSchema: schema.menusContribtion
|
||||
jsonSchema: schema.menusContribution
|
||||
}).setHandler(extensions => {
|
||||
|
||||
// remove all previous menu registrations
|
||||
@@ -406,6 +415,14 @@ ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyM
|
||||
}
|
||||
}
|
||||
|
||||
if (item.precondition) {
|
||||
command.precondition = ContextKeyExpr.deserialize(item.precondition);
|
||||
}
|
||||
|
||||
if (alt && item.precondition) {
|
||||
alt.precondition = command.precondition;
|
||||
}
|
||||
|
||||
const registration = MenuRegistry.appendMenuItem(menu, {
|
||||
command,
|
||||
alt,
|
||||
|
||||
@@ -210,11 +210,10 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
|
||||
}
|
||||
}
|
||||
|
||||
public $postMessage(handle: WebviewPanelHandle | WebviewInsetHandle, message: any): Promise<boolean> {
|
||||
public async $postMessage(handle: WebviewPanelHandle | WebviewInsetHandle, message: any): Promise<boolean> {
|
||||
if (typeof handle === 'number') {
|
||||
this.getWebviewElement(handle).sendMessage(message);
|
||||
return Promise.resolve(true);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
const webview = this.getWebview(handle);
|
||||
const editors = this._editorService.visibleControls
|
||||
@@ -222,11 +221,17 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
|
||||
.map(e => e as WebviewEditor)
|
||||
.filter(e => e.input!.matches(webview));
|
||||
|
||||
for (const editor of editors) {
|
||||
editor.sendMessage(message);
|
||||
if (editors.length > 0) {
|
||||
editors[0].sendMessage(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
return Promise.resolve(editors.length > 0);
|
||||
if (webview.webview) {
|
||||
webview.webview.sendMessage(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -307,11 +307,8 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
|
||||
const handle = this._trackerFactoryHandleCounter++;
|
||||
this._trackerFactories.push({ type, handle, factory });
|
||||
|
||||
this._debugServiceProxy.$registerDebugAdapterTrackerFactory(type, handle);
|
||||
|
||||
return new Disposable(() => {
|
||||
this._trackerFactories = this._trackerFactories.filter(p => p.factory !== factory); // remove
|
||||
this._debugServiceProxy.$unregisterDebugAdapterTrackerFactory(handle);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -333,11 +333,6 @@ export class SimpleExtensionGalleryService implements IExtensionGalleryService {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
loadAllDependencies(dependencies: IExtensionIdentifier[], token: CancellationToken): Promise<IGalleryExtension[]> {
|
||||
// @ts-ignore
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
getExtensionsReport(): Promise<IReportedExtension[]> {
|
||||
// @ts-ignore
|
||||
return Promise.resolve(undefined);
|
||||
|
||||
@@ -24,7 +24,7 @@ import { IViewContainersRegistry, ViewContainer, Extensions as ViewContainerExte
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { TaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks';
|
||||
import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
|
||||
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
|
||||
import { ITerminalConfiguration } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
|
||||
export const VIEWLET_ID = 'workbench.view.debug';
|
||||
export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID);
|
||||
@@ -111,7 +111,7 @@ export interface IExpression extends IReplElement, IExpressionContainer {
|
||||
}
|
||||
|
||||
export interface IDebugger {
|
||||
createDebugAdapter(session: IDebugSession, outputService: IOutputService): Promise<IDebugAdapter>;
|
||||
createDebugAdapter(session: IDebugSession): Promise<IDebugAdapter>;
|
||||
runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise<number | undefined>;
|
||||
getCustomTelemetryService(): Promise<TelemetryService | undefined>;
|
||||
}
|
||||
@@ -573,13 +573,7 @@ export interface ITerminalSettings {
|
||||
osxExec: string,
|
||||
linuxExec: string
|
||||
};
|
||||
integrated: {
|
||||
shell: {
|
||||
osx: string,
|
||||
windows: string,
|
||||
linux: string
|
||||
}
|
||||
};
|
||||
integrated: ITerminalConfiguration;
|
||||
}
|
||||
|
||||
export interface IConfigurationManager {
|
||||
@@ -609,7 +603,6 @@ export interface IConfigurationManager {
|
||||
|
||||
activateDebuggers(activationEvent: string, debugType?: string): Promise<void>;
|
||||
|
||||
needsToRunInExtHost(debugType: string): boolean;
|
||||
hasDebugConfigurationProvider(debugType: string): boolean;
|
||||
|
||||
registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable;
|
||||
@@ -618,9 +611,6 @@ export interface IConfigurationManager {
|
||||
registerDebugAdapterDescriptorFactory(debugAdapterDescriptorFactory: IDebugAdapterDescriptorFactory): IDisposable;
|
||||
unregisterDebugAdapterDescriptorFactory(debugAdapterDescriptorFactory: IDebugAdapterDescriptorFactory): void;
|
||||
|
||||
registerDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): IDisposable;
|
||||
unregisterDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): void;
|
||||
|
||||
resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: any): Promise<any>;
|
||||
getDebugAdapterDescriptor(session: IDebugSession): Promise<IAdapterDescriptor | undefined>;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, ITerminalSettings, ITerminalLauncher, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory, IDebugAdapterTrackerFactory, IDebugService } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, ITerminalSettings, ITerminalLauncher, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory, IDebugService } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { Debugger } from 'vs/workbench/contrib/debug/node/debugger';
|
||||
import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
@@ -52,7 +52,6 @@ export class ConfigurationManager implements IConfigurationManager {
|
||||
private _onDidSelectConfigurationName = new Emitter<void>();
|
||||
private configProviders: IDebugConfigurationProvider[];
|
||||
private adapterDescriptorFactories: IDebugAdapterDescriptorFactory[];
|
||||
private adapterTrackerFactories: IDebugAdapterTrackerFactory[];
|
||||
private debugAdapterFactories: Map<string, IDebugAdapterFactory>;
|
||||
private terminalLauncher: ITerminalLauncher;
|
||||
private debugConfigurationTypeContext: IContextKey<string>;
|
||||
@@ -72,7 +71,6 @@ export class ConfigurationManager implements IConfigurationManager {
|
||||
) {
|
||||
this.configProviders = [];
|
||||
this.adapterDescriptorFactories = [];
|
||||
this.adapterTrackerFactories = [];
|
||||
this.debuggers = [];
|
||||
this.toDispose = [];
|
||||
this.registerListeners(lifecycleService);
|
||||
@@ -164,24 +162,6 @@ export class ConfigurationManager implements IConfigurationManager {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
// debug adapter trackers
|
||||
|
||||
registerDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): IDisposable {
|
||||
this.adapterTrackerFactories.push(debugAdapterTrackerFactory);
|
||||
return {
|
||||
dispose: () => {
|
||||
this.unregisterDebugAdapterTrackerFactory(debugAdapterTrackerFactory);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
unregisterDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): void {
|
||||
const ix = this.adapterTrackerFactories.indexOf(debugAdapterTrackerFactory);
|
||||
if (ix >= 0) {
|
||||
this.adapterTrackerFactories.splice(ix, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// debug configurations
|
||||
|
||||
registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable {
|
||||
@@ -206,13 +186,6 @@ export class ConfigurationManager implements IConfigurationManager {
|
||||
return providers.length > 0;
|
||||
}
|
||||
|
||||
needsToRunInExtHost(debugType: string): boolean {
|
||||
|
||||
// if the given debugType matches any registered tracker factory we need to run the DA in the EH
|
||||
const providers = this.adapterTrackerFactories.filter(p => p.type === debugType || p.type === '*');
|
||||
return providers.length > 0;
|
||||
}
|
||||
|
||||
resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: IConfig): Promise<IConfig | null | undefined> {
|
||||
return this.activateDebuggers('onDebugResolve', type).then(() => {
|
||||
// pipe the config through the promises sequentially. Append at the end the '*' types
|
||||
|
||||
@@ -25,7 +25,6 @@ import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { normalizeDriveLetter } from 'vs/base/common/labels';
|
||||
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
@@ -62,7 +61,6 @@ export class DebugSession implements IDebugSession {
|
||||
private _parentSession: IDebugSession | undefined,
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IOutputService private readonly outputService: IOutputService,
|
||||
@IWindowService private readonly windowService: IWindowService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IViewletService private readonly viewletService: IViewletService,
|
||||
@@ -167,7 +165,7 @@ export class DebugSession implements IDebugSession {
|
||||
|
||||
return dbgr.getCustomTelemetryService().then(customTelemetryService => {
|
||||
|
||||
return dbgr.createDebugAdapter(this, this.outputService).then(debugAdapter => {
|
||||
return dbgr.createDebugAdapter(this).then(debugAdapter => {
|
||||
|
||||
this.raw = new RawDebugSession(debugAdapter, dbgr, this.telemetryService, customTelemetryService, this.environmentService);
|
||||
|
||||
|
||||
@@ -11,11 +11,8 @@ import { isObject } from 'vs/base/common/types';
|
||||
import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc';
|
||||
import { IJSONSchema, IJSONSchemaSnippet } from 'vs/base/common/jsonSchema';
|
||||
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { IConfig, IDebuggerContribution, IDebugAdapterExecutable, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IConfigurationManager, IDebugAdapter, ITerminalSettings, IDebugger, IDebugSession, IAdapterDescriptor, IDebugAdapterServer } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { IConfig, IDebuggerContribution, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IConfigurationManager, IDebugAdapter, ITerminalSettings, IDebugger, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
|
||||
import { ExecutableDebugAdapter, SocketDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter';
|
||||
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
|
||||
import * as ConfigurationResolverUtils from 'vs/workbench/services/configurationResolver/common/configurationResolverUtils';
|
||||
import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
|
||||
@@ -38,7 +35,6 @@ export class Debugger implements IDebugger {
|
||||
constructor(private configurationManager: IConfigurationManager, dbgContribution: IDebuggerContribution, extensionDescription: IExtensionDescription,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@ITextResourcePropertiesService private readonly resourcePropertiesService: ITextResourcePropertiesService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IConfigurationResolverService private readonly configurationResolverService: IConfigurationResolverService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
) {
|
||||
@@ -97,97 +93,25 @@ export class Debugger implements IDebugger {
|
||||
}
|
||||
}
|
||||
|
||||
public createDebugAdapter(session: IDebugSession, outputService: IOutputService): Promise<IDebugAdapter> {
|
||||
public createDebugAdapter(session: IDebugSession): Promise<IDebugAdapter> {
|
||||
return this.configurationManager.activateDebuggers('onDebugAdapterProtocolTracker', this.type).then(_ => {
|
||||
if (this.inExtHost()) {
|
||||
const da = this.configurationManager.createDebugAdapter(session);
|
||||
if (da) {
|
||||
return Promise.resolve(da);
|
||||
}
|
||||
throw new Error(nls.localize('cannot.find.da', "Cannot find debug adapter for type '{0}'.", this.type));
|
||||
} else {
|
||||
return this.getAdapterDescriptor(session).then(adapterDescriptor => {
|
||||
switch (adapterDescriptor.type) {
|
||||
case 'executable':
|
||||
return new ExecutableDebugAdapter(adapterDescriptor, this.type, outputService);
|
||||
case 'server':
|
||||
return new SocketDebugAdapter(adapterDescriptor);
|
||||
case 'implementation':
|
||||
// TODO@AW: this.inExtHost() should now return true
|
||||
return Promise.resolve(this.configurationManager.createDebugAdapter(session));
|
||||
default:
|
||||
throw new Error('unknown descriptor type');
|
||||
}
|
||||
}).catch(err => {
|
||||
if (err && err.message) {
|
||||
throw new Error(nls.localize('cannot.create.da.with.err', "Cannot create debug adapter ({0}).", err.message));
|
||||
} else {
|
||||
throw new Error(nls.localize('cannot.create.da', "Cannot create debug adapter."));
|
||||
}
|
||||
});
|
||||
const da = this.configurationManager.createDebugAdapter(session);
|
||||
if (da) {
|
||||
return Promise.resolve(da);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getAdapterDescriptor(session: IDebugSession): Promise<IAdapterDescriptor> {
|
||||
|
||||
// a "debugServer" attribute in the launch config takes precedence
|
||||
if (typeof session.configuration.debugServer === 'number') {
|
||||
return Promise.resolve(<IDebugAdapterServer>{
|
||||
type: 'server',
|
||||
port: session.configuration.debugServer
|
||||
});
|
||||
}
|
||||
|
||||
// try the new "createDebugAdapterDescriptor" and the deprecated "provideDebugAdapter" API
|
||||
return this.configurationManager.getDebugAdapterDescriptor(session).then(adapter => {
|
||||
|
||||
if (adapter) {
|
||||
return adapter;
|
||||
}
|
||||
|
||||
// try deprecated command based extension API "adapterExecutableCommand" to determine the executable
|
||||
if (this.debuggerContribution.adapterExecutableCommand) {
|
||||
console.info('debugAdapterExecutable attribute in package.json is deprecated and support for it will be removed soon; please use DebugAdapterDescriptorFactory.createDebugAdapterDescriptor instead.');
|
||||
const rootFolder = session.root ? session.root.uri.toString() : undefined;
|
||||
return this.commandService.executeCommand<IDebugAdapterExecutable>(this.debuggerContribution.adapterExecutableCommand, rootFolder).then(ae => {
|
||||
if (ae) {
|
||||
return <IAdapterDescriptor>{
|
||||
type: 'executable',
|
||||
command: ae.command,
|
||||
args: ae.args || []
|
||||
};
|
||||
}
|
||||
throw new Error('command adapterExecutableCommand did not return proper command.');
|
||||
});
|
||||
}
|
||||
|
||||
// fallback: use executable information from package.json
|
||||
const ae = ExecutableDebugAdapter.platformAdapterExecutable(this.mergedExtensionDescriptions, this.type);
|
||||
if (ae === undefined) {
|
||||
throw new Error('no executable specified in package.json');
|
||||
}
|
||||
return ae;
|
||||
throw new Error(nls.localize('cannot.find.da', "Cannot find debug adapter for type '{0}'.", this.type));
|
||||
});
|
||||
}
|
||||
|
||||
substituteVariables(folder: IWorkspaceFolder | undefined, config: IConfig): Promise<IConfig> {
|
||||
if (this.inExtHost()) {
|
||||
return this.configurationManager.substituteVariables(this.type, folder, config).then(config => {
|
||||
return this.configurationResolverService.resolveWithInteractionReplace(folder, config, 'launch', this.variables);
|
||||
});
|
||||
} else {
|
||||
return this.configurationManager.substituteVariables(this.type, folder, config).then(config => {
|
||||
return this.configurationResolverService.resolveWithInteractionReplace(folder, config, 'launch', this.variables);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise<number | undefined> {
|
||||
const config = this.configurationService.getValue<ITerminalSettings>('terminal');
|
||||
return this.configurationManager.runInTerminal(this.inExtHost() ? this.type : '*', args, config);
|
||||
}
|
||||
|
||||
private inExtHost(): boolean {
|
||||
return true;
|
||||
return this.configurationManager.runInTerminal(this.type, args, config);
|
||||
}
|
||||
|
||||
get label(): string {
|
||||
|
||||
@@ -10,6 +10,7 @@ import * as pfs from 'vs/base/node/pfs';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { ITerminalLauncher, ITerminalSettings } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
import { getDefaultShell } from 'vs/workbench/contrib/terminal/node/terminal';
|
||||
|
||||
const TERMINAL_TITLE = nls.localize('console.title', "VS Code Console");
|
||||
|
||||
@@ -314,13 +315,13 @@ export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments
|
||||
let shell: string;
|
||||
const shell_config = config.integrated.shell;
|
||||
if (env.isWindows) {
|
||||
shell = shell_config.windows;
|
||||
shell = shell_config.windows || getDefaultShell(env.Platform.Windows);
|
||||
shellType = ShellType.cmd;
|
||||
} else if (env.isLinux) {
|
||||
shell = shell_config.linux;
|
||||
shell = shell_config.linux || getDefaultShell(env.Platform.Linux);
|
||||
shellType = ShellType.bash;
|
||||
} else if (env.isMacintosh) {
|
||||
shell = shell_config.osx;
|
||||
shell = shell_config.osx || getDefaultShell(env.Platform.Mac);
|
||||
shellType = ShellType.bash;
|
||||
} else {
|
||||
throw new Error('Unknown platform');
|
||||
|
||||
@@ -14,7 +14,7 @@ import { DebugSession } from 'vs/workbench/contrib/debug/electron-browser/debugS
|
||||
import { ReplModel } from 'vs/workbench/contrib/debug/common/replModel';
|
||||
|
||||
function createMockSession(model: DebugModel, name = 'mockSession', parentSession?: DebugSession | undefined): DebugSession {
|
||||
return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, parentSession, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!);
|
||||
return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, parentSession, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!);
|
||||
}
|
||||
|
||||
suite('Debug - Model', () => {
|
||||
@@ -436,7 +436,7 @@ suite('Debug - Model', () => {
|
||||
// Repl output
|
||||
|
||||
test('repl output', () => {
|
||||
const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!);
|
||||
const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!);
|
||||
const repl = new ReplModel(session);
|
||||
repl.appendToRepl('first line\n', severity.Error);
|
||||
repl.appendToRepl('second line ', severity.Error);
|
||||
|
||||
@@ -130,7 +130,7 @@ suite('Debug - Debugger', () => {
|
||||
const testResourcePropertiesService = new TestTextResourcePropertiesService(configurationService);
|
||||
|
||||
setup(() => {
|
||||
_debugger = new Debugger(configurationManager, debuggerContribution, extensionDescriptor0, configurationService, testResourcePropertiesService, undefined!, undefined!, undefined!);
|
||||
_debugger = new Debugger(configurationManager, debuggerContribution, extensionDescriptor0, configurationService, testResourcePropertiesService, undefined!, undefined!);
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
|
||||
@@ -19,6 +19,8 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib
|
||||
import { IAsyncDataSource, ITreeNode } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
|
||||
export interface IExtensionTemplateData {
|
||||
icon: HTMLImageElement;
|
||||
@@ -217,4 +219,46 @@ export class ExtensionsTree extends WorkbenchAsyncDataTree<IExtensionData, IExte
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtensionData implements IExtensionData {
|
||||
|
||||
readonly extension: IExtension;
|
||||
readonly parent: IExtensionData | null;
|
||||
private readonly getChildrenExtensionIds: (extension: IExtension) => string[];
|
||||
private readonly childrenExtensionIds: string[];
|
||||
private readonly extensionsWorkbenchService: IExtensionsWorkbenchService;
|
||||
|
||||
constructor(extension: IExtension, parent: IExtensionData | null, getChildrenExtensionIds: (extension: IExtension) => string[], extensionsWorkbenchService: IExtensionsWorkbenchService) {
|
||||
this.extension = extension;
|
||||
this.parent = parent;
|
||||
this.getChildrenExtensionIds = getChildrenExtensionIds;
|
||||
this.extensionsWorkbenchService = extensionsWorkbenchService;
|
||||
this.childrenExtensionIds = this.getChildrenExtensionIds(extension);
|
||||
}
|
||||
|
||||
get hasChildren(): boolean {
|
||||
return isNonEmptyArray(this.childrenExtensionIds);
|
||||
}
|
||||
|
||||
async getChildren(): Promise<IExtensionData[] | null> {
|
||||
if (this.hasChildren) {
|
||||
const localById = this.extensionsWorkbenchService.local.reduce((result, e) => { result.set(e.identifier.id.toLowerCase(), e); return result; }, new Map<string, IExtension>());
|
||||
const result: IExtension[] = [];
|
||||
const toQuery: string[] = [];
|
||||
for (const extensionId of this.childrenExtensionIds) {
|
||||
const id = extensionId.toLowerCase();
|
||||
const local = localById.get(id);
|
||||
if (local) {
|
||||
result.push(local);
|
||||
} else {
|
||||
toQuery.push(id);
|
||||
}
|
||||
}
|
||||
const galleryResult = await this.extensionsWorkbenchService.queryGallery({ names: this.childrenExtensionIds, pageSize: this.childrenExtensionIds.length }, CancellationToken.None);
|
||||
result.push(...galleryResult.firstPage);
|
||||
return result.map(extension => new ExtensionData(extension, this, this.getChildrenExtensionIds, this.extensionsWorkbenchService));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -69,14 +69,6 @@ export interface IExtension {
|
||||
readonly isMalicious: boolean;
|
||||
}
|
||||
|
||||
export interface IExtensionDependencies {
|
||||
dependencies: IExtensionDependencies[];
|
||||
hasDependencies: boolean;
|
||||
identifier: string;
|
||||
extension: IExtension;
|
||||
dependent: IExtensionDependencies | null;
|
||||
}
|
||||
|
||||
export const SERVICE_ID = 'extensionsWorkbenchService';
|
||||
|
||||
export const IExtensionsWorkbenchService = createDecorator<IExtensionsWorkbenchService>(SERVICE_ID);
|
||||
@@ -97,7 +89,6 @@ export interface IExtensionsWorkbenchService {
|
||||
installVersion(extension: IExtension, version: string): Promise<IExtension>;
|
||||
reinstall(extension: IExtension): Promise<IExtension>;
|
||||
setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise<void>;
|
||||
loadDependencies(extension: IExtension, token: CancellationToken): Promise<IExtensionDependencies | null>;
|
||||
open(extension: IExtension, sideByside?: boolean): Promise<any>;
|
||||
checkForUpdates(): Promise<void>;
|
||||
allowedBadgeProviders: string[];
|
||||
|
||||
@@ -24,7 +24,7 @@ import { IExtensionTipsService } from 'vs/platform/extensionManagement/common/ex
|
||||
import { IExtensionManifest, IKeyBinding, IView, IViewContainer, ExtensionType } from 'vs/platform/extensions/common/extensions';
|
||||
import { ResolvedKeybinding, KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput';
|
||||
import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, IExtension, IExtensionDependencies, ExtensionContainers } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, IExtension, ExtensionContainers } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { RatingsWidget, InstallCountWidget, RemoteBadgeWidget } from 'vs/workbench/contrib/extensions/electron-browser/extensionsWidgets';
|
||||
import { EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
@@ -43,13 +43,13 @@ import { Color } from 'vs/base/common/color';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ExtensionsTree, IExtensionData } from 'vs/workbench/contrib/extensions/browser/extensionsViewer';
|
||||
import { ExtensionsTree, ExtensionData } from 'vs/workbench/contrib/extensions/browser/extensionsViewer';
|
||||
import { ShowCurrentReleaseNotesAction } from 'vs/workbench/contrib/update/electron-browser/update';
|
||||
import { KeybindingParser } from 'vs/base/common/keybindingParser';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { getDefaultValue } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { isUndefined, withUndefinedAsNull } from 'vs/base/common/types';
|
||||
import { isUndefined } from 'vs/base/common/types';
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
@@ -181,7 +181,6 @@ export class ExtensionEditor extends BaseEditor {
|
||||
private extensionReadme: Cache<string> | null;
|
||||
private extensionChangelog: Cache<string> | null;
|
||||
private extensionManifest: Cache<IExtensionManifest | null> | null;
|
||||
private extensionDependencies: Cache<IExtensionDependencies | null> | null;
|
||||
|
||||
private layoutParticipants: ILayoutParticipant[] = [];
|
||||
private contentDisposables: IDisposable[] = [];
|
||||
@@ -210,7 +209,6 @@ export class ExtensionEditor extends BaseEditor {
|
||||
this.extensionReadme = null;
|
||||
this.extensionChangelog = null;
|
||||
this.extensionManifest = null;
|
||||
this.extensionDependencies = null;
|
||||
}
|
||||
|
||||
createEditor(parent: HTMLElement): void {
|
||||
@@ -298,7 +296,6 @@ export class ExtensionEditor extends BaseEditor {
|
||||
this.extensionReadme = new Cache(() => createCancelablePromise(token => extension.getReadme(token)));
|
||||
this.extensionChangelog = new Cache(() => createCancelablePromise(token => extension.getChangelog(token)));
|
||||
this.extensionManifest = new Cache(() => createCancelablePromise(token => extension.getManifest(token)));
|
||||
this.extensionDependencies = new Cache(() => createCancelablePromise(token => this.extensionsWorkbenchService.loadDependencies(extension, token)));
|
||||
|
||||
const remoteBadge = this.instantiationService.createInstance(RemoteBadgeWidget, this.iconContainer, true);
|
||||
const onError = Event.once(domEvent(this.icon, 'error'));
|
||||
@@ -643,69 +640,28 @@ export class ExtensionEditor extends BaseEditor {
|
||||
}
|
||||
|
||||
private openDependencies(extension: IExtension): Promise<IActiveElement> {
|
||||
if (extension.dependencies.length === 0) {
|
||||
if (arrays.isFalsyOrEmpty(extension.dependencies)) {
|
||||
append(this.content, $('p.nocontent')).textContent = localize('noDependencies', "No Dependencies");
|
||||
return Promise.resolve(this.content);
|
||||
}
|
||||
|
||||
return this.loadContents(() => this.extensionDependencies!.get())
|
||||
.then<IActiveElement, IActiveElement>(extensionDependencies => {
|
||||
if (extensionDependencies) {
|
||||
const content = $('div', { class: 'subcontent' });
|
||||
const scrollableContent = new DomScrollableElement(content, {});
|
||||
append(this.content, scrollableContent.getDomNode());
|
||||
this.contentDisposables.push(scrollableContent);
|
||||
const content = $('div', { class: 'subcontent' });
|
||||
const scrollableContent = new DomScrollableElement(content, {});
|
||||
append(this.content, scrollableContent.getDomNode());
|
||||
this.contentDisposables.push(scrollableContent);
|
||||
|
||||
const dependenciesTree = this.renderDependencies(content, extensionDependencies);
|
||||
const layout = () => {
|
||||
scrollableContent.scanDomNode();
|
||||
const scrollDimensions = scrollableContent.getScrollDimensions();
|
||||
dependenciesTree.layout(scrollDimensions.height);
|
||||
};
|
||||
const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout });
|
||||
this.contentDisposables.push(toDisposable(removeLayoutParticipant));
|
||||
const dependenciesTree = this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extension, null, extension => extension.dependencies || [], this.extensionsWorkbenchService), content);
|
||||
const layout = () => {
|
||||
scrollableContent.scanDomNode();
|
||||
const scrollDimensions = scrollableContent.getScrollDimensions();
|
||||
dependenciesTree.layout(scrollDimensions.height);
|
||||
};
|
||||
const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { layout });
|
||||
this.contentDisposables.push(toDisposable(removeLayoutParticipant));
|
||||
|
||||
this.contentDisposables.push(dependenciesTree);
|
||||
scrollableContent.scanDomNode();
|
||||
return { focus() { dependenciesTree.domFocus(); } };
|
||||
} else {
|
||||
append(this.content, $('p.nocontent')).textContent = localize('noDependencies', "No Dependencies");
|
||||
return Promise.resolve(this.content);
|
||||
}
|
||||
}, error => {
|
||||
append(this.content, $('p.nocontent')).textContent = error;
|
||||
this.notificationService.error(error);
|
||||
return this.content;
|
||||
});
|
||||
}
|
||||
|
||||
private renderDependencies(container: HTMLElement, extensionDependencies: IExtensionDependencies): ExtensionsTree {
|
||||
class ExtensionData implements IExtensionData {
|
||||
|
||||
private readonly extensionDependencies: IExtensionDependencies;
|
||||
|
||||
constructor(extensionDependencies: IExtensionDependencies) {
|
||||
this.extensionDependencies = extensionDependencies;
|
||||
}
|
||||
|
||||
get extension(): IExtension {
|
||||
return this.extensionDependencies.extension;
|
||||
}
|
||||
|
||||
get parent(): IExtensionData | null {
|
||||
return this.extensionDependencies.dependent ? new ExtensionData(this.extensionDependencies.dependent) : null;
|
||||
}
|
||||
|
||||
get hasChildren(): boolean {
|
||||
return this.extensionDependencies.hasDependencies;
|
||||
}
|
||||
|
||||
getChildren(): Promise<IExtensionData[] | null> {
|
||||
return this.extensionDependencies.dependencies ? Promise.resolve(this.extensionDependencies.dependencies.map(d => new ExtensionData(d))) : Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
return this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extensionDependencies), container);
|
||||
this.contentDisposables.push(dependenciesTree);
|
||||
scrollableContent.scanDomNode();
|
||||
return Promise.resolve({ focus() { dependenciesTree.domFocus(); } });
|
||||
}
|
||||
|
||||
private openExtensionPack(extension: IExtension): Promise<IActiveElement> {
|
||||
@@ -714,7 +670,7 @@ export class ExtensionEditor extends BaseEditor {
|
||||
append(this.content, scrollableContent.getDomNode());
|
||||
this.contentDisposables.push(scrollableContent);
|
||||
|
||||
const extensionsPackTree = this.renderExtensionPack(content, extension);
|
||||
const extensionsPackTree = this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extension, null, extension => extension.extensionPack || [], this.extensionsWorkbenchService), content);
|
||||
const layout = () => {
|
||||
scrollableContent.scanDomNode();
|
||||
const scrollDimensions = scrollableContent.getScrollDimensions();
|
||||
@@ -728,35 +684,6 @@ export class ExtensionEditor extends BaseEditor {
|
||||
return Promise.resolve({ focus() { extensionsPackTree.domFocus(); } });
|
||||
}
|
||||
|
||||
private renderExtensionPack(container: HTMLElement, extension: IExtension): ExtensionsTree {
|
||||
const extensionsWorkbenchService = this.extensionsWorkbenchService;
|
||||
class ExtensionData implements IExtensionData {
|
||||
|
||||
readonly extension: IExtension;
|
||||
readonly parent: IExtensionData | null;
|
||||
|
||||
constructor(extension: IExtension, parent?: IExtensionData) {
|
||||
this.extension = extension;
|
||||
this.parent = withUndefinedAsNull(parent);
|
||||
}
|
||||
|
||||
get hasChildren(): boolean {
|
||||
return this.extension.extensionPack.length > 0;
|
||||
}
|
||||
|
||||
getChildren(): Promise<IExtensionData[] | null> {
|
||||
if (this.hasChildren) {
|
||||
const names = arrays.distinct(this.extension.extensionPack, e => e.toLowerCase());
|
||||
return extensionsWorkbenchService.queryGallery({ names, pageSize: names.length }, CancellationToken.None)
|
||||
.then(result => result.firstPage.map(extension => new ExtensionData(extension, this)));
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
return this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extension), container);
|
||||
}
|
||||
|
||||
private renderSettings(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
|
||||
const contributes = manifest.contributes;
|
||||
const configuration = contributes && contributes.configuration;
|
||||
|
||||
@@ -1558,7 +1558,7 @@ export class InstallExtensionsAction extends OpenExtensionsViewletAction {
|
||||
export class ShowEnabledExtensionsAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.extensions.action.showEnabledExtensions';
|
||||
static LABEL = localize('showEnabledExtensions', 'Show Enabled Extensions');
|
||||
static LABEL = localize('showEnabledExtensions', "Show Enabled Extensions");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
|
||||
@@ -23,8 +23,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
// {{SQL CARBON EDIT}}
|
||||
import { IExtension, IExtensionDependencies, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, ExtensionsPolicyKey, ExtensionsPolicy } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, ExtensionsPolicyKey, ExtensionsPolicy } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IURLService, IURLHandler } from 'vs/platform/url/common/url';
|
||||
import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput';
|
||||
@@ -323,53 +322,6 @@ ${this.description}
|
||||
}
|
||||
}
|
||||
|
||||
class ExtensionDependencies implements IExtensionDependencies {
|
||||
|
||||
private _hasDependencies: boolean | null = null;
|
||||
|
||||
constructor(private _extension: IExtension, private _identifier: string, private _map: Map<string, IExtension>, private _dependent: IExtensionDependencies | null = null) { }
|
||||
|
||||
get hasDependencies(): boolean {
|
||||
if (this._hasDependencies === null) {
|
||||
this._hasDependencies = this.computeHasDependencies();
|
||||
}
|
||||
return this._hasDependencies;
|
||||
}
|
||||
|
||||
get extension(): IExtension {
|
||||
return this._extension;
|
||||
}
|
||||
|
||||
get identifier(): string {
|
||||
return this._identifier;
|
||||
}
|
||||
|
||||
get dependent(): IExtensionDependencies | null {
|
||||
return this._dependent;
|
||||
}
|
||||
|
||||
get dependencies(): IExtensionDependencies[] {
|
||||
if (!this.hasDependencies) {
|
||||
return [];
|
||||
}
|
||||
return this._extension.dependencies.map(id => new ExtensionDependencies(this._map.get(id)!, id, this._map, this));
|
||||
}
|
||||
|
||||
private computeHasDependencies(): boolean {
|
||||
if (this._extension && this._extension.dependencies.length > 0) {
|
||||
let dependent = this._dependent;
|
||||
while (dependent !== null) {
|
||||
if (dependent.identifier === this.identifier) {
|
||||
return false;
|
||||
}
|
||||
dependent = dependent.dependent;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class Extensions extends Disposable {
|
||||
|
||||
private readonly _onChange: Emitter<Extension | undefined> = new Emitter<Extension | undefined>();
|
||||
@@ -666,27 +618,6 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
||||
});
|
||||
}
|
||||
|
||||
loadDependencies(extension: IExtension, token: CancellationToken): Promise<IExtensionDependencies | null> {
|
||||
if (!extension.dependencies.length) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
return this.extensionService.getExtensionsReport()
|
||||
.then(report => {
|
||||
const maliciousSet = getMaliciousExtensionsSet(report);
|
||||
|
||||
return this.galleryService.loadAllDependencies((<Extension>extension).dependencies.map(id => ({ id })), token)
|
||||
.then(galleryExtensions => {
|
||||
const extensions: IExtension[] = [...this.local, ...galleryExtensions.map(galleryExtension => this.fromGallery(galleryExtension, maliciousSet))];
|
||||
const map = new Map<string, IExtension>();
|
||||
for (const extension of extensions) {
|
||||
map.set(extension.identifier.id, extension);
|
||||
}
|
||||
return new ExtensionDependencies(extension, extension.identifier.id, map);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
open(extension: IExtension, sideByside: boolean = false): Promise<any> {
|
||||
return Promise.resolve(this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension), undefined, sideByside ? SIDE_GROUP : ACTIVE_GROUP));
|
||||
}
|
||||
|
||||
-242
@@ -480,248 +480,6 @@ suite('ExtensionsWorkbenchServiceTest', () => {
|
||||
assert.ok(target.calledOnce);
|
||||
});
|
||||
|
||||
test('test extension dependencies when empty', async () => {
|
||||
testObject = await aWorkbenchService();
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a')));
|
||||
|
||||
return testObject.queryGallery(CancellationToken.None).then(page => {
|
||||
return testObject.loadDependencies(page.firstPage[0], CancellationToken.None).then(dependencies => {
|
||||
assert.equal(null, dependencies);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('test one level extension dependencies without cycle', async () => {
|
||||
testObject = await aWorkbenchService();
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.b', 'pub.c', 'pub.d'] })));
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'loadAllDependencies', [aGalleryExtension('b'), aGalleryExtension('c'), aGalleryExtension('d')]);
|
||||
|
||||
return testObject.queryGallery(CancellationToken.None).then(page => {
|
||||
const extension = page.firstPage[0];
|
||||
return testObject.loadDependencies(extension, CancellationToken.None).then(actual => {
|
||||
assert.ok(actual!.hasDependencies);
|
||||
assert.equal(extension, actual!.extension);
|
||||
assert.equal(null, actual!.dependent);
|
||||
assert.equal(3, actual!.dependencies.length);
|
||||
assert.equal('pub.a', actual!.identifier);
|
||||
let dependent = actual;
|
||||
|
||||
actual = dependent!.dependencies[0];
|
||||
assert.ok(!actual.hasDependencies);
|
||||
assert.equal('pub.b', actual.extension.identifier.id);
|
||||
assert.equal('pub.b', actual.identifier);
|
||||
assert.equal(dependent, actual.dependent);
|
||||
assert.equal(0, actual.dependencies.length);
|
||||
|
||||
actual = dependent!.dependencies[1];
|
||||
assert.ok(!actual.hasDependencies);
|
||||
assert.equal('pub.c', actual.extension.identifier.id);
|
||||
assert.equal('pub.c', actual.identifier);
|
||||
assert.equal(dependent, actual.dependent);
|
||||
assert.equal(0, actual.dependencies.length);
|
||||
|
||||
actual = dependent!.dependencies[2];
|
||||
assert.ok(!actual.hasDependencies);
|
||||
assert.equal('pub.d', actual.extension.identifier.id);
|
||||
assert.equal('pub.d', actual.identifier);
|
||||
assert.equal(dependent, actual.dependent);
|
||||
assert.equal(0, actual.dependencies.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('test one level extension dependencies with cycle', async () => {
|
||||
testObject = await aWorkbenchService();
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.b', 'pub.a'] })));
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'loadAllDependencies', [aGalleryExtension('b'), aGalleryExtension('a')]);
|
||||
|
||||
return testObject.queryGallery(CancellationToken.None).then(page => {
|
||||
const extension = page.firstPage[0];
|
||||
return testObject.loadDependencies(extension, CancellationToken.None).then(actual => {
|
||||
assert.ok(actual!.hasDependencies);
|
||||
assert.equal(extension, actual!.extension);
|
||||
assert.equal(null, actual!.dependent);
|
||||
assert.equal(2, actual!.dependencies.length);
|
||||
assert.equal('pub.a', actual!.identifier);
|
||||
let dependent = actual;
|
||||
|
||||
actual = dependent!.dependencies[0]!;
|
||||
assert.ok(!actual.hasDependencies);
|
||||
assert.equal('pub.b', actual.extension.identifier.id);
|
||||
assert.equal('pub.b', actual.identifier);
|
||||
assert.equal(dependent, actual.dependent);
|
||||
assert.equal(0, actual.dependencies.length);
|
||||
|
||||
actual = dependent!.dependencies[1]!;
|
||||
assert.ok(!actual.hasDependencies);
|
||||
assert.equal('pub.a', actual.extension.identifier.id);
|
||||
assert.equal('pub.a', actual.identifier);
|
||||
assert.equal(dependent, actual.dependent);
|
||||
assert.equal(0, actual.dependencies.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('test one level extension dependencies with missing dependencies', async () => {
|
||||
testObject = await aWorkbenchService();
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.b', 'pub.a'] })));
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'loadAllDependencies', [aGalleryExtension('a')]);
|
||||
|
||||
return testObject.queryGallery(CancellationToken.None).then(page => {
|
||||
const extension = page.firstPage[0];
|
||||
return testObject.loadDependencies(extension, CancellationToken.None).then(actual => {
|
||||
assert.ok(actual!.hasDependencies);
|
||||
assert.equal(extension, actual!.extension);
|
||||
assert.equal(null, actual!.dependent);
|
||||
assert.equal(2, actual!.dependencies.length);
|
||||
assert.equal('pub.a', actual!.identifier);
|
||||
let dependent = actual;
|
||||
|
||||
actual = dependent!.dependencies[0]!;
|
||||
assert.ok(!actual.hasDependencies);
|
||||
assert.equal(null, actual.extension);
|
||||
assert.equal('pub.b', actual.identifier);
|
||||
assert.equal(dependent, actual.dependent);
|
||||
assert.equal(0, actual.dependencies.length);
|
||||
|
||||
actual = dependent!.dependencies[1]!;
|
||||
assert.ok(!actual.hasDependencies);
|
||||
assert.equal('pub.a', actual.extension.identifier.id);
|
||||
assert.equal('pub.a', actual.identifier);
|
||||
assert.equal(dependent, actual.dependent);
|
||||
assert.equal(0, actual.dependencies.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('test one level extension dependencies with in built dependencies', async () => {
|
||||
const local = aLocalExtension('inbuilt', {}, { type: ExtensionType.System });
|
||||
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
|
||||
testObject = await aWorkbenchService();
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.inbuilt', 'pub.a'] })));
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'loadAllDependencies', [aGalleryExtension('a')]);
|
||||
|
||||
return testObject.queryGallery(CancellationToken.None).then(page => {
|
||||
const extension = page.firstPage[0];
|
||||
return testObject.loadDependencies(extension, CancellationToken.None).then(actual => {
|
||||
assert.ok(actual!.hasDependencies);
|
||||
assert.equal(extension, actual!.extension);
|
||||
assert.equal(null, actual!.dependent);
|
||||
assert.equal(2, actual!.dependencies.length);
|
||||
assert.equal('pub.a', actual!.identifier);
|
||||
let dependent = actual;
|
||||
|
||||
actual = dependent!.dependencies[0]!;
|
||||
assert.ok(!actual.hasDependencies);
|
||||
assert.equal('pub.inbuilt', actual.extension.identifier.id);
|
||||
assert.equal('pub.inbuilt', actual.identifier);
|
||||
assert.equal(dependent, actual.dependent);
|
||||
assert.equal(0, actual.dependencies.length);
|
||||
|
||||
|
||||
actual = dependent!.dependencies[1]!;
|
||||
assert.ok(!actual.hasDependencies);
|
||||
assert.equal('pub.a', actual.extension.identifier.id);
|
||||
assert.equal('pub.a', actual.identifier);
|
||||
assert.equal(dependent, actual.dependent);
|
||||
assert.equal(0, actual.dependencies.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('test more than one level of extension dependencies', async () => {
|
||||
const local = aLocalExtension('c', { extensionDependencies: ['pub.d'] }, { type: ExtensionType.System });
|
||||
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
|
||||
testObject = await aWorkbenchService();
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', {}, { dependencies: ['pub.b', 'pub.c'] })));
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'loadAllDependencies', [
|
||||
aGalleryExtension('b', {}, { dependencies: ['pub.d', 'pub.e'] }),
|
||||
aGalleryExtension('d', {}, { dependencies: ['pub.f', 'pub.c'] }),
|
||||
aGalleryExtension('e')]);
|
||||
|
||||
return testObject.queryGallery(CancellationToken.None).then(page => {
|
||||
const extension = page.firstPage[0];
|
||||
return testObject.loadDependencies(extension, CancellationToken.None).then(a => {
|
||||
assert.ok(a!.hasDependencies);
|
||||
assert.equal(extension, a!.extension);
|
||||
assert.equal(null, a!.dependent);
|
||||
assert.equal(2, a!.dependencies.length);
|
||||
assert.equal('pub.a', a!.identifier);
|
||||
|
||||
let b = a!.dependencies[0];
|
||||
assert.ok(b.hasDependencies);
|
||||
assert.equal('pub.b', b.extension.identifier.id);
|
||||
assert.equal('pub.b', b.identifier);
|
||||
assert.equal(a, b.dependent);
|
||||
assert.equal(2, b.dependencies.length);
|
||||
|
||||
let c = a!.dependencies[1];
|
||||
assert.ok(c.hasDependencies);
|
||||
assert.equal('pub.c', c.extension.identifier.id);
|
||||
assert.equal('pub.c', c.identifier);
|
||||
assert.equal(a, c.dependent);
|
||||
assert.equal(1, c.dependencies.length);
|
||||
|
||||
let d = b.dependencies[0];
|
||||
assert.ok(d.hasDependencies);
|
||||
assert.equal('pub.d', d.extension.identifier.id);
|
||||
assert.equal('pub.d', d.identifier);
|
||||
assert.equal(b, d.dependent);
|
||||
assert.equal(2, d.dependencies.length);
|
||||
|
||||
let e = b.dependencies[1];
|
||||
assert.ok(!e.hasDependencies);
|
||||
assert.equal('pub.e', e.extension.identifier.id);
|
||||
assert.equal('pub.e', e.identifier);
|
||||
assert.equal(b, e.dependent);
|
||||
assert.equal(0, e.dependencies.length);
|
||||
|
||||
let f = d.dependencies[0];
|
||||
assert.ok(!f.hasDependencies);
|
||||
assert.equal(null, f.extension);
|
||||
assert.equal('pub.f', f.identifier);
|
||||
assert.equal(d, f.dependent);
|
||||
assert.equal(0, f.dependencies.length);
|
||||
|
||||
c = d.dependencies[1];
|
||||
assert.ok(c.hasDependencies);
|
||||
assert.equal('pub.c', c.extension.identifier.id);
|
||||
assert.equal('pub.c', c.identifier);
|
||||
assert.equal(d, c.dependent);
|
||||
assert.equal(1, c.dependencies.length);
|
||||
|
||||
d = c.dependencies[0];
|
||||
assert.ok(!d.hasDependencies);
|
||||
assert.equal('pub.d', d.extension.identifier.id);
|
||||
assert.equal('pub.d', d.identifier);
|
||||
assert.equal(c, d.dependent);
|
||||
assert.equal(0, d.dependencies.length);
|
||||
|
||||
c = a!.dependencies[1];
|
||||
d = c.dependencies[0];
|
||||
assert.ok(d.hasDependencies);
|
||||
assert.equal('pub.d', d.extension.identifier.id);
|
||||
assert.equal('pub.d', d.identifier);
|
||||
assert.equal(c, d.dependent);
|
||||
assert.equal(2, d.dependencies.length);
|
||||
|
||||
f = d.dependencies[0];
|
||||
assert.ok(!f.hasDependencies);
|
||||
assert.equal(null, f.extension);
|
||||
assert.equal('pub.f', f.identifier);
|
||||
assert.equal(d, f.dependent);
|
||||
assert.equal(0, f.dependencies.length);
|
||||
|
||||
c = d.dependencies[1];
|
||||
assert.ok(!c.hasDependencies);
|
||||
assert.equal('pub.c', c.extension.identifier.id);
|
||||
assert.equal('pub.c', c.identifier);
|
||||
assert.equal(d, c.dependent);
|
||||
assert.equal(0, c.dependencies.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('test uninstalled extensions are always enabled', async () => {
|
||||
return instantiationService.get(IExtensionEnablementService).setEnablement([aLocalExtension('b')], EnablementState.Disabled)
|
||||
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement([aLocalExtension('c')], EnablementState.WorkspaceDisabled))
|
||||
|
||||
+1
-1
@@ -94,7 +94,7 @@ CommandsRegistry.registerCommand({
|
||||
const directoriesToOpen = distinct(stats.filter(data => data.success).map(({ stat }) => stat!.isDirectory ? stat!.resource.fsPath : paths.dirname(stat!.resource.fsPath)));
|
||||
return directoriesToOpen.map(dir => {
|
||||
if (configurationService.getValue<IExternalTerminalConfiguration>().terminal.explorerKind === 'integrated') {
|
||||
const instance = integratedTerminalService.createTerminal({ cwd: dir }, true);
|
||||
const instance = integratedTerminalService.createTerminal({ cwd: dir });
|
||||
if (instance && (resources.length === 1 || !resource || dir === resource.fsPath || dir === paths.dirname(resource.fsPath))) {
|
||||
integratedTerminalService.setActiveInstance(instance);
|
||||
integratedTerminalService.showPanel(true);
|
||||
|
||||
@@ -196,7 +196,7 @@ export const tocData: ITOCEntry = {
|
||||
]
|
||||
};
|
||||
|
||||
export const knownAcronyms = new Set();
|
||||
export const knownAcronyms = new Set<string>();
|
||||
[
|
||||
'css',
|
||||
'html',
|
||||
@@ -209,3 +209,7 @@ export const knownAcronyms = new Set();
|
||||
'id',
|
||||
'php',
|
||||
].forEach(str => knownAcronyms.add(str));
|
||||
|
||||
export const knownTermMappings = new Map<string, string>();
|
||||
knownTermMappings.set('power shell', 'PowerShell');
|
||||
knownTermMappings.set('powershell', 'PowerShell');
|
||||
|
||||
@@ -11,7 +11,7 @@ import { localize } from 'vs/nls';
|
||||
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { SettingsTarget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets';
|
||||
import { ITOCEntry, knownAcronyms } from 'vs/workbench/contrib/preferences/browser/settingsLayout';
|
||||
import { ITOCEntry, knownAcronyms, knownTermMappings } from 'vs/workbench/contrib/preferences/browser/settingsLayout';
|
||||
import { MODIFIED_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences';
|
||||
import { IExtensionSetting, ISearchResult, ISetting, SettingValueType } from 'vs/workbench/services/preferences/common/preferences';
|
||||
|
||||
@@ -404,8 +404,8 @@ export function settingKeyToDisplayFormat(key: string, groupId = ''): { category
|
||||
}
|
||||
|
||||
function wordifyKey(key: string): string {
|
||||
return key
|
||||
.replace(/\.([a-z0-9])/g, (match, p1) => ` › ${p1.toUpperCase()}`) // Replace dot with spaced '>'
|
||||
key = key
|
||||
.replace(/\.([a-z0-9])/g, (_, p1) => ` › ${p1.toUpperCase()}`) // Replace dot with spaced '>'
|
||||
.replace(/([a-z0-9])([A-Z])/g, '$1 $2') // Camel case to spacing, fooBar => foo Bar
|
||||
.replace(/^[a-z]/g, match => match.toUpperCase()) // Upper casing all first letters, foo => Foo
|
||||
.replace(/\b\w+\b/g, match => { // Upper casing known acronyms
|
||||
@@ -413,6 +413,12 @@ function wordifyKey(key: string): string {
|
||||
match.toUpperCase() :
|
||||
match;
|
||||
});
|
||||
|
||||
for (let [k, v] of knownTermMappings) {
|
||||
key = key.replace(new RegExp(`\\b${k}\\b`, 'gi'), v);
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
function trimCategoryForGroup(category: string, groupId: string): string {
|
||||
|
||||
@@ -1225,8 +1225,14 @@ export class SettingsEditor2 extends BaseEditor {
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearFilterLinkContainer.style.display = this.viewState.tagFilters && this.viewState.tagFilters.size > 0
|
||||
? 'initial'
|
||||
: 'none';
|
||||
|
||||
if (!this.searchResultModel) {
|
||||
this.countElement.style.display = 'none';
|
||||
DOM.removeClass(this.rootElement, 'no-results');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.tocTreeModel && this.tocTreeModel.settingsTreeRoot) {
|
||||
@@ -1239,9 +1245,6 @@ export class SettingsEditor2 extends BaseEditor {
|
||||
|
||||
this.countElement.style.display = 'block';
|
||||
DOM.toggleClass(this.rootElement, 'no-results', count === 0);
|
||||
this.clearFilterLinkContainer.style.display = this.viewState.tagFilters && this.viewState.tagFilters.size > 0
|
||||
? 'initial'
|
||||
: 'none';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,20 @@ suite('SettingsTree', () => {
|
||||
category: '',
|
||||
label: 'Foo'
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
settingKeyToDisplayFormat('foo.1leading.number'),
|
||||
{
|
||||
category: 'Foo › 1leading',
|
||||
label: 'Number'
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
settingKeyToDisplayFormat('foo.1Leading.number'),
|
||||
{
|
||||
category: 'Foo › 1 Leading',
|
||||
label: 'Number'
|
||||
});
|
||||
});
|
||||
|
||||
test('settingKeyToDisplayFormat - with category', () => {
|
||||
@@ -101,19 +115,21 @@ suite('SettingsTree', () => {
|
||||
category: 'Something Else',
|
||||
label: 'Etc'
|
||||
});
|
||||
});
|
||||
|
||||
test('settingKeyToDisplayFormat - known acronym/term', () => {
|
||||
assert.deepEqual(
|
||||
settingKeyToDisplayFormat('foo.1leading.number'),
|
||||
settingKeyToDisplayFormat('css.someCssSetting'),
|
||||
{
|
||||
category: 'Foo › 1leading',
|
||||
label: 'Number'
|
||||
category: 'CSS',
|
||||
label: 'Some CSS Setting'
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
settingKeyToDisplayFormat('foo.1Leading.number'),
|
||||
settingKeyToDisplayFormat('powershell.somePowerShellSetting'),
|
||||
{
|
||||
category: 'Foo › 1 Leading',
|
||||
label: 'Number'
|
||||
category: 'PowerShell',
|
||||
label: 'Some PowerShell Setting'
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,428 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { STATUS_BAR_HOST_NAME_BACKGROUND, STATUS_BAR_HOST_NAME_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
|
||||
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
|
||||
import { RemoteExtensionLogFileName, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
import { MenuId, IMenuService, MenuItemAction, IMenu } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchContributionsExtensions } from 'vs/workbench/common/contributions';
|
||||
import { IOutputChannelRegistry, Extensions as OutputExt } from 'vs/workbench/contrib/output/common/output';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { DialogChannel } from 'vs/platform/dialogs/node/dialogIpc';
|
||||
import { DownloadServiceChannel } from 'vs/platform/download/node/downloadIpc';
|
||||
import { LogLevelSetterChannel } from 'vs/platform/log/node/logIpc';
|
||||
import { ipcRenderer as ipc } from 'electron';
|
||||
import { IDiagnosticInfoOptions, IRemoteDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnosticsService';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IProgressService2, IProgress, IProgressStep, ProgressLocation } from 'vs/platform/progress/common/progress';
|
||||
import { PersistenConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { ReloadWindowAction } from 'vs/workbench/electron-browser/actions/windowActions';
|
||||
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
|
||||
const WINDOW_ACTIONS_COMMAND_ID = 'remote.showActions';
|
||||
|
||||
export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
private windowIndicatorEntry: IStatusbarEntryAccessor | undefined;
|
||||
private windowCommandMenu: IMenu;
|
||||
private hasWindowActions: boolean = false;
|
||||
private remoteAuthority: string | undefined;
|
||||
private disconnected: boolean = false;
|
||||
|
||||
constructor(
|
||||
@IStatusbarService private readonly statusbarService: IStatusbarService,
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
@ILabelService private readonly labelService: ILabelService,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@IMenuService private menuService: IMenuService,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.windowCommandMenu = this.menuService.createMenu(MenuId.StatusBarWindowIndicatorMenu, this.contextKeyService);
|
||||
this._register(this.windowCommandMenu);
|
||||
|
||||
this._register(CommandsRegistry.registerCommand(WINDOW_ACTIONS_COMMAND_ID, _ => this.showIndicatorActions(this.windowCommandMenu)));
|
||||
|
||||
this.remoteAuthority = environmentService.configuration.remoteAuthority;
|
||||
if (this.remoteAuthority) {
|
||||
// Pending entry until extensions are ready
|
||||
this.renderWindowIndicator(nls.localize('host.open', "$(sync~spin) Opening Remote..."));
|
||||
}
|
||||
|
||||
extensionService.whenInstalledExtensionsRegistered().then(_ => {
|
||||
if (this.remoteAuthority) {
|
||||
this._register(this.labelService.onDidChangeFormatters(e => this.updateWindowIndicator()));
|
||||
remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority).then(() => this.setDisconnected(false), () => this.setDisconnected(true));
|
||||
}
|
||||
this._register(this.windowCommandMenu.onDidChange(e => this.updateWindowActions()));
|
||||
this.updateWindowIndicator();
|
||||
});
|
||||
|
||||
const connection = remoteAgentService.getConnection();
|
||||
if (connection) {
|
||||
this._register(connection.onDidStateChange((e) => {
|
||||
switch (e.type) {
|
||||
case PersistenConnectionEventType.ConnectionLost:
|
||||
case PersistenConnectionEventType.ReconnectionPermanentFailure:
|
||||
case PersistenConnectionEventType.ReconnectionRunning:
|
||||
case PersistenConnectionEventType.ReconnectionWait:
|
||||
this.setDisconnected(true);
|
||||
break;
|
||||
case PersistenConnectionEventType.ConnectionGain:
|
||||
this.setDisconnected(false);
|
||||
break;
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private setDisconnected(isDisconnected: boolean): void {
|
||||
if (this.disconnected !== isDisconnected) {
|
||||
this.disconnected = isDisconnected;
|
||||
this.updateWindowIndicator();
|
||||
}
|
||||
}
|
||||
|
||||
private updateWindowIndicator(): void {
|
||||
const windowActionCommand = this.windowCommandMenu.getActions().length ? WINDOW_ACTIONS_COMMAND_ID : undefined;
|
||||
if (this.remoteAuthority) {
|
||||
const hostLabel = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.remoteAuthority) || this.remoteAuthority;
|
||||
if (!this.disconnected) {
|
||||
this.renderWindowIndicator(`$(remote) ${hostLabel}`, nls.localize('host.tooltip', "Editing on {0}", hostLabel), windowActionCommand);
|
||||
} else {
|
||||
this.renderWindowIndicator(`$(alert) ${nls.localize('disconnectedFrom', "Disconnected from")} ${hostLabel}`, nls.localize('host.tooltipDisconnected', "Disconnected from {0}", hostLabel));
|
||||
}
|
||||
} else {
|
||||
if (windowActionCommand) {
|
||||
this.renderWindowIndicator(`$(remote)`, nls.localize('noHost.tooltip', "Open a remote window"), windowActionCommand);
|
||||
} else if (this.windowIndicatorEntry) {
|
||||
this.windowIndicatorEntry.dispose();
|
||||
this.windowIndicatorEntry = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateWindowActions() {
|
||||
const newHasWindowActions = this.windowCommandMenu.getActions().length > 0;
|
||||
if (newHasWindowActions !== this.hasWindowActions) {
|
||||
this.hasWindowActions = newHasWindowActions;
|
||||
this.updateWindowIndicator();
|
||||
}
|
||||
}
|
||||
|
||||
private renderWindowIndicator(text: string, tooltip?: string, command?: string): void {
|
||||
const properties: IStatusbarEntry = {
|
||||
backgroundColor: themeColorFromId(STATUS_BAR_HOST_NAME_BACKGROUND), color: themeColorFromId(STATUS_BAR_HOST_NAME_FOREGROUND), text, tooltip, command
|
||||
};
|
||||
if (this.windowIndicatorEntry) {
|
||||
this.windowIndicatorEntry.update(properties);
|
||||
} else {
|
||||
this.windowIndicatorEntry = this.statusbarService.addEntry(properties, StatusbarAlignment.LEFT, Number.MAX_VALUE /* first entry */);
|
||||
}
|
||||
}
|
||||
|
||||
private showIndicatorActions(menu: IMenu) {
|
||||
|
||||
const actions = menu.getActions();
|
||||
|
||||
const items: (IQuickPickItem | IQuickPickSeparator)[] = [];
|
||||
for (let actionGroup of actions) {
|
||||
if (items.length) {
|
||||
items.push({ type: 'separator' });
|
||||
}
|
||||
for (let action of actionGroup[1]) {
|
||||
if (action instanceof MenuItemAction) {
|
||||
let label = typeof action.item.title === 'string' ? action.item.title : action.item.title.value;
|
||||
if (action.item.category) {
|
||||
const category = typeof action.item.category === 'string' ? action.item.category : action.item.category.value;
|
||||
label = nls.localize('cat.title', "{0}: {1}", category, label);
|
||||
}
|
||||
items.push({
|
||||
type: 'item',
|
||||
id: action.item.id,
|
||||
label
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const quickPick = this.quickInputService.createQuickPick();
|
||||
quickPick.items = items;
|
||||
quickPick.canSelectMany = false;
|
||||
quickPick.onDidAccept(_ => {
|
||||
const selectedItems = quickPick.selectedItems;
|
||||
if (selectedItems.length === 1) {
|
||||
this.commandService.executeCommand(selectedItems[0].id!);
|
||||
}
|
||||
quickPick.hide();
|
||||
});
|
||||
quickPick.show();
|
||||
}
|
||||
}
|
||||
|
||||
class LogOutputChannels extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
constructor(
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService
|
||||
) {
|
||||
super();
|
||||
remoteAgentService.getEnvironment().then(remoteEnv => {
|
||||
if (remoteEnv) {
|
||||
const outputChannelRegistry = Registry.as<IOutputChannelRegistry>(OutputExt.OutputChannels);
|
||||
outputChannelRegistry.registerChannel({ id: 'remoteExtensionLog', label: nls.localize('remoteExtensionLog', "Remote Server"), file: resources.joinPath(remoteEnv.logsPath, `${RemoteExtensionLogFileName}.log`), log: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class RemoteChannelsContribution implements IWorkbenchContribution {
|
||||
|
||||
constructor(
|
||||
@ILogService logService: ILogService,
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IDialogService dialogService: IDialogService
|
||||
) {
|
||||
const connection = remoteAgentService.getConnection();
|
||||
if (connection) {
|
||||
connection.registerChannel('dialog', new DialogChannel(dialogService));
|
||||
connection.registerChannel('download', new DownloadServiceChannel());
|
||||
connection.registerChannel('loglevel', new LogLevelSetterChannel(logService));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RemoteAgentDiagnosticListener implements IWorkbenchContribution {
|
||||
constructor(
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
@ILabelService labelService: ILabelService
|
||||
) {
|
||||
ipc.on('vscode:getDiagnosticInfo', (event: Event, request: { replyChannel: string, args: IDiagnosticInfoOptions }): void => {
|
||||
const connection = remoteAgentService.getConnection();
|
||||
if (connection) {
|
||||
const hostName = labelService.getHostLabel(REMOTE_HOST_SCHEME, connection.remoteAuthority);
|
||||
remoteAgentService.getDiagnosticInfo(request.args)
|
||||
.then(info => {
|
||||
if (info) {
|
||||
(info as IRemoteDiagnosticInfo).hostName = hostName;
|
||||
}
|
||||
|
||||
ipc.send(request.replyChannel, info);
|
||||
})
|
||||
.catch(e => {
|
||||
const errorMessage = e && e.message ? `Fetching remote diagnostics for '${hostName}' failed: ${e.message}` : `Fetching remote diagnostics for '${hostName}' failed.`;
|
||||
ipc.send(request.replyChannel, { hostName, errorMessage });
|
||||
});
|
||||
} else {
|
||||
ipc.send(request.replyChannel);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class ProgressReporter {
|
||||
private _currentProgress: IProgress<IProgressStep> | null = null;
|
||||
private lastReport: string | null = null;
|
||||
|
||||
constructor(currentProgress: IProgress<IProgressStep> | null) {
|
||||
this._currentProgress = currentProgress;
|
||||
}
|
||||
|
||||
set currentProgress(progress: IProgress<IProgressStep>) {
|
||||
this._currentProgress = progress;
|
||||
}
|
||||
|
||||
report(message?: string) {
|
||||
if (message) {
|
||||
this.lastReport = message;
|
||||
}
|
||||
|
||||
if (this.lastReport && this._currentProgress) {
|
||||
this._currentProgress.report({ message: this.lastReport });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RemoteAgentConnectionStatusListener implements IWorkbenchContribution {
|
||||
constructor(
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
@IProgressService2 progressService: IProgressService2,
|
||||
@IDialogService dialogService: IDialogService,
|
||||
@ICommandService commandService: ICommandService
|
||||
) {
|
||||
const connection = remoteAgentService.getConnection();
|
||||
if (connection) {
|
||||
let currentProgressPromiseResolve: (() => void) | null = null;
|
||||
let progressReporter: ProgressReporter | null = null;
|
||||
let currentTimer: ReconnectionTimer | null = null;
|
||||
|
||||
connection.onDidStateChange((e) => {
|
||||
if (currentTimer) {
|
||||
currentTimer.dispose();
|
||||
currentTimer = null;
|
||||
}
|
||||
switch (e.type) {
|
||||
case PersistenConnectionEventType.ConnectionLost:
|
||||
if (!currentProgressPromiseResolve) {
|
||||
let promise = new Promise<void>((resolve) => currentProgressPromiseResolve = resolve);
|
||||
progressService!.withProgress(
|
||||
{ location: ProgressLocation.Dialog },
|
||||
(progress: IProgress<IProgressStep> | null) => { progressReporter = new ProgressReporter(progress!); return promise; },
|
||||
() => {
|
||||
currentProgressPromiseResolve!();
|
||||
promise = new Promise<void>((resolve) => currentProgressPromiseResolve = resolve);
|
||||
progressService!.withProgress({ location: ProgressLocation.Notification }, (progress) => { if (progressReporter) { progressReporter.currentProgress = progress; } return promise; });
|
||||
progressReporter!.report();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
progressReporter!.report(nls.localize('connectionLost', "Connection Lost"));
|
||||
break;
|
||||
case PersistenConnectionEventType.ReconnectionWait:
|
||||
currentTimer = new ReconnectionTimer(progressReporter!, Date.now() + 1000 * e.durationSeconds);
|
||||
break;
|
||||
case PersistenConnectionEventType.ReconnectionRunning:
|
||||
progressReporter!.report(nls.localize('reconnectionRunning', "Attempting to reconnect..."));
|
||||
break;
|
||||
case PersistenConnectionEventType.ReconnectionPermanentFailure:
|
||||
currentProgressPromiseResolve!();
|
||||
currentProgressPromiseResolve = null;
|
||||
progressReporter = null;
|
||||
|
||||
dialogService.show(Severity.Error, nls.localize('reconnectionPermanentFailure', "Cannot reconnect. Please reload the window."), [nls.localize('reloadWindow', "Reload Window"), nls.localize('cancel', "Cancel")], { cancelId: 1 }).then(choice => {
|
||||
// Reload the window
|
||||
if (choice === 0) {
|
||||
commandService.executeCommand(ReloadWindowAction.ID);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case PersistenConnectionEventType.ConnectionGain:
|
||||
currentProgressPromiseResolve!();
|
||||
currentProgressPromiseResolve = null;
|
||||
progressReporter = null;
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ReconnectionTimer implements IDisposable {
|
||||
private readonly _progressReporter: ProgressReporter;
|
||||
private readonly _completionTime: number;
|
||||
private readonly _token: NodeJS.Timeout;
|
||||
|
||||
constructor(progressReporter: ProgressReporter, completionTime: number) {
|
||||
this._progressReporter = progressReporter;
|
||||
this._completionTime = completionTime;
|
||||
this._token = setInterval(() => this._render(), 1000);
|
||||
this._render();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
clearInterval(this._token);
|
||||
}
|
||||
|
||||
private _render() {
|
||||
const remainingTimeMs = this._completionTime - Date.now();
|
||||
if (remainingTimeMs < 0) {
|
||||
return;
|
||||
}
|
||||
const remainingTime = Math.ceil(remainingTimeMs / 1000);
|
||||
if (remainingTime === 1) {
|
||||
this._progressReporter.report(nls.localize('reconnectionWaitOne', "Attempting to reconnect in {0} second...", remainingTime));
|
||||
} else {
|
||||
this._progressReporter.report(nls.localize('reconnectionWaitMany', "Attempting to reconnect in {0} seconds...", remainingTime));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RemoteTelemetryEnablementUpdater extends Disposable implements IWorkbenchContribution {
|
||||
constructor(
|
||||
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.updateRemoteTelemetryEnablement();
|
||||
|
||||
this._register(configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('telemetry.enableTelemetry')) {
|
||||
this.updateRemoteTelemetryEnablement();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private updateRemoteTelemetryEnablement(): Promise<void> {
|
||||
if (!this.configurationService.getValue('telemetry.enableTelemetry')) {
|
||||
return this.remoteAgentService.disableTelemetry();
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchContributionsExtensions.Workbench);
|
||||
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteChannelsContribution, LifecyclePhase.Starting);
|
||||
workbenchContributionsRegistry.registerWorkbenchContribution(LogOutputChannels, LifecyclePhase.Eventually);
|
||||
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentDiagnosticListener, LifecyclePhase.Eventually);
|
||||
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentConnectionStatusListener, LifecyclePhase.Eventually);
|
||||
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteWindowActiveIndicator, LifecyclePhase.Starting);
|
||||
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteTelemetryEnablementUpdater, LifecyclePhase.Ready);
|
||||
|
||||
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
|
||||
.registerConfiguration({
|
||||
id: 'remote',
|
||||
title: nls.localize('remote', "Remote"),
|
||||
type: 'object',
|
||||
properties: {
|
||||
'remote.extensionKind': {
|
||||
type: 'object',
|
||||
markdownDescription: nls.localize('remote.extensionKind', "Override the kind of an extension. `ui` extensions are installed and run on the local machine while `workspace` extensions are run on the remote. By overriding an extension's default kind using this setting, you specify if that extension should be installed and enabled locally or remotely."),
|
||||
patternProperties: {
|
||||
'([a-z0-9A-Z][a-z0-9\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\-A-Z]*)$': {
|
||||
type: 'string',
|
||||
enum: [
|
||||
'ui',
|
||||
'workspace'
|
||||
],
|
||||
enumDescriptions: [
|
||||
nls.localize('ui', "UI extension kind. In a remote window, such extensions are enabled only when available on the local machine."),
|
||||
nls.localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote.")
|
||||
],
|
||||
default: 'ui'
|
||||
},
|
||||
},
|
||||
default: {
|
||||
'pub.name': 'ui'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -342,6 +342,9 @@ export class SearchView extends ViewletPanel {
|
||||
return this.inputPatternExcludes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning: a bit expensive due to updating the view title
|
||||
*/
|
||||
protected updateActions(): void {
|
||||
for (const action of this.actions) {
|
||||
action.update();
|
||||
@@ -1386,6 +1389,8 @@ export class SearchView extends ViewletPanel {
|
||||
|
||||
let visibleMatches = 0;
|
||||
|
||||
let updatedActionsForFileCount = false;
|
||||
|
||||
// Handle UI updates in an interval to show frequent progress and results
|
||||
const uiRefreshHandle: any = setInterval(() => {
|
||||
if (!this.searching) {
|
||||
@@ -1399,7 +1404,9 @@ export class SearchView extends ViewletPanel {
|
||||
visibleMatches = fileCount;
|
||||
this.refreshAndUpdateCount();
|
||||
}
|
||||
if (fileCount > 0) {
|
||||
|
||||
if (fileCount > 0 && !updatedActionsForFileCount) {
|
||||
updatedActionsForFileCount = true;
|
||||
this.updateActions();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
@@ -274,7 +274,7 @@ export class QueryBuilder {
|
||||
* Split search paths (./ or ../ or absolute paths in the includePatterns) into absolute paths and globs applied to those paths
|
||||
*/
|
||||
private expandSearchPathPatterns(searchPaths: string[]): ISearchPathPattern[] {
|
||||
if (this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY || !searchPaths || !searchPaths.length) {
|
||||
if (!searchPaths || !searchPaths.length) {
|
||||
// No workspace => ignore search paths
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import { Schemas } from 'vs/base/common/network';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
|
||||
export const TERMINAL_PICKER_PREFIX = 'term ';
|
||||
|
||||
@@ -85,7 +86,7 @@ export class ToggleTerminalAction extends TogglePanelAction {
|
||||
if (this.terminalService.terminalInstances.length === 0) {
|
||||
// If there is not yet an instance attempt to create it here so that we can suggest a
|
||||
// new shell on Windows (and not do so when the panel is restored on reload).
|
||||
const newTerminalInstance = this.terminalService.createTerminal(undefined, true);
|
||||
const newTerminalInstance = this.terminalService.createTerminal(undefined);
|
||||
const toDispose = newTerminalInstance.onProcessIdReady(() => {
|
||||
newTerminalInstance.focus();
|
||||
toDispose.dispose();
|
||||
@@ -329,7 +330,7 @@ export class CreateNewTerminalAction extends Action {
|
||||
if (folders.length <= 1) {
|
||||
// Allow terminal service to handle the path when there is only a
|
||||
// single root
|
||||
instancePromise = Promise.resolve(this.terminalService.createTerminal(undefined, true));
|
||||
instancePromise = Promise.resolve(this.terminalService.createTerminal(undefined));
|
||||
} else {
|
||||
const options: IPickOptions<IQuickPickItem> = {
|
||||
placeHolder: nls.localize('workbench.action.terminal.newWorkspacePlaceholder', "Select current working directory for new terminal")
|
||||
@@ -339,7 +340,7 @@ export class CreateNewTerminalAction extends Action {
|
||||
// Don't create the instance if the workspace picker was canceled
|
||||
return null;
|
||||
}
|
||||
return this.terminalService.createTerminal({ cwd: workspace.uri }, true);
|
||||
return this.terminalService.createTerminal({ cwd: workspace.uri });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -366,7 +367,7 @@ export class CreateNewInActiveWorkspaceTerminalAction extends Action {
|
||||
}
|
||||
|
||||
public run(event?: any): Promise<any> {
|
||||
const instance = this.terminalService.createTerminal(undefined, true);
|
||||
const instance = this.terminalService.createTerminal(undefined);
|
||||
if (!instance) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
@@ -720,6 +721,14 @@ export class SwitchTerminalAction extends Action {
|
||||
if (!item || !item.split) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
if (item === SwitchTerminalActionViewItem.SEPARATOR) {
|
||||
this.terminalService.refreshActiveTab();
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
if (item === SelectDefaultShellWindowsTerminalAction.LABEL) {
|
||||
this.terminalService.refreshActiveTab();
|
||||
return this.terminalService.selectDefaultWindowsShell();
|
||||
}
|
||||
const selectedTabIndex = parseInt(item.split(':')[0], 10) - 1;
|
||||
this.terminalService.setActiveTabByIndex(selectedTabIndex);
|
||||
return this.terminalService.showPanel(true);
|
||||
@@ -728,11 +737,14 @@ export class SwitchTerminalAction extends Action {
|
||||
|
||||
export class SwitchTerminalActionViewItem extends SelectActionViewItem {
|
||||
|
||||
public static readonly SEPARATOR = '─────────';
|
||||
|
||||
constructor(
|
||||
action: IAction,
|
||||
@ITerminalService private readonly terminalService: ITerminalService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextViewService contextViewService: IContextViewService
|
||||
@IContextViewService contextViewService: IContextViewService,
|
||||
@IWorkbenchEnvironmentService private workbenchEnvironmentService: IWorkbenchEnvironmentService
|
||||
) {
|
||||
super(null, action, terminalService.getTabLabels().map(label => <ISelectOptionItem>{ text: label }), terminalService.activeTabIndex, contextViewService, { ariaLabel: nls.localize('terminals', 'Open Terminals.') });
|
||||
|
||||
@@ -743,7 +755,13 @@ export class SwitchTerminalActionViewItem extends SelectActionViewItem {
|
||||
}
|
||||
|
||||
private _updateItems(): void {
|
||||
this.setOptions(this.terminalService.getTabLabels().map(label => <ISelectOptionItem>{ text: label }), this.terminalService.activeTabIndex);
|
||||
const items = this.terminalService.getTabLabels().map(label => <ISelectOptionItem>{ text: label });
|
||||
let enableSelectDefaultShell = this.workbenchEnvironmentService.configuration.remoteAuthority ? false : isWindows;
|
||||
if (enableSelectDefaultShell) {
|
||||
items.push({ text: SwitchTerminalActionViewItem.SEPARATOR });
|
||||
items.push({ text: SelectDefaultShellWindowsTerminalAction.LABEL });
|
||||
}
|
||||
this.setOptions(items, this.terminalService.activeTabIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -635,7 +635,7 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
|
||||
private _measureRenderTime(): void {
|
||||
const frameTimes: number[] = [];
|
||||
const textRenderLayer = this._xterm._core.renderer._renderLayers[0];
|
||||
const textRenderLayer = this._xterm._core._renderCoordinator._renderer._renderLayers[0];
|
||||
const originalOnGridChanged = textRenderLayer.onGridChanged;
|
||||
|
||||
const evaluateCanvasRenderer = () => {
|
||||
@@ -647,11 +647,7 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
const promptChoices: IPromptChoice[] = [
|
||||
{
|
||||
label: nls.localize('yes', "Yes"),
|
||||
run: () => {
|
||||
this._configurationService.updateValue('terminal.integrated.rendererType', 'dom', ConfigurationTarget.USER).then(() => {
|
||||
this._notificationService.info(nls.localize('terminal.rendererInAllNewTerminals', "The terminal is now using the fallback renderer."));
|
||||
});
|
||||
}
|
||||
run: () => this._configurationService.updateValue('terminal.integrated.rendererType', 'dom', ConfigurationTarget.USER)
|
||||
} as IPromptChoice,
|
||||
{
|
||||
label: nls.localize('no', "No"),
|
||||
@@ -1264,7 +1260,7 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
// maximize on Windows/Linux would fire an event saying that the terminal was not
|
||||
// visible.
|
||||
if (this._xterm.getOption('rendererType') === 'canvas') {
|
||||
this._xterm._core.renderer.onIntersectionChange({ intersectionRatio: 1 });
|
||||
this._xterm._core._renderCoordinator._onIntersectionChange({ intersectionRatio: 1 });
|
||||
// HACK: Force a refresh of the screen to ensure links are refresh corrected.
|
||||
// This can probably be removed when the above hack is fixed in Chromium.
|
||||
this._xterm.refresh(0, this._xterm.rows - 1);
|
||||
|
||||
@@ -88,6 +88,9 @@ export class TerminalLinkHandler {
|
||||
this._gitDiffPostImagePattern = /^\+\+\+ b\/(\S*)/;
|
||||
|
||||
this._tooltipCallback = (e: MouseEvent) => {
|
||||
if (!this._widgetManager) {
|
||||
return;
|
||||
}
|
||||
if (this._terminalService && this._terminalService.configHelper.config.rendererType === 'dom') {
|
||||
const target = (e.target as HTMLElement);
|
||||
this._widgetManager.showMessage(target.offsetLeft, target.offsetTop, this._getLinkHoverString());
|
||||
@@ -115,7 +118,11 @@ export class TerminalLinkHandler {
|
||||
const options: ILinkMatcherOptions = {
|
||||
matchIndex,
|
||||
tooltipCallback: this._tooltipCallback,
|
||||
leaveCallback: () => this._widgetManager.closeMessage(),
|
||||
leaveCallback: () => {
|
||||
if (this._widgetManager) {
|
||||
this._widgetManager.closeMessage();
|
||||
}
|
||||
},
|
||||
willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e),
|
||||
priority: CUSTOM_LINK_PRIORITY
|
||||
};
|
||||
@@ -132,7 +139,11 @@ export class TerminalLinkHandler {
|
||||
this._xterm.webLinksInit(wrappedHandler, {
|
||||
validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateWebLink(uri, callback),
|
||||
tooltipCallback: this._tooltipCallback,
|
||||
leaveCallback: () => this._widgetManager.closeMessage(),
|
||||
leaveCallback: () => {
|
||||
if (this._widgetManager) {
|
||||
this._widgetManager.closeMessage();
|
||||
}
|
||||
},
|
||||
willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e)
|
||||
});
|
||||
}
|
||||
@@ -144,7 +155,11 @@ export class TerminalLinkHandler {
|
||||
this._xterm.registerLinkMatcher(this._localLinkRegex, wrappedHandler, {
|
||||
validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateLocalLink(uri, callback),
|
||||
tooltipCallback: this._tooltipCallback,
|
||||
leaveCallback: () => this._widgetManager.closeMessage(),
|
||||
leaveCallback: () => {
|
||||
if (this._widgetManager) {
|
||||
this._widgetManager.closeMessage();
|
||||
}
|
||||
},
|
||||
willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e),
|
||||
priority: LOCAL_LINK_PRIORITY
|
||||
});
|
||||
@@ -158,7 +173,11 @@ export class TerminalLinkHandler {
|
||||
matchIndex: 1,
|
||||
validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateLocalLink(uri, callback),
|
||||
tooltipCallback: this._tooltipCallback,
|
||||
leaveCallback: () => this._widgetManager.closeMessage(),
|
||||
leaveCallback: () => {
|
||||
if (this._widgetManager) {
|
||||
this._widgetManager.closeMessage();
|
||||
}
|
||||
},
|
||||
willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e),
|
||||
priority: LOCAL_LINK_PRIORITY
|
||||
};
|
||||
@@ -242,12 +261,16 @@ export class TerminalLinkHandler {
|
||||
private _getLinkHoverString(): string {
|
||||
const editorConf = this._configurationService.getValue<{ multiCursorModifier: 'ctrlCmd' | 'alt' }>('editor');
|
||||
if (editorConf.multiCursorModifier === 'ctrlCmd') {
|
||||
return nls.localize('terminalLinkHandler.followLinkAlt', 'Alt + click to follow link');
|
||||
if (platform.isMacintosh) {
|
||||
return nls.localize('terminalLinkHandler.followLinkAlt.mac', "Option + click to follow link");
|
||||
} else {
|
||||
return nls.localize('terminalLinkHandler.followLinkAlt', "Alt + click to follow link");
|
||||
}
|
||||
}
|
||||
if (platform.isMacintosh) {
|
||||
return nls.localize('terminalLinkHandler.followLinkCmd', 'Cmd + click to follow link');
|
||||
return nls.localize('terminalLinkHandler.followLinkCmd', "Cmd + click to follow link");
|
||||
}
|
||||
return nls.localize('terminalLinkHandler.followLinkCtrl', 'Ctrl + click to follow link');
|
||||
return nls.localize('terminalLinkHandler.followLinkCtrl', "Ctrl + click to follow link");
|
||||
}
|
||||
|
||||
private get osPath(): IPath {
|
||||
|
||||
@@ -3,21 +3,19 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, IShellLaunchConfig, NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { TerminalService as CommonTerminalService } from 'vs/workbench/contrib/terminal/common/terminalService';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { TerminalPanel } from 'vs/workbench/contrib/terminal/browser/terminalPanel';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { TerminalTab } from 'vs/workbench/contrib/terminal/browser/terminalTab';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance';
|
||||
@@ -36,7 +34,6 @@ export abstract class TerminalService extends CommonTerminalService implements I
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IDialogService dialogService: IDialogService,
|
||||
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
|
||||
@IWorkbenchEnvironmentService private _environmentService: IWorkbenchEnvironmentService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService
|
||||
@@ -52,7 +49,7 @@ export abstract class TerminalService extends CommonTerminalService implements I
|
||||
return instance;
|
||||
}
|
||||
|
||||
public createTerminal(shell: IShellLaunchConfig = {}, wasNewTerminalAction?: boolean): ITerminalInstance {
|
||||
public createTerminal(shell: IShellLaunchConfig = {}): ITerminalInstance {
|
||||
const terminalTab = this._instantiationService.createInstance(TerminalTab,
|
||||
this._terminalFocusContextKey,
|
||||
this.configHelper,
|
||||
@@ -68,74 +65,9 @@ export abstract class TerminalService extends CommonTerminalService implements I
|
||||
this.setActiveInstanceByIndex(0);
|
||||
}
|
||||
this._onInstancesChanged.fire();
|
||||
this._suggestShellChange(wasNewTerminalAction);
|
||||
return instance;
|
||||
}
|
||||
|
||||
private _suggestShellChange(wasNewTerminalAction?: boolean): void {
|
||||
// Only suggest on Windows since $SHELL works great for macOS/Linux
|
||||
if (!platform.isWindows) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._environmentService.configuration.remoteAuthority) {
|
||||
// Don't suggest if the opened workspace is remote
|
||||
return;
|
||||
}
|
||||
|
||||
// Only suggest when the terminal instance is being created by an explicit user action to
|
||||
// launch a terminal, as opposed to something like tasks, debug, panel restore, etc.
|
||||
if (!wasNewTerminalAction) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._environmentService.configuration.remoteAuthority) {
|
||||
// Don't suggest if the opened workspace is remote
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't suggest if the user has explicitly opted out
|
||||
const neverSuggest = this._storageService.getBoolean(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, StorageScope.GLOBAL, false);
|
||||
if (neverSuggest) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Never suggest if the setting is non-default already (ie. they set the setting manually)
|
||||
if (this.configHelper.config.shell.windows !== this.getDefaultShell(platform.Platform.Windows)) {
|
||||
this._storageService.store(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, true, StorageScope.GLOBAL);
|
||||
return;
|
||||
}
|
||||
|
||||
this._notificationService.prompt(
|
||||
Severity.Info,
|
||||
nls.localize('terminal.integrated.chooseWindowsShellInfo', "You can change the default terminal shell by selecting the customize button."),
|
||||
[{
|
||||
label: nls.localize('customize', "Customize"),
|
||||
run: () => {
|
||||
this.selectDefaultWindowsShell().then(shell => {
|
||||
if (!shell) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
// Launch a new instance with the newly selected shell
|
||||
const instance = this.createTerminal({
|
||||
executable: shell,
|
||||
args: this.configHelper.config.shellArgs.windows
|
||||
});
|
||||
if (instance) {
|
||||
this.setActiveInstance(instance);
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
label: nls.localize('never again', "Don't Show Again"),
|
||||
isSecondary: true,
|
||||
run: () => this._storageService.store(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, true, StorageScope.GLOBAL)
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
public focusFindWidget(): Promise<void> {
|
||||
return this.showPanel(false).then(() => {
|
||||
const panel = this._panelService.getActivePanel() as TerminalPanel;
|
||||
|
||||
@@ -37,7 +37,6 @@ export const KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED = new RawContextKey
|
||||
export const KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_NOT_FOCUSED: ContextKeyExpr = KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED.toNegated();
|
||||
|
||||
export const IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY = 'terminal.integrated.isWorkspaceShellAllowed';
|
||||
export const NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY = 'terminal.integrated.neverSuggestSelectWindowsShell';
|
||||
export const NEVER_MEASURE_RENDER_TIME_STORAGE_KEY = 'terminal.integrated.neverMeasureRenderTime';
|
||||
|
||||
// The creation of extension host terminals is delayed by this value (milliseconds). The purpose of
|
||||
@@ -64,9 +63,9 @@ export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '50
|
||||
|
||||
export interface ITerminalConfiguration {
|
||||
shell: {
|
||||
linux: string;
|
||||
osx: string;
|
||||
windows: string;
|
||||
linux: string | null;
|
||||
osx: string | null;
|
||||
windows: string | null;
|
||||
};
|
||||
shellArgs: {
|
||||
linux: string[];
|
||||
@@ -216,10 +215,8 @@ export interface ITerminalService {
|
||||
/**
|
||||
* Creates a terminal.
|
||||
* @param shell The shell launch configuration to use.
|
||||
* @param wasNewTerminalAction Whether this was triggered by a new terminal action, if so a
|
||||
* default shell selection dialog may display.
|
||||
*/
|
||||
createTerminal(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance;
|
||||
createTerminal(shell?: IShellLaunchConfig): ITerminalInstance;
|
||||
|
||||
/**
|
||||
* Creates a terminal renderer.
|
||||
@@ -245,6 +242,12 @@ export interface ITerminalService {
|
||||
setActiveTabToPrevious(): void;
|
||||
setActiveTabByIndex(tabIndex: number): void;
|
||||
|
||||
/**
|
||||
* Fire the onActiveTabChanged event, this will trigger the terminal dropdown to be updated,
|
||||
* among other things.
|
||||
*/
|
||||
refreshActiveTab(): void;
|
||||
|
||||
showPanel(focus?: boolean): Promise<void>;
|
||||
hidePanel(): void;
|
||||
focusFindWidget(): Promise<void>;
|
||||
|
||||
@@ -98,6 +98,7 @@ function _getLangEnvVariable(locale?: string) {
|
||||
es: 'ES',
|
||||
fi: 'FI',
|
||||
fr: 'FR',
|
||||
hu: 'HU',
|
||||
it: 'IT',
|
||||
ja: 'JP',
|
||||
ko: 'KR',
|
||||
|
||||
@@ -41,7 +41,7 @@ export abstract class TerminalService implements ITerminalService {
|
||||
public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; }
|
||||
public get terminalTabs(): ITerminalTab[] { return this._terminalTabs; }
|
||||
|
||||
private readonly _onActiveTabChanged = new Emitter<void>();
|
||||
protected readonly _onActiveTabChanged = new Emitter<void>();
|
||||
public get onActiveTabChanged(): Event<void> { return this._onActiveTabChanged.event; }
|
||||
protected readonly _onInstanceCreated = new Emitter<ITerminalInstance>();
|
||||
public get onInstanceCreated(): Event<ITerminalInstance> { return this._onInstanceCreated.event; }
|
||||
@@ -104,6 +104,7 @@ export abstract class TerminalService implements ITerminalService {
|
||||
protected abstract _getWslPath(path: string): Promise<string>;
|
||||
protected abstract _getWindowsBuildNumber(): number;
|
||||
|
||||
public abstract refreshActiveTab(): void;
|
||||
public abstract createTerminal(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance;
|
||||
public abstract createInstance(terminalFocusContextKey: IContextKey<boolean>, configHelper: ITerminalConfigHelper, container: HTMLElement, shellLaunchConfig: IShellLaunchConfig, doCreateProcess: boolean): ITerminalInstance;
|
||||
public abstract getDefaultShell(platform: Platform): string;
|
||||
|
||||
@@ -20,7 +20,6 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { ipcRenderer as ipc } from 'electron';
|
||||
import { IOpenFileRequest } from 'vs/platform/windows/common/windows';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IQuickInputService, IQuickPickItem, IPickOptions } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
@@ -45,11 +44,10 @@ export class TerminalService extends BrowserTerminalService implements ITerminal
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IDialogService dialogService: IDialogService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService
|
||||
) {
|
||||
super(contextKeyService, panelService, layoutService, lifecycleService, storageService, notificationService, dialogService, instantiationService, environmentService, extensionService, fileService, remoteAgentService);
|
||||
super(contextKeyService, panelService, layoutService, lifecycleService, storageService, notificationService, dialogService, instantiationService, extensionService, fileService, remoteAgentService);
|
||||
|
||||
this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper, linuxDistro);
|
||||
ipc.on('vscode:openFiles', (_event: any, request: IOpenFileRequest) => {
|
||||
@@ -102,6 +100,11 @@ export class TerminalService extends BrowserTerminalService implements ITerminal
|
||||
return getDefaultShell(p);
|
||||
}
|
||||
|
||||
public refreshActiveTab(): void {
|
||||
// Fire active instances changed
|
||||
this._onActiveTabChanged.fire();
|
||||
}
|
||||
|
||||
public selectDefaultWindowsShell(): Promise<string | undefined> {
|
||||
return this._detectWindowsShells().then(shells => {
|
||||
const options: IPickOptions<IQuickPickItem> = {
|
||||
|
||||
@@ -62,13 +62,14 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
|
||||
!is32ProcessOn64Windows &&
|
||||
getWindowsBuildNumber() >= 18309;
|
||||
|
||||
const options: pty.IPtyForkOptions = {
|
||||
const options: pty.IPtyForkOptions | pty.IWindowsPtyForkOptions = {
|
||||
name: shellName,
|
||||
cwd,
|
||||
env,
|
||||
cols,
|
||||
rows,
|
||||
experimentalUseConpty: useConpty
|
||||
experimentalUseConpty: useConpty,
|
||||
conptyInheritCursor: true
|
||||
};
|
||||
|
||||
// TODO: Need to verify whether executable is on $PATH, otherwise things like cmd.exe will break
|
||||
@@ -91,14 +92,14 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
|
||||
this._processStartupComplete = new Promise<void>(c => {
|
||||
this.onProcessIdReady(() => c());
|
||||
});
|
||||
ptyProcess.on('data', (data) => {
|
||||
ptyProcess.on('data', data => {
|
||||
this._onProcessData.fire(data);
|
||||
if (this._closeTimeout) {
|
||||
clearTimeout(this._closeTimeout);
|
||||
this._queueProcessExit();
|
||||
}
|
||||
});
|
||||
ptyProcess.on('exit', (code) => {
|
||||
ptyProcess.on('exit', code => {
|
||||
this._exitCode = code;
|
||||
this._queueProcessExit();
|
||||
});
|
||||
@@ -126,12 +127,14 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
|
||||
setTimeout(() => {
|
||||
this._sendProcessTitle(ptyProcess);
|
||||
}, 0);
|
||||
// Setup polling
|
||||
this._titleInterval = setInterval(() => {
|
||||
if (this._currentTitle !== ptyProcess.process) {
|
||||
this._sendProcessTitle(ptyProcess);
|
||||
}
|
||||
}, 200);
|
||||
// Setup polling for non-Windows, for Windows `process` doesn't change
|
||||
if (!platform.isWindows) {
|
||||
this._titleInterval = setInterval(() => {
|
||||
if (this._currentTitle !== ptyProcess.process) {
|
||||
this._sendProcessTitle(ptyProcess);
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
|
||||
// Allow any trailing data events to be sent before the exit event is sent.
|
||||
@@ -189,7 +192,7 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable {
|
||||
if (this._isDisposed || !this._ptyProcess) {
|
||||
return;
|
||||
}
|
||||
this._logService.trace('IPty#write', data);
|
||||
this._logService.trace('IPty#write', `${data.length} characters`);
|
||||
this._ptyProcess.write(data);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import * as modes from 'vs/editor/common/modes';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
|
||||
@@ -118,7 +118,7 @@ class WebviewProtocolProvider extends Disposable {
|
||||
private readonly _extensionLocation: URI | undefined,
|
||||
private readonly _getLocalResourceRoots: () => ReadonlyArray<URI>,
|
||||
private readonly _environmentService: IEnvironmentService,
|
||||
private readonly _textFileService: ITextFileService,
|
||||
private readonly _fileService: IFileService,
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -137,11 +137,11 @@ class WebviewProtocolProvider extends Disposable {
|
||||
|
||||
const appRootUri = URI.file(this._environmentService.appRoot);
|
||||
|
||||
registerFileProtocol(contents, WebviewProtocol.CoreResource, this._textFileService, undefined, () => [
|
||||
registerFileProtocol(contents, WebviewProtocol.CoreResource, this._fileService, undefined, () => [
|
||||
appRootUri
|
||||
]);
|
||||
|
||||
registerFileProtocol(contents, WebviewProtocol.VsCodeResource, this._textFileService, this._extensionLocation, () =>
|
||||
registerFileProtocol(contents, WebviewProtocol.VsCodeResource, this._fileService, this._extensionLocation, () =>
|
||||
this._getLocalResourceRoots()
|
||||
);
|
||||
}
|
||||
@@ -165,7 +165,8 @@ class WebviewPortMappingProvider extends Disposable {
|
||||
|
||||
session.onBeforeRequest(async (details) => {
|
||||
const uri = URI.parse(details.url);
|
||||
if (uri.scheme !== 'http' && uri.scheme !== 'https') {
|
||||
const allowedSchemes = ['http', 'https', 'ws', 'wss'];
|
||||
if (allowedSchemes.indexOf(uri.scheme) === -1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -379,7 +380,7 @@ export class WebviewElement extends Disposable implements Webview {
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@IFileService fileService: IFileService,
|
||||
@ITunnelService tunnelService: ITunnelService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@@ -422,7 +423,7 @@ export class WebviewElement extends Disposable implements Webview {
|
||||
this._options.extension ? this._options.extension.location : undefined,
|
||||
() => (this.content.options.localResourceRoots || []),
|
||||
environmentService,
|
||||
textFileService));
|
||||
fileService));
|
||||
|
||||
this._register(new WebviewPortMappingProvider(
|
||||
session,
|
||||
|
||||
@@ -7,17 +7,20 @@ import { extname, sep } from 'vs/base/common/path';
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import * as electron from 'electron';
|
||||
|
||||
type BufferProtocolCallback = (buffer?: Buffer | electron.MimeTypedBuffer | { error: number }) => void;
|
||||
|
||||
export const enum WebviewProtocol {
|
||||
CoreResource = 'vscode-core-resource',
|
||||
VsCodeResource = 'vscode-resource',
|
||||
}
|
||||
|
||||
function resolveContent(textFileService: ITextFileService, resource: URI, mime: string, callback: any): void {
|
||||
textFileService.read(resource, { encoding: 'binary' }).then(contents => {
|
||||
function resolveContent(fileService: IFileService, resource: URI, mime: string, callback: BufferProtocolCallback): void {
|
||||
fileService.readFile(resource).then(contents => {
|
||||
callback({
|
||||
data: Buffer.from(contents.value, contents.encoding),
|
||||
data: Buffer.from(contents.value.buffer),
|
||||
mimeType: mime
|
||||
});
|
||||
}, (err) => {
|
||||
@@ -27,9 +30,9 @@ function resolveContent(textFileService: ITextFileService, resource: URI, mime:
|
||||
}
|
||||
|
||||
export function registerFileProtocol(
|
||||
contents: Electron.WebContents,
|
||||
contents: electron.WebContents,
|
||||
protocol: WebviewProtocol,
|
||||
textFileService: ITextFileService,
|
||||
fileService: IFileService,
|
||||
extensionLocation: URI | undefined,
|
||||
getRoots: () => ReadonlyArray<URI>
|
||||
) {
|
||||
@@ -51,10 +54,10 @@ export function registerFileProtocol(
|
||||
requestResourcePath: requestUri.path
|
||||
})
|
||||
});
|
||||
resolveContent(textFileService, redirectedUri, getMimeType(requestUri), callback);
|
||||
resolveContent(fileService, redirectedUri, getMimeType(requestUri), callback);
|
||||
return;
|
||||
} else {
|
||||
resolveContent(textFileService, normalizedPath, getMimeType(normalizedPath), callback);
|
||||
resolveContent(fileService, normalizedPath, getMimeType(normalizedPath), callback);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ export default () => `
|
||||
<li><a href="command:workbench.action.openDocumentationUrl">${escape(localize('welcomePage.productDocumentation', "Product documentation"))}</a></li>
|
||||
<li><a href="https://github.com/Microsoft/vscode">${escape(localize('welcomePage.gitHubRepository', "GitHub repository"))}</a></li>
|
||||
<li><a href="http://stackoverflow.com/questions/tagged/vscode?sort=votes&pageSize=50">${escape(localize('welcomePage.stackOverflow', "Stack Overflow"))}</a></li>
|
||||
<li><a href="command:workbench.action.openNewsletterSignupUrl">${escape(localize('welcomePage.newsletterSignup', "Join our Newsletter"))}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<p class="showOnStartup"><input type="checkbox" id="showOnStartup" class="checkbox"> <label class="caption" for="showOnStartup">${escape(localize('welcomePage.showOnStartup', "Show welcome page on startup"))}</label></p>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Action } from 'vs/base/common/actions';
|
||||
import * as nls from 'vs/nls';
|
||||
import product from 'vs/platform/product/node/product';
|
||||
import { isMacintosh, isLinux, language } from 'vs/base/common/platform';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
export class KeybindingsReferenceAction extends Action {
|
||||
|
||||
@@ -91,7 +92,31 @@ export class OpenTipsAndTricksUrlAction extends Action {
|
||||
|
||||
run(): Promise<void> {
|
||||
window.open(OpenTipsAndTricksUrlAction.URL);
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenNewsletterSignupUrlAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.openNewsletterSignupUrl';
|
||||
static readonly LABEL = nls.localize('newsletterSignup', "Signup for the VS Code Newsletter");
|
||||
private telemetryService: ITelemetryService;
|
||||
private static readonly URL = product.newsletterSignupUrl;
|
||||
static readonly AVAILABLE = !!OpenNewsletterSignupUrlAction.URL;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@ITelemetryService telemetryService: ITelemetryService
|
||||
) {
|
||||
super(id, label);
|
||||
this.telemetryService = telemetryService;
|
||||
}
|
||||
|
||||
run(): Promise<void> {
|
||||
this.telemetryService.getTelemetryInfo().then(info => {
|
||||
window.open(`${OpenNewsletterSignupUrlAction.URL}?machineId=${encodeURIComponent(info.machineId)}`);
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
|
||||
import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform';
|
||||
import { KeybindingsReferenceAction, OpenDocumentationUrlAction, OpenIntroductoryVideosUrlAction, OpenTipsAndTricksUrlAction, OpenTwitterUrlAction, OpenRequestFeatureUrlAction, OpenPrivacyStatementUrlAction, OpenLicenseUrlAction } from 'vs/workbench/electron-browser/actions/helpActions';
|
||||
import { KeybindingsReferenceAction, OpenDocumentationUrlAction, OpenIntroductoryVideosUrlAction, OpenTipsAndTricksUrlAction, OpenTwitterUrlAction, OpenRequestFeatureUrlAction, OpenPrivacyStatementUrlAction, OpenLicenseUrlAction, OpenNewsletterSignupUrlAction } from 'vs/workbench/electron-browser/actions/helpActions';
|
||||
import { ToggleSharedProcessAction, InspectContextKeysAction, ToggleScreencastModeAction, ToggleDevToolsAction } from 'vs/workbench/electron-browser/actions/developerActions';
|
||||
import { ShowAboutDialogAction, ZoomResetAction, ZoomOutAction, ZoomInAction, ToggleFullScreenAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, QuickSwitchWindow, QuickOpenRecentAction, inRecentFilesPickerContextKey, OpenRecentAction, ReloadWindowWithExtensionsDisabledAction, NewWindowTabHandler, ReloadWindowAction, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler } from 'vs/workbench/electron-browser/actions/windowActions';
|
||||
import { AddRootFolderAction, GlobalRemoveRootFolderAction, OpenWorkspaceAction, SaveWorkspaceAsAction, OpenWorkspaceConfigFileAction, DuplicateWorkspaceInNewWindowAction, OpenFileFolderAction, OpenFileAction, OpenFolderAction, CloseWorkspaceAction, OpenLocalFileAction, OpenLocalFolderAction, OpenLocalFileFolderAction } from 'vs/workbench/browser/actions/workspaceActions';
|
||||
@@ -207,6 +207,10 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/electron-brow
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenTipsAndTricksUrlAction, OpenTipsAndTricksUrlAction.ID, OpenTipsAndTricksUrlAction.LABEL), 'Help: Tips and Tricks', helpCategory);
|
||||
}
|
||||
|
||||
if (OpenNewsletterSignupUrlAction.AVAILABLE) {
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNewsletterSignupUrlAction, OpenNewsletterSignupUrlAction.ID, OpenNewsletterSignupUrlAction.LABEL), 'Help: Tips and Tricks', helpCategory);
|
||||
}
|
||||
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenTwitterUrlAction, OpenTwitterUrlAction.ID, OpenTwitterUrlAction.LABEL), 'Help: Join Us on Twitter', helpCategory);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenRequestFeatureUrlAction, OpenRequestFeatureUrlAction.ID, OpenRequestFeatureUrlAction.LABEL), 'Help: Search Feature Requests', helpCategory);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLicenseUrlAction, OpenLicenseUrlAction.ID, OpenLicenseUrlAction.LABEL), 'Help: View License', helpCategory);
|
||||
|
||||
@@ -23,7 +23,7 @@ import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron
|
||||
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
|
||||
import { webFrame } from 'electron';
|
||||
import { ISingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { createBufferSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { ConsoleLogService, MultiplexLogService, ILogService } from 'vs/platform/log/common/log';
|
||||
import { StorageService } from 'vs/platform/storage/node/storageService';
|
||||
import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/node/logIpc';
|
||||
@@ -322,7 +322,7 @@ class CodeRendererMain extends Disposable {
|
||||
}
|
||||
|
||||
private createLogService(mainProcessService: IMainProcessService, environmentService: IWorkbenchEnvironmentService): ILogService {
|
||||
const spdlogService = createSpdLogService(`renderer${this.configuration.windowId}`, this.configuration.logLevel, environmentService.logsPath);
|
||||
const spdlogService = createBufferSpdLogService(`renderer${this.configuration.windowId}`, this.configuration.logLevel, environmentService.logsPath);
|
||||
const consoleLogService = new ConsoleLogService(this.configuration.logLevel);
|
||||
const logService = new MultiplexLogService([consoleLogService, spdlogService]);
|
||||
const logLevelClient = new LogLevelSetterChannelClient(mainProcessService.getChannel('loglevel'));
|
||||
|
||||
@@ -29,6 +29,7 @@ export class RemoteUserConfiguration extends Disposable {
|
||||
private readonly _cachedConfiguration: CachedUserConfiguration;
|
||||
private readonly _configurationFileService: IConfigurationFileService;
|
||||
private _userConfiguration: UserConfiguration | CachedUserConfiguration;
|
||||
private _userConfigurationInitializationPromise: Promise<ConfigurationModel> | null = null;
|
||||
|
||||
private readonly _onDidChangeConfiguration: Emitter<ConfigurationModel> = this._register(new Emitter<ConfigurationModel>());
|
||||
public readonly onDidChangeConfiguration: Event<ConfigurationModel> = this._onDidChangeConfiguration.event;
|
||||
@@ -46,7 +47,8 @@ export class RemoteUserConfiguration extends Disposable {
|
||||
if (environment) {
|
||||
const userConfiguration = this._register(new UserConfiguration(environment.settingsPath, MACHINE_SCOPES, this._configurationFileService));
|
||||
this._register(userConfiguration.onDidChangeConfiguration(configurationModel => this.onDidUserConfigurationChange(configurationModel)));
|
||||
const configurationModel = await userConfiguration.initialize();
|
||||
this._userConfigurationInitializationPromise = userConfiguration.initialize();
|
||||
const configurationModel = await this._userConfigurationInitializationPromise;
|
||||
this._userConfiguration.dispose();
|
||||
this._userConfiguration = userConfiguration;
|
||||
this.onDidUserConfigurationChange(configurationModel);
|
||||
@@ -54,8 +56,20 @@ export class RemoteUserConfiguration extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
initialize(): Promise<ConfigurationModel> {
|
||||
return this._userConfiguration.initialize();
|
||||
async initialize(): Promise<ConfigurationModel> {
|
||||
if (this._userConfiguration instanceof UserConfiguration) {
|
||||
return this._userConfiguration.initialize();
|
||||
}
|
||||
|
||||
// Initialize cached configuration
|
||||
let configurationModel = await this._userConfiguration.initialize();
|
||||
if (this._userConfigurationInitializationPromise) {
|
||||
// Use user configuration
|
||||
configurationModel = await this._userConfigurationInitializationPromise;
|
||||
this._userConfigurationInitializationPromise = null;
|
||||
}
|
||||
|
||||
return configurationModel;
|
||||
}
|
||||
|
||||
reload(): Promise<ConfigurationModel> {
|
||||
|
||||
@@ -543,8 +543,10 @@ export class WorkspaceService extends Disposable implements IConfigurationServic
|
||||
}
|
||||
|
||||
private onRemoteUserConfigurationChanged(userConfiguration: ConfigurationModel): void {
|
||||
const keys = this._configuration.compareAndUpdateRemoteUserConfiguration(userConfiguration);
|
||||
this.triggerConfigurationChange(keys, ConfigurationTarget.USER);
|
||||
if (this._configuration) {
|
||||
const keys = this._configuration.compareAndUpdateRemoteUserConfiguration(userConfiguration);
|
||||
this.triggerConfigurationChange(keys, ConfigurationTarget.USER);
|
||||
}
|
||||
}
|
||||
|
||||
private onWorkspaceConfigurationChanged(): Promise<void> {
|
||||
|
||||
+174
-1
@@ -22,7 +22,7 @@ import { ConfigurationEditingErrorCode } from 'vs/workbench/services/configurati
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
|
||||
import { ConfigurationTarget, IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/workbenchTestServices';
|
||||
import { workbenchInstantiationService, TestTextFileService, RemoteFileSystemProvider } from 'vs/workbench/test/workbenchTestServices';
|
||||
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
@@ -42,6 +42,8 @@ import { NullLogService } from 'vs/platform/log/common/log';
|
||||
import { DiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider';
|
||||
import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache';
|
||||
import { ConfigurationFileService } from 'vs/workbench/services/configuration/node/configurationFileService';
|
||||
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
|
||||
import { IConfigurationCache } from 'vs/workbench/services/configuration/common/configuration';
|
||||
|
||||
class SettingsTestEnvironmentService extends EnvironmentService {
|
||||
|
||||
@@ -95,6 +97,177 @@ suite('WorkspaceContextService - Folder', () => {
|
||||
// {{SQL CARBON EDIT}} - Remove tests
|
||||
assert.equal(0, 0);
|
||||
});
|
||||
|
||||
test('configuration of newly added folder is available on configuration change event', async () => {
|
||||
// {{SQL CARBON EDIT}} - Remove tests
|
||||
assert.equal(0, 0);
|
||||
});
|
||||
});
|
||||
|
||||
suite('WorkspaceConfigurationService - Remote Folder', () => {
|
||||
|
||||
let workspaceName = `testWorkspace${uuid.generateUuid()}`, parentResource: string, workspaceDir: string, testObject: WorkspaceService, globalSettingsFile: string, remoteSettingsFile: string, instantiationService: TestInstantiationService, resolveRemoteEnvironment: () => void;
|
||||
const remoteAuthority = 'configuraiton-tests';
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
|
||||
const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService());
|
||||
|
||||
suiteSetup(() => {
|
||||
configurationRegistry.registerConfiguration({
|
||||
'id': '_test',
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'configurationService.remote.applicationSetting': {
|
||||
'type': 'string',
|
||||
'default': 'isSet',
|
||||
scope: ConfigurationScope.APPLICATION
|
||||
},
|
||||
'configurationService.remote.machineSetting': {
|
||||
'type': 'string',
|
||||
'default': 'isSet',
|
||||
scope: ConfigurationScope.MACHINE
|
||||
},
|
||||
'configurationService.remote.testSetting': {
|
||||
'type': 'string',
|
||||
'default': 'isSet',
|
||||
scope: ConfigurationScope.RESOURCE
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
setup(() => {
|
||||
return setUpFolderWorkspace(workspaceName)
|
||||
.then(({ parentDir, folderDir }) => {
|
||||
|
||||
parentResource = parentDir;
|
||||
workspaceDir = folderDir;
|
||||
globalSettingsFile = path.join(parentDir, 'settings.json');
|
||||
remoteSettingsFile = path.join(parentDir, 'remote-settings.json');
|
||||
|
||||
instantiationService = <TestInstantiationService>workbenchInstantiationService();
|
||||
const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile);
|
||||
const remoteEnvironmentPromise = new Promise<Partial<IRemoteAgentEnvironment>>(c => resolveRemoteEnvironment = () => c({ settingsPath: URI.file(remoteSettingsFile).with({ scheme: Schemas.vscodeRemote, authority: remoteAuthority }) }));
|
||||
const remoteAgentService = instantiationService.stub(IRemoteAgentService, <Partial<IRemoteAgentService>>{ getEnvironment: () => remoteEnvironmentPromise });
|
||||
const fileService = new FileService(new NullLogService());
|
||||
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
|
||||
const configurationFileService = new ConfigurationFileService();
|
||||
configurationFileService.fileService = fileService;
|
||||
const configurationCache: IConfigurationCache = { read: () => Promise.resolve(''), write: () => Promise.resolve(), remove: () => Promise.resolve() };
|
||||
testObject = new WorkspaceService({ userSettingsResource: URI.file(environmentService.appSettingsPath), configurationCache, remoteAuthority }, configurationFileService, remoteAgentService);
|
||||
instantiationService.stub(IWorkspaceContextService, testObject);
|
||||
instantiationService.stub(IConfigurationService, testObject);
|
||||
instantiationService.stub(IEnvironmentService, environmentService);
|
||||
instantiationService.stub(IFileService, fileService);
|
||||
});
|
||||
});
|
||||
|
||||
async function initialize(): Promise<void> {
|
||||
await testObject.initialize(convertToWorkspacePayload(URI.file(workspaceDir)));
|
||||
instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService));
|
||||
instantiationService.stub(ITextModelService, <ITextModelService>instantiationService.createInstance(TextModelResolverService));
|
||||
testObject.acquireInstantiationService(instantiationService);
|
||||
}
|
||||
|
||||
function registerRemoteFileSystemProvider(): void {
|
||||
instantiationService.get(IFileService).registerProvider(Schemas.vscodeRemote, new RemoteFileSystemProvider(diskFileSystemProvider, remoteAuthority));
|
||||
}
|
||||
|
||||
function registerRemoteFileSystemProviderOnActivation(): void {
|
||||
const disposable = instantiationService.get(IFileService).onWillActivateFileSystemProvider(e => {
|
||||
if (e.scheme === Schemas.vscodeRemote) {
|
||||
disposable.dispose();
|
||||
e.join(Promise.resolve().then(() => registerRemoteFileSystemProvider()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
teardown(() => {
|
||||
if (testObject) {
|
||||
(<WorkspaceService>testObject).dispose();
|
||||
}
|
||||
if (parentResource) {
|
||||
return pfs.rimraf(parentResource, pfs.RimRafMode.MOVE);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
test('remote settings override globals', async () => {
|
||||
fs.writeFileSync(remoteSettingsFile, '{ "configurationService.remote.machineSetting": "remoteValue" }');
|
||||
registerRemoteFileSystemProvider();
|
||||
resolveRemoteEnvironment();
|
||||
await initialize();
|
||||
assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue');
|
||||
});
|
||||
|
||||
test('remote settings override globals after remote provider is registered on activation', async () => {
|
||||
fs.writeFileSync(remoteSettingsFile, '{ "configurationService.remote.machineSetting": "remoteValue" }');
|
||||
resolveRemoteEnvironment();
|
||||
registerRemoteFileSystemProviderOnActivation();
|
||||
await initialize();
|
||||
assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue');
|
||||
});
|
||||
|
||||
test('remote settings override globals after remote environment is resolved', async () => {
|
||||
fs.writeFileSync(remoteSettingsFile, '{ "configurationService.remote.machineSetting": "remoteValue" }');
|
||||
registerRemoteFileSystemProvider();
|
||||
await initialize();
|
||||
const promise = new Promise((c, e) => {
|
||||
testObject.onDidChangeConfiguration(event => {
|
||||
try {
|
||||
assert.equal(event.source, ConfigurationTarget.USER);
|
||||
assert.deepEqual(event.affectedKeys, ['configurationService.remote.machineSetting']);
|
||||
assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue');
|
||||
c();
|
||||
} catch (error) {
|
||||
e(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
resolveRemoteEnvironment();
|
||||
return promise;
|
||||
});
|
||||
|
||||
test('remote settings override globals after remote provider is registered on activation and remote environment is resolved', async () => {
|
||||
fs.writeFileSync(remoteSettingsFile, '{ "configurationService.remote.machineSetting": "remoteValue" }');
|
||||
registerRemoteFileSystemProviderOnActivation();
|
||||
await initialize();
|
||||
const promise = new Promise((c, e) => {
|
||||
testObject.onDidChangeConfiguration(event => {
|
||||
try {
|
||||
assert.equal(event.source, ConfigurationTarget.USER);
|
||||
assert.deepEqual(event.affectedKeys, ['configurationService.remote.machineSetting']);
|
||||
assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue');
|
||||
c();
|
||||
} catch (error) {
|
||||
e(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
resolveRemoteEnvironment();
|
||||
return promise;
|
||||
});
|
||||
|
||||
// test('update remote settings', async () => {
|
||||
// registerRemoteFileSystemProvider();
|
||||
// resolveRemoteEnvironment();
|
||||
// await initialize();
|
||||
// assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'isSet');
|
||||
// const promise = new Promise((c, e) => {
|
||||
// testObject.onDidChangeConfiguration(event => {
|
||||
// try {
|
||||
// assert.equal(event.source, ConfigurationTarget.USER);
|
||||
// assert.deepEqual(event.affectedKeys, ['configurationService.remote.machineSetting']);
|
||||
// assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue');
|
||||
// c();
|
||||
// } catch (error) {
|
||||
// e(error);
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// fs.writeFileSync(remoteSettingsFile, '{ "configurationService.remote.machineSetting": "remoteValue" }');
|
||||
// return promise;
|
||||
// });
|
||||
|
||||
});
|
||||
|
||||
function getWorkspaceId(configPath: URI): string {
|
||||
|
||||
@@ -279,9 +279,8 @@ export class RemoteFileDialog {
|
||||
if (!equalsIgnoreCase(value, this.constructFullUserPath()) && !this.isBadSubpath(value)) {
|
||||
this.filePickBox.validationMessage = undefined;
|
||||
const filePickBoxUri = this.filePickBoxValue();
|
||||
const valueUri = resources.removeTrailingPathSeparator(filePickBoxUri);
|
||||
let updated: UpdateResult = UpdateResult.NotUpdated;
|
||||
if (!resources.isEqual(resources.removeTrailingPathSeparator(this.currentFolder), valueUri, true)) {
|
||||
if (!resources.isEqual(this.currentFolder, filePickBoxUri, true)) {
|
||||
updated = await this.tryUpdateItems(value, filePickBoxUri);
|
||||
}
|
||||
if (updated === UpdateResult.NotUpdated) {
|
||||
@@ -660,7 +659,8 @@ export class RemoteFileDialog {
|
||||
if (force && trailing) {
|
||||
// Keep the cursor position in front of the save as name.
|
||||
this.filePickBox.valueSelection = [this.filePickBox.value.length - trailing.length, this.filePickBox.value.length - trailing.length];
|
||||
} else {
|
||||
} else if (!trailing) {
|
||||
// If there is trailing, we don't move the cursor. If there is no trailing, cursor goes at the end.
|
||||
this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length];
|
||||
}
|
||||
this.filePickBox.busy = false;
|
||||
|
||||
@@ -16,6 +16,12 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { EnablementState, IExtensionEnablementService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { BetterMergeId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IInitDataProvider, RemoteExtensionHostClient } from 'vs/workbench/services/extensions/electron-browser/remoteExtensionHostClient';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { IRemoteAuthorityResolverService, ResolvedAuthority, RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil';
|
||||
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import pkg from 'vs/platform/product/node/package';
|
||||
@@ -35,6 +41,7 @@ import { Schemas } from 'vs/base/common/network';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
|
||||
import { PersistenConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
|
||||
const hasOwnProperty = Object.hasOwnProperty;
|
||||
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
|
||||
@@ -64,6 +71,8 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
|
||||
public _serviceBrand: any;
|
||||
|
||||
private _remoteExtensionsEnvironmentData: Map<string, IRemoteAgentEnvironment>;
|
||||
|
||||
private readonly _extensionHostLogsLocation: URI;
|
||||
private readonly _registry: ExtensionDescriptionRegistry;
|
||||
private readonly _installedExtensionsReady: Barrier;
|
||||
@@ -102,6 +111,9 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
@IExtensionEnablementService private readonly _extensionEnablementService: IExtensionEnablementService,
|
||||
@IExtensionManagementService private readonly _extensionManagementService: IExtensionManagementService,
|
||||
@IWindowService private readonly _windowService: IWindowService,
|
||||
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
|
||||
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
|
||||
@IFileService fileService: IFileService
|
||||
) {
|
||||
@@ -112,7 +124,9 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
e.join(this.activateByEvent(`onFileSystem:${e.scheme}`));
|
||||
}));
|
||||
|
||||
this._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${this._windowService.windowId}`));
|
||||
this._remoteExtensionsEnvironmentData = new Map<string, IRemoteAgentEnvironment>();
|
||||
|
||||
this._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${_windowService.windowId}`));
|
||||
this._registry = new ExtensionDescriptionRegistry([]);
|
||||
this._installedExtensionsReady = new Barrier();
|
||||
this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment;
|
||||
@@ -425,6 +439,17 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
}
|
||||
}
|
||||
|
||||
private _createProvider(remoteAuthority: string): IInitDataProvider {
|
||||
return {
|
||||
remoteAuthority: remoteAuthority,
|
||||
getInitData: () => {
|
||||
return this._installedExtensionsReady.wait().then(() => {
|
||||
return this._remoteExtensionsEnvironmentData.get(remoteAuthority)!;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private _startExtensionHostProcess(isInitialStart: boolean, initialActivationEvents: string[]): void {
|
||||
this._stopExtensionHostProcess();
|
||||
|
||||
@@ -436,52 +461,63 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
} else {
|
||||
// restart case
|
||||
autoStart = true;
|
||||
extensions = this.getExtensions();
|
||||
extensions = this.getExtensions().then((extensions) => extensions.filter(ext => ext.extensionLocation.scheme === Schemas.file));
|
||||
}
|
||||
|
||||
const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, autoStart, extensions, this._extensionHostLogsLocation);
|
||||
const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, extHostProcessWorker, null, initialActivationEvents);
|
||||
extHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal));
|
||||
extHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal, true));
|
||||
extHostProcessManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ isResponsive: responsiveState === ResponsiveState.Responsive }); });
|
||||
this._extensionHostProcessManagers.push(extHostProcessManager);
|
||||
|
||||
const remoteAgentConnection = this._remoteAgentService.getConnection();
|
||||
if (remoteAgentConnection) {
|
||||
const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, this.getExtensions(), this._createProvider(remoteAgentConnection.remoteAuthority));
|
||||
const remoteExtHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, remoteExtHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents);
|
||||
remoteExtHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal, false));
|
||||
remoteExtHostProcessManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ isResponsive: responsiveState === ResponsiveState.Responsive }); });
|
||||
this._extensionHostProcessManagers.push(remoteExtHostProcessManager);
|
||||
}
|
||||
}
|
||||
|
||||
private _onExtensionHostCrashed(code: number, signal: string | null): void {
|
||||
private _onExtensionHostCrashed(code: number, signal: string | null, showNotification: boolean): void {
|
||||
console.error('Extension host terminated unexpectedly. Code: ', code, ' Signal: ', signal);
|
||||
this._stopExtensionHostProcess();
|
||||
|
||||
if (code === 55) {
|
||||
this._notificationService.prompt(
|
||||
Severity.Error,
|
||||
nls.localize('extensionService.versionMismatchCrash', "Extension host cannot start: version mismatch."),
|
||||
if (showNotification) {
|
||||
if (code === 55) {
|
||||
this._notificationService.prompt(
|
||||
Severity.Error,
|
||||
nls.localize('extensionService.versionMismatchCrash', "Extension host cannot start: version mismatch."),
|
||||
[{
|
||||
label: nls.localize('relaunch', "Relaunch VS Code"),
|
||||
run: () => {
|
||||
this._instantiationService.invokeFunction((accessor) => {
|
||||
const windowsService = accessor.get(IWindowsService);
|
||||
windowsService.relaunch({});
|
||||
});
|
||||
}
|
||||
}]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let message = nls.localize('extensionService.crash', "Extension host terminated unexpectedly.");
|
||||
if (code === 87) {
|
||||
message = nls.localize('extensionService.unresponsiveCrash', "Extension host terminated because it was not responsive.");
|
||||
}
|
||||
|
||||
this._notificationService.prompt(Severity.Error, message,
|
||||
[{
|
||||
label: nls.localize('relaunch', "Relaunch VS Code"),
|
||||
run: () => {
|
||||
this._instantiationService.invokeFunction((accessor) => {
|
||||
const windowsService = accessor.get(IWindowsService);
|
||||
windowsService.relaunch({});
|
||||
});
|
||||
}
|
||||
label: nls.localize('devTools', "Open Developer Tools"),
|
||||
run: () => this._windowService.openDevTools()
|
||||
},
|
||||
{
|
||||
label: nls.localize('restart', "Restart Extension Host"),
|
||||
run: () => this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents))
|
||||
}]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let message = nls.localize('extensionService.crash', "Extension host terminated unexpectedly.");
|
||||
if (code === 87) {
|
||||
message = nls.localize('extensionService.unresponsiveCrash', "Extension host terminated because it was not responsive.");
|
||||
}
|
||||
|
||||
this._notificationService.prompt(Severity.Error, message,
|
||||
[{
|
||||
label: nls.localize('devTools', "Open Developer Tools"),
|
||||
run: () => this._windowService.openDevTools()
|
||||
},
|
||||
{
|
||||
label: nls.localize('restart', "Restart Extension Host"),
|
||||
run: () => this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents))
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
// ---- begin IExtensionService
|
||||
@@ -590,12 +626,112 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
});
|
||||
}
|
||||
|
||||
private async _resolveAuthorityAgain(): Promise<void> {
|
||||
const remoteAuthority = this._environmentService.configuration.remoteAuthority;
|
||||
if (!remoteAuthority) {
|
||||
return;
|
||||
}
|
||||
|
||||
const extensionHost = this._extensionHostProcessManagers[0];
|
||||
this._remoteAuthorityResolverService.clearResolvedAuthority(remoteAuthority);
|
||||
try {
|
||||
const resolvedAuthority = await extensionHost.resolveAuthority(remoteAuthority);
|
||||
this._remoteAuthorityResolverService.setResolvedAuthority(resolvedAuthority);
|
||||
} catch (err) {
|
||||
this._remoteAuthorityResolverService.setResolvedAuthorityError(remoteAuthority, err);
|
||||
}
|
||||
}
|
||||
|
||||
private async _scanAndHandleExtensions(): Promise<void> {
|
||||
this._extensionScanner.startScanningExtensions(this.createLogger());
|
||||
|
||||
const remoteAuthority = this._environmentService.configuration.remoteAuthority;
|
||||
const extensionHost = this._extensionHostProcessManagers[0];
|
||||
const extensions = await this._extensionScanner.scannedExtensions;
|
||||
const enabledExtensions = await this._getRuntimeExtensions(extensions);
|
||||
|
||||
let localExtensions = await this._extensionScanner.scannedExtensions;
|
||||
|
||||
if (remoteAuthority) {
|
||||
let resolvedAuthority: ResolvedAuthority;
|
||||
|
||||
try {
|
||||
resolvedAuthority = await extensionHost.resolveAuthority(remoteAuthority);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
const plusIndex = remoteAuthority.indexOf('+');
|
||||
const authorityFriendlyName = plusIndex > 0 ? remoteAuthority.substr(0, plusIndex) : remoteAuthority;
|
||||
if (!RemoteAuthorityResolverError.isHandledNotAvailable(err)) {
|
||||
this._notificationService.notify({ severity: Severity.Error, message: nls.localize('resolveAuthorityFailure', "Resolving the authority `{0}` failed", authorityFriendlyName) });
|
||||
} else {
|
||||
console.log(`Not showing a notification for the error`);
|
||||
}
|
||||
|
||||
this._remoteAuthorityResolverService.setResolvedAuthorityError(remoteAuthority, err);
|
||||
|
||||
// Proceed with the local extension host
|
||||
await this._startLocalExtensionHost(extensionHost, localExtensions);
|
||||
return;
|
||||
}
|
||||
|
||||
// set the resolved authority
|
||||
this._remoteAuthorityResolverService.setResolvedAuthority(resolvedAuthority);
|
||||
|
||||
// monitor for breakage
|
||||
const connection = this._remoteAgentService.getConnection();
|
||||
if (connection) {
|
||||
connection.onDidStateChange(async (e) => {
|
||||
const remoteAuthority = this._environmentService.configuration.remoteAuthority;
|
||||
if (!remoteAuthority) {
|
||||
return;
|
||||
}
|
||||
if (e.type === PersistenConnectionEventType.ConnectionLost) {
|
||||
this._remoteAuthorityResolverService.clearResolvedAuthority(remoteAuthority);
|
||||
}
|
||||
});
|
||||
connection.onReconnecting(() => this._resolveAuthorityAgain());
|
||||
}
|
||||
|
||||
// fetch the remote environment
|
||||
const remoteEnv = (await this._remoteAgentService.getEnvironment())!;
|
||||
|
||||
// revive URIs
|
||||
remoteEnv.extensions.forEach((extension) => {
|
||||
(<any>extension).extensionLocation = URI.revive(extension.extensionLocation);
|
||||
});
|
||||
|
||||
// remove UI extensions from the remote extensions
|
||||
remoteEnv.extensions = remoteEnv.extensions.filter(extension => !isUIExtension(extension, this._configurationService));
|
||||
|
||||
// remove non-UI extensions from the local extensions
|
||||
localExtensions = localExtensions.filter(extension => extension.isBuiltin || isUIExtension(extension, this._configurationService));
|
||||
|
||||
// in case of overlap, the remote wins
|
||||
const isRemoteExtension = new Set<string>();
|
||||
remoteEnv.extensions.forEach(extension => isRemoteExtension.add(ExtensionIdentifier.toKey(extension.identifier)));
|
||||
localExtensions = localExtensions.filter(extension => !isRemoteExtension.has(ExtensionIdentifier.toKey(extension.identifier)));
|
||||
|
||||
// compute enabled extensions
|
||||
const enabledExtensions = await this._getRuntimeExtensions((<IExtensionDescription[]>[]).concat(remoteEnv.extensions).concat(localExtensions));
|
||||
|
||||
// remove disabled extensions
|
||||
const isEnabled = new Set<string>();
|
||||
enabledExtensions.forEach(extension => isEnabled.add(ExtensionIdentifier.toKey(extension.identifier)));
|
||||
remoteEnv.extensions = remoteEnv.extensions.filter(extension => isEnabled.has(ExtensionIdentifier.toKey(extension.identifier)));
|
||||
localExtensions = localExtensions.filter(extension => isEnabled.has(ExtensionIdentifier.toKey(extension.identifier)));
|
||||
|
||||
// save for remote extension's init data
|
||||
this._remoteExtensionsEnvironmentData.set(remoteAuthority, remoteEnv);
|
||||
|
||||
this._handleExtensionPoints(enabledExtensions);
|
||||
extensionHost.start(localExtensions.map(extension => extension.identifier));
|
||||
this._releaseBarrier();
|
||||
|
||||
} else {
|
||||
await this._startLocalExtensionHost(extensionHost, localExtensions);
|
||||
}
|
||||
}
|
||||
|
||||
private async _startLocalExtensionHost(extensionHost: ExtensionHostProcessManager, localExtensions: IExtensionDescription[]): Promise<void> {
|
||||
const enabledExtensions = await this._getRuntimeExtensions(localExtensions);
|
||||
|
||||
this._handleExtensionPoints(enabledExtensions);
|
||||
extensionHost.start(enabledExtensions.map(extension => extension.identifier).filter(id => this._registry.containsExtension(id)));
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ipcRenderer as ipc } from 'electron';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import product from 'vs/platform/product/node/product';
|
||||
import pkg from 'vs/platform/product/node/package';
|
||||
import { connectRemoteAgentExtensionHost, IRemoteExtensionHostStartParams, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IInitData } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
|
||||
import { IExtensionHostStarter } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
|
||||
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
|
||||
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFactory';
|
||||
import { IExtensionHostDebugService } from 'vs/workbench/services/extensions/common/extensionHostDebug';
|
||||
|
||||
export interface IInitDataProvider {
|
||||
readonly remoteAuthority: string;
|
||||
getInitData(): Promise<IRemoteAgentEnvironment>;
|
||||
}
|
||||
|
||||
export class RemoteExtensionHostClient extends Disposable implements IExtensionHostStarter {
|
||||
|
||||
private _onCrashed: Emitter<[number, string | null]> = this._register(new Emitter<[number, string | null]>());
|
||||
public readonly onCrashed: Event<[number, string | null]> = this._onCrashed.event;
|
||||
|
||||
private _protocol: PersistentProtocol | null;
|
||||
|
||||
private readonly _isExtensionDevHost: boolean;
|
||||
private readonly _isExtensionDevTestFromCli: boolean;
|
||||
|
||||
private _terminating: boolean;
|
||||
|
||||
constructor(
|
||||
private readonly _allExtensions: Promise<IExtensionDescription[]>,
|
||||
private readonly _initDataProvider: IInitDataProvider,
|
||||
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
|
||||
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
@IWindowService private readonly _windowService: IWindowService,
|
||||
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@ILabelService private readonly _labelService: ILabelService,
|
||||
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
@IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService
|
||||
) {
|
||||
super();
|
||||
this._protocol = null;
|
||||
this._terminating = false;
|
||||
|
||||
this._register(this._lifecycleService.onShutdown(reason => this.dispose()));
|
||||
|
||||
const devOpts = parseExtensionDevOptions(this._environmentService);
|
||||
this._isExtensionDevHost = devOpts.isExtensionDevHost;
|
||||
this._isExtensionDevTestFromCli = devOpts.isExtensionDevTestFromCli;
|
||||
}
|
||||
|
||||
public start(): Promise<IMessagePassingProtocol> {
|
||||
const options: IConnectionOptions = {
|
||||
isBuilt: this._environmentService.isBuilt,
|
||||
commit: product.commit,
|
||||
webSocketFactory: nodeWebSocketFactory,
|
||||
addressProvider: {
|
||||
getAddress: async () => {
|
||||
const { host, port } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority);
|
||||
return { host, port };
|
||||
}
|
||||
}
|
||||
};
|
||||
return this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority).then((resolvedAuthority) => {
|
||||
|
||||
const startParams: IRemoteExtensionHostStartParams = {
|
||||
language: platform.language,
|
||||
debugId: this._environmentService.debugExtensionHost.debugId,
|
||||
break: this._environmentService.debugExtensionHost.break,
|
||||
port: this._environmentService.debugExtensionHost.port,
|
||||
};
|
||||
|
||||
const extDevLocs = this._environmentService.extensionDevelopmentLocationURI;
|
||||
|
||||
let debugOk = true;
|
||||
if (extDevLocs && extDevLocs.length > 0) {
|
||||
// TODO@AW: handles only first path in array
|
||||
if (extDevLocs[0].scheme === Schemas.file) {
|
||||
debugOk = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!debugOk) {
|
||||
startParams.break = false;
|
||||
}
|
||||
|
||||
return connectRemoteAgentExtensionHost(options, startParams).then(result => {
|
||||
let { protocol, debugPort } = result;
|
||||
const isExtensionDevelopmentDebug = typeof debugPort === 'number';
|
||||
if (debugOk && this._environmentService.isExtensionDevelopment && this._environmentService.debugExtensionHost.debugId && debugPort) {
|
||||
this._extensionHostDebugService.attachSession(this._environmentService.debugExtensionHost.debugId, debugPort, this._initDataProvider.remoteAuthority);
|
||||
}
|
||||
|
||||
protocol.onClose(() => {
|
||||
this._onExtHostConnectionLost();
|
||||
});
|
||||
|
||||
protocol.onSocketClose(() => {
|
||||
if (this._isExtensionDevHost) {
|
||||
this._onExtHostConnectionLost();
|
||||
}
|
||||
});
|
||||
|
||||
// 1) wait for the incoming `ready` event and send the initialization data.
|
||||
// 2) wait for the incoming `initialized` event.
|
||||
return new Promise<IMessagePassingProtocol>((resolve, reject) => {
|
||||
|
||||
let handle = setTimeout(() => {
|
||||
reject('timeout');
|
||||
}, 60 * 1000);
|
||||
|
||||
const disposable = protocol.onMessage(msg => {
|
||||
|
||||
if (isMessageOfType(msg, MessageType.Ready)) {
|
||||
// 1) Extension Host is ready to receive messages, initialize it
|
||||
this._createExtHostInitData(isExtensionDevelopmentDebug).then(data => protocol.send(VSBuffer.fromString(JSON.stringify(data))));
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMessageOfType(msg, MessageType.Initialized)) {
|
||||
// 2) Extension Host is initialized
|
||||
|
||||
clearTimeout(handle);
|
||||
|
||||
// stop listening for messages here
|
||||
disposable.dispose();
|
||||
|
||||
// release this promise
|
||||
this._protocol = protocol;
|
||||
resolve(protocol);
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(`received unexpected message during handshake phase from the extension host: `, msg);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _onExtHostConnectionLost(): void {
|
||||
if (this._terminating) {
|
||||
// Expected termination path (we asked the process to terminate)
|
||||
return;
|
||||
}
|
||||
|
||||
// Unexpected termination
|
||||
if (!this._isExtensionDevHost) {
|
||||
this._onCrashed.fire([0, null]);
|
||||
}
|
||||
|
||||
// Expected development extension termination: When the extension host goes down we also shutdown the window
|
||||
else if (!this._isExtensionDevTestFromCli) {
|
||||
this._windowService.closeWindow();
|
||||
}
|
||||
|
||||
// When CLI testing make sure to exit with proper exit code
|
||||
else {
|
||||
ipc.send('vscode:exit', 0);
|
||||
}
|
||||
}
|
||||
|
||||
private _createExtHostInitData(isExtensionDevelopmentDebug: boolean): Promise<IInitData> {
|
||||
return Promise.all([this._allExtensions, this._telemetryService.getTelemetryInfo(), this._initDataProvider.getInitData()]).then(([allExtensions, telemetryInfo, remoteExtensionHostData]) => {
|
||||
// Collect all identifiers for extension ids which can be considered "resolved"
|
||||
const resolvedExtensions = allExtensions.filter(extension => !extension.main).map(extension => extension.identifier);
|
||||
const hostExtensions = allExtensions.filter(extension => extension.main && extension.api === 'none').map(extension => extension.identifier);
|
||||
const workspace = this._contextService.getWorkspace();
|
||||
const r: IInitData = {
|
||||
commit: product.commit,
|
||||
version: pkg.version,
|
||||
parentPid: remoteExtensionHostData.pid,
|
||||
environment: {
|
||||
isExtensionDevelopmentDebug,
|
||||
appRoot: remoteExtensionHostData.appRoot,
|
||||
appSettingsHome: remoteExtensionHostData.appSettingsHome,
|
||||
appName: product.nameLong,
|
||||
appUriScheme: product.urlProtocol,
|
||||
appLanguage: platform.language,
|
||||
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
|
||||
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
|
||||
globalStorageHome: remoteExtensionHostData.globalStorageHome,
|
||||
userHome: remoteExtensionHostData.userHome
|
||||
},
|
||||
workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : {
|
||||
configuration: workspace.configuration,
|
||||
id: workspace.id,
|
||||
name: this._labelService.getWorkspaceLabel(workspace)
|
||||
},
|
||||
resolvedExtensions: resolvedExtensions,
|
||||
hostExtensions: hostExtensions,
|
||||
extensions: remoteExtensionHostData.extensions,
|
||||
telemetryInfo,
|
||||
logLevel: this._logService.getLevel(),
|
||||
logsLocation: remoteExtensionHostData.extensionHostLogsPath,
|
||||
autoStart: true,
|
||||
remoteAuthority: this._initDataProvider.remoteAuthority,
|
||||
};
|
||||
return r;
|
||||
});
|
||||
}
|
||||
|
||||
getInspectPort(): number | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this._terminating = true;
|
||||
|
||||
if (this._protocol) {
|
||||
// Send the extension host a request to terminate itself
|
||||
// (graceful termination)
|
||||
const socket = this._protocol.getSocket();
|
||||
this._protocol.send(createMessageOfType(MessageType.Terminate));
|
||||
this._protocol.sendDisconnect();
|
||||
this._protocol.dispose();
|
||||
socket.end();
|
||||
this._protocol = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import { IInitData, MainThreadConsoleShape } from 'vs/workbench/api/common/extHo
|
||||
import { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessage, IExtHostReadyMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
|
||||
import { ExtensionHostMain, IExitFn, ILogServiceFn } from 'vs/workbench/services/extensions/node/extensionHostMain';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { createBufferSpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ISchemeTransformer } from 'vs/workbench/api/common/extHostLanguageFeatures';
|
||||
import { IURITransformer } from 'vs/base/common/uriIpc';
|
||||
@@ -72,7 +72,7 @@ function patchPatchedConsole(mainThreadConsole: MainThreadConsoleShape): void {
|
||||
};
|
||||
}
|
||||
|
||||
const createLogService: ILogServiceFn = initData => createSpdLogService(ExtensionHostLogFileName, initData.logLevel, initData.logsLocation.fsPath);
|
||||
const createLogService: ILogServiceFn = initData => createBufferSpdLogService(ExtensionHostLogFileName, initData.logLevel, initData.logsLocation.fsPath);
|
||||
|
||||
interface IRendererConnection {
|
||||
protocol: IMessagePassingProtocol;
|
||||
|
||||
@@ -18,6 +18,8 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens
|
||||
import { localize } from 'vs/nls';
|
||||
import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { values } from 'vs/base/common/map';
|
||||
|
||||
export class MultiExtensionManagementService extends Disposable implements IExtensionManagementService {
|
||||
|
||||
@@ -145,7 +147,7 @@ export class MultiExtensionManagementService extends Disposable implements IExte
|
||||
// Install only on remote server
|
||||
const promise = this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.install(vsix);
|
||||
// Install UI Dependencies on local server
|
||||
await this.installUIDependencies(manifest);
|
||||
await this.installUIDependenciesAndPackedExtensions(manifest);
|
||||
return promise;
|
||||
}
|
||||
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.install(vsix);
|
||||
@@ -165,8 +167,8 @@ export class MultiExtensionManagementService extends Disposable implements IExte
|
||||
}
|
||||
// Install only on remote server
|
||||
const promise = this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.installFromGallery(gallery);
|
||||
// Install UI Dependencies on local server
|
||||
await this.installUIDependencies(manifest);
|
||||
// Install UI dependencies and packed extensions on local server
|
||||
await this.installUIDependenciesAndPackedExtensions(manifest);
|
||||
return promise;
|
||||
} else {
|
||||
return Promise.reject(localize('Manifest is not found', "Installing Extension {0} failed: Manifest is not found.", gallery.displayName || gallery.name));
|
||||
@@ -175,18 +177,11 @@ export class MultiExtensionManagementService extends Disposable implements IExte
|
||||
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(gallery);
|
||||
}
|
||||
|
||||
private async installUIDependencies(manifest: IExtensionManifest): Promise<void> {
|
||||
if (manifest.extensionDependencies && manifest.extensionDependencies.length) {
|
||||
const dependencies = await this.extensionGalleryService.loadAllDependencies(manifest.extensionDependencies.map(id => ({ id })), CancellationToken.None);
|
||||
if (dependencies.length) {
|
||||
await Promise.all(dependencies.map(async d => {
|
||||
const manifest = await this.extensionGalleryService.getManifest(d, CancellationToken.None);
|
||||
if (manifest && isUIExtension(manifest, this.configurationService)) {
|
||||
await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(d);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
private async installUIDependenciesAndPackedExtensions(manifest: IExtensionManifest): Promise<void> {
|
||||
const uiExtensions = await this.getAllUIDependenciesAndPackedExtensions(manifest, CancellationToken.None);
|
||||
const installed = await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getInstalled();
|
||||
const toInstall = uiExtensions.filter(e => installed.every(i => !areSameExtensions(i.identifier, e.identifier)));
|
||||
await Promise.all(toInstall.map(d => this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(d)));
|
||||
}
|
||||
|
||||
getExtensionsReport(): Promise<IReportedExtension[]> {
|
||||
@@ -196,6 +191,49 @@ export class MultiExtensionManagementService extends Disposable implements IExte
|
||||
private getServer(extension: ILocalExtension): IExtensionManagementServer | null {
|
||||
return this.extensionManagementServerService.getExtensionManagementServer(extension.location);
|
||||
}
|
||||
|
||||
private async getAllUIDependenciesAndPackedExtensions(manifest: IExtensionManifest, token: CancellationToken): Promise<IGalleryExtension[]> {
|
||||
const result = new Map<string, IGalleryExtension>();
|
||||
const extensions = [...(manifest.extensionPack || []), ...(manifest.extensionDependencies || [])];
|
||||
await this.getAllUIDependenciesAndPackedExtensionsRecursively(extensions, result, token);
|
||||
return values(result);
|
||||
}
|
||||
|
||||
private async getAllUIDependenciesAndPackedExtensionsRecursively(toGet: string[], result: Map<string, IGalleryExtension>, token: CancellationToken): Promise<void> {
|
||||
if (toGet.length === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const extensions = (await this.extensionGalleryService.query({ names: toGet, pageSize: toGet.length }, token)).firstPage;
|
||||
const manifests = await Promise.all(extensions.map(e => this.extensionGalleryService.getManifest(e, token)));
|
||||
const uiExtensionsManifests: IExtensionManifest[] = [];
|
||||
for (let idx = 0; idx < extensions.length; idx++) {
|
||||
const extension = extensions[idx];
|
||||
const manifest = manifests[idx];
|
||||
if (manifest && isUIExtension(manifest, this.configurationService)) {
|
||||
result.set(extension.identifier.id.toLowerCase(), extension);
|
||||
uiExtensionsManifests.push(manifest);
|
||||
}
|
||||
}
|
||||
toGet = [];
|
||||
for (const uiExtensionManifest of uiExtensionsManifests) {
|
||||
if (isNonEmptyArray(uiExtensionManifest.extensionDependencies)) {
|
||||
for (const id of uiExtensionManifest.extensionDependencies) {
|
||||
if (!result.has(id.toLowerCase())) {
|
||||
toGet.push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isNonEmptyArray(uiExtensionManifest.extensionPack)) {
|
||||
for (const id of uiExtensionManifest.extensionPack) {
|
||||
if (!result.has(id.toLowerCase())) {
|
||||
toGet.push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.getAllUIDependenciesAndPackedExtensionsRecursively(toGet, result, token);
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IExtensionManagementService, MultiExtensionManagementService);
|
||||
|
||||
@@ -6,20 +6,25 @@
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IRemoteAgentConnection } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { AbstractRemoteAgentService } from 'vs/workbench/services/remote/common/abstractRemoteAgentService';
|
||||
import { AbstractRemoteAgentService, RemoteAgentConnection } from 'vs/workbench/services/remote/common/abstractRemoteAgentService';
|
||||
import { IProductService } from 'vs/platform/product/common/product';
|
||||
import { browserWebSocketFactory } from 'vs/platform/remote/browser/browserWebSocketFactory';
|
||||
|
||||
export class RemoteAgentService extends AbstractRemoteAgentService {
|
||||
|
||||
private readonly _connection: IRemoteAgentConnection | null = null;
|
||||
|
||||
constructor(
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IProductService productService: IProductService,
|
||||
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService
|
||||
) {
|
||||
super(environmentService);
|
||||
const authority = document.location.host;
|
||||
this._connection = this._register(new RemoteAgentConnection(authority, productService.commit, browserWebSocketFactory, environmentService, remoteAuthorityResolverService));
|
||||
}
|
||||
|
||||
getConnection(): IRemoteAgentConnection | null {
|
||||
return null;
|
||||
return this._connection;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,111 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as net from 'net';
|
||||
import { Barrier } from 'vs/base/common/async';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import product from 'vs/platform/product/node/product';
|
||||
import { connectRemoteAgentTunnel, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
|
||||
import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFactory';
|
||||
|
||||
export async function createRemoteTunnel(options: IConnectionOptions, tunnelRemotePort: number): Promise<RemoteTunnel> {
|
||||
const tunnel = new NodeRemoteTunnel(options, tunnelRemotePort);
|
||||
return tunnel.waitForReady();
|
||||
}
|
||||
|
||||
class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
|
||||
|
||||
public readonly tunnelRemotePort: number;
|
||||
public readonly tunnelLocalPort: number;
|
||||
|
||||
private readonly _options: IConnectionOptions;
|
||||
private readonly _server: net.Server;
|
||||
private readonly _barrier: Barrier;
|
||||
|
||||
private readonly _listeningListener: () => void;
|
||||
private readonly _connectionListener: (socket: net.Socket) => void;
|
||||
|
||||
constructor(options: IConnectionOptions, tunnelRemotePort: number) {
|
||||
super();
|
||||
this._options = options;
|
||||
this._server = net.createServer();
|
||||
this._barrier = new Barrier();
|
||||
|
||||
this._listeningListener = () => this._barrier.open();
|
||||
this._server.on('listening', this._listeningListener);
|
||||
|
||||
this._connectionListener = (socket) => this._onConnection(socket);
|
||||
this._server.on('connection', this._connectionListener);
|
||||
|
||||
this.tunnelRemotePort = tunnelRemotePort;
|
||||
this.tunnelLocalPort = (<net.AddressInfo>this._server.listen(0).address()).port;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
this._server.removeListener('listening', this._listeningListener);
|
||||
this._server.removeListener('connection', this._connectionListener);
|
||||
this._server.close();
|
||||
}
|
||||
|
||||
public async waitForReady(): Promise<this> {
|
||||
await this._barrier.wait();
|
||||
return this;
|
||||
}
|
||||
|
||||
private async _onConnection(localSocket: net.Socket): Promise<void> {
|
||||
// pause reading on the socket until we have a chance to forward its data
|
||||
localSocket.pause();
|
||||
|
||||
const protocol = await connectRemoteAgentTunnel(this._options, this.tunnelRemotePort);
|
||||
const remoteSocket = (<NodeSocket>protocol.getSocket()).socket;
|
||||
const dataChunk = protocol.readEntireBuffer();
|
||||
protocol.dispose();
|
||||
|
||||
if (dataChunk.byteLength > 0) {
|
||||
localSocket.write(dataChunk.buffer);
|
||||
}
|
||||
|
||||
localSocket.on('end', () => remoteSocket.end());
|
||||
localSocket.on('close', () => remoteSocket.end());
|
||||
remoteSocket.on('end', () => localSocket.end());
|
||||
remoteSocket.on('close', () => localSocket.end());
|
||||
|
||||
localSocket.pipe(remoteSocket);
|
||||
remoteSocket.pipe(localSocket);
|
||||
}
|
||||
}
|
||||
|
||||
export class TunnelService implements ITunnelService {
|
||||
_serviceBrand: any;
|
||||
|
||||
public constructor(
|
||||
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
|
||||
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
) {
|
||||
}
|
||||
|
||||
openTunnel(remotePort: number): Promise<RemoteTunnel> | undefined {
|
||||
return undefined;
|
||||
const remoteAuthority = this.environmentService.configuration.remoteAuthority;
|
||||
if (!remoteAuthority) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const options: IConnectionOptions = {
|
||||
isBuilt: this.environmentService.isBuilt,
|
||||
commit: product.commit,
|
||||
webSocketFactory: nodeWebSocketFactory,
|
||||
addressProvider: {
|
||||
getAddress: async () => {
|
||||
const { host, port } = await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority);
|
||||
return { host, port };
|
||||
}
|
||||
}
|
||||
};
|
||||
return createRemoteTunnel(options, remotePort);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@ export class SearchService implements IRawSearchService {
|
||||
|
||||
// Pattern match on results
|
||||
const results: IRawFileMatch[] = [];
|
||||
const normalizedSearchValueLowercase = prepareQuery(searchValue).value;
|
||||
const normalizedSearchValueLowercase = prepareQuery(searchValue).lowercase;
|
||||
for (const entry of cachedEntries) {
|
||||
|
||||
// Check if this entry is a match for the search value
|
||||
|
||||
@@ -83,6 +83,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/
|
||||
import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService';
|
||||
import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer';
|
||||
import { BrowserTextFileService } from 'vs/workbench/services/textfile/browser/textFileService';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput {
|
||||
return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined);
|
||||
@@ -1598,3 +1599,32 @@ export class NullFileSystemProvider implements IFileSystemProvider {
|
||||
read?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> { return Promise.resolve(undefined!); }
|
||||
write?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> { return Promise.resolve(undefined!); }
|
||||
}
|
||||
|
||||
export class RemoteFileSystemProvider implements IFileSystemProvider {
|
||||
|
||||
constructor(private readonly diskFileSystemProvider: IFileSystemProvider, private readonly remoteAuthority: string) { }
|
||||
|
||||
readonly capabilities: FileSystemProviderCapabilities = this.diskFileSystemProvider.capabilities;
|
||||
readonly onDidChangeCapabilities: Event<void> = this.diskFileSystemProvider.onDidChangeCapabilities;
|
||||
|
||||
readonly onDidChangeFile: Event<IFileChange[]> = Event.map(this.diskFileSystemProvider.onDidChangeFile, changes => changes.map(c => { c.resource = c.resource.with({ scheme: Schemas.vscodeRemote, authority: this.remoteAuthority }); return c; }));
|
||||
watch(resource: URI, opts: IWatchOptions): IDisposable { return this.diskFileSystemProvider.watch(this.toFileResource(resource), opts); }
|
||||
|
||||
stat(resource: URI): Promise<IStat> { return this.diskFileSystemProvider.stat(this.toFileResource(resource)); }
|
||||
mkdir(resource: URI): Promise<void> { return this.diskFileSystemProvider.mkdir(this.toFileResource(resource)); }
|
||||
readdir(resource: URI): Promise<[string, FileType][]> { return this.diskFileSystemProvider.readdir(this.toFileResource(resource)); }
|
||||
delete(resource: URI, opts: FileDeleteOptions): Promise<void> { return this.diskFileSystemProvider.delete(this.toFileResource(resource), opts); }
|
||||
|
||||
rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> { return this.diskFileSystemProvider.rename(this.toFileResource(from), this.toFileResource(to), opts); }
|
||||
copy(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> { return this.diskFileSystemProvider.copy!(this.toFileResource(from), this.toFileResource(to), opts); }
|
||||
|
||||
readFile(resource: URI): Promise<Uint8Array> { return this.diskFileSystemProvider.readFile!(this.toFileResource(resource)); }
|
||||
writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> { return this.diskFileSystemProvider.writeFile!(this.toFileResource(resource), content, opts); }
|
||||
|
||||
open(resource: URI, opts: FileOpenOptions): Promise<number> { return this.diskFileSystemProvider.open!(this.toFileResource(resource), opts); }
|
||||
close(fd: number): Promise<void> { return this.diskFileSystemProvider.close!(fd); }
|
||||
read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> { return this.diskFileSystemProvider.read!(fd, pos, data, offset, length); }
|
||||
write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> { return this.diskFileSystemProvider.write!(fd, pos, data, offset, length); }
|
||||
|
||||
private toFileResource(resource: URI): URI { return resource.with({ scheme: Schemas.file, authority: '' }); }
|
||||
}
|
||||
|
||||
@@ -372,9 +372,11 @@ import 'vs/workbench/contrib/relauncher/electron-browser/relauncher.contribution
|
||||
// Tasks
|
||||
import 'vs/workbench/contrib/tasks/electron-browser/task.contribution';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
// Remote
|
||||
import 'vs/workbench/contrib/remote/electron-browser/remote.contribution';
|
||||
|
||||
// Emmet
|
||||
// import 'vs/workbench/contrib/emmet/browser/emmet.contribution';
|
||||
// import 'vs/workbench/contrib/emmet/browser/emmet.contribution'; {{SQL CARBON EDIT}} @anthonydresser comment our emmet
|
||||
|
||||
// CodeEditor Contributions
|
||||
import 'vs/workbench/contrib/codeEditor/browser/codeEditor.contribution';
|
||||
|
||||
Reference in New Issue
Block a user