mirror of
https://github.com/microsoft/vscode.git
synced 2026-06-06 07:35:52 +01:00
Merge branch 'main' into proposal-130231
This commit is contained in:
@@ -452,8 +452,12 @@ async function getTranslations() {
|
||||
}
|
||||
const version = await getLatestStableVersion(updateUrl);
|
||||
const languageIds = Object.keys(Languages);
|
||||
return await Promise.all(languageIds.map(languageId => getNLS(resourceUrlTemplate, languageId, version)
|
||||
const result = await Promise.allSettled(languageIds.map(languageId => getNLS(resourceUrlTemplate, languageId, version)
|
||||
.catch(err => { console.warn(`Missing translation: ${languageId}@${version}`); return Promise.reject(err); })
|
||||
.then(languageTranslations => ({ languageId, languageTranslations }))));
|
||||
return result
|
||||
.filter((r) => r.status === 'fulfilled')
|
||||
.map(r => r.value);
|
||||
}
|
||||
async function main() {
|
||||
const [policies, translations] = await Promise.all([parsePolicies(), getTranslations()]);
|
||||
|
||||
@@ -585,7 +585,8 @@ const Languages = {
|
||||
};
|
||||
|
||||
type LanguageTranslations = { [moduleName: string]: { [nlsKey: string]: string } };
|
||||
type Translations = { languageId: string; languageTranslations: LanguageTranslations }[];
|
||||
type Translation = { languageId: string; languageTranslations: LanguageTranslations };
|
||||
type Translations = Translation[];
|
||||
|
||||
async function getLatestStableVersion(updateUrl: string) {
|
||||
const res = await fetch(`${updateUrl}/api/update/darwin/stable/latest`);
|
||||
@@ -643,10 +644,15 @@ async function getTranslations(): Promise<Translations> {
|
||||
const version = await getLatestStableVersion(updateUrl);
|
||||
const languageIds = Object.keys(Languages);
|
||||
|
||||
return await Promise.all(languageIds.map(
|
||||
const result = await Promise.allSettled(languageIds.map(
|
||||
languageId => getNLS(resourceUrlTemplate, languageId, version)
|
||||
.catch(err => { console.warn(`Missing translation: ${languageId}@${version}`); return Promise.reject(err); })
|
||||
.then(languageTranslations => ({ languageId, languageTranslations }))
|
||||
));
|
||||
|
||||
return result
|
||||
.filter((r): r is PromiseFulfilledResult<Translation> => r.status === 'fulfilled')
|
||||
.map(r => r.value);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"engines": {
|
||||
"vscode": "^1.20.0"
|
||||
},
|
||||
"main": "./out/extension.node",
|
||||
"main": "./out/extension",
|
||||
"browser": "./dist/browser/extension",
|
||||
"categories": [
|
||||
"Programming Languages"
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "code-oss-dev",
|
||||
"version": "1.70.0",
|
||||
"distro": "954078b4ad7e8d2b00615cfda5d89e5de196f696",
|
||||
"distro": "73c5eeb6818a9483d7a4bc2b9328223485a59de6",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
},
|
||||
@@ -85,12 +85,12 @@
|
||||
"vscode-proxy-agent": "^0.12.0",
|
||||
"vscode-regexpp": "^3.1.0",
|
||||
"vscode-textmate": "7.0.1",
|
||||
"xterm": "4.20.0-beta.5",
|
||||
"xterm": "4.20.0-beta.6",
|
||||
"xterm-addon-search": "0.10.0-beta.1",
|
||||
"xterm-addon-serialize": "0.8.0-beta.1",
|
||||
"xterm-addon-unicode11": "0.4.0-beta.3",
|
||||
"xterm-addon-webgl": "0.13.0-beta.2",
|
||||
"xterm-headless": "4.20.0-beta.5",
|
||||
"xterm-addon-webgl": "0.13.0-beta.3",
|
||||
"xterm-headless": "4.20.0-beta.6",
|
||||
"yauzl": "^2.9.2",
|
||||
"yazl": "^2.4.3"
|
||||
},
|
||||
|
||||
+3
-3
@@ -24,12 +24,12 @@
|
||||
"vscode-proxy-agent": "^0.12.0",
|
||||
"vscode-regexpp": "^3.1.0",
|
||||
"vscode-textmate": "7.0.1",
|
||||
"xterm": "4.20.0-beta.5",
|
||||
"xterm": "4.20.0-beta.6",
|
||||
"xterm-addon-search": "0.10.0-beta.1",
|
||||
"xterm-addon-serialize": "0.8.0-beta.1",
|
||||
"xterm-addon-unicode11": "0.4.0-beta.3",
|
||||
"xterm-addon-webgl": "0.13.0-beta.2",
|
||||
"xterm-headless": "4.20.0-beta.5",
|
||||
"xterm-addon-webgl": "0.13.0-beta.3",
|
||||
"xterm-headless": "4.20.0-beta.6",
|
||||
"yauzl": "^2.9.2",
|
||||
"yazl": "^2.4.3"
|
||||
},
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
"tas-client-umd": "0.1.6",
|
||||
"vscode-oniguruma": "1.6.1",
|
||||
"vscode-textmate": "7.0.1",
|
||||
"xterm": "4.20.0-beta.5",
|
||||
"xterm": "4.20.0-beta.6",
|
||||
"xterm-addon-search": "0.10.0-beta.1",
|
||||
"xterm-addon-unicode11": "0.4.0-beta.3",
|
||||
"xterm-addon-webgl": "0.13.0-beta.2"
|
||||
"xterm-addon-webgl": "0.13.0-beta.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,12 +78,12 @@ xterm-addon-unicode11@0.4.0-beta.3:
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa"
|
||||
integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q==
|
||||
|
||||
xterm-addon-webgl@0.13.0-beta.2:
|
||||
version "0.13.0-beta.2"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.2.tgz#f58a7a3641ad7c8ac82dd24cfb0165656ed9ac1c"
|
||||
integrity sha512-98tX0BkpD402RoCO6SyikUXpzCn9/OQhlXsRmM/kRFCxMWWofStWTXzCPhN0MjIx2IdGueDjCmnShhidwihErg==
|
||||
xterm-addon-webgl@0.13.0-beta.3:
|
||||
version "0.13.0-beta.3"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.3.tgz#2b456c3105238e64b40a30787d6335f5f6f85abb"
|
||||
integrity sha512-DFGcXAolA0VTsOLIKcORxUOp/FTJdD/YiRzKVLARjgOycwVRKvW2L5Tge8Z7ysZ16sKfnV2vCXyonXYfUWozXw==
|
||||
|
||||
xterm@4.20.0-beta.5:
|
||||
version "4.20.0-beta.5"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.5.tgz#d707b0dcb477a554135fb767b24003fced079866"
|
||||
integrity sha512-KBWfk9UPBKRy662DVGGTZEcW1becEjYvlyWbn2hLj9h2gy6Q4EEEEbggJh8I7SGwdFizl+apHQGhEOZmFCA70w==
|
||||
xterm@4.20.0-beta.6:
|
||||
version "4.20.0-beta.6"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.6.tgz#3ed87ba383a5cf44284098278f714df7113e3e3c"
|
||||
integrity sha512-xJd6vyOuYo4Ht/hTY3DyXGIj0U6kHjr2vWQ1lRmearo3t7QKf7uqOAAfTLeWt/g1P8qe/r0DnsNTeag6vI9RVw==
|
||||
|
||||
+12
-12
@@ -803,20 +803,20 @@ xterm-addon-unicode11@0.4.0-beta.3:
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa"
|
||||
integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q==
|
||||
|
||||
xterm-addon-webgl@0.13.0-beta.2:
|
||||
version "0.13.0-beta.2"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.2.tgz#f58a7a3641ad7c8ac82dd24cfb0165656ed9ac1c"
|
||||
integrity sha512-98tX0BkpD402RoCO6SyikUXpzCn9/OQhlXsRmM/kRFCxMWWofStWTXzCPhN0MjIx2IdGueDjCmnShhidwihErg==
|
||||
xterm-addon-webgl@0.13.0-beta.3:
|
||||
version "0.13.0-beta.3"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.3.tgz#2b456c3105238e64b40a30787d6335f5f6f85abb"
|
||||
integrity sha512-DFGcXAolA0VTsOLIKcORxUOp/FTJdD/YiRzKVLARjgOycwVRKvW2L5Tge8Z7ysZ16sKfnV2vCXyonXYfUWozXw==
|
||||
|
||||
xterm-headless@4.20.0-beta.5:
|
||||
version "4.20.0-beta.5"
|
||||
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.20.0-beta.5.tgz#edcff27eb6437d158e6aea2ed7658e783bee5641"
|
||||
integrity sha512-8SnVUsuNUrQ5P0XU/9Iau3uK7Tf8q/p0KHHwkwJXVxZDIlaDH9XKSs91U9BjJJE3sJgRxH4NSiDYR3vFLSFpxw==
|
||||
xterm-headless@4.20.0-beta.6:
|
||||
version "4.20.0-beta.6"
|
||||
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.20.0-beta.6.tgz#bd016379e9fac47e5b8870d567cdf330cf6f49fc"
|
||||
integrity sha512-EV0V7pxMKI0OEcOCD+6vdXq6rBARr7dSN3PovTsZnDWg5dmvUb2eEmz6BTejJj3UVd/JXNEmEXM+tCh97rDCDg==
|
||||
|
||||
xterm@4.20.0-beta.5:
|
||||
version "4.20.0-beta.5"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.5.tgz#d707b0dcb477a554135fb767b24003fced079866"
|
||||
integrity sha512-KBWfk9UPBKRy662DVGGTZEcW1becEjYvlyWbn2hLj9h2gy6Q4EEEEbggJh8I7SGwdFizl+apHQGhEOZmFCA70w==
|
||||
xterm@4.20.0-beta.6:
|
||||
version "4.20.0-beta.6"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.6.tgz#3ed87ba383a5cf44284098278f714df7113e3e3c"
|
||||
integrity sha512-xJd6vyOuYo4Ht/hTY3DyXGIj0U6kHjr2vWQ1lRmearo3t7QKf7uqOAAfTLeWt/g1P8qe/r0DnsNTeag6vI9RVw==
|
||||
|
||||
yallist@^4.0.0:
|
||||
version "4.0.0"
|
||||
|
||||
@@ -42,8 +42,13 @@
|
||||
outline-offset: -1px !important;
|
||||
}
|
||||
|
||||
.monaco-button-dropdown > .monaco-dropdown-button {
|
||||
margin-left: 1px;
|
||||
.monaco-button-dropdown .monaco-button-dropdown-separator {
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.monaco-button-dropdown .monaco-button-dropdown-separator > div {
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.monaco-description-button {
|
||||
|
||||
@@ -247,6 +247,8 @@ export class ButtonWithDropdown extends Disposable implements IButton {
|
||||
private readonly button: Button;
|
||||
private readonly action: Action;
|
||||
private readonly dropdownButton: Button;
|
||||
private readonly separatorContainer: HTMLDivElement;
|
||||
private readonly separator: HTMLDivElement;
|
||||
|
||||
readonly element: HTMLElement;
|
||||
private readonly _onDidClick = this._register(new Emitter<Event | undefined>());
|
||||
@@ -263,6 +265,13 @@ export class ButtonWithDropdown extends Disposable implements IButton {
|
||||
this._register(this.button.onDidClick(e => this._onDidClick.fire(e)));
|
||||
this.action = this._register(new Action('primaryAction', this.button.label, undefined, true, async () => this._onDidClick.fire(undefined)));
|
||||
|
||||
this.separatorContainer = document.createElement('div');
|
||||
this.separatorContainer.classList.add('monaco-button-dropdown-separator');
|
||||
|
||||
this.separator = document.createElement('div');
|
||||
this.separatorContainer.appendChild(this.separator);
|
||||
this.element.appendChild(this.separatorContainer);
|
||||
|
||||
this.dropdownButton = this._register(new Button(this.element, { ...options, title: false, supportIcons: true }));
|
||||
this.dropdownButton.element.title = localize("button dropdown more actions", 'More Actions...');
|
||||
this.dropdownButton.element.classList.add('monaco-dropdown-button');
|
||||
@@ -299,6 +308,10 @@ export class ButtonWithDropdown extends Disposable implements IButton {
|
||||
style(styles: IButtonStyles): void {
|
||||
this.button.style(styles);
|
||||
this.dropdownButton.style(styles);
|
||||
|
||||
// Separator
|
||||
this.separatorContainer.style.backgroundColor = styles.buttonBackground?.toString() ?? '';
|
||||
this.separator.style.backgroundColor = styles.buttonForeground?.toString() ?? '';
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
|
||||
@@ -527,6 +527,16 @@ export class Grid<T extends IView = IView> extends Disposable {
|
||||
return this.gridview.resizeView(location, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether all other {@link IView views} are at their minimum size.
|
||||
*
|
||||
* @param view The reference {@link IView view}.
|
||||
*/
|
||||
isViewSizeMaximized(view: T): boolean {
|
||||
const location = this.getViewLocation(view);
|
||||
return this.gridview.isViewSizeMaximized(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of a {@link IView view}.
|
||||
*
|
||||
|
||||
@@ -592,6 +592,10 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
this.splitview.resizeView(index, size);
|
||||
}
|
||||
|
||||
isChildSizeMaximized(index: number): boolean {
|
||||
return this.splitview.isViewSizeMaximized(index);
|
||||
}
|
||||
|
||||
distributeViewSizes(recursive = false): void {
|
||||
this.splitview.distributeViewSizes();
|
||||
|
||||
@@ -1431,6 +1435,27 @@ export class GridView implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether all other {@link IView views} are at their minimum size.
|
||||
*
|
||||
* @param location The {@link GridLocation location} of the view.
|
||||
*/
|
||||
isViewSizeMaximized(location: GridLocation): boolean {
|
||||
const [ancestors, node] = this.getNode(location);
|
||||
|
||||
if (!(node instanceof LeafNode)) {
|
||||
throw new Error('Invalid location');
|
||||
}
|
||||
|
||||
for (let i = 0; i < ancestors.length; i++) {
|
||||
if (!ancestors[i].isChildSizeMaximized(location[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Distribute the size among all {@link IView views} within the entire
|
||||
* grid or within a single {@link SplitView}.
|
||||
|
||||
@@ -931,6 +931,23 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
|
||||
this.state = State.Idle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether all other {@link IView views} are at their minimum size.
|
||||
*/
|
||||
isViewSizeMaximized(index: number): boolean {
|
||||
if (index < 0 || index >= this.viewItems.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const item of this.viewItems) {
|
||||
if (item !== this.viewItems[index] && item.size > item.minimumSize) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Distribute the entire {@link SplitView} size among all {@link IView views}.
|
||||
*/
|
||||
|
||||
@@ -52,7 +52,7 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
|
||||
const ISSUE_REPORTER_HINT = /--vscode-window-kind=issue-reporter/;
|
||||
const PROCESS_EXPLORER_HINT = /--vscode-window-kind=process-explorer/;
|
||||
const UTILITY_NETWORK_HINT = /--utility-sub-type=network/;
|
||||
const UTILITY_EXTENSION_HOST_HINT = /--vscode-utility-kind=extensionHost/;
|
||||
const UTILITY_EXTENSION_HOST_HINT = /--vscode-utility-kind=extension-host/;
|
||||
const WINDOWS_CRASH_REPORTER = /--crashes-directory/;
|
||||
const WINDOWS_PTY = /\\pipe\\winpty-control/;
|
||||
const WINDOWS_CONSOLE_HOST = /conhost\.exe/;
|
||||
|
||||
@@ -450,6 +450,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
private _matchOnDescription = false;
|
||||
private _matchOnDetail = false;
|
||||
private _matchOnLabel = true;
|
||||
private _matchOnLabelMode: 'fuzzy' | 'contiguous' = 'fuzzy';
|
||||
private _sortByLabel = true;
|
||||
private _autoFocusOnList = true;
|
||||
private _keepScrollPosition = false;
|
||||
@@ -595,6 +596,15 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
this.update();
|
||||
}
|
||||
|
||||
get matchOnLabelMode() {
|
||||
return this._matchOnLabelMode;
|
||||
}
|
||||
|
||||
set matchOnLabelMode(matchOnLabelMode: 'fuzzy' | 'contiguous') {
|
||||
this._matchOnLabelMode = matchOnLabelMode;
|
||||
this.update();
|
||||
}
|
||||
|
||||
get sortByLabel() {
|
||||
return this._sortByLabel;
|
||||
}
|
||||
@@ -994,6 +1004,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
this.ui.list.matchOnDescription = this.matchOnDescription;
|
||||
this.ui.list.matchOnDetail = this.matchOnDetail;
|
||||
this.ui.list.matchOnLabel = this.matchOnLabel;
|
||||
this.ui.list.matchOnLabelMode = this.matchOnLabelMode;
|
||||
this.ui.list.sortByLabel = this.sortByLabel;
|
||||
if (this.itemsUpdated) {
|
||||
this.itemsUpdated = false;
|
||||
|
||||
@@ -17,10 +17,11 @@ import { compareAnything } from 'vs/base/common/comparers';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IMatch } from 'vs/base/common/filters';
|
||||
import { matchesFuzzyIconAware, parseLabelWithIcons } from 'vs/base/common/iconLabels';
|
||||
import { IParsedLabelWithIcons, matchesFuzzyIconAware, parseLabelWithIcons } from 'vs/base/common/iconLabels';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { ltrim } from 'vs/base/common/strings';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { IQuickInputOptions } from 'vs/base/parts/quickinput/browser/quickInput';
|
||||
import { getIconClass } from 'vs/base/parts/quickinput/browser/quickInputUtils';
|
||||
@@ -258,6 +259,7 @@ export class QuickInputList {
|
||||
matchOnDescription = false;
|
||||
matchOnDetail = false;
|
||||
matchOnLabel = true;
|
||||
matchOnLabelMode: 'fuzzy' | 'contiguous' = 'fuzzy';
|
||||
matchOnMeta = true;
|
||||
sortByLabel = true;
|
||||
private readonly _onChangedAllVisibleChecked = new Emitter<boolean>();
|
||||
@@ -610,6 +612,8 @@ export class QuickInputList {
|
||||
this.list.layout();
|
||||
return false;
|
||||
}
|
||||
|
||||
const queryWithWhitespace = query;
|
||||
query = query.trim();
|
||||
|
||||
// Reset filtering
|
||||
@@ -628,7 +632,12 @@ export class QuickInputList {
|
||||
else {
|
||||
let currentSeparator: IQuickPickSeparator | undefined;
|
||||
this.elements.forEach(element => {
|
||||
const labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneLabel))) : undefined;
|
||||
let labelHighlights: IMatch[] | undefined;
|
||||
if (this.matchOnLabelMode === 'fuzzy') {
|
||||
labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneLabel))) : undefined;
|
||||
} else {
|
||||
labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesContiguousIconAware(queryWithWhitespace, parseLabelWithIcons(element.saneLabel))) : undefined;
|
||||
}
|
||||
const descriptionHighlights = this.matchOnDescription ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneDescription || ''))) : undefined;
|
||||
const detailHighlights = this.matchOnDetail ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneDetail || ''))) : undefined;
|
||||
const metaHighlights = this.matchOnMeta ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneMeta || ''))) : undefined;
|
||||
@@ -726,6 +735,43 @@ export class QuickInputList {
|
||||
}
|
||||
}
|
||||
|
||||
export function matchesContiguousIconAware(query: string, target: IParsedLabelWithIcons): IMatch[] | null {
|
||||
|
||||
const { text, iconOffsets } = target;
|
||||
|
||||
// Return early if there are no icon markers in the word to match against
|
||||
if (!iconOffsets || iconOffsets.length === 0) {
|
||||
return matchesContiguous(query, text);
|
||||
}
|
||||
|
||||
// Trim the word to match against because it could have leading
|
||||
// whitespace now if the word started with an icon
|
||||
const wordToMatchAgainstWithoutIconsTrimmed = ltrim(text, ' ');
|
||||
const leadingWhitespaceOffset = text.length - wordToMatchAgainstWithoutIconsTrimmed.length;
|
||||
|
||||
// match on value without icon
|
||||
const matches = matchesContiguous(query, wordToMatchAgainstWithoutIconsTrimmed);
|
||||
|
||||
// Map matches back to offsets with icon and trimming
|
||||
if (matches) {
|
||||
for (const match of matches) {
|
||||
const iconOffset = iconOffsets[match.start + leadingWhitespaceOffset] /* icon offsets at index */ + leadingWhitespaceOffset /* overall leading whitespace offset */;
|
||||
match.start += iconOffset;
|
||||
match.end += iconOffset;
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
function matchesContiguous(word: string, wordToMatchAgainst: string): IMatch[] | null {
|
||||
const matchIndex = wordToMatchAgainst.toLowerCase().indexOf(word.toLowerCase());
|
||||
if (matchIndex !== -1) {
|
||||
return [{ start: matchIndex, end: matchIndex + word.length }];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function compareEntries(elementA: ListElement, elementB: ListElement, lookFor: string): number {
|
||||
|
||||
const labelHighlightsA = elementA.labelHighlights || [];
|
||||
|
||||
@@ -292,6 +292,13 @@ export interface IQuickPick<T extends IQuickPickItem> extends IQuickInput {
|
||||
|
||||
matchOnLabel: boolean;
|
||||
|
||||
/**
|
||||
* The mode to filter label with. Fuzzy will use fuzzy searching and
|
||||
* contiguous will make filter entries that do not contain the exact string
|
||||
* (including whitespace). This defaults to `'fuzzy'`.
|
||||
*/
|
||||
matchOnLabelMode: 'fuzzy' | 'contiguous';
|
||||
|
||||
sortByLabel: boolean;
|
||||
|
||||
autoFocusOnList: boolean;
|
||||
|
||||
@@ -852,12 +852,12 @@ suite('Map', () => {
|
||||
|
||||
for (const item of keys) {
|
||||
tst.set(item, true);
|
||||
assert.ok(tst._isBalanced());
|
||||
assert.ok(tst._isBalanced(), `SET${item}|${keys.map(String).join()}`);
|
||||
}
|
||||
|
||||
for (const item of keys) {
|
||||
tst.delete(item);
|
||||
assert.ok(tst._isBalanced());
|
||||
assert.ok(tst._isBalanced(), `DEL${item}|${keys.map(String).join()}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, DisposableStore, Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { LinkedList } from 'vs/base/common/linkedList';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { ICodeEditorOpenHandler, ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { IContentDecorationRenderOptions, IDecorationRenderOptions, IThemeDecorationRenderOptions, isThemeColor } from 'vs/editor/common/editorCommon';
|
||||
import { IModelDecorationOptions, IModelDecorationOverviewRulerOptions, InjectedTextOptions, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model';
|
||||
import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
|
||||
@@ -42,6 +43,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC
|
||||
protected _globalStyleSheet: GlobalStyleSheet | null;
|
||||
private readonly _decorationOptionProviders = new Map<string, IModelDecorationOptionsProvider>();
|
||||
private readonly _editorStyleSheets = new Map<string, RefCountedStyleSheet>();
|
||||
private readonly _codeEditorOpenHandlers = new LinkedList<ICodeEditorOpenHandler>();
|
||||
|
||||
constructor(
|
||||
@IThemeService private readonly _themeService: IThemeService,
|
||||
@@ -247,7 +249,21 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC
|
||||
}
|
||||
|
||||
abstract getActiveCodeEditor(): ICodeEditor | null;
|
||||
abstract openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null>;
|
||||
|
||||
async openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
|
||||
for (const handler of this._codeEditorOpenHandlers) {
|
||||
const candidate = await handler(input, source, sideBySide);
|
||||
if (candidate !== null) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
registerCodeEditorOpenHandler(handler: ICodeEditorOpenHandler): IDisposable {
|
||||
const rm = this._codeEditorOpenHandlers.unshift(handler);
|
||||
return toDisposable(rm);
|
||||
}
|
||||
}
|
||||
|
||||
export class ModelTransientSettingWatcher {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { IModelDecorationOptions, ITextModel } from 'vs/editor/common/model';
|
||||
import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export const ICodeEditorService = createDecorator<ICodeEditorService>('codeEditorService');
|
||||
|
||||
@@ -53,4 +54,9 @@ export interface ICodeEditorService {
|
||||
|
||||
getActiveCodeEditor(): ICodeEditor | null;
|
||||
openCodeEditor(input: ITextResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null>;
|
||||
registerCodeEditorOpenHandler(handler: ICodeEditorOpenHandler): IDisposable;
|
||||
}
|
||||
|
||||
export interface ICodeEditorOpenHandler {
|
||||
(input: ITextResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null>;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { IRange } from 'vs/editor/common/core/range';
|
||||
import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IResourceEditorInput, ITextResourceEditorInput } from 'vs/platform/editor/common/editor';
|
||||
import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
@@ -31,6 +31,13 @@ export class StandaloneCodeEditorService extends AbstractCodeEditorService {
|
||||
this.onCodeEditorRemove(() => this._checkContextKey());
|
||||
this._editorIsOpen = contextKeyService.createKey('editorIsOpen', false);
|
||||
this._activeCodeEditor = null;
|
||||
|
||||
this.registerCodeEditorOpenHandler(async (input, source, sideBySide) => {
|
||||
if (!source) {
|
||||
return null;
|
||||
}
|
||||
return this.doOpenEditor(source, input);
|
||||
});
|
||||
}
|
||||
|
||||
private _checkContextKey(): void {
|
||||
@@ -52,13 +59,6 @@ export class StandaloneCodeEditorService extends AbstractCodeEditorService {
|
||||
return this._activeCodeEditor;
|
||||
}
|
||||
|
||||
public openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
|
||||
if (!source) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
return Promise.resolve(this.doOpenEditor(source, input));
|
||||
}
|
||||
|
||||
private doOpenEditor(editor: ICodeEditor, input: ITextResourceEditorInput): ICodeEditor | null {
|
||||
const model = this.findModel(editor, input.resource);
|
||||
|
||||
@@ -22,7 +22,7 @@ export class TestCodeEditorService extends AbstractCodeEditorService {
|
||||
return null;
|
||||
}
|
||||
public lastInput?: IResourceEditorInput;
|
||||
openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
|
||||
override openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
|
||||
this.lastInput = input;
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
@@ -324,7 +324,7 @@ class UtilityExtensionHostProcess extends Disposable {
|
||||
const modulePath = FileAccess.asFileUri('bootstrap-fork.js', require).fsPath;
|
||||
const args: string[] = ['--type=extensionHost', '--skipWorkspaceStorageLock'];
|
||||
const execArgv: string[] = opts.execArgv || [];
|
||||
execArgv.push(`--vscode-utility-kind=extensionHost`);
|
||||
execArgv.push(`--vscode-utility-kind=extension-host`);
|
||||
const env: { [key: string]: any } = { ...opts.env };
|
||||
|
||||
// Make sure all values are strings, otherwise the process will not start
|
||||
|
||||
@@ -443,11 +443,11 @@ export interface IShellLaunchConfig {
|
||||
|
||||
/**
|
||||
* A string including ANSI escape sequences that will be written to the terminal emulator
|
||||
* _before_ the terminal process has launched, a trailing \n is added at the end of the string.
|
||||
* This allows for example the terminal instance to display a styled message as the first line
|
||||
* of the terminal. Use \x1b over \033 or \e for the escape control character.
|
||||
* _before_ the terminal process has launched, when a string is specified, a trailing \n is
|
||||
* added at the end. This allows for example the terminal instance to display a styled message
|
||||
* as the first line of the terminal. Use \x1b over \033 or \e for the escape control character.
|
||||
*/
|
||||
initialText?: string;
|
||||
initialText?: string | { text: string; trailingNewLine: boolean };
|
||||
|
||||
/**
|
||||
* Custom PTY/pseudoterminal process to use.
|
||||
|
||||
@@ -12,3 +12,26 @@ export function escapeNonWindowsPath(path: string): string {
|
||||
newPath = newPath.replace(bannedChars, '');
|
||||
return `'${newPath}'`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapses the user's home directory into `~` if it exists within the path, this gives a shorter
|
||||
* path that is more suitable within the context of a terminal.
|
||||
*/
|
||||
export function collapseTildePath(path: string | undefined, userHome: string | undefined, separator: string): string {
|
||||
if (!path) {
|
||||
return '';
|
||||
}
|
||||
if (!userHome) {
|
||||
return path;
|
||||
}
|
||||
// Trim the trailing separator from the end if it exists
|
||||
if (userHome.match(/[\/\\]$/)) {
|
||||
userHome = userHome.slice(0, userHome.length - 1);
|
||||
}
|
||||
const normalizedPath = path.replace(/\\/g, '/').toLowerCase();
|
||||
const normalizedUserHome = userHome.replace(/\\/g, '/').toLowerCase();
|
||||
if (!normalizedPath.includes(normalizedUserHome)) {
|
||||
return path;
|
||||
}
|
||||
return `~${separator}${path.slice(userHome.length + 1)}`;
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ export class PtyService extends Disposable implements IPtyService {
|
||||
executableEnv,
|
||||
options
|
||||
};
|
||||
const persistentProcess = new PersistentTerminalProcess(id, process, workspaceId, workspaceName, shouldPersist, cols, rows, processLaunchOptions, unicodeVersion, this._reconnectConstants, this._logService, isReviving ? shellLaunchConfig.initialText : undefined, rawReviveBuffer, shellLaunchConfig.icon, shellLaunchConfig.color, shellLaunchConfig.name, shellLaunchConfig.fixedDimensions);
|
||||
const persistentProcess = new PersistentTerminalProcess(id, process, workspaceId, workspaceName, shouldPersist, cols, rows, processLaunchOptions, unicodeVersion, this._reconnectConstants, this._logService, isReviving && typeof shellLaunchConfig.initialText === 'string' ? shellLaunchConfig.initialText : undefined, rawReviveBuffer, shellLaunchConfig.icon, shellLaunchConfig.color, shellLaunchConfig.name, shellLaunchConfig.fixedDimensions);
|
||||
process.onDidChangeProperty(property => this._onDidChangeProperty.fire({ id, property }));
|
||||
process.onProcessExit(event => {
|
||||
persistentProcess.dispose();
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { strictEqual } from 'assert';
|
||||
import { collapseTildePath } from 'vs/platform/terminal/common/terminalEnvironment';
|
||||
|
||||
suite('terminalEnvironment', () => {
|
||||
suite('collapseTildePath', () => {
|
||||
test('should return empty string for a falsy path', () => {
|
||||
strictEqual(collapseTildePath('', '/foo', '/'), '');
|
||||
strictEqual(collapseTildePath(undefined, '/foo', '/'), '');
|
||||
});
|
||||
test('should return path for a falsy user home', () => {
|
||||
strictEqual(collapseTildePath('/foo', '', '/'), '/foo');
|
||||
strictEqual(collapseTildePath('/foo', undefined, '/'), '/foo');
|
||||
});
|
||||
test('should not collapse when user home isn\'t present', () => {
|
||||
strictEqual(collapseTildePath('/foo', '/bar', '/'), '/foo');
|
||||
strictEqual(collapseTildePath('C:\\foo', 'C:\\bar', '\\'), 'C:\\foo');
|
||||
});
|
||||
test('should collapse with Windows separators', () => {
|
||||
strictEqual(collapseTildePath('C:\\foo\\bar', 'C:\\foo', '\\'), '~\\bar');
|
||||
strictEqual(collapseTildePath('C:\\foo\\bar', 'C:\\foo\\', '\\'), '~\\bar');
|
||||
strictEqual(collapseTildePath('C:\\foo\\bar\\baz', 'C:\\foo\\', '\\'), '~\\bar\\baz');
|
||||
strictEqual(collapseTildePath('C:\\foo\\bar\\baz', 'C:\\foo', '\\'), '~\\bar\\baz');
|
||||
});
|
||||
test('should collapse mixed case with Windows separators', () => {
|
||||
strictEqual(collapseTildePath('c:\\foo\\bar', 'C:\\foo', '\\'), '~\\bar');
|
||||
strictEqual(collapseTildePath('C:\\foo\\bar\\baz', 'c:\\foo', '\\'), '~\\bar\\baz');
|
||||
});
|
||||
test('should collapse with Posix separators', () => {
|
||||
strictEqual(collapseTildePath('/foo/bar', '/foo', '/'), '~/bar');
|
||||
strictEqual(collapseTildePath('/foo/bar', '/foo/', '/'), '~/bar');
|
||||
strictEqual(collapseTildePath('/foo/bar/baz', '/foo', '/'), '~/bar/baz');
|
||||
strictEqual(collapseTildePath('/foo/bar/baz', '/foo/', '/'), '~/bar/baz');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -184,7 +184,7 @@ export interface IUserDataSyncStoreClient {
|
||||
delete(resource: ServerResource, ref: string | null): Promise<void>;
|
||||
|
||||
getAllRefs(resource: ServerResource): Promise<IResourceRefHandle[]>;
|
||||
resolveContent(resource: ServerResource, ref: string): Promise<string | null>;
|
||||
resolveContent(resource: ServerResource, ref: string, headers?: IHeaders): Promise<string | null>;
|
||||
}
|
||||
|
||||
export const IUserDataSyncStoreService = createDecorator<IUserDataSyncStoreService>('IUserDataSyncStoreService');
|
||||
|
||||
@@ -244,13 +244,13 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync
|
||||
return result.map(({ url, created }) => ({ ref: relativePath(uri, uri.with({ path: url }))!, created: created * 1000 /* Server returns in seconds */ }));
|
||||
}
|
||||
|
||||
async resolveContent(resource: ServerResource, ref: string): Promise<string | null> {
|
||||
async resolveContent(resource: ServerResource, ref: string, headers: IHeaders = {}): Promise<string | null> {
|
||||
if (!this.userDataSyncStoreUrl) {
|
||||
throw new Error('No settings sync store url configured.');
|
||||
}
|
||||
|
||||
const url = joinPath(this.userDataSyncStoreUrl, 'resource', resource, ref).toString();
|
||||
const headers: IHeaders = {};
|
||||
headers = { ...headers };
|
||||
headers['Cache-Control'] = 'no-cache';
|
||||
|
||||
const context = await this.request(url, { type: 'GET', headers }, [], CancellationToken.None);
|
||||
|
||||
@@ -135,8 +135,6 @@ export interface IEditorGroupView extends IDisposable, ISerializableView, IEdito
|
||||
|
||||
readonly titleHeight: IEditorGroupTitleHeight;
|
||||
|
||||
readonly isMinimized: boolean;
|
||||
|
||||
readonly disposed: boolean;
|
||||
|
||||
setActive(isActive: boolean): void;
|
||||
|
||||
@@ -573,24 +573,31 @@ abstract class AbstractCloseAllAction extends Action {
|
||||
override async run(): Promise<void> {
|
||||
|
||||
// Depending on the editor and auto save configuration,
|
||||
// split dirty editors into buckets
|
||||
// split editors into buckets for handling confirmation
|
||||
|
||||
const dirtyEditorsWithDefaultConfirm = new Set<IEditorIdentifier>();
|
||||
const dirtyAutoSaveOnFocusChangeEditors = new Set<IEditorIdentifier>();
|
||||
const dirtyAutoSaveOnWindowChangeEditors = new Set<IEditorIdentifier>();
|
||||
const dirtyEditorsWithCustomConfirm = new Map<string /* typeId */, Set<IEditorIdentifier>>();
|
||||
const editorsWithCustomConfirm = new Map<string /* typeId */, Set<IEditorIdentifier>>();
|
||||
|
||||
for (const { editor, groupId } of this.editorService.getEditors(EditorsOrder.SEQUENTIAL, { excludeSticky: this.excludeSticky })) {
|
||||
if (!editor.isDirty() || editor.isSaving()) {
|
||||
continue; // only interested in dirty editors that are not in the process of saving
|
||||
let confirmClose = false;
|
||||
if (editor.closeHandler) {
|
||||
confirmClose = editor.closeHandler.showConfirm(); // custom handling of confirmation on close
|
||||
} else {
|
||||
confirmClose = editor.isDirty() && !editor.isSaving(); // default confirm only when dirty and not saving
|
||||
}
|
||||
|
||||
if (!confirmClose) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Editor has custom confirm implementation
|
||||
if (typeof editor.confirm === 'function') {
|
||||
let customEditorsToConfirm = dirtyEditorsWithCustomConfirm.get(editor.typeId);
|
||||
if (typeof editor.closeHandler?.confirm === 'function') {
|
||||
let customEditorsToConfirm = editorsWithCustomConfirm.get(editor.typeId);
|
||||
if (!customEditorsToConfirm) {
|
||||
customEditorsToConfirm = new Set();
|
||||
dirtyEditorsWithCustomConfirm.set(editor.typeId, customEditorsToConfirm);
|
||||
editorsWithCustomConfirm.set(editor.typeId, customEditorsToConfirm);
|
||||
}
|
||||
|
||||
customEditorsToConfirm.add({ editor, groupId });
|
||||
@@ -619,7 +626,7 @@ abstract class AbstractCloseAllAction extends Action {
|
||||
if (dirtyEditorsWithDefaultConfirm.size > 0) {
|
||||
const editors = Array.from(dirtyEditorsWithDefaultConfirm.values());
|
||||
|
||||
await this.revealDirtyEditors(editors); // help user make a decision by revealing editors
|
||||
await this.revealEditorsToConfirm(editors); // help user make a decision by revealing editors
|
||||
|
||||
const confirmation = await this.fileDialogService.showSaveConfirm(editors.map(({ editor }) => {
|
||||
if (editor instanceof SideBySideEditorInput) {
|
||||
@@ -642,12 +649,12 @@ abstract class AbstractCloseAllAction extends Action {
|
||||
}
|
||||
|
||||
// 2.) Show custom confirm based dialog
|
||||
for (const [, editorIdentifiers] of dirtyEditorsWithCustomConfirm) {
|
||||
for (const [, editorIdentifiers] of editorsWithCustomConfirm) {
|
||||
const editors = Array.from(editorIdentifiers.values());
|
||||
|
||||
await this.revealDirtyEditors(editors); // help user make a decision by revealing editors
|
||||
await this.revealEditorsToConfirm(editors); // help user make a decision by revealing editors
|
||||
|
||||
const confirmation = await firstOrDefault(editors)?.editor.confirm?.(editors);
|
||||
const confirmation = await firstOrDefault(editors)?.editor.closeHandler?.confirm?.(editors);
|
||||
if (typeof confirmation === 'number') {
|
||||
switch (confirmation) {
|
||||
case ConfirmResult.CANCEL:
|
||||
@@ -683,7 +690,7 @@ abstract class AbstractCloseAllAction extends Action {
|
||||
return this.doCloseAll();
|
||||
}
|
||||
|
||||
private async revealDirtyEditors(editors: ReadonlyArray<IEditorIdentifier>): Promise<void> {
|
||||
private async revealEditorsToConfirm(editors: ReadonlyArray<IEditorIdentifier>): Promise<void> {
|
||||
try {
|
||||
const handledGroups = new Set<GroupIdentifier>();
|
||||
for (const { editor, groupId } of editors) {
|
||||
@@ -1016,7 +1023,7 @@ export class MinimizeOtherGroupsAction extends Action {
|
||||
}
|
||||
|
||||
override async run(): Promise<void> {
|
||||
this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
|
||||
this.editorGroupService.arrangeGroups(GroupsArrangement.MAXIMIZE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1067,7 +1074,7 @@ export class MaximizeGroupAction extends Action {
|
||||
if (this.editorService.activeEditor) {
|
||||
this.layoutService.setPartHidden(true, Parts.SIDEBAR_PART);
|
||||
this.layoutService.setPartHidden(true, Parts.AUXILIARYBAR_PART);
|
||||
this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
|
||||
this.editorGroupService.arrangeGroups(GroupsArrangement.MAXIMIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -781,14 +781,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
return this.titleAreaControl.getHeight();
|
||||
}
|
||||
|
||||
get isMinimized(): boolean {
|
||||
if (!this.dimension) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.dimension.width === this.minimumWidth || this.dimension.height === this.minimumHeight;
|
||||
}
|
||||
|
||||
notifyIndexChanged(newIndex: number): void {
|
||||
if (this._index !== newIndex) {
|
||||
this._index = newIndex;
|
||||
@@ -1322,16 +1314,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
//#region closeEditor()
|
||||
|
||||
async closeEditor(editor: EditorInput | undefined = this.activeEditor || undefined, options?: ICloseEditorOptions): Promise<boolean> {
|
||||
return this.doCloseEditorWithDirtyHandling(editor, options);
|
||||
return this.doCloseEditorWithConfirmationHandling(editor, options);
|
||||
}
|
||||
|
||||
private async doCloseEditorWithDirtyHandling(editor: EditorInput | undefined = this.activeEditor || undefined, options?: ICloseEditorOptions, internalOptions?: IInternalEditorCloseOptions): Promise<boolean> {
|
||||
private async doCloseEditorWithConfirmationHandling(editor: EditorInput | undefined = this.activeEditor || undefined, options?: ICloseEditorOptions, internalOptions?: IInternalEditorCloseOptions): Promise<boolean> {
|
||||
if (!editor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for dirty and veto
|
||||
const veto = await this.handleDirtyClosing([editor]);
|
||||
// Check for confirmation and veto
|
||||
const veto = await this.handleCloseConfirmation([editor]);
|
||||
if (veto) {
|
||||
return false;
|
||||
}
|
||||
@@ -1461,7 +1453,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
return this.model.closeEditor(editor, internalOptions?.context)?.editorIndex;
|
||||
}
|
||||
|
||||
private async handleDirtyClosing(editors: EditorInput[]): Promise<boolean /* veto */> {
|
||||
private async handleCloseConfirmation(editors: EditorInput[]): Promise<boolean /* veto */> {
|
||||
if (!editors.length) {
|
||||
return false; // no veto
|
||||
}
|
||||
@@ -1470,15 +1462,15 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
|
||||
// To prevent multiple confirmation dialogs from showing up one after the other
|
||||
// we check if a pending confirmation is currently showing and if so, join that
|
||||
let handleDirtyClosingPromise = this.mapEditorToPendingConfirmation.get(editor);
|
||||
if (!handleDirtyClosingPromise) {
|
||||
handleDirtyClosingPromise = this.doHandleDirtyClosing(editor);
|
||||
this.mapEditorToPendingConfirmation.set(editor, handleDirtyClosingPromise);
|
||||
let handleCloseConfirmationPromise = this.mapEditorToPendingConfirmation.get(editor);
|
||||
if (!handleCloseConfirmationPromise) {
|
||||
handleCloseConfirmationPromise = this.doHandleCloseConfirmation(editor);
|
||||
this.mapEditorToPendingConfirmation.set(editor, handleCloseConfirmationPromise);
|
||||
}
|
||||
|
||||
let veto: boolean;
|
||||
try {
|
||||
veto = await handleDirtyClosingPromise;
|
||||
veto = await handleCloseConfirmationPromise;
|
||||
} finally {
|
||||
this.mapEditorToPendingConfirmation.delete(editor);
|
||||
}
|
||||
@@ -1489,12 +1481,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
}
|
||||
|
||||
// Otherwise continue with the remainders
|
||||
return this.handleDirtyClosing(editors);
|
||||
return this.handleCloseConfirmation(editors);
|
||||
}
|
||||
|
||||
private async doHandleDirtyClosing(editor: EditorInput, options?: { skipAutoSave: boolean }): Promise<boolean /* veto */> {
|
||||
if (!editor.isDirty() || editor.isSaving()) {
|
||||
return false; // editor must be dirty and not saving
|
||||
private async doHandleCloseConfirmation(editor: EditorInput, options?: { skipAutoSave: boolean }): Promise<boolean /* veto */> {
|
||||
if (!this.shouldConfirmClose(editor)) {
|
||||
return false; // no veto
|
||||
}
|
||||
|
||||
if (editor instanceof SideBySideEditorInput && this.model.contains(editor.primary)) {
|
||||
@@ -1531,10 +1523,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
// on auto-save configuration.
|
||||
// However, make sure to respect `skipAutoSave` option in case the automated
|
||||
// save fails which would result in the editor never closing.
|
||||
// Also, we only do this if no custom confirmation handling is implemented.
|
||||
let confirmation = ConfirmResult.CANCEL;
|
||||
let saveReason = SaveReason.EXPLICIT;
|
||||
let autoSave = false;
|
||||
if (!editor.hasCapability(EditorInputCapabilities.Untitled) && !options?.skipAutoSave) {
|
||||
if (!editor.hasCapability(EditorInputCapabilities.Untitled) && !options?.skipAutoSave && !editor.closeHandler) {
|
||||
|
||||
// Auto-save on focus change: save, because a dialog would steal focus
|
||||
// (see https://github.com/microsoft/vscode/issues/108752)
|
||||
@@ -1554,15 +1547,15 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
}
|
||||
}
|
||||
|
||||
// No auto-save on focus change: ask user
|
||||
// No auto-save on focus change or custom confirmation handler: ask user
|
||||
if (!autoSave) {
|
||||
|
||||
// Switch to editor that we want to handle and confirm to save/revert
|
||||
// Switch to editor that we want to handle for confirmation
|
||||
await this.doOpenEditor(editor);
|
||||
|
||||
// Let editor handle confirmation if implemented
|
||||
if (typeof editor.confirm === 'function') {
|
||||
confirmation = await editor.confirm();
|
||||
if (typeof editor.closeHandler?.confirm === 'function') {
|
||||
confirmation = await editor.closeHandler.confirm();
|
||||
}
|
||||
|
||||
// Show a file specific confirmation
|
||||
@@ -1578,11 +1571,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
}
|
||||
}
|
||||
|
||||
// It could be that the editor saved meanwhile or is saving, so we check
|
||||
// It could be that the editor's choice of confirmation has changed
|
||||
// given the check for confirmation is long running, so we check
|
||||
// again to see if anything needs to happen before closing for good.
|
||||
// This can happen for example if autoSave: onFocusChange is configured
|
||||
// This can happen for example if `autoSave: onFocusChange` is configured
|
||||
// so that the save happens when the dialog opens.
|
||||
if (!editor.isDirty() || editor.isSaving()) {
|
||||
if (!this.shouldConfirmClose(editor)) {
|
||||
return confirmation === ConfirmResult.CANCEL ? true : false;
|
||||
}
|
||||
|
||||
@@ -1595,7 +1589,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
// we handle the dirty editor again but this time ensuring to
|
||||
// show the confirm dialog
|
||||
// (see https://github.com/microsoft/vscode/issues/108752)
|
||||
return this.doHandleDirtyClosing(editor, { skipAutoSave: true });
|
||||
return this.doHandleCloseConfirmation(editor, { skipAutoSave: true });
|
||||
}
|
||||
|
||||
return editor.isDirty(); // veto if still dirty
|
||||
@@ -1621,6 +1615,14 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
}
|
||||
}
|
||||
|
||||
private shouldConfirmClose(editor: EditorInput): boolean {
|
||||
if (editor.closeHandler) {
|
||||
return editor.closeHandler.showConfirm(); // custom handling of confirmation on close
|
||||
}
|
||||
|
||||
return editor.isDirty() && !editor.isSaving(); // editor must be dirty and not saving
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region closeEditors()
|
||||
@@ -1632,8 +1634,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
|
||||
const editors = this.doGetEditorsToClose(args);
|
||||
|
||||
// Check for dirty and veto
|
||||
const veto = await this.handleDirtyClosing(editors.slice(0));
|
||||
// Check for confirmation and veto
|
||||
const veto = await this.handleCloseConfirmation(editors.slice(0));
|
||||
if (veto) {
|
||||
return false;
|
||||
}
|
||||
@@ -1714,8 +1716,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for dirty and veto
|
||||
const veto = await this.handleDirtyClosing(this.model.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE, options));
|
||||
// Check for confirmation and veto
|
||||
const veto = await this.handleCloseConfirmation(this.model.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE, options));
|
||||
if (veto) {
|
||||
return false;
|
||||
}
|
||||
@@ -1795,7 +1797,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
this.doCloseEditor(editor, false, { context: EditorCloseContext.REPLACE });
|
||||
closed = true;
|
||||
} else {
|
||||
closed = await this.doCloseEditorWithDirtyHandling(editor, { preserveFocus: true }, { context: EditorCloseContext.REPLACE });
|
||||
closed = await this.doCloseEditorWithConfirmationHandling(editor, { preserveFocus: true }, { context: EditorCloseContext.REPLACE });
|
||||
}
|
||||
|
||||
if (!closed) {
|
||||
@@ -1815,7 +1817,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
if (activeReplacement.forceReplaceDirty) {
|
||||
this.doCloseEditor(activeReplacement.editor, false, { context: EditorCloseContext.REPLACE });
|
||||
} else {
|
||||
await this.doCloseEditorWithDirtyHandling(activeReplacement.editor, { preserveFocus: true }, { context: EditorCloseContext.REPLACE });
|
||||
await this.doCloseEditorWithConfirmationHandling(activeReplacement.editor, { preserveFocus: true }, { context: EditorCloseContext.REPLACE });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -362,32 +362,22 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
|
||||
case GroupsArrangement.EVEN:
|
||||
this.gridWidget.distributeViewSizes();
|
||||
break;
|
||||
case GroupsArrangement.MINIMIZE_OTHERS:
|
||||
case GroupsArrangement.MAXIMIZE:
|
||||
this.gridWidget.maximizeViewSize(target);
|
||||
break;
|
||||
case GroupsArrangement.TOGGLE:
|
||||
if (this.isGroupMaximized(target)) {
|
||||
this.arrangeGroups(GroupsArrangement.EVEN);
|
||||
} else {
|
||||
this.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
|
||||
this.arrangeGroups(GroupsArrangement.MAXIMIZE);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private isGroupMaximized(targetGroup: IEditorGroupView): boolean {
|
||||
for (const group of this.groups) {
|
||||
if (group === targetGroup) {
|
||||
continue; // ignore target group
|
||||
}
|
||||
|
||||
if (!group.isMinimized) {
|
||||
return false; // target cannot be maximized if one group is not minimized
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
isGroupMaximized(targetGroup: IEditorGroupView): boolean {
|
||||
return this.gridWidget.isViewSizeMaximized(targetGroup);
|
||||
}
|
||||
|
||||
setGroupOrientation(orientation: GroupOrientation): void {
|
||||
@@ -609,7 +599,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
|
||||
if (this.gridWidget) {
|
||||
const viewSize = this.gridWidget.getViewSize(group);
|
||||
if (viewSize.width === group.minimumWidth || viewSize.height === group.minimumHeight) {
|
||||
this.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS, group);
|
||||
this.arrangeGroups(GroupsArrangement.MAXIMIZE, group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,31 @@ import { EditorInputCapabilities, Verbosity, GroupIdentifier, ISaveOptions, IRev
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { ConfirmResult } from 'vs/platform/dialogs/common/dialogs';
|
||||
|
||||
export interface IEditorCloseHandler {
|
||||
|
||||
/**
|
||||
* If `true`, will call into the `confirm` method to ask for confirmation
|
||||
* before closing the editor.
|
||||
*/
|
||||
showConfirm(): boolean;
|
||||
|
||||
/**
|
||||
* Allows an editor to control what should happen when the editor
|
||||
* (or a list of editor of the same kind) is being closed.
|
||||
*
|
||||
* By default a file specific dialog will open if the editor is
|
||||
* dirty and not in the process of saving.
|
||||
*
|
||||
* If the editor is not dealing with files or another condition
|
||||
* should be used besides dirty state, this method should be
|
||||
* implemented to show a different dialog.
|
||||
*
|
||||
* @param editors if more than one editor is closed, will pass in
|
||||
* each editor of the same kind to be able to show a combined dialog.
|
||||
*/
|
||||
confirm(editors?: ReadonlyArray<IEditorIdentifier>): Promise<ConfirmResult>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Editor inputs are lightweight objects that can be passed to the workbench API to open inside the editor part.
|
||||
* Each editor input is mapped to an editor that is capable of opening it through the Platform facade.
|
||||
@@ -45,6 +70,12 @@ export abstract class EditorInput extends AbstractEditorInput {
|
||||
|
||||
private disposed: boolean = false;
|
||||
|
||||
/**
|
||||
* Optional: subclasses can override to implement
|
||||
* custom confirmation on close behavior.
|
||||
*/
|
||||
readonly closeHandler?: IEditorCloseHandler;
|
||||
|
||||
/**
|
||||
* Unique type identifier for this input. Every editor input of the
|
||||
* same class should share the same type identifier. The type identifier
|
||||
@@ -168,20 +199,6 @@ export abstract class EditorInput extends AbstractEditorInput {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional: if this method is implemented, allows an editor to
|
||||
* control what should happen when the editor (or a list of editors
|
||||
* of the same kind) is dirty and there is an intent to close it.
|
||||
*
|
||||
* By default a file specific dialog will open. If the editor is
|
||||
* not dealing with files, this method should be implemented to
|
||||
* show a different dialog.
|
||||
*
|
||||
* @param editors if more than one editor is closed, will pass in
|
||||
* each editor of the same kind to be able to show a combined dialog.
|
||||
*/
|
||||
confirm?(editors?: ReadonlyArray<IEditorIdentifier>): Promise<ConfirmResult>;
|
||||
|
||||
/**
|
||||
* Saves the editor. The provided groupId helps implementors
|
||||
* to e.g. preserve view state of the editor and re-open it
|
||||
|
||||
@@ -403,8 +403,7 @@ class DocumentSymbolsOutlineCreator implements IOutlineCreator<IEditorPane, Docu
|
||||
readonly dispose: () => void;
|
||||
|
||||
constructor(
|
||||
@IOutlineService outlineService: IOutlineService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IOutlineService outlineService: IOutlineService
|
||||
) {
|
||||
const reg = outlineService.registerOutlineCreator(this);
|
||||
this.dispose = () => reg.dispose();
|
||||
@@ -427,7 +426,7 @@ class DocumentSymbolsOutlineCreator implements IOutlineCreator<IEditorPane, Docu
|
||||
return undefined;
|
||||
}
|
||||
const firstLoadBarrier = new Barrier();
|
||||
const result = this._instantiationService.createInstance(DocumentSymbolsOutline, editor, target, firstLoadBarrier);
|
||||
const result = editor.invokeWithinContext(accessor => accessor.get(IInstantiationService).createInstance(DocumentSymbolsOutline, editor!, target, firstLoadBarrier));
|
||||
await firstLoadBarrier.wait();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
|
||||
super();
|
||||
|
||||
if (this.environmentService.editSessionId !== undefined) {
|
||||
void this.applyEditSession(this.environmentService.editSessionId).finally(() => this.environmentService.editSessionId = undefined);
|
||||
void this.resumeEditSession(this.environmentService.editSessionId).finally(() => this.environmentService.editSessionId = undefined);
|
||||
}
|
||||
|
||||
this.configurationService.onDidChangeConfiguration((e) => {
|
||||
@@ -132,7 +132,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
|
||||
|
||||
this.registerContinueEditSessionAction();
|
||||
|
||||
this.registerApplyLatestEditSessionAction();
|
||||
this.registerResumeLatestEditSessionAction();
|
||||
this.registerStoreLatestEditSessionAction();
|
||||
|
||||
this.registerContinueInLocalFolderAction();
|
||||
@@ -171,9 +171,9 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
|
||||
}));
|
||||
}
|
||||
|
||||
private registerApplyLatestEditSessionAction(): void {
|
||||
private registerResumeLatestEditSessionAction(): void {
|
||||
const that = this;
|
||||
this._register(registerAction2(class ApplyLatestEditSessionAction extends Action2 {
|
||||
this._register(registerAction2(class ResumeLatestEditSessionAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.experimental.editSessions.actions.resumeLatest',
|
||||
@@ -186,8 +186,8 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
await that.progressService.withProgress({
|
||||
location: ProgressLocation.Notification,
|
||||
title: localize('applying edit session', 'Applying edit session...')
|
||||
}, async () => await that.applyEditSession());
|
||||
title: localize('resuming edit session', 'Resuming edit session...')
|
||||
}, async () => await that.resumeEditSession());
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -213,26 +213,24 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
|
||||
}));
|
||||
}
|
||||
|
||||
async applyEditSession(ref?: string): Promise<void> {
|
||||
if (ref !== undefined) {
|
||||
this.logService.info(`Applying edit session with ref ${ref}.`);
|
||||
}
|
||||
async resumeEditSession(ref?: string): Promise<void> {
|
||||
this.logService.info(ref !== undefined ? `Resuming edit session with ref ${ref}...` : 'Resuming edit session...');
|
||||
|
||||
const data = await this.editSessionsWorkbenchService.read(ref);
|
||||
if (!data) {
|
||||
if (ref === undefined) {
|
||||
this.notificationService.info(localize('no edit session', 'There are no edit sessions to apply.'));
|
||||
this.notificationService.info(localize('no edit session', 'There are no edit sessions to resume.'));
|
||||
} else {
|
||||
this.notificationService.warn(localize('no edit session content for ref', 'Could not apply edit session contents for ID {0}.', ref));
|
||||
this.notificationService.warn(localize('no edit session content for ref', 'Could not resume edit session contents for ID {0}.', ref));
|
||||
}
|
||||
this.logService.info(`Aborting applying edit session as no edit session content is available to be applied from ref ${ref}.`);
|
||||
this.logService.info(`Aborting resuming edit session as no edit session content is available to be applied from ref ${ref}.`);
|
||||
return;
|
||||
}
|
||||
const editSession = data.editSession;
|
||||
ref = data.ref;
|
||||
|
||||
if (editSession.version > EditSessionSchemaVersion) {
|
||||
this.notificationService.error(localize('client too old', "Please upgrade to a newer version of {0} to apply this edit session.", this.productService.nameLong));
|
||||
this.notificationService.error(localize('client too old', "Please upgrade to a newer version of {0} to resume this edit session.", this.productService.nameLong));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -266,7 +264,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
|
||||
if (hasLocalUncommittedChanges) {
|
||||
// TODO@joyceerhl Provide the option to diff files which would be overwritten by edit session contents
|
||||
const result = await this.dialogService.confirm({
|
||||
message: localize('apply edit session warning', 'Applying your edit session may overwrite your existing uncommitted changes. Do you want to proceed?'),
|
||||
message: localize('resume edit session warning', 'Resuming your edit session may overwrite your existing uncommitted changes. Do you want to proceed?'),
|
||||
type: 'warning',
|
||||
title: EDIT_SESSION_SYNC_CATEGORY.value
|
||||
});
|
||||
@@ -287,8 +285,8 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
|
||||
await this.editSessionsWorkbenchService.delete(ref);
|
||||
this.logService.info(`Deleted edit session with ref ${ref}.`);
|
||||
} catch (ex) {
|
||||
this.logService.error('Failed to apply edit session, reason: ', (ex as Error).toString());
|
||||
this.notificationService.error(localize('apply failed', "Failed to apply your edit session."));
|
||||
this.logService.error('Failed to resume edit session, reason: ', (ex as Error).toString());
|
||||
this.notificationService.error(localize('resume failed', "Failed to resume your edit session."));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,11 +14,13 @@ import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IRequestService } from 'vs/platform/request/common/request';
|
||||
import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { IAuthenticationProvider } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { createSyncHeaders, IAuthenticationProvider } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
|
||||
import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { EDIT_SESSIONS_SIGNED_IN, EditSession, EDIT_SESSION_SYNC_CATEGORY, IEditSessionsWorkbenchService, EDIT_SESSIONS_SIGNED_IN_KEY, IEditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessions';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
|
||||
type ExistingSession = IQuickPickItem & { session: AuthenticationSession & { providerId: string } };
|
||||
type AuthenticationProviderOption = IQuickPickItem & { provider: IAuthenticationProvider };
|
||||
@@ -47,6 +49,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IRequestService private readonly requestService: IRequestService,
|
||||
@IDialogService private readonly dialogService: IDialogService,
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -73,7 +76,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
|
||||
throw new Error('Please sign in to store your edit session.');
|
||||
}
|
||||
|
||||
return this.storeClient!.write('editSessions', JSON.stringify(editSession), null);
|
||||
return this.storeClient!.write('editSessions', JSON.stringify(editSession), null, createSyncHeaders(generateUuid()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,11 +92,12 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
|
||||
}
|
||||
|
||||
let content: string | undefined | null;
|
||||
const headers = createSyncHeaders(generateUuid());
|
||||
try {
|
||||
if (ref !== undefined) {
|
||||
content = await this.storeClient?.resolveContent('editSessions', ref);
|
||||
content = await this.storeClient?.resolveContent('editSessions', ref, headers);
|
||||
} else {
|
||||
const result = await this.storeClient?.read('editSessions', null);
|
||||
const result = await this.storeClient?.read('editSessions', null, headers);
|
||||
content = result?.content;
|
||||
ref = result?.ref;
|
||||
}
|
||||
@@ -160,7 +164,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
|
||||
const existing = await this.getExistingSession();
|
||||
if (existing !== undefined) {
|
||||
this.logService.trace(`Found existing authentication session with ID ${existingSessionId}`);
|
||||
this.#authenticationInfo = { sessionId: existing.session.id, token: existing.session.accessToken, providerId: existing.session.providerId };
|
||||
this.#authenticationInfo = { sessionId: existing.session.id, token: existing.session.idToken ?? existing.session.accessToken, providerId: existing.session.providerId };
|
||||
this.storeClient.setAuthToken(this.#authenticationInfo.token, this.#authenticationInfo.providerId);
|
||||
return true;
|
||||
}
|
||||
@@ -169,7 +173,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
|
||||
// Ask the user to pick a preferred account
|
||||
const session = await this.getAccountPreference();
|
||||
if (session !== undefined) {
|
||||
this.#authenticationInfo = { sessionId: session.id, token: session.accessToken, providerId: session.providerId };
|
||||
this.#authenticationInfo = { sessionId: session.id, token: session.idToken ?? session.accessToken, providerId: session.providerId };
|
||||
this.storeClient.setAuthToken(this.#authenticationInfo.token, this.#authenticationInfo.providerId);
|
||||
this.existingSessionId = session.id;
|
||||
this.logService.trace(`Saving authentication session preference for ID ${session.id}.`);
|
||||
@@ -350,8 +354,19 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
|
||||
});
|
||||
}
|
||||
|
||||
run() {
|
||||
that.clearAuthenticationPreference();
|
||||
async run() {
|
||||
const result = await that.dialogService.confirm({
|
||||
type: 'info',
|
||||
message: localize('sign out of edit sessions clear data prompt', 'Do you want to sign out of edit sessions?'),
|
||||
checkbox: { label: localize('delete all edit sessions', 'Delete all stored edit sessions from the cloud.') },
|
||||
primaryButton: localize('clear data confirm', 'Yes'),
|
||||
});
|
||||
if (result.confirmed) {
|
||||
if (result.checkboxChecked) {
|
||||
that.storeClient?.delete('editSessions', null);
|
||||
}
|
||||
that.clearAuthenticationPreference();
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -112,8 +112,8 @@ suite('Edit session sync', () => {
|
||||
// Create root folder
|
||||
await fileService.createFolder(folderUri);
|
||||
|
||||
// Apply edit session
|
||||
await editSessionsContribution.applyEditSession();
|
||||
// Resume edit session
|
||||
await editSessionsContribution.resumeEditSession();
|
||||
|
||||
// Verify edit session was correctly applied
|
||||
assert.equal((await fileService.readFile(fileUri)).value.toString(), fileContents);
|
||||
|
||||
@@ -33,7 +33,7 @@ export class WebLocaleService implements ILocaleService {
|
||||
|
||||
const restartDialog = await this.dialogService.confirm({
|
||||
type: 'info',
|
||||
message: localize('relaunchDisplayLanguageMessage', "{0} needs to reload to change the display language", this.productService.nameLong),
|
||||
message: localize('relaunchDisplayLanguageMessage', "To change the display language, {0} needs to reload", this.productService.nameLong),
|
||||
detail: localize('relaunchDisplayLanguageDetail', "Press the reload button to refresh the page and set the display language to {0}.", languagePackItem.label),
|
||||
primaryButton: localize({ key: 'reload', comment: ['&& denotes a mnemonic character'] }, "&&Reload"),
|
||||
});
|
||||
@@ -52,7 +52,7 @@ export class WebLocaleService implements ILocaleService {
|
||||
|
||||
const restartDialog = await this.dialogService.confirm({
|
||||
type: 'info',
|
||||
message: localize('clearDisplayLanguageMessage', "{0} needs to reload to change the display language", this.productService.nameLong),
|
||||
message: localize('clearDisplayLanguageMessage', "To change the display language, {0} needs to reload", this.productService.nameLong),
|
||||
detail: localize('clearDisplayLanguageDetail', "Press the reload button to refresh the page and use your browser's language."),
|
||||
primaryButton: localize({ key: 'reload', comment: ['&& denotes a mnemonic character'] }, "&&Reload"),
|
||||
});
|
||||
|
||||
@@ -130,7 +130,7 @@ export class NativeLocaleService implements ILocaleService {
|
||||
private async showRestartDialog(languageName: string) {
|
||||
const restartDialog = await this.dialogService.confirm({
|
||||
type: 'info',
|
||||
message: localize('restartDisplayLanguageMessage', "{0} needs to restart to change the display language", this.productService.nameLong),
|
||||
message: localize('restartDisplayLanguageMessage', "To change the display language, {0} needs to restart", this.productService.nameLong),
|
||||
detail: localize(
|
||||
'restartDisplayLanguageDetail',
|
||||
"Press the restart button to restart {0} and set the display language to {1}.",
|
||||
|
||||
@@ -8,11 +8,13 @@ import { registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor';
|
||||
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||
import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor';
|
||||
import { CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextConflict, GoToPreviousConflict, OpenMergeEditor, ToggleActiveConflictInput1, ToggleActiveConflictInput2, SetColumnLayout, SetMixedLayout, OpenBaseFile } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands';
|
||||
import { CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextConflict, GoToPreviousConflict, OpenBaseFile, OpenMergeEditor, SetColumnLayout, SetMixedLayout, ToggleActiveConflictInput1, ToggleActiveConflictInput2 } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands';
|
||||
import { MergeEditorCopyContentsToJSON, MergeEditorOpenContents } from 'vs/workbench/contrib/mergeEditor/browser/commands/devCommands';
|
||||
import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput';
|
||||
import { MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor';
|
||||
import { MergeEditor, MergeEditorOpenHandlerContribution } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { MergeEditorSerializer } from './mergeEditorSerializer';
|
||||
|
||||
Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane).registerEditorPane(
|
||||
@@ -47,3 +49,8 @@ registerAction2(ToggleActiveConflictInput2);
|
||||
|
||||
registerAction2(CompareInput1WithBaseCommand);
|
||||
registerAction2(CompareInput2WithBaseCommand);
|
||||
|
||||
|
||||
Registry
|
||||
.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
|
||||
.registerWorkbenchContribution(MergeEditorOpenHandlerContribution, LifecyclePhase.Restored);
|
||||
|
||||
@@ -14,15 +14,13 @@ import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IEditorIdentifier, IUntypedEditorInput } from 'vs/workbench/common/editor';
|
||||
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
|
||||
import { EditorInput, IEditorCloseHandler } from 'vs/workbench/common/editor/editorInput';
|
||||
import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput';
|
||||
import { EditorWorkerServiceDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer';
|
||||
import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
|
||||
import { ILanguageSupport, ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { assertType } from 'vs/base/common/types';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { autorun } from 'vs/base/common/observable';
|
||||
|
||||
export class MergeEditorInputData {
|
||||
constructor(
|
||||
@@ -39,7 +37,8 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
|
||||
|
||||
private _model?: MergeEditorModel;
|
||||
private _outTextModel?: ITextFileEditorModel;
|
||||
private _ignoreUnhandledConflictsForDirtyState?: true;
|
||||
|
||||
override closeHandler: MergeEditorCloseHandler | undefined;
|
||||
|
||||
constructor(
|
||||
public readonly base: URI,
|
||||
@@ -48,8 +47,6 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
|
||||
public readonly result: URI,
|
||||
@IInstantiationService private readonly _instaService: IInstantiationService,
|
||||
@ITextModelService private readonly _textModelService: ITextModelService,
|
||||
@IDialogService private readonly _dialogService: IDialogService,
|
||||
@IFilesConfigurationService private readonly _filesConfigurationService: IFilesConfigurationService,
|
||||
@IEditorService editorService: IEditorService,
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@ILabelService labelService: ILabelService,
|
||||
@@ -118,6 +115,13 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
|
||||
},
|
||||
);
|
||||
|
||||
// set/unset the closeHandler whenever unhandled conflicts are detected
|
||||
const closeHandler = this._instaService.createInstance(MergeEditorCloseHandler, this._model);
|
||||
this._store.add(autorun('closeHandler', reader => {
|
||||
const value = this._model!.hasUnhandledConflicts.read(reader);
|
||||
this.closeHandler = value ? closeHandler : undefined;
|
||||
}));
|
||||
|
||||
await this._model.onInitialized;
|
||||
|
||||
this._store.add(this._model);
|
||||
@@ -125,11 +129,8 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
|
||||
this._store.add(input1);
|
||||
this._store.add(input2);
|
||||
this._store.add(result);
|
||||
|
||||
this._store.add(Event.fromObservable(this._model.hasUnhandledConflicts)(() => this._onDidChangeDirty.fire(undefined)));
|
||||
}
|
||||
|
||||
this._ignoreUnhandledConflictsForDirtyState = undefined;
|
||||
return this._model;
|
||||
}
|
||||
|
||||
@@ -146,66 +147,57 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
|
||||
// ---- FileEditorInput
|
||||
|
||||
override isDirty(): boolean {
|
||||
const textModelDirty = Boolean(this._outTextModel?.isDirty());
|
||||
if (textModelDirty) {
|
||||
// text model dirty -> 3wm is dirty
|
||||
return true;
|
||||
}
|
||||
if (!this._ignoreUnhandledConflictsForDirtyState) {
|
||||
// unhandled conflicts -> 3wm is dirty UNLESS we explicitly set this input
|
||||
// to ignore unhandled conflicts for the dirty-state. This happens only
|
||||
// after confirming to ignore unhandled changes
|
||||
return Boolean(this._model && this._model.hasUnhandledConflicts.get());
|
||||
}
|
||||
return false;
|
||||
return Boolean(this._outTextModel?.isDirty());
|
||||
}
|
||||
|
||||
override async confirm(editors?: ReadonlyArray<IEditorIdentifier>): Promise<ConfirmResult> {
|
||||
setLanguageId(languageId: string, _setExplicitly?: boolean): void {
|
||||
this._model?.setLanguageId(languageId);
|
||||
}
|
||||
|
||||
const inputs: MergeEditorInput[] = [this];
|
||||
if (editors) {
|
||||
for (const { editor } of editors) {
|
||||
if (editor instanceof MergeEditorInput) {
|
||||
inputs.push(editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
// implement get/set languageId
|
||||
// implement get/set encoding
|
||||
}
|
||||
|
||||
const inputsWithUnhandledConflicts = inputs
|
||||
class MergeEditorCloseHandler implements IEditorCloseHandler {
|
||||
|
||||
private _ignoreUnhandledConflicts: boolean = false;
|
||||
|
||||
constructor(
|
||||
private readonly _model: MergeEditorModel,
|
||||
@IDialogService private readonly _dialogService: IDialogService,
|
||||
) { }
|
||||
|
||||
showConfirm(): boolean {
|
||||
// unhandled conflicts -> 3wm asks to confirm UNLESS we explicitly set this input
|
||||
// to ignore unhandled conflicts. This happens only after confirming to ignore unhandled changes
|
||||
return !this._ignoreUnhandledConflicts && this._model.hasUnhandledConflicts.get();
|
||||
}
|
||||
|
||||
async confirm(editors?: readonly IEditorIdentifier[] | undefined): Promise<ConfirmResult> {
|
||||
|
||||
const handler: MergeEditorCloseHandler[] = [this];
|
||||
editors?.forEach(candidate => candidate.editor.closeHandler instanceof MergeEditorCloseHandler && handler.push(candidate.editor.closeHandler));
|
||||
|
||||
const inputsWithUnhandledConflicts = handler
|
||||
.filter(input => input._model && input._model.hasUnhandledConflicts.get());
|
||||
|
||||
if (inputsWithUnhandledConflicts.length === 0) {
|
||||
// shouldn't happen
|
||||
return ConfirmResult.SAVE;
|
||||
}
|
||||
|
||||
const actions: string[] = [];
|
||||
const actions: string[] = [
|
||||
localize('unhandledConflicts.ignore', "Continue with Conflicts"),
|
||||
localize('unhandledConflicts.discard', "Discard Merge Changes"),
|
||||
localize('unhandledConflicts.cancel', "Cancel"),
|
||||
];
|
||||
const options = {
|
||||
cancelId: 0,
|
||||
detail: inputs.length > 1
|
||||
? localize('unhandledConflicts.detailN', 'Merge conflicts in {0} editors will remain unhandled.', inputs.length)
|
||||
cancelId: 2,
|
||||
detail: handler.length > 1
|
||||
? localize('unhandledConflicts.detailN', 'Merge conflicts in {0} editors will remain unhandled.', handler.length)
|
||||
: localize('unhandledConflicts.detail1', 'Merge conflicts in this editor will remain unhandled.')
|
||||
};
|
||||
|
||||
const isAnyAutoSave = this._filesConfigurationService.getAutoSaveMode() !== AutoSaveMode.OFF;
|
||||
if (!isAnyAutoSave) {
|
||||
// manual-save: FYI and discard
|
||||
actions.push(
|
||||
localize('unhandledConflicts.manualSaveIgnore', "Save and Continue with Conflicts"), // 0
|
||||
localize('unhandledConflicts.discard', "Discard Merge Changes"), // 1
|
||||
localize('unhandledConflicts.manualSaveNoSave', "Don't Save"), // 2
|
||||
);
|
||||
|
||||
} else {
|
||||
// auto-save: only FYI
|
||||
actions.push(
|
||||
localize('unhandledConflicts.ignore', "Continue with Conflicts"), // 0
|
||||
localize('unhandledConflicts.discard', "Discard Merge Changes"), // 1
|
||||
);
|
||||
}
|
||||
|
||||
actions.push(localize('unhandledConflicts.cancel', "Cancel"));
|
||||
options.cancelId = actions.length - 1;
|
||||
|
||||
const { choice } = await this._dialogService.show(
|
||||
Severity.Info,
|
||||
localize('unhandledConflicts.msg', 'Do you want to continue with unhandled conflicts?'), // 1
|
||||
@@ -220,8 +212,8 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
|
||||
|
||||
// save or revert: in both cases we tell the inputs to ignore unhandled conflicts
|
||||
// for the dirty state computation.
|
||||
for (const input of inputs) {
|
||||
input._ignoreUnhandledConflictsForDirtyState = true;
|
||||
for (const input of handler) {
|
||||
input._ignoreUnhandledConflicts = true;
|
||||
}
|
||||
|
||||
if (choice === 0) {
|
||||
@@ -230,7 +222,7 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
|
||||
|
||||
} else if (choice === 1) {
|
||||
// discard: undo all changes and save original (pre-merge) state
|
||||
for (const input of inputs) {
|
||||
for (const input of handler) {
|
||||
input._discardMergeChanges();
|
||||
}
|
||||
return ConfirmResult.SAVE;
|
||||
@@ -242,8 +234,6 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
|
||||
}
|
||||
|
||||
private _discardMergeChanges(): void {
|
||||
assertType(this._model !== undefined);
|
||||
|
||||
const chunks: string[] = [];
|
||||
while (true) {
|
||||
const chunk = this._model.resultSnapshot.read();
|
||||
@@ -254,11 +244,4 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
|
||||
}
|
||||
this._model.result.setValue(chunks.join());
|
||||
}
|
||||
|
||||
setLanguageId(languageId: string, _setExplicitly?: boolean): void {
|
||||
this._model?.setLanguageId(languageId);
|
||||
}
|
||||
|
||||
// implement get/set languageId
|
||||
// implement get/set encoding
|
||||
}
|
||||
|
||||
@@ -11,12 +11,13 @@ import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { BugIndicatingError } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { autorunWithStore, IObservable } from 'vs/base/common/observable';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import 'vs/css!./media/mergeEditor';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||
import { IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon';
|
||||
@@ -26,7 +27,7 @@ import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/men
|
||||
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { IEditorOptions, ITextEditorOptions, ITextResourceEditorInput } from 'vs/platform/editor/common/editor';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
@@ -35,7 +36,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor';
|
||||
import { AbstractTextEditor } from 'vs/workbench/browser/parts/editor/textEditor';
|
||||
import { EditorInputWithOptions, EditorResourceAccessor, IEditorOpenContext } from 'vs/workbench/common/editor';
|
||||
import { IEditorOpenContext } from 'vs/workbench/common/editor';
|
||||
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
|
||||
import { applyTextEditorOptions } from 'vs/workbench/common/editor/editorOptions';
|
||||
import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput';
|
||||
@@ -46,7 +47,6 @@ import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/v
|
||||
import { ctxBaseResourceScheme, ctxIsMergeEditor, ctxMergeEditorLayout, MergeEditorLayoutTypes } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor';
|
||||
import { settingsSashBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry';
|
||||
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import './colors';
|
||||
import { InputCodeEditorView } from './editors/inputCodeEditorView';
|
||||
@@ -86,9 +86,9 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
|
||||
private readonly _sessionDisposables = new DisposableStore();
|
||||
|
||||
private _grid!: Grid<IView>;
|
||||
private readonly input1View = this._register(this.instantiation.createInstance(InputCodeEditorView, 1));
|
||||
private readonly input2View = this._register(this.instantiation.createInstance(InputCodeEditorView, 2));
|
||||
private readonly inputResultView = this._register(this.instantiation.createInstance(ResultCodeEditorView));
|
||||
private readonly input1View = this._register(this.instantiationService.createInstance(InputCodeEditorView, 1));
|
||||
private readonly input2View = this._register(this.instantiationService.createInstance(InputCodeEditorView, 2));
|
||||
private readonly inputResultView = this._register(this.instantiationService.createInstance(ResultCodeEditorView));
|
||||
|
||||
private readonly _layoutMode: MergeEditorLayout;
|
||||
private readonly _ctxIsMergeEditor: IContextKey<boolean>;
|
||||
@@ -103,7 +103,7 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
|
||||
}
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly instantiation: IInstantiationService,
|
||||
@IInstantiationService instantiation: IInstantiationService,
|
||||
@ILabelService private readonly _labelService: ILabelService,
|
||||
@IMenuService private readonly _menuService: IMenuService,
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@@ -115,7 +115,6 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
|
||||
@IEditorService editorService: IEditorService,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IEditorResolverService private readonly _editorResolverService: IEditorResolverService,
|
||||
) {
|
||||
super(MergeEditor.ID, telemetryService, instantiation, storageService, textResourceConfigurationService, themeService, editorService, editorGroupService, fileService);
|
||||
|
||||
@@ -186,7 +185,7 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
|
||||
createAndFillInActionBarActions(toolbarMenu, { renderShortTitle: true, shouldForwardArgs: true }, actions);
|
||||
if (actions.length > 0) {
|
||||
const [first] = actions;
|
||||
const acceptBtn = this.instantiation.createInstance(FloatingClickWidget, this.inputResultView.editor, first.label, first.id);
|
||||
const acceptBtn = this.instantiationService.createInstance(FloatingClickWidget, this.inputResultView.editor, first.label, first.id);
|
||||
toolbarMenuDisposables.add(acceptBtn.onClick(() => first.run(this.inputResultView.editor.getModel()?.uri)));
|
||||
toolbarMenuDisposables.add(acceptBtn);
|
||||
acceptBtn.render();
|
||||
@@ -296,7 +295,6 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
|
||||
await super.setInput(input, options, context, token);
|
||||
|
||||
this._sessionDisposables.clear();
|
||||
this._toggleEditorOverwrite(true);
|
||||
|
||||
const model = await input.resolve();
|
||||
this._model = model;
|
||||
@@ -309,16 +307,17 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
|
||||
this._ctxBaseResourceScheme.set(model.base.uri.scheme);
|
||||
|
||||
const viewState = this.loadEditorViewState(input, context);
|
||||
this._applyViewState(viewState);
|
||||
|
||||
this._sessionDisposables.add(thenIfNotDisposed(model.onInitialized, () => {
|
||||
const firstConflict = model.modifiedBaseRanges.get().find(r => r.isConflicting);
|
||||
if (!firstConflict) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.input1View.editor.revealLineInCenter(firstConflict.input1Range.startLineNumber);
|
||||
}));
|
||||
if (viewState) {
|
||||
this._applyViewState(viewState);
|
||||
} else {
|
||||
this._sessionDisposables.add(thenIfNotDisposed(model.onInitialized, () => {
|
||||
const firstConflict = model.modifiedBaseRanges.get().find(r => r.isConflicting);
|
||||
if (!firstConflict) {
|
||||
return;
|
||||
}
|
||||
this.input1View.editor.revealLineInCenter(firstConflict.input1Range.startLineNumber);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
this._sessionDisposables.add(autorunWithStore((reader, store) => {
|
||||
@@ -373,7 +372,6 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
|
||||
super.clearInput();
|
||||
|
||||
this._sessionDisposables.clear();
|
||||
this._toggleEditorOverwrite(false);
|
||||
|
||||
for (const { editor } of [this.input1View, this.input2View, this.inputResultView]) {
|
||||
editor.setModel(null);
|
||||
@@ -405,39 +403,6 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
|
||||
}
|
||||
|
||||
this._ctxIsMergeEditor.set(visible);
|
||||
this._toggleEditorOverwrite(visible);
|
||||
}
|
||||
|
||||
private readonly _editorOverrideHandle = this._store.add(new MutableDisposable());
|
||||
|
||||
private _toggleEditorOverwrite(haveIt: boolean) {
|
||||
if (!haveIt) {
|
||||
this._editorOverrideHandle.clear();
|
||||
return;
|
||||
}
|
||||
// this is RATHER UGLY. I dynamically register an editor for THIS (editor,input) so that
|
||||
// navigating within the merge editor works, e.g navigating from the outline or breakcrumps
|
||||
// or revealing a definition, reference etc
|
||||
// TODO@jrieken @bpasero @lramos15
|
||||
const input = this.input;
|
||||
if (input instanceof MergeEditorInput) {
|
||||
this._editorOverrideHandle.value = this._editorResolverService.registerEditor(
|
||||
`${input.result.scheme}:${input.result.fsPath}`,
|
||||
{
|
||||
id: `${this.getId()}/fake`,
|
||||
label: this.input?.getName()!,
|
||||
priority: RegisteredEditorPriority.exclusive
|
||||
},
|
||||
{},
|
||||
(candidate): EditorInputWithOptions => {
|
||||
const resource = EditorResourceAccessor.getCanonicalUri(candidate);
|
||||
if (!isEqual(resource, this.model?.result.uri)) {
|
||||
throw new Error(`Expected to be called WITH ${input.result.toString()}`);
|
||||
}
|
||||
return { editor: input };
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- interact with "outside world" via`getControl`, `scopedContextKeyService`: we only expose the result-editor keep the others internal
|
||||
@@ -506,6 +471,37 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
|
||||
}
|
||||
}
|
||||
|
||||
export class MergeEditorOpenHandlerContribution extends Disposable {
|
||||
|
||||
constructor(
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@ICodeEditorService codeEditorService: ICodeEditorService,
|
||||
) {
|
||||
super();
|
||||
this._store.add(codeEditorService.registerCodeEditorOpenHandler(this.openCodeEditorFromMergeEditor.bind(this)));
|
||||
}
|
||||
|
||||
private async openCodeEditorFromMergeEditor(input: ITextResourceEditorInput, _source: ICodeEditor | null, sideBySide?: boolean | undefined): Promise<ICodeEditor | null> {
|
||||
const activePane = this._editorService.activeEditorPane;
|
||||
if (!sideBySide
|
||||
&& input.options
|
||||
&& activePane instanceof MergeEditor
|
||||
&& activePane.getControl()
|
||||
&& activePane.input instanceof MergeEditorInput
|
||||
&& isEqual(input.resource, activePane.input.result)
|
||||
) {
|
||||
// Special: stay inside the merge editor when it is active and when the input
|
||||
// targets the result editor of the merge editor.
|
||||
const targetEditor = <ICodeEditor>activePane.getControl()!;
|
||||
applyTextEditorOptions(input.options, targetEditor, ScrollType.Smooth);
|
||||
return targetEditor;
|
||||
}
|
||||
|
||||
// cannot handle this
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
type IMergeEditorViewState = ICodeEditorViewState & {
|
||||
readonly input1State?: ICodeEditorViewState;
|
||||
readonly input2State?: ICodeEditorViewState;
|
||||
|
||||
@@ -41,6 +41,8 @@ import { Disposable } from 'vs/base/common/lifecycle';
|
||||
const OpenInEditorCommandId = 'search.action.openInEditor';
|
||||
const OpenNewEditorToSideCommandId = 'search.action.openNewEditorToSide';
|
||||
const FocusQueryEditorWidgetCommandId = 'search.action.focusQueryEditorWidget';
|
||||
const FocusQueryEditorFilesToIncludeCommandId = 'search.action.focusFilesToInclude';
|
||||
const FocusQueryEditorFilesToExcludeCommandId = 'search.action.focusFilesToExclude';
|
||||
|
||||
const ToggleSearchEditorCaseSensitiveCommandId = 'toggleSearchEditorCaseSensitive';
|
||||
const ToggleSearchEditorWholeWordCommandId = 'toggleSearchEditorWholeWord';
|
||||
@@ -374,6 +376,44 @@ registerAction2(class extends Action2 {
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: FocusQueryEditorFilesToIncludeCommandId,
|
||||
title: { value: localize('search.action.focusFilesToInclude', "Focus Search Editor Files to Include"), original: 'Focus Search Editor Files to Include' },
|
||||
category,
|
||||
f1: true,
|
||||
precondition: SearchEditorConstants.InSearchEditor,
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor) {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const input = editorService.activeEditor;
|
||||
if (input instanceof SearchEditorInput) {
|
||||
(editorService.activeEditorPane as SearchEditor).focusFilesToIncludeInput();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: FocusQueryEditorFilesToExcludeCommandId,
|
||||
title: { value: localize('search.action.focusFilesToExclude', "Focus Search Editor Files to Exclude"), original: 'Focus Search Editor Files to Exclude' },
|
||||
category,
|
||||
f1: true,
|
||||
precondition: SearchEditorConstants.InSearchEditor,
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor) {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const input = editorService.activeEditor;
|
||||
if (input instanceof SearchEditorInput) {
|
||||
(editorService.activeEditorPane as SearchEditor).focusFilesToExcludeInput();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
|
||||
@@ -275,6 +275,20 @@ export class SearchEditor extends AbstractTextCodeEditor<SearchEditorViewState>
|
||||
this.queryEditorWidget.searchInput.focus();
|
||||
}
|
||||
|
||||
focusFilesToIncludeInput() {
|
||||
if (!this.showingIncludesExcludes) {
|
||||
this.toggleIncludesExcludes(true);
|
||||
}
|
||||
this.inputPatternIncludes.focus();
|
||||
}
|
||||
|
||||
focusFilesToExcludeInput() {
|
||||
if (!this.showingIncludesExcludes) {
|
||||
this.toggleIncludesExcludes(true);
|
||||
}
|
||||
this.inputPatternExcludes.focus();
|
||||
}
|
||||
|
||||
focusNextInput() {
|
||||
if (this.queryEditorWidget.searchInputHasFocus()) {
|
||||
if (this.showingIncludesExcludes) {
|
||||
|
||||
@@ -1157,7 +1157,10 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
|
||||
}, 'Executing task: {0}', commandLine), { excludeLeadingNewLine: true }) + this.taskShellIntegrationOutputSequence;
|
||||
}
|
||||
} else {
|
||||
shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence;
|
||||
shellLaunchConfig.initialText = {
|
||||
text: this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence,
|
||||
trailingNewLine: false
|
||||
};
|
||||
}
|
||||
} else {
|
||||
const commandExecutable = (task.command.runtime !== RuntimeType.CustomExecution) ? CommandString.value(command) : undefined;
|
||||
@@ -1197,7 +1200,10 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
|
||||
}, 'Executing task: {0}', `${shellLaunchConfig.executable} ${getArgsToEcho(shellLaunchConfig.args)}`), { excludeLeadingNewLine: true }) + this.taskShellIntegrationOutputSequence;
|
||||
}
|
||||
} else {
|
||||
shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence;
|
||||
shellLaunchConfig.initialText = {
|
||||
text: this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence,
|
||||
trailingNewLine: false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,23 +110,22 @@ __vsc_precmd() {
|
||||
}
|
||||
|
||||
__vsc_preexec() {
|
||||
if [ "$__vsc_in_command_execution" = "0" ]; then
|
||||
__vsc_initialized=1
|
||||
__vsc_in_command_execution="1"
|
||||
if [[ ! "$BASH_COMMAND" =~ ^__vsc_prompt* ]]; then
|
||||
__vsc_current_command=$BASH_COMMAND
|
||||
else
|
||||
__vsc_current_command=""
|
||||
fi
|
||||
__vsc_command_output_start
|
||||
__vsc_initialized=1
|
||||
if [[ ! "$BASH_COMMAND" =~ ^__vsc_prompt* ]]; then
|
||||
__vsc_current_command=$BASH_COMMAND
|
||||
else
|
||||
__vsc_current_command=""
|
||||
fi
|
||||
__vsc_command_output_start
|
||||
}
|
||||
|
||||
# Debug trapping/preexec inspired by starship (ISC)
|
||||
if [[ -n "${bash_preexec_imported:-}" ]]; then
|
||||
__vsc_preexec_only() {
|
||||
__vsc_status="$?"
|
||||
__vsc_preexec
|
||||
if [ "$__vsc_in_command_execution" = "0" ]; then
|
||||
__vsc_in_command_execution="1"
|
||||
__vsc_preexec
|
||||
fi
|
||||
}
|
||||
precmd_functions+=(__vsc_prompt_cmd)
|
||||
preexec_functions+=(__vsc_preexec_only)
|
||||
@@ -134,15 +133,19 @@ else
|
||||
__vsc_dbg_trap="$(trap -p DEBUG | cut -d' ' -f3 | tr -d \')"
|
||||
if [[ -z "$__vsc_dbg_trap" ]]; then
|
||||
__vsc_preexec_only() {
|
||||
__vsc_status="$?"
|
||||
__vsc_preexec
|
||||
if [ "$__vsc_in_command_execution" = "0" ]; then
|
||||
__vsc_in_command_execution="1"
|
||||
__vsc_preexec
|
||||
fi
|
||||
}
|
||||
trap '__vsc_preexec_only "$_"' DEBUG
|
||||
elif [[ "$__vsc_dbg_trap" != '__vsc_preexec "$_"' && "$__vsc_dbg_trap" != '__vsc_preexec_all "$_"' ]]; then
|
||||
__vsc_preexec_all() {
|
||||
__vsc_status="$?"
|
||||
builtin eval ${__vsc_dbg_trap}
|
||||
__vsc_preexec
|
||||
if [ "$__vsc_in_command_execution" = "0" ]; then
|
||||
__vsc_in_command_execution="1"
|
||||
builtin eval ${__vsc_dbg_trap}
|
||||
__vsc_preexec
|
||||
fi
|
||||
}
|
||||
trap '__vsc_preexec_all "$_"' DEBUG
|
||||
fi
|
||||
@@ -151,6 +154,7 @@ fi
|
||||
__vsc_update_prompt
|
||||
|
||||
__vsc_prompt_cmd_original() {
|
||||
__vsc_status="$?"
|
||||
if [[ ${IFS+set} ]]; then
|
||||
__vsc_original_ifs="$IFS"
|
||||
fi
|
||||
|
||||
@@ -9,7 +9,7 @@ import { dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { EditorInputCapabilities, IEditorIdentifier, IUntypedEditorInput } from 'vs/workbench/common/editor';
|
||||
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
|
||||
import { EditorInput, IEditorCloseHandler } from 'vs/workbench/common/editor/editorInput';
|
||||
import { ITerminalInstance, ITerminalInstanceService, terminalEditorId } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { getColorClass, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -23,21 +23,22 @@ import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/termin
|
||||
import { ConfirmResult, IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export class TerminalEditorInput extends EditorInput {
|
||||
|
||||
protected readonly _onDidRequestAttach = this._register(new Emitter<ITerminalInstance>());
|
||||
readonly onDidRequestAttach = this._onDidRequestAttach.event;
|
||||
export class TerminalEditorInput extends EditorInput implements IEditorCloseHandler {
|
||||
|
||||
static readonly ID = 'workbench.editors.terminal';
|
||||
|
||||
override readonly closeHandler = this;
|
||||
|
||||
private _isDetached = false;
|
||||
private _isShuttingDown = false;
|
||||
private _isReverted = false;
|
||||
private _copyLaunchConfig?: IShellLaunchConfig;
|
||||
private _terminalEditorFocusContextKey: IContextKey<boolean>;
|
||||
|
||||
private _group: IEditorGroup | undefined;
|
||||
|
||||
protected readonly _onDidRequestAttach = this._register(new Emitter<ITerminalInstance>());
|
||||
readonly onDidRequestAttach = this._onDidRequestAttach.event;
|
||||
|
||||
setGroup(group: IEditorGroup | undefined) {
|
||||
this._group = group;
|
||||
}
|
||||
@@ -64,13 +65,6 @@ export class TerminalEditorInput extends EditorInput {
|
||||
}
|
||||
this._terminalInstance = instance;
|
||||
this._setupInstanceListeners();
|
||||
|
||||
// Refresh dirty state when the confirm on kill setting is changed
|
||||
this._configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(TerminalSettingId.ConfirmOnKill)) {
|
||||
this._onDidChangeDirty.fire();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
override copy(): EditorInput {
|
||||
@@ -95,7 +89,7 @@ export class TerminalEditorInput extends EditorInput {
|
||||
return this._isDetached ? undefined : this._terminalInstance;
|
||||
}
|
||||
|
||||
override isDirty(): boolean {
|
||||
showConfirm(): boolean {
|
||||
if (this._isReverted) {
|
||||
return false;
|
||||
}
|
||||
@@ -106,7 +100,7 @@ export class TerminalEditorInput extends EditorInput {
|
||||
return false;
|
||||
}
|
||||
|
||||
override async confirm(terminals?: ReadonlyArray<IEditorIdentifier>): Promise<ConfirmResult> {
|
||||
async confirm(terminals?: ReadonlyArray<IEditorIdentifier>): Promise<ConfirmResult> {
|
||||
const { choice } = await this._dialogService.show(
|
||||
Severity.Warning,
|
||||
localize('confirmDirtyTerminal.message', "Do you want to terminate running processes?"),
|
||||
@@ -148,12 +142,6 @@ export class TerminalEditorInput extends EditorInput {
|
||||
|
||||
this._terminalEditorFocusContextKey = TerminalContextKeys.editorFocus.bindTo(_contextKeyService);
|
||||
|
||||
// Refresh dirty state when the confirm on kill setting is changed
|
||||
this._configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(TerminalSettingId.ConfirmOnKill)) {
|
||||
this._onDidChangeDirty.fire();
|
||||
}
|
||||
});
|
||||
if (_terminalInstance) {
|
||||
this._setupInstanceListeners();
|
||||
}
|
||||
@@ -180,7 +168,6 @@ export class TerminalEditorInput extends EditorInput {
|
||||
instance.onIconChanged(() => this._onDidChangeLabel.fire()),
|
||||
instance.onDidFocus(() => this._terminalEditorFocusContextKey.set(true)),
|
||||
instance.onDidBlur(() => this._terminalEditorFocusContextKey.reset()),
|
||||
instance.onDidChangeHasChildProcesses(() => this._onDidChangeDirty.fire()),
|
||||
instance.statusList.onDidChangePrimaryStatus(() => this._onDidChangeLabel.fire())
|
||||
];
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
|
||||
import { TerminalCapabilityStoreMultiplexer } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore';
|
||||
import { IProcessDataEvent, IProcessPropertyMap, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, PosixShellType, ProcessPropertyType, TerminalExitReason, TerminalIcon, TerminalLocation, TerminalSettingId, TerminalShellType, TitleEventSource, WindowsShellType } from 'vs/platform/terminal/common/terminal';
|
||||
import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnvironment';
|
||||
import { escapeNonWindowsPath, collapseTildePath } from 'vs/platform/terminal/common/terminalEnvironment';
|
||||
import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
@@ -357,6 +357,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
private readonly _terminalHasFixedWidth: IContextKey<boolean>,
|
||||
private readonly _terminalShellTypeContextKey: IContextKey<string>,
|
||||
private readonly _terminalAltBufferActiveContextKey: IContextKey<boolean>,
|
||||
private readonly _terminalInRunCommandPicker: IContextKey<boolean>,
|
||||
private readonly _configHelper: TerminalConfigHelper,
|
||||
private _shellLaunchConfig: IShellLaunchConfig,
|
||||
resource: URI | undefined,
|
||||
@@ -700,7 +701,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
// Write initial text, deferring onLineFeed listener when applicable to avoid firing
|
||||
// onLineData events containing initialText
|
||||
if (this._shellLaunchConfig.initialText) {
|
||||
this.xterm.raw.writeln(this._shellLaunchConfig.initialText, () => {
|
||||
this._writeInitialText(this.xterm, () => {
|
||||
lineDataEventAddon.onLineData(e => this._onLineData.fire(e));
|
||||
});
|
||||
} else {
|
||||
@@ -823,7 +824,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
this._linkManager.openRecentLink(type);
|
||||
}
|
||||
|
||||
async runRecent(type: 'command' | 'cwd'): Promise<void> {
|
||||
async runRecent(type: 'command' | 'cwd', filterMode?: 'fuzzy' | 'contiguous', value?: string): Promise<void> {
|
||||
if (!this.xterm) {
|
||||
return;
|
||||
}
|
||||
@@ -853,7 +854,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
if (label.length === 0 || commandMap.has(label)) {
|
||||
continue;
|
||||
}
|
||||
let description = `${entry.cwd}`;
|
||||
let description = collapseTildePath(entry.cwd, this._userHome, this._processManager?.os === OperatingSystem.Windows ? '\\' : '/');
|
||||
if (entry.exitCode) {
|
||||
// Since you cannot get the last command's exit code on pwsh, just whether it failed
|
||||
// or not, -1 is treated specially as simply failed
|
||||
@@ -970,44 +971,67 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
}
|
||||
const outputProvider = this._instantiationService.createInstance(TerminalOutputProvider);
|
||||
const quickPick = this._quickInputService.createQuickPick();
|
||||
quickPick.items = items;
|
||||
const originalItems = items;
|
||||
quickPick.items = [...originalItems];
|
||||
quickPick.sortByLabel = false;
|
||||
quickPick.placeholder = placeholder;
|
||||
return new Promise<void>(r => {
|
||||
quickPick.onDidTriggerItemButton(async e => {
|
||||
if (e.button === removeFromCommandHistoryButton) {
|
||||
if (type === 'command') {
|
||||
this._instantiationService.invokeFunction(getCommandHistory)?.remove(e.item.label);
|
||||
} else {
|
||||
this._instantiationService.invokeFunction(getDirectoryHistory)?.remove(e.item.label);
|
||||
}
|
||||
quickPick.customButton = true;
|
||||
quickPick.matchOnLabelMode = filterMode || 'contiguous';
|
||||
if (filterMode === 'fuzzy') {
|
||||
quickPick.customLabel = nls.localize('terminal.contiguousSearch', 'Use Contiguous Search');
|
||||
quickPick.onDidCustom(() => {
|
||||
quickPick.hide();
|
||||
this.runRecent(type, 'contiguous', quickPick.value);
|
||||
});
|
||||
} else {
|
||||
quickPick.customLabel = nls.localize('terminal.fuzzySearch', 'Use Fuzzy Search');
|
||||
quickPick.onDidCustom(() => {
|
||||
quickPick.hide();
|
||||
this.runRecent(type, 'fuzzy', quickPick.value);
|
||||
});
|
||||
}
|
||||
quickPick.onDidTriggerItemButton(async e => {
|
||||
if (e.button === removeFromCommandHistoryButton) {
|
||||
if (type === 'command') {
|
||||
this._instantiationService.invokeFunction(getCommandHistory)?.remove(e.item.label);
|
||||
} else {
|
||||
const selectedCommand = (e.item as Item).command;
|
||||
const output = selectedCommand?.getOutput();
|
||||
if (output && selectedCommand?.command) {
|
||||
const textContent = await outputProvider.provideTextContent(URI.from(
|
||||
{
|
||||
scheme: TerminalOutputProvider.scheme,
|
||||
path: `${selectedCommand.command}... ${fromNow(selectedCommand.timestamp, true)}`,
|
||||
fragment: output,
|
||||
query: `terminal-output-${selectedCommand.timestamp}-${this.instanceId}`
|
||||
}));
|
||||
if (textContent) {
|
||||
await this._editorService.openEditor({
|
||||
resource: textContent.uri
|
||||
});
|
||||
}
|
||||
this._instantiationService.invokeFunction(getDirectoryHistory)?.remove(e.item.label);
|
||||
}
|
||||
} else {
|
||||
const selectedCommand = (e.item as Item).command;
|
||||
const output = selectedCommand?.getOutput();
|
||||
if (output && selectedCommand?.command) {
|
||||
const textContent = await outputProvider.provideTextContent(URI.from(
|
||||
{
|
||||
scheme: TerminalOutputProvider.scheme,
|
||||
path: `${selectedCommand.command}... ${fromNow(selectedCommand.timestamp, true)}`,
|
||||
fragment: output,
|
||||
query: `terminal-output-${selectedCommand.timestamp}-${this.instanceId}`
|
||||
}));
|
||||
if (textContent) {
|
||||
await this._editorService.openEditor({
|
||||
resource: textContent.uri
|
||||
});
|
||||
}
|
||||
}
|
||||
quickPick.hide();
|
||||
});
|
||||
quickPick.onDidAccept(() => {
|
||||
const result = quickPick.activeItems[0];
|
||||
this.sendText(type === 'cwd' ? `cd ${result.label}` : result.label, !quickPick.keyMods.alt);
|
||||
quickPick.hide();
|
||||
});
|
||||
}
|
||||
quickPick.hide();
|
||||
});
|
||||
quickPick.onDidAccept(() => {
|
||||
const result = quickPick.activeItems[0];
|
||||
this.sendText(type === 'cwd' ? `cd ${result.label}` : result.label, !quickPick.keyMods.alt);
|
||||
quickPick.hide();
|
||||
});
|
||||
if (value) {
|
||||
quickPick.value = value;
|
||||
}
|
||||
return new Promise<void>(r => {
|
||||
quickPick.show();
|
||||
quickPick.onDidHide(() => r());
|
||||
this._terminalInRunCommandPicker.set(true);
|
||||
quickPick.onDidHide(() => {
|
||||
this._terminalInRunCommandPicker.set(false);
|
||||
r();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1441,9 +1465,15 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
}
|
||||
|
||||
async sendText(text: string, addNewLine: boolean): Promise<void> {
|
||||
// Apply bracketed paste sequences if the terminal has the mode enabled, this will prevent
|
||||
// the text from triggering keybindings https://github.com/microsoft/vscode/issues/153592
|
||||
if (this.xterm?.raw.modes.bracketedPasteMode) {
|
||||
text = `\x1b[200~${text}\x1b[201~`;
|
||||
}
|
||||
|
||||
// Normalize line endings to 'enter' press.
|
||||
text = text.replace(/\r?\n/g, '\r');
|
||||
if (addNewLine && text.substr(text.length - 1) !== '\r') {
|
||||
if (addNewLine && text[text.length - 1] !== '\r') {
|
||||
text += '\r';
|
||||
}
|
||||
|
||||
@@ -1684,7 +1714,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
|
||||
const parsedExitResult = parseExitResult(exitCodeOrError, this.shellLaunchConfig, this._processManager.processState, this._initialCwd);
|
||||
|
||||
if (this._usedShellIntegrationInjection && (this._processManager.processState === ProcessState.KilledDuringLaunch || this._processManager.processState === ProcessState.KilledByProcess)) {
|
||||
if (this._usedShellIntegrationInjection && this._processManager.processState === ProcessState.KilledDuringLaunch && parsedExitResult?.code !== 0) {
|
||||
this._relaunchWithShellIntegrationDisabled(parsedExitResult?.message);
|
||||
this._onExit.fire(exitCodeOrError);
|
||||
return;
|
||||
@@ -1807,29 +1837,49 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
}
|
||||
}
|
||||
|
||||
private _writeInitialText(xterm: XtermTerminal, callback?: () => void): void {
|
||||
if (!this._shellLaunchConfig.initialText) {
|
||||
callback?.();
|
||||
return;
|
||||
}
|
||||
const text = typeof this._shellLaunchConfig.initialText === 'string'
|
||||
? this._shellLaunchConfig.initialText
|
||||
: this._shellLaunchConfig.initialText?.text;
|
||||
if (typeof this._shellLaunchConfig.initialText === 'string') {
|
||||
xterm.raw.writeln(text, callback);
|
||||
} else {
|
||||
if (this._shellLaunchConfig.initialText.trailingNewLine) {
|
||||
xterm.raw.writeln(text, callback);
|
||||
} else {
|
||||
xterm.raw.write(text, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async reuseTerminal(shell: IShellLaunchConfig, reset: boolean = false): Promise<void> {
|
||||
// Unsubscribe any key listener we may have.
|
||||
this._pressAnyKeyToCloseListener?.dispose();
|
||||
this._pressAnyKeyToCloseListener = undefined;
|
||||
|
||||
if (this.xterm) {
|
||||
const xterm = this.xterm;
|
||||
if (xterm) {
|
||||
if (!reset) {
|
||||
// Ensure new processes' output starts at start of new line
|
||||
await new Promise<void>(r => this.xterm!.raw.write('\n\x1b[G', r));
|
||||
await new Promise<void>(r => xterm.raw.write('\n\x1b[G', r));
|
||||
}
|
||||
|
||||
// Print initialText if specified
|
||||
if (shell.initialText) {
|
||||
await new Promise<void>(r => this.xterm!.raw.writeln(shell.initialText!, r));
|
||||
await new Promise<void>(r => this._writeInitialText(xterm, r));
|
||||
}
|
||||
|
||||
// Clean up waitOnExit state
|
||||
if (this._isExiting && this._shellLaunchConfig.waitOnExit) {
|
||||
this.xterm.raw.options.disableStdin = false;
|
||||
xterm.raw.options.disableStdin = false;
|
||||
this._isExiting = false;
|
||||
}
|
||||
if (reset) {
|
||||
this.xterm.clearDecorations();
|
||||
xterm.clearDecorations();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst
|
||||
private _terminalHasFixedWidth: IContextKey<boolean>;
|
||||
private _terminalShellTypeContextKey: IContextKey<string>;
|
||||
private _terminalAltBufferActiveContextKey: IContextKey<boolean>;
|
||||
private _terminalInRunCommandPicker: IContextKey<boolean>;
|
||||
private _configHelper: TerminalConfigHelper;
|
||||
|
||||
private readonly _onDidCreateInstance = new Emitter<ITerminalInstance>();
|
||||
@@ -37,6 +38,7 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst
|
||||
this._terminalHasFixedWidth = TerminalContextKeys.terminalHasFixedWidth.bindTo(this._contextKeyService);
|
||||
this._terminalShellTypeContextKey = TerminalContextKeys.shellType.bindTo(this._contextKeyService);
|
||||
this._terminalAltBufferActiveContextKey = TerminalContextKeys.altBufferActive.bindTo(this._contextKeyService);
|
||||
this._terminalInRunCommandPicker = TerminalContextKeys.inTerminalRunCommandPicker.bindTo(this._contextKeyService);
|
||||
this._configHelper = _instantiationService.createInstance(TerminalConfigHelper);
|
||||
}
|
||||
|
||||
@@ -49,6 +51,7 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst
|
||||
this._terminalHasFixedWidth,
|
||||
this._terminalShellTypeContextKey,
|
||||
this._terminalAltBufferActiveContextKey,
|
||||
this._terminalInRunCommandPicker,
|
||||
this._configHelper,
|
||||
shellLaunchConfig,
|
||||
resource
|
||||
|
||||
@@ -417,6 +417,7 @@ export function setupTerminalMenus(): void {
|
||||
order: 2,
|
||||
when: ContextKeyExpr.and(
|
||||
ContextKeyExpr.equals('view', TERMINAL_VIEW_ID),
|
||||
ContextKeyExpr.notEquals(`config.${TerminalSettingId.TabsHideCondition}`, 'never'),
|
||||
ContextKeyExpr.or(
|
||||
ContextKeyExpr.not(`config.${TerminalSettingId.TabsEnabled}`),
|
||||
ContextKeyExpr.and(
|
||||
@@ -451,6 +452,7 @@ export function setupTerminalMenus(): void {
|
||||
order: 3,
|
||||
when: ContextKeyExpr.and(
|
||||
ContextKeyExpr.equals('view', TERMINAL_VIEW_ID),
|
||||
ContextKeyExpr.notEquals(`config.${TerminalSettingId.TabsHideCondition}`, 'never'),
|
||||
ContextKeyExpr.or(
|
||||
ContextKeyExpr.not(`config.${TerminalSettingId.TabsEnabled}`),
|
||||
ContextKeyExpr.and(
|
||||
|
||||
@@ -31,6 +31,7 @@ export const enum TerminalContextKeyStrings {
|
||||
TabsSingularSelection = 'terminalTabsSingularSelection',
|
||||
SplitTerminal = 'terminalSplitTerminal',
|
||||
ShellType = 'terminalShellType',
|
||||
InTerminalRunCommandPicker = 'inTerminalRunCommandPicker',
|
||||
}
|
||||
|
||||
export namespace TerminalContextKeys {
|
||||
@@ -119,4 +120,7 @@ export namespace TerminalContextKeys {
|
||||
|
||||
/** Whether the focused tab's terminal is a split terminal. */
|
||||
export const splitTerminal = new RawContextKey<boolean>(TerminalContextKeyStrings.SplitTerminal, false, localize('isSplitTerminalContextKey', "Whether the focused tab's terminal is a split terminal."));
|
||||
|
||||
/** Whether the terminal run command picker is currently open. */
|
||||
export const inTerminalRunCommandPicker = new RawContextKey<boolean>(TerminalContextKeyStrings.InTerminalRunCommandPicker, false, localize('inTerminalRunCommandPickerContextKey', "Whether the terminal run command picker is currently open."));
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ const testFilterDescriptions: { [K in TestFilterTerm]: string } = {
|
||||
export class TestingExplorerFilter extends BaseActionViewItem {
|
||||
private input!: SuggestEnabledInputWithHistory;
|
||||
private wrapper!: HTMLDivElement;
|
||||
private readonly history: StoredValue<string[]> = this.instantiationService.createInstance(StoredValue, {
|
||||
private readonly history: StoredValue<{ values: string[]; lastValue: string } | string[]> = this.instantiationService.createInstance(StoredValue, {
|
||||
key: 'testing.filterHistory2',
|
||||
scope: StorageScope.WORKSPACE,
|
||||
target: StorageTarget.USER
|
||||
@@ -65,9 +65,12 @@ export class TestingExplorerFilter extends BaseActionViewItem {
|
||||
const wrapper = this.wrapper = dom.$('.testing-filter-wrapper');
|
||||
container.appendChild(wrapper);
|
||||
|
||||
const history = this.history.get([]);
|
||||
if (history.length) {
|
||||
this.state.setText(history[history.length - 1]);
|
||||
let history = this.history.get({ lastValue: '', values: [] });
|
||||
if (history instanceof Array) {
|
||||
history = { lastValue: '', values: history };
|
||||
}
|
||||
if (history.lastValue) {
|
||||
this.state.setText(history.lastValue);
|
||||
}
|
||||
|
||||
const input = this.input = this._register(this.instantiationService.createInstance(ContextScopedSuggestEnabledInputWithHistory, {
|
||||
@@ -94,7 +97,7 @@ export class TestingExplorerFilter extends BaseActionViewItem {
|
||||
value: this.state.text.value,
|
||||
placeholderText: localize('testExplorerFilter', "Filter (e.g. text, !exclude, @tag)"),
|
||||
},
|
||||
history
|
||||
history: history.values
|
||||
}));
|
||||
this._register(attachSuggestEnabledInputBoxStyler(input, this.themeService));
|
||||
|
||||
@@ -145,12 +148,7 @@ export class TestingExplorerFilter extends BaseActionViewItem {
|
||||
* Persists changes to the input history.
|
||||
*/
|
||||
public saveState() {
|
||||
const history = this.input.getHistory();
|
||||
if (history.length) {
|
||||
this.history.store(history);
|
||||
} else {
|
||||
this.history.delete();
|
||||
}
|
||||
this.history.store({ lastValue: this.input.getValue(), values: this.input.getHistory() });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -56,6 +56,7 @@ import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc';
|
||||
import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy';
|
||||
import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService';
|
||||
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
|
||||
import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals';
|
||||
|
||||
export class DesktopMain extends Disposable {
|
||||
|
||||
@@ -184,6 +185,9 @@ export class DesktopMain extends Disposable {
|
||||
if (logService.getLevel() === LogLevel.Trace) {
|
||||
logService.trace('workbench#open(): with configuration', safeStringify(this.configuration));
|
||||
}
|
||||
if (process.sandboxed) {
|
||||
logService.info('Electron sandbox mode is enabled!');
|
||||
}
|
||||
|
||||
// Shared Process
|
||||
const sharedProcessService = new SharedProcessService(this.configuration.windowId, logService);
|
||||
|
||||
@@ -24,6 +24,9 @@ export class CodeEditorService extends AbstractCodeEditorService {
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
) {
|
||||
super(themeService);
|
||||
|
||||
this.registerCodeEditorOpenHandler(this.doOpenCodeEditor.bind(this));
|
||||
this.registerCodeEditorOpenHandler(this.doOpenCodeEditorFromDiff.bind(this));
|
||||
}
|
||||
|
||||
getActiveCodeEditor(): ICodeEditor | null {
|
||||
@@ -44,7 +47,7 @@ export class CodeEditorService extends AbstractCodeEditorService {
|
||||
return null;
|
||||
}
|
||||
|
||||
async openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
|
||||
private async doOpenCodeEditorFromDiff(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
|
||||
|
||||
// Special case: If the active editor is a diff editor and the request to open originates and
|
||||
// targets the modified side of it, we just apply the request there to prevent opening the modified
|
||||
@@ -66,10 +69,10 @@ export class CodeEditorService extends AbstractCodeEditorService {
|
||||
return targetEditor;
|
||||
}
|
||||
|
||||
// Open using our normal editor service
|
||||
return this.doOpenCodeEditor(input, source, sideBySide);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Open using our normal editor service
|
||||
private async doOpenCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise<ICodeEditor | null> {
|
||||
|
||||
// Special case: we want to detect the request to open an editor that
|
||||
|
||||
@@ -47,7 +47,7 @@ export const enum GroupsArrangement {
|
||||
* Make the current active group consume the maximum
|
||||
* amount of space possible.
|
||||
*/
|
||||
MINIMIZE_OTHERS,
|
||||
MAXIMIZE,
|
||||
|
||||
/**
|
||||
* Size all groups evenly.
|
||||
|
||||
@@ -1661,7 +1661,7 @@ suite('EditorService', () => {
|
||||
editor = await service.openEditor(input2, { pinned: true, activation: EditorActivation.ACTIVATE }, sideGroup);
|
||||
assert.strictEqual(part.activeGroup, sideGroup);
|
||||
|
||||
part.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
|
||||
part.arrangeGroups(GroupsArrangement.MAXIMIZE);
|
||||
editor = await service.openEditor(input1, { pinned: true, preserveFocus: true, activation: EditorActivation.RESTORE }, rootGroup);
|
||||
assert.strictEqual(part.activeGroup, sideGroup);
|
||||
});
|
||||
@@ -1681,13 +1681,13 @@ suite('EditorService', () => {
|
||||
assert.strictEqual(part.activeGroup, sideGroup);
|
||||
assert.notStrictEqual(rootGroup, sideGroup);
|
||||
|
||||
part.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS, part.activeGroup);
|
||||
part.arrangeGroups(GroupsArrangement.MAXIMIZE, part.activeGroup);
|
||||
|
||||
await rootGroup.closeEditor(input2);
|
||||
assert.strictEqual(part.activeGroup, sideGroup);
|
||||
|
||||
assert.strictEqual(rootGroup.isMinimized, true);
|
||||
assert.strictEqual(part.activeGroup.isMinimized, false);
|
||||
assert(!part.isGroupMaximized(rootGroup));
|
||||
assert(part.isGroupMaximized(part.activeGroup));
|
||||
});
|
||||
|
||||
test('active editor change / visible editor change events', async function () {
|
||||
|
||||
@@ -853,7 +853,6 @@ export class TestEditorGroupView implements IEditorGroupView {
|
||||
titleHeight!: IEditorGroupTitleHeight;
|
||||
|
||||
isEmpty = true;
|
||||
isMinimized = false;
|
||||
|
||||
onWillDispose: Event<void> = Event.None;
|
||||
onDidModelChange: Event<IGroupModelChangeEvent> = Event.None;
|
||||
|
||||
@@ -12112,20 +12112,20 @@ xterm-addon-unicode11@0.4.0-beta.3:
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa"
|
||||
integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q==
|
||||
|
||||
xterm-addon-webgl@0.13.0-beta.2:
|
||||
version "0.13.0-beta.2"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.2.tgz#f58a7a3641ad7c8ac82dd24cfb0165656ed9ac1c"
|
||||
integrity sha512-98tX0BkpD402RoCO6SyikUXpzCn9/OQhlXsRmM/kRFCxMWWofStWTXzCPhN0MjIx2IdGueDjCmnShhidwihErg==
|
||||
xterm-addon-webgl@0.13.0-beta.3:
|
||||
version "0.13.0-beta.3"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.3.tgz#2b456c3105238e64b40a30787d6335f5f6f85abb"
|
||||
integrity sha512-DFGcXAolA0VTsOLIKcORxUOp/FTJdD/YiRzKVLARjgOycwVRKvW2L5Tge8Z7ysZ16sKfnV2vCXyonXYfUWozXw==
|
||||
|
||||
xterm-headless@4.20.0-beta.5:
|
||||
version "4.20.0-beta.5"
|
||||
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.20.0-beta.5.tgz#edcff27eb6437d158e6aea2ed7658e783bee5641"
|
||||
integrity sha512-8SnVUsuNUrQ5P0XU/9Iau3uK7Tf8q/p0KHHwkwJXVxZDIlaDH9XKSs91U9BjJJE3sJgRxH4NSiDYR3vFLSFpxw==
|
||||
xterm-headless@4.20.0-beta.6:
|
||||
version "4.20.0-beta.6"
|
||||
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.20.0-beta.6.tgz#bd016379e9fac47e5b8870d567cdf330cf6f49fc"
|
||||
integrity sha512-EV0V7pxMKI0OEcOCD+6vdXq6rBARr7dSN3PovTsZnDWg5dmvUb2eEmz6BTejJj3UVd/JXNEmEXM+tCh97rDCDg==
|
||||
|
||||
xterm@4.20.0-beta.5:
|
||||
version "4.20.0-beta.5"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.5.tgz#d707b0dcb477a554135fb767b24003fced079866"
|
||||
integrity sha512-KBWfk9UPBKRy662DVGGTZEcW1becEjYvlyWbn2hLj9h2gy6Q4EEEEbggJh8I7SGwdFizl+apHQGhEOZmFCA70w==
|
||||
xterm@4.20.0-beta.6:
|
||||
version "4.20.0-beta.6"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.6.tgz#3ed87ba383a5cf44284098278f714df7113e3e3c"
|
||||
integrity sha512-xJd6vyOuYo4Ht/hTY3DyXGIj0U6kHjr2vWQ1lRmearo3t7QKf7uqOAAfTLeWt/g1P8qe/r0DnsNTeag6vI9RVw==
|
||||
|
||||
y18n@^3.2.1:
|
||||
version "3.2.2"
|
||||
|
||||
Reference in New Issue
Block a user