Merge remote-tracking branch 'origin/main' into tyriar/141006

This commit is contained in:
Daniel Imms 2022-02-17 12:22:36 -08:00
commit 7de1a818a8
85 changed files with 1150 additions and 593 deletions

View file

@ -1,9 +1,2 @@
{
"notebook": [
"rchiodo",
"greazer",
"donjayamanne",
"jilljac",
"IanMatthewHuff"
]
}

View file

@ -23,6 +23,16 @@ steps:
displayName: Download server build dependencies
condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'))
- script: |
set -e
# Start X server
/etc/init.d/xvfb start
# Start dbus session
DBUS_LAUNCH_RESULT=$(sudo dbus-daemon --config-file=/usr/share/dbus-1/system.conf --print-address)
echo "##vso[task.setvariable variable=DBUS_SESSION_BUS_ADDRESS]$DBUS_LAUNCH_RESULT"
displayName: Setup system services
condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'))
- script: |
set -e
tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz
@ -241,7 +251,7 @@ steps:
- script: |
set -e
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \
yarn smoketest-no-compile --web --headless --electronArgs="--disable-dev-shm-usage --use-gl=swiftshader"
yarn smoketest-no-compile --web --headless --electronArgs="--disable-dev-shm-usage"
timeoutInMinutes: 10
displayName: Run smoke tests (Browser, Chromium)
condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
@ -249,7 +259,7 @@ steps:
- script: |
set -e
APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)
yarn smoketest-no-compile --build "$APP_PATH" --electronArgs="--disable-dev-shm-usage --use-gl=swiftshader"
yarn smoketest-no-compile --build "$APP_PATH" --electronArgs="--disable-dev-shm-usage"
# Increased timeout because this test downloads stable code
timeoutInMinutes: 20
displayName: Run smoke tests (Electron)
@ -259,7 +269,7 @@ steps:
set -e
APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \
yarn smoketest-no-compile --build "$APP_PATH" --remote --electronArgs="--disable-dev-shm-usage --use-gl=swiftshader"
yarn smoketest-no-compile --build "$APP_PATH" --remote --electronArgs="--disable-dev-shm-usage"
timeoutInMinutes: 10
displayName: Run smoke tests (Remote)
condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false'))

View file

@ -13,7 +13,7 @@
"notebookEditorEdit"
],
"activationEvents": [
"onNotebook:jupyter-notebook"
"*"
],
"extensionKind": [
"workspace",
@ -28,6 +28,12 @@
}
},
"contributes": {
"commands": [
{
"command": "ipynb.newUntitledIpynb",
"title": "Jupyter Notebook"
}
],
"notebooks": [
{
"type": "jupyter-notebook",
@ -39,7 +45,15 @@
],
"priority": "default"
}
]
],
"menus": {
"file/newFile": [
{
"command": "ipynb.newUntitledIpynb",
"when": "!jupyterEnabled"
}
]
}
},
"scripts": {
"compile": "npx gulp compile-extension:ipynb",

View file

@ -22,7 +22,10 @@ export function getPreferredLanguage(metadata?: nbformat.INotebookMetadata) {
(metadata?.kernelspec as any)?.language;
// Default to python language only if the Python extension is installed.
const defaultLanguage = extensions.getExtension('ms-python.python') ? 'python' : 'plaintext';
const defaultLanguage =
extensions.getExtension('ms-python.python')
? 'python'
: (extensions.getExtension('ms-dotnettools.dotnet-interactive-vscode') ? 'csharp' : 'python');
// Note, whatever language is returned here, when the user selects a kernel, the cells (of blank documents) get updated based on that kernel selection.
return translateKernelLanguageToMonaco(jupyterLanguage || defaultLanguage);

View file

@ -37,6 +37,30 @@ export function activate(context: vscode.ExtensionContext) {
}
}));
context.subscriptions.push(vscode.commands.registerCommand('ipynb.newUntitledIpynb', async () => {
const language = 'python';
const cell = new vscode.NotebookCellData(vscode.NotebookCellKind.Code, '', language);
const data = new vscode.NotebookData([cell]);
data.metadata = {
custom: {
cells: [],
metadata: {
orig_nbformat: 4
},
nbformat: 4,
nbformat_minor: 2
}
};
const doc = await vscode.workspace.openNotebookDocument('jupyter-notebook', data);
await vscode.window.showNotebookDocument(doc);
}));
// Update new file contribution
vscode.extensions.onDidChange(() => {
vscode.commands.executeCommand('setContext', 'jupyterEnabled', vscode.extensions.getExtension('ms-toolsai.jupyter'));
});
vscode.commands.executeCommand('setContext', 'jupyterEnabled', vscode.extensions.getExtension('ms-toolsai.jupyter'));
return {
exportNotebook: (notebook: vscode.NotebookData): string => {
return exportNotebook(notebook, serializer);

View file

@ -204,8 +204,8 @@ export const activate: ActivationFunction<void> = (ctx) => {
previewNode.classList.add('emptyMarkdownCell');
} else {
previewNode.classList.remove('emptyMarkdownCell');
const unsanitizedRenderedMarkdown = markdownIt.render(text);
const markdownText = outputInfo.mime.startsWith('text/x-') ? `\`\`\`${outputInfo.mime.substr(7)}\n${text}\n\`\`\`` : text;
const unsanitizedRenderedMarkdown = markdownIt.render(markdownText);
previewNode.innerHTML = (ctx.workspace.isTrusted
? unsanitizedRenderedMarkdown
: DOMPurify.sanitize(unsanitizedRenderedMarkdown, sanitizerOptions)) as string;

View file

@ -47,7 +47,79 @@
"entrypoint": "./notebook-out/index.js",
"mimeTypes": [
"text/markdown",
"text/latex"
"text/latex",
"text/x-css",
"text/x-html",
"text/x-json",
"text/x-typescript",
"text/x-abap",
"text/x-apex",
"text/x-azcli",
"text/x-bat",
"text/x-cameligo",
"text/x-clojure",
"text/x-coffee",
"text/x-cpp",
"text/x-csharp",
"text/x-csp",
"text/x-css",
"text/x-dart",
"text/x-dockerfile",
"text/x-ecl",
"text/x-fsharp",
"text/x-go",
"text/x-graphql",
"text/x-handlebars",
"text/x-hcl",
"text/x-html",
"text/x-ini",
"text/x-java",
"text/x-javascript",
"text/x-julia",
"text/x-kotlin",
"text/x-less",
"text/x-lexon",
"text/x-lua",
"text/x-m3",
"text/x-markdown",
"text/x-mips",
"text/x-msdax",
"text/x-mysql",
"text/x-objective-c/objective",
"text/x-pascal",
"text/x-pascaligo",
"text/x-perl",
"text/x-pgsql",
"text/x-php",
"text/x-postiats",
"text/x-powerquery",
"text/x-powershell",
"text/x-pug",
"text/x-python",
"text/x-r",
"text/x-razor",
"text/x-redis",
"text/x-redshift",
"text/x-restructuredtext",
"text/x-ruby",
"text/x-rust",
"text/x-sb",
"text/x-scala",
"text/x-scheme",
"text/x-scss",
"text/x-shell",
"text/x-solidity",
"text/x-sophia",
"text/x-sql",
"text/x-st",
"text/x-swift",
"text/x-systemverilog",
"text/x-tcl",
"text/x-twig",
"text/x-typescript",
"text/x-vb",
"text/x-xml",
"text/x-yaml"
]
}
],

View file

@ -182,7 +182,24 @@ export class AzureActiveDirectoryService {
//#region session operations
async getSessions(scopes?: string[]): Promise<vscode.AuthenticationSession[]> {
Logger.info(`Getting sessions for ${scopes?.join(',') ?? 'all scopes'}...`);
if (!scopes) {
Logger.info('Getting sessions for all scopes...');
const sessions = this._tokens.map(token => this.convertToSessionSync(token));
Logger.info(`Got ${sessions.length} sessions for all scopes...`);
return sessions;
}
const modifiedScopes = [...scopes];
if (!modifiedScopes.includes('openid')) {
modifiedScopes.push('openid');
}
if (!modifiedScopes.includes('email')) {
modifiedScopes.push('email');
}
let orderedScopes = modifiedScopes.sort().join(' ');
Logger.info(`Getting sessions for the following scopes: ${orderedScopes}`);
if (this._refreshingPromise) {
Logger.info('Refreshing in progress. Waiting for completion before continuing.');
try {
@ -191,15 +208,19 @@ export class AzureActiveDirectoryService {
// this will get logged in the refresh function.
}
}
if (!scopes) {
const sessions = this._tokens.map(token => this.convertToSessionSync(token));
Logger.info(`Got ${sessions.length} sessions for all scopes...`);
return sessions;
let matchingTokens = this._tokens.filter(token => token.scope === orderedScopes);
// The user may still have a token that doesn't have the openid & email scopes so check for that as well.
// Eventually, we should remove this and force the user to re-log in so that we don't have any sessions
// without an idtoken.
if (!matchingTokens.length) {
orderedScopes = scopes.sort().join(' ');
Logger.trace(`No session found with idtoken scopes... Using fallback scope list of: ${orderedScopes}`);
matchingTokens = this._tokens.filter(token => token.scope === orderedScopes);
}
const orderedScopes = scopes.sort().join(' ');
const matchingTokens = this._tokens.filter(token => token.scope === orderedScopes);
Logger.info(`Got ${matchingTokens.length} sessions for ${scopes?.join(',')}...`);
Logger.info(`Got ${matchingTokens.length} sessions for scopes: ${orderedScopes}`);
return Promise.all(matchingTokens.map(token => this.convertToSession(token)));
}
@ -210,6 +231,7 @@ export class AzureActiveDirectoryService {
if (!scopes.includes('email')) {
scopes.push('email');
}
scopes = scopes.sort();
const scopeData: IScopeData = {
scopes,
scopeStr: scopes.join(' '),
@ -397,15 +419,23 @@ export class AzureActiveDirectoryService {
try {
if (json.id_token) {
Logger.info('Attempting to parse id_token instead since access_token was not parsable');
claims = JSON.parse(Buffer.from(json.id_token.split('.')[1], 'base64').toString());
} else {
Logger.info('Attempting to parse access_token instead since no id_token was included in the response.');
claims = JSON.parse(Buffer.from(json.access_token.split('.')[1], 'base64').toString());
}
} catch (e) {
throw e;
}
let label;
if (claims.name && claims.email) {
label = `${claims.name} - ${claims.email}`;
} else {
label = claims.email ?? claims.unique_name ?? claims.preferred_username ?? 'user@example.com';
}
const id = `${claims.tid}/${(claims.oid ?? (claims.altsecid ?? '' + claims.ipd ?? ''))}`;
return {
expiresIn: json.expires_in,
expiresAt: json.expires_in ? Date.now() + json.expires_in * 1000 : undefined,
@ -413,10 +443,10 @@ export class AzureActiveDirectoryService {
idToken: json.id_token,
refreshToken: json.refresh_token,
scope: scopeData.scopeStr,
sessionId: existingId || `${claims.tid}/${(claims.oid || (claims.altsecid || '' + claims.ipd || ''))}/${uuid()}`,
sessionId: existingId || `${id}/${uuid()}`,
account: {
label: `${claims.name} - ${claims.email}` || claims.email || claims.unique_name || claims.preferred_username || 'user@example.com',
id: `${claims.tid}/${(claims.oid || (claims.altsecid || '' + claims.ipd || ''))}`
label,
id
}
};
}

View file

@ -147,7 +147,7 @@ function renderStream(outputInfo: OutputItem, container: HTMLElement, error: boo
function renderText(outputInfo: OutputItem, container: HTMLElement, ctx: RendererContext<void> & { readonly settings: { readonly lineLimit: number } }): void {
const contentNode = document.createElement('div');
contentNode.classList.add('.output-plaintext');
contentNode.classList.add('output-plaintext');
const text = outputInfo.text();
truncatedArrayOfString(outputInfo.id, [text], ctx.settings.lineLimit, contentNode);
container.appendChild(contentNode);
@ -160,6 +160,7 @@ export const activate: ActivationFunction<void> = (ctx) => {
const style = document.createElement('style');
style.textContent = `
.output-plaintext,
.output-stream {
line-height: 22px;
font-family: var(--notebook-cell-output-font-family);

View file

@ -785,6 +785,11 @@
],
"default": "auto",
"markdownDescription": "%typescript.preferences.quoteStyle%",
"markdownEnumDescriptions": [
"%typescript.preferences.quoteStyle.auto%",
"%typescript.preferences.quoteStyle.single%",
"%typescript.preferences.quoteStyle.double%"
],
"scope": "language-overridable"
},
"typescript.preferences.quoteStyle": {
@ -796,6 +801,11 @@
],
"default": "auto",
"markdownDescription": "%typescript.preferences.quoteStyle%",
"markdownEnumDescriptions": [
"%typescript.preferences.quoteStyle.auto%",
"%typescript.preferences.quoteStyle.single%",
"%typescript.preferences.quoteStyle.double%"
],
"scope": "language-overridable"
},
"javascript.preferences.importModuleSpecifier": {
@ -878,7 +888,7 @@
"none"
],
"markdownEnumDescriptions": [
"%typescript.preferences.jsxAttributeCompletionStyle.auto%",
"%javascript.preferences.jsxAttributeCompletionStyle.auto%",
"%typescript.preferences.jsxAttributeCompletionStyle.braces%",
"%typescript.preferences.jsxAttributeCompletionStyle.none%"
],

View file

@ -113,7 +113,10 @@
"taskDefinition.tsconfig.description": "The tsconfig file that defines the TS build.",
"javascript.suggestionActions.enabled": "Enable/disable suggestion diagnostics for JavaScript files in the editor.",
"typescript.suggestionActions.enabled": "Enable/disable suggestion diagnostics for TypeScript files in the editor.",
"typescript.preferences.quoteStyle": "Preferred quote style to use for quick fixes: `single` quotes, `double` quotes, or `auto` infer quote type from existing imports.",
"typescript.preferences.quoteStyle": "Preferred quote style to use for quick fixes.",
"typescript.preferences.quoteStyle.single": "Always use single quotes: `'`",
"typescript.preferences.quoteStyle.double": "Always use double quotes: `\"`",
"typescript.preferences.quoteStyle.auto": "Infer quote type from existing code",
"typescript.preferences.importModuleSpecifier": "Preferred path style for auto imports.",
"typescript.preferences.importModuleSpecifier.shortest": "Prefers a non-relative import only if one is available that has fewer path segments than a relative import.",
"typescript.preferences.importModuleSpecifier.relative": "Prefers a relative path to the imported file location.",
@ -125,7 +128,8 @@
"typescript.preferences.importModuleSpecifierEnding.index": "Shorten `./component/index.js` to `./component/index`.",
"typescript.preferences.importModuleSpecifierEnding.js": "Do not shorten path endings; include the `.js` extension.",
"typescript.preferences.jsxAttributeCompletionStyle": "Preferred style for JSX attribute completions.",
"typescript.preferences.jsxAttributeCompletionStyle.auto": "Insert `={}` or `=\"\"` after attribute names based on the prop type.",
"javascript.preferences.jsxAttributeCompletionStyle.auto": "Insert `={}` or `=\"\"` after attribute names based on the prop type. See `javascript.preferences.quoteStyle` to control the type of quotes used for string attributes.",
"typescript.preferences.jsxAttributeCompletionStyle.auto": "Insert `={}` or `=\"\"` after attribute names based on the prop type. See `typescript.preferences.quoteStyle` to control the type of quotes used for string attributes.",
"typescript.preferences.jsxAttributeCompletionStyle.braces": "Insert `={}` after attribute names.",
"typescript.preferences.jsxAttributeCompletionStyle.none": "Only insert attribute names.",
"typescript.preferences.includePackageJsonAutoImports": "Enable/disable searching `package.json` dependencies for available auto imports.",

View file

@ -15,9 +15,6 @@ function workspaceFile(...segments: string[]) {
return vscode.Uri.joinPath(vscode.workspace.workspaceFolders![0].uri, ...segments);
}
const testDocument = workspaceFile('bower.json');
suite('vscode API - webview', () => {
const disposables: vscode.Disposable[] = [];
@ -117,7 +114,7 @@ suite('vscode API - webview', () => {
assert.strictEqual(firstResponse.value, 1);
// Swap away from the webview
const doc = await vscode.workspace.openTextDocument(testDocument);
const doc = await vscode.workspace.openTextDocument(workspaceFile('bower.json'));
await vscode.window.showTextDocument(doc);
// And then back
@ -131,7 +128,7 @@ suite('vscode API - webview', () => {
});
test.skip('webviews should preserve their context when they are moved between view columns', async () => { // TODO@mjbvz https://github.com/microsoft/vscode/issues/141001
const doc = await vscode.workspace.openTextDocument(testDocument);
const doc = await vscode.workspace.openTextDocument(workspaceFile('bower.json'));
await vscode.window.showTextDocument(doc, vscode.ViewColumn.One);
// Open webview in same column
@ -164,7 +161,7 @@ suite('vscode API - webview', () => {
assert.strictEqual((await firstResponse).value, 1);
// Swap away from the webview
const doc = await vscode.workspace.openTextDocument(testDocument);
const doc = await vscode.workspace.openTextDocument(workspaceFile('bower.json'));
await vscode.window.showTextDocument(doc);
// And then back
@ -204,7 +201,7 @@ suite('vscode API - webview', () => {
assert.strictEqual(Math.round((await firstResponse).value), 100);
// Swap away from the webview
const doc = await vscode.workspace.openTextDocument(testDocument);
const doc = await vscode.workspace.openTextDocument(workspaceFile('bower.json'));
await vscode.window.showTextDocument(doc);
// And then back
@ -226,7 +223,7 @@ suite('vscode API - webview', () => {
assert.strictEqual((await firstResponse).value, 1);
// Swap away from the webview
const doc = await vscode.workspace.openTextDocument(testDocument);
const doc = await vscode.workspace.openTextDocument(workspaceFile('bower.json'));
await vscode.window.showTextDocument(doc);
// Try posting a message to our hidden webview

View file

@ -371,8 +371,7 @@ suite('vscode API - window', () => {
});
//#region Tabs API tests
// eslint-disable-next-line code-no-test-only
test.only('Tabs - move tab', async function () {
test('Tabs - move tab', async function () {
const [docA, docB, docC] = await Promise.all([
workspace.openTextDocument(await createRandomFile()),
workspace.openTextDocument(await createRandomFile()),
@ -393,9 +392,43 @@ suite('vscode API - window', () => {
assert.strictEqual(group2Tabs.length, 1);
await group1Tabs[0].move(1, ViewColumn.One);
console.log('Tab moved - Integration test');
});
/*
test('Tabs - vscode.open & vscode.diff', async function () {
// Simple function to get the active tab
const getActiveTab = () => {
return window.tabGroups.all.find(g => g.isActive)?.activeTab;
};
const [docA, docB, docC] = await Promise.all([
workspace.openTextDocument(await createRandomFile()),
workspace.openTextDocument(await createRandomFile()),
workspace.openTextDocument(await createRandomFile())
]);
await window.showTextDocument(docA, { viewColumn: ViewColumn.One, preview: false });
await window.showTextDocument(docB, { viewColumn: ViewColumn.One, preview: false });
await window.showTextDocument(docC, { viewColumn: ViewColumn.Two, preview: false });
const commandFile = await createRandomFile();
await commands.executeCommand('vscode.open', commandFile, ViewColumn.Three);
// Ensure active tab is correct after calling vscode.opn
assert.strictEqual(getActiveTab()?.viewColumn, ViewColumn.Three);
const leftDiff = await createRandomFile();
const rightDiff = await createRandomFile();
await commands.executeCommand('vscode.diff', leftDiff, rightDiff, 'Diff', { viewColumn: ViewColumn.Four, preview: false });
assert.strictEqual(getActiveTab()?.viewColumn, ViewColumn.Four);
const tabs = window.tabGroups.all.map(g => g.tabs).flat(1);
assert.strictEqual(tabs.length, 5);
assert.strictEqual(tabs[0].resource?.toString(), docA.uri.toString());
assert.strictEqual(tabs[1].resource?.toString(), docB.uri.toString());
assert.strictEqual(tabs[2].resource?.toString(), docC.uri.toString());
assert.strictEqual(tabs[3].resource?.toString(), commandFile.toString());
});
test('Tabs - Ensure tabs getter is correct', async function () {
// Reduce test timeout as this test should be quick, so even with 3 retries it will be under 60s.
this.timeout(10000);
@ -418,7 +451,7 @@ suite('vscode API - window', () => {
const rightDiff = await createRandomFile();
await commands.executeCommand('vscode.diff', leftDiff, rightDiff, 'Diff', { viewColumn: ViewColumn.Three, preview: false });
const tabs = window.tabs;
const tabs = window.tabGroups.all.map(g => g.tabs).flat(1);
assert.strictEqual(tabs.length, 5);
// All resources should match the text documents as they're the only tabs currently open
@ -436,6 +469,7 @@ suite('vscode API - window', () => {
assert.strictEqual(tabs[4].viewColumn, ViewColumn.Three);
});
/*
test('Tabs - ensure active tab is correct', async () => {
const [docA, docB, docC] = await Promise.all([

View file

@ -85,12 +85,12 @@
"vscode-proxy-agent": "^0.12.0",
"vscode-regexpp": "^3.1.0",
"vscode-textmate": "6.0.0",
"xterm": "4.18.0-beta.5",
"xterm": "4.18.0-beta.6",
"xterm-addon-search": "0.9.0-beta.10",
"xterm-addon-serialize": "0.7.0-beta.8",
"xterm-addon-unicode11": "0.4.0-beta.3",
"xterm-addon-webgl": "0.12.0-beta.24",
"xterm-headless": "4.18.0-beta.5",
"xterm-headless": "4.18.0-beta.6",
"yauzl": "^2.9.2",
"yazl": "^2.4.3"
},

View file

@ -24,12 +24,12 @@
"vscode-proxy-agent": "^0.12.0",
"vscode-regexpp": "^3.1.0",
"vscode-textmate": "6.0.0",
"xterm": "4.18.0-beta.5",
"xterm": "4.18.0-beta.6",
"xterm-addon-search": "0.9.0-beta.10",
"xterm-addon-serialize": "0.7.0-beta.8",
"xterm-addon-unicode11": "0.4.0-beta.3",
"xterm-addon-webgl": "0.12.0-beta.24",
"xterm-headless": "4.18.0-beta.5",
"xterm-headless": "4.18.0-beta.6",
"yauzl": "^2.9.2",
"yazl": "^2.4.3"
},

View file

@ -10,7 +10,7 @@
"tas-client-umd": "0.1.4",
"vscode-oniguruma": "1.6.1",
"vscode-textmate": "6.0.0",
"xterm": "4.18.0-beta.5",
"xterm": "4.18.0-beta.6",
"xterm-addon-search": "0.9.0-beta.10",
"xterm-addon-unicode11": "0.4.0-beta.3",
"xterm-addon-webgl": "0.12.0-beta.24"

View file

@ -128,7 +128,7 @@ xterm-addon-webgl@0.12.0-beta.24:
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.24.tgz#5c17256933991856554c95c9bd1eaab42e9727a0"
integrity sha512-+wZxKReEOlfN9JRHyikoffA6Do61/THR7QY35ajkQo0lLutKr6hTd/TLTuZh0PhFVelgTgudpXqlP++Lc0WFIA==
xterm@4.18.0-beta.5:
version "4.18.0-beta.5"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.18.0-beta.5.tgz#69d7c6255d9245437302d07fac3d070f167d73a2"
integrity sha512-ZpCix34QB+B72F3Ulux3fD/BJ+K5KhJcUBZg2jut9vVVEaydr8xRVic5v9x18L3ejIgnGnSSZci6NPF7uaQiDg==
xterm@4.18.0-beta.6:
version "4.18.0-beta.6"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.18.0-beta.6.tgz#65ebbe50e36eb202c57aa07e76062fc17431a06b"
integrity sha512-3hVi8fJX0S01vuI1RnFOtTyW3L7x/gZU8IfIUrwjbfKlaoQqkny0YFVlLGR0X3OjPFuR0oowsU+P+wysj6P2fQ==

View file

@ -928,15 +928,15 @@ xterm-addon-webgl@0.12.0-beta.24:
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.24.tgz#5c17256933991856554c95c9bd1eaab42e9727a0"
integrity sha512-+wZxKReEOlfN9JRHyikoffA6Do61/THR7QY35ajkQo0lLutKr6hTd/TLTuZh0PhFVelgTgudpXqlP++Lc0WFIA==
xterm-headless@4.18.0-beta.5:
version "4.18.0-beta.5"
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.18.0-beta.5.tgz#31abe8053462451b0603e252afdb328bcbb0e674"
integrity sha512-Whf53jmnA8QbuceNieR6dGtvqresnInxLyaFaGHRWwizbjF7OmTN/r0hFxH4ul8codmK+GAZ+GnM1OCyJ0aFfg==
xterm-headless@4.18.0-beta.6:
version "4.18.0-beta.6"
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.18.0-beta.6.tgz#ee4b2a1925603dfb1fb79b259142edb04c5ee4d4"
integrity sha512-yWKFgY7oKz/EoP0blLalcgjnCRxldPRW/SBObBkgrMAmN4ItiDrQV4aUZjXLrhlOJ8BmeJdw4TjaBbUXnvWaBQ==
xterm@4.18.0-beta.5:
version "4.18.0-beta.5"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.18.0-beta.5.tgz#69d7c6255d9245437302d07fac3d070f167d73a2"
integrity sha512-ZpCix34QB+B72F3Ulux3fD/BJ+K5KhJcUBZg2jut9vVVEaydr8xRVic5v9x18L3ejIgnGnSSZci6NPF7uaQiDg==
xterm@4.18.0-beta.6:
version "4.18.0-beta.6"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.18.0-beta.6.tgz#65ebbe50e36eb202c57aa07e76062fc17431a06b"
integrity sha512-3hVi8fJX0S01vuI1RnFOtTyW3L7x/gZU8IfIUrwjbfKlaoQqkny0YFVlLGR0X3OjPFuR0oowsU+P+wysj6P2fQ==
yauzl@^2.9.2:
version "2.10.0"

View file

@ -9,8 +9,8 @@ pushd %~dp0\..
set NODE_ENV=development
set VSCODE_DEV=1
:: Sync built-in extensions
call yarn download-builtin-extensions
:: Get electron, compile, built-in extensions
if "%VSCODE_SKIP_PRELAUNCH%"=="" node build/lib/preLaunch.js
:: Node executable
FOR /F "tokens=*" %%g IN ('node build/lib/node.js') do (SET NODE=%%g)

View file

@ -10,8 +10,10 @@ fi
function code() {
cd $ROOT
# Sync built-in extensions
yarn download-builtin-extensions
# Get electron, compile, built-in extensions
if [[ -z "${VSCODE_SKIP_PRELAUNCH}" ]]; then
node build/lib/preLaunch.js
fi
NODE=$(node build/lib/node.js)
if [ ! -e $NODE ];then

View file

@ -6,9 +6,9 @@ if [[ "$OSTYPE" == "darwin"* ]]; then
ROOT=$(dirname $(dirname $(realpath "$0")))
else
ROOT=$(dirname $(dirname $(readlink -f $0)))
# --disable-dev-shm-usage --use-gl=swiftshader: when run on docker containers where size of /dev/shm
# --disable-dev-shm-usage: when run on docker containers where size of /dev/shm
# partition < 64MB which causes OOM failure for chromium compositor that uses the partition for shared memory
LINUX_EXTRA_ARGS="--disable-dev-shm-usage --use-gl=swiftshader"
LINUX_EXTRA_ARGS="--disable-dev-shm-usage"
fi
VSCODEUSERDATADIR=`mktemp -d 2>/dev/null`

View file

@ -6,9 +6,9 @@ if [[ "$OSTYPE" == "darwin"* ]]; then
ROOT=$(dirname $(dirname $(realpath "$0")))
else
ROOT=$(dirname $(dirname $(readlink -f $0)))
# --disable-dev-shm-usage --use-gl=swiftshader: when run on docker containers where size of /dev/shm
# --disable-dev-shm-usage: when run on docker containers where size of /dev/shm
# partition < 64MB which causes OOM failure for chromium compositor that uses the partition for shared memory
LINUX_EXTRA_ARGS="--disable-dev-shm-usage --use-gl=swiftshader"
LINUX_EXTRA_ARGS="--disable-dev-shm-usage"
fi
VSCODEUSERDATADIR=`mktemp -d 2>/dev/null`

View file

@ -6,9 +6,9 @@ if [[ "$OSTYPE" == "darwin"* ]]; then
ROOT=$(dirname $(dirname $(realpath "$0")))
else
ROOT=$(dirname $(dirname $(readlink -f $0)))
# --disable-dev-shm-usage --use-gl=swiftshader: when run on docker containers where size of /dev/shm
# --disable-dev-shm-usage: when run on docker containers where size of /dev/shm
# partition < 64MB which causes OOM failure for chromium compositor that uses the partition for shared memory
LINUX_EXTRA_ARGS="--disable-dev-shm-usage --use-gl=swiftshader"
LINUX_EXTRA_ARGS="--disable-dev-shm-usage"
fi
cd $ROOT

View file

@ -5,53 +5,34 @@
export class FastDomNode<T extends HTMLElement> {
public readonly domNode: T;
private _maxWidth: number;
private _width: number;
private _height: number;
private _top: number;
private _left: number;
private _bottom: number;
private _right: number;
private _fontFamily: string;
private _fontWeight: string;
private _fontSize: number;
private _fontFeatureSettings: string;
private _lineHeight: number;
private _letterSpacing: number;
private _className: string;
private _display: string;
private _position: string;
private _visibility: string;
private _backgroundColor: string;
private _layerHint: boolean;
private _contain: 'none' | 'strict' | 'content' | 'size' | 'layout' | 'style' | 'paint';
private _boxShadow: string;
private _maxWidth: number = -1;
private _width: number = -1;
private _height: number = -1;
private _top: number = -1;
private _left: number = -1;
private _bottom: number = -1;
private _right: number = -1;
private _fontFamily: string = '';
private _fontWeight: string = '';
private _fontSize: number = -1;
private _fontStyle: string = '';
private _fontFeatureSettings: string = '';
private _textDecoration: string = '';
private _lineHeight: number = -1;
private _letterSpacing: number = -100;
private _className: string = '';
private _display: string = '';
private _position: string = '';
private _visibility: string = '';
private _color: string = '';
private _backgroundColor: string = '';
private _layerHint: boolean = false;
private _contain: 'none' | 'strict' | 'content' | 'size' | 'layout' | 'style' | 'paint' = 'none';
private _boxShadow: string = '';
constructor(domNode: T) {
this.domNode = domNode;
this._maxWidth = -1;
this._width = -1;
this._height = -1;
this._top = -1;
this._left = -1;
this._bottom = -1;
this._right = -1;
this._fontFamily = '';
this._fontWeight = '';
this._fontSize = -1;
this._fontFeatureSettings = '';
this._lineHeight = -1;
this._letterSpacing = -100;
this._className = '';
this._display = '';
this._position = '';
this._visibility = '';
this._backgroundColor = '';
this._layerHint = false;
this._contain = 'none';
this._boxShadow = '';
}
constructor(
public readonly domNode: T
) { }
public setMaxWidth(maxWidth: number): void {
if (this._maxWidth === maxWidth) {
@ -141,6 +122,14 @@ export class FastDomNode<T extends HTMLElement> {
this.domNode.style.fontSize = this._fontSize + 'px';
}
public setFontStyle(fontStyle: string): void {
if (this._fontStyle === fontStyle) {
return;
}
this._fontStyle = fontStyle;
this.domNode.style.fontStyle = this._fontStyle;
}
public setFontFeatureSettings(fontFeatureSettings: string): void {
if (this._fontFeatureSettings === fontFeatureSettings) {
return;
@ -149,6 +138,14 @@ export class FastDomNode<T extends HTMLElement> {
this.domNode.style.fontFeatureSettings = this._fontFeatureSettings;
}
public setTextDecoration(textDecoration: string): void {
if (this._textDecoration === textDecoration) {
return;
}
this._textDecoration = textDecoration;
this.domNode.style.textDecoration = this._textDecoration;
}
public setLineHeight(lineHeight: number): void {
if (this._lineHeight === lineHeight) {
return;
@ -202,6 +199,14 @@ export class FastDomNode<T extends HTMLElement> {
this.domNode.style.visibility = this._visibility;
}
public setColor(color: string): void {
if (this._color === color) {
return;
}
this._color = color;
this.domNode.style.color = this._color;
}
public setBackgroundColor(backgroundColor: string): void {
if (this._backgroundColor === backgroundColor) {
return;

View file

@ -221,6 +221,14 @@ export class ActionBar extends Disposable implements IActionRunner {
container.appendChild(this.domNode);
}
private refreshRole(): void {
if (this.length() >= 2) {
this.actionsList.setAttribute('role', 'toolbar');
} else {
this.actionsList.setAttribute('role', 'presentation');
}
}
setAriaLabel(label: string): void {
if (label) {
this.actionsList.setAttribute('aria-label', label);
@ -350,6 +358,7 @@ export class ActionBar extends Disposable implements IActionRunner {
// After a clear actions might be re-added to simply toggle some actions. We should preserve focus #97128
this.focus(this.focusedItem);
}
this.refreshRole();
}
getWidth(index: number): number {
@ -379,6 +388,7 @@ export class ActionBar extends Disposable implements IActionRunner {
this.actionsList.removeChild(this.actionsList.childNodes[index]);
dispose(this.viewItems.splice(index, 1));
this._actionIds.splice(index, 1);
this.refreshRole();
}
}
@ -387,6 +397,7 @@ export class ActionBar extends Disposable implements IActionRunner {
this.viewItems = [];
this._actionIds = [];
DOM.clearNode(this.actionsList);
this.refreshRole();
}
length(): number {

View file

@ -22,6 +22,7 @@ import { ISpliceable } from 'vs/base/common/sequence';
import { IListDragAndDrop, IListDragEvent, IListGestureEvent, IListMouseEvent, IListRenderer, IListTouchEvent, IListVirtualDelegate, ListDragOverEffect } from 'vs/base/browser/ui/list/list';
import { RangeMap, shift } from 'vs/base/browser/ui/list/rangeMap';
import { IRow, RowCache } from 'vs/base/browser/ui/list/rowCache';
import { IObservableValue } from 'vs/base/common/observableValue';
interface IItem<T> {
readonly id: string;
@ -35,6 +36,7 @@ interface IItem<T> {
uri: string | undefined;
dropTarget: boolean;
dragStartDisposable: IDisposable;
checkedDisposable: IDisposable;
}
export interface IListViewDragAndDrop<T> extends IListDragAndDrop<T> {
@ -45,7 +47,7 @@ export interface IListViewAccessibilityProvider<T> {
getSetSize?(element: T, index: number, listLength: number): number;
getPosInSet?(element: T, index: number): number;
getRole?(element: T): string | undefined;
isChecked?(element: T): boolean | undefined;
isChecked?(element: T): boolean | IObservableValue<boolean> | undefined;
}
export interface IListViewOptionsUpdate {
@ -174,7 +176,7 @@ class ListViewAccessibilityProvider<T> implements Required<IListViewAccessibilit
readonly getSetSize: (element: any, index: number, listLength: number) => number;
readonly getPosInSet: (element: any, index: number) => number;
readonly getRole: (element: T) => string | undefined;
readonly isChecked: (element: T) => boolean | undefined;
readonly isChecked: (element: T) => boolean | IObservableValue<boolean> | undefined;
constructor(accessibilityProvider?: IListViewAccessibilityProvider<T>) {
if (accessibilityProvider?.getSetSize) {
@ -515,7 +517,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
row: null,
uri: undefined,
dropTarget: false,
dragStartDisposable: Disposable.None
dragStartDisposable: Disposable.None,
checkedDisposable: Disposable.None
}));
let deleted: IItem<T>[];
@ -783,8 +786,13 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
item.row.domNode.setAttribute('role', role);
const checked = this.accessibilityProvider.isChecked(item.element);
if (typeof checked !== 'undefined') {
item.row.domNode.setAttribute('aria-checked', String(!!checked));
if (typeof checked === 'boolean') {
item.row!.domNode.setAttribute('aria-checked', String(!!checked));
} else if (checked) {
const update = (checked: boolean) => item.row!.domNode.setAttribute('aria-checked', String(!!checked));
update(checked.value);
item.checkedDisposable = checked.onDidChange(update);
}
if (!item.row.domNode.parentElement) {
@ -865,6 +873,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
private removeItemFromDOM(index: number): void {
const item = this.items[index];
item.dragStartDisposable.dispose();
item.checkedDisposable.dispose();
if (item.row) {
const renderer = this.renderers.get(item.templateId);

View file

@ -20,6 +20,7 @@ import { Iterable } from 'vs/base/common/iterator';
import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { ScrollEvent } from 'vs/base/common/scrollable';
import { IThemable } from 'vs/base/common/styler';
import { isIterable } from 'vs/base/common/types';
interface IAsyncDataTreeNode<TInput, T> {
element: TInput | T;
@ -764,15 +765,19 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
if (!node.hasChildren) {
childrenPromise = Promise.resolve(Iterable.empty());
} else {
const slowTimeout = timeout(800);
const children = this.doGetChildren(node);
if (isIterable(children)) {
childrenPromise = Promise.resolve(children);
} else {
const slowTimeout = timeout(800);
slowTimeout.then(() => {
node.slow = true;
this._onDidChangeNodeSlowState.fire(node);
}, _ => null);
slowTimeout.then(() => {
node.slow = true;
this._onDidChangeNodeSlowState.fire(node);
}, _ => null);
childrenPromise = this.doGetChildren(node)
.finally(() => slowTimeout.cancel());
childrenPromise = children.finally(() => slowTimeout.cancel());
}
}
try {
@ -796,21 +801,20 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
}
}
private doGetChildren(node: IAsyncDataTreeNode<TInput, T>): Promise<Iterable<T>> {
private doGetChildren(node: IAsyncDataTreeNode<TInput, T>): Promise<Iterable<T>> | Iterable<T> {
let result = this.refreshPromises.get(node);
if (result) {
return result;
}
result = createCancelablePromise(async () => {
const children = await this.dataSource.getChildren(node.element!);
const children = this.dataSource.getChildren(node.element!);
if (isIterable(children)) {
return this.processChildren(children);
});
this.refreshPromises.set(node, result);
return result.finally(() => { this.refreshPromises.delete(node); });
} else {
result = createCancelablePromise(async () => this.processChildren(await children));
this.refreshPromises.set(node, result);
return result.finally(() => { this.refreshPromises.delete(node); });
}
}
private _onDidChangeCollapseState({ node, deep }: ICollapseStateChangeEvent<IAsyncDataTreeNode<TInput, T> | null, any>): void {

View file

@ -543,6 +543,13 @@ export class Codicon implements CSSIcon {
public static readonly layoutCentered = new Codicon('layout-centered', { fontCharacter: '\\ebf7' });
public static readonly target = new Codicon('target', { fontCharacter: '\\ebf8' });
public static readonly indent = new Codicon('indent', { fontCharacter: '\\ebf9' });
public static readonly recordSmall = new Codicon('record-small', { fontCharacter: '\\ebfa' });
public static readonly errorSmall = new Codicon('error-small', { fontCharacter: '\\ebfb' });
public static readonly arrowCircleDown = new Codicon('arrow-circle-down', { fontCharacter: '\\ebfc' });
public static readonly arrowCircleLeft = new Codicon('arrow-circle-left', { fontCharacter: '\\ebfd' });
public static readonly arrowCircleRight = new Codicon('arrow-circle-right', { fontCharacter: '\\ebfe' });
public static readonly arrowCircleUp = new Codicon('arrow-circle-up', { fontCharacter: '\\ebff' });
// derived icons, that could become separate icons

View file

@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
export interface IObservableValue<T> {
onDidChange: Event<T>;
readonly value: T;
}
export const staticObservableValue = <T>(value: T): IObservableValue<T> => ({
onDidChange: Event.None,
value,
});
export class MutableObservableValue<T> extends Disposable implements IObservableValue<T> {
private readonly changeEmitter = this._register(new Emitter<T>());
public readonly onDidChange = this.changeEmitter.event;
public get value() {
return this._value;
}
public set value(v: T) {
if (v !== this._value) {
this._value = v;
this.changeEmitter.fire(v);
}
}
constructor(private _value: T) {
super();
}
}

View file

@ -522,7 +522,7 @@ export class ChannelClient implements IChannelClient, IDisposable {
},
listen(event: string, arg: any) {
if (that.isDisposed) {
return Promise.reject(errors.canceled());
return Event.None;
}
return that.requestEvent(channelName, event, arg);
}

View file

@ -31,6 +31,7 @@ import { localize } from 'vs/nls';
const $ = dom.$;
interface IListElement {
readonly hasCheckbox: boolean;
readonly index: number;
readonly item: IQuickPickItem;
readonly saneLabel: string;
@ -47,6 +48,7 @@ interface IListElement {
}
class ListElement implements IListElement, IDisposable {
hasCheckbox!: boolean;
index!: number;
item!: IQuickPickItem;
saneLabel!: string;
@ -446,7 +448,9 @@ export class QuickInputList {
.filter(s => !!s)
.join(', ');
const hasCheckbox = this.parent.classList.contains('show-checkboxes');
result.push(new ListElement({
hasCheckbox,
index,
item,
saneLabel,
@ -751,7 +755,18 @@ class QuickInputAccessibilityProvider implements IListAccessibilityProvider<List
return 'listbox';
}
getRole() {
return 'option';
getRole(element: ListElement) {
return element.hasCheckbox ? 'checkbox' : 'option';
}
isChecked(element: ListElement) {
if (!element.hasCheckbox) {
return undefined;
}
return {
value: element.checked,
onDidChange: element.onChecked
};
}
}

View file

@ -299,7 +299,8 @@ export class TextAreaHandler extends ViewPart {
// the selection.
//
// However, the text on the current line needs to be made visible because
// some IME methods allow to glyphs on the current line (by pressing arrow keys).
// some IME methods allow to move to other glyphs on the current line
// (by pressing arrow keys).
//
// (1) The textarea might contain only some parts of the current line,
// like the word before the selection. Also, the content inside the textarea
@ -308,7 +309,7 @@ export class TextAreaHandler extends ViewPart {
//
// (2) Also, we should not make \t characters visible, because their rendering
// inside the <textarea> will not align nicely with our rendering. We therefore
// can hide some of the leading text on the current line.
// will hide (if necessary) some of the leading text on the current line.
const ta = this.textArea.domNode;
const modelSelection = this._modelSelections[0];
@ -346,7 +347,7 @@ export class TextAreaHandler extends ViewPart {
return { distanceToModelLineEnd };
})();
// Scroll to reveal the location in the editor
// Scroll to reveal the location in the editor where composition occurs
this._context.viewModel.revealRange(
'keyboard',
true,
@ -620,7 +621,6 @@ export class TextAreaHandler extends ViewPart {
if (startPosition && endPosition && visibleStart && visibleEnd && visibleEnd.left >= this._scrollLeft && visibleStart.left <= this._scrollLeft + this._contentWidth) {
const top = (this._context.viewLayout.getVerticalOffsetForLineNumber(this._primaryCursorPosition.lineNumber) - this._scrollTop);
const lineCount = this._newlinecount(this.textArea.domNode.value.substr(0, this.textArea.domNode.selectionStart));
this.textArea.domNode.scrollTop = lineCount * this._lineHeight;
let scrollLeft = this._visibleTextArea.widthOfHiddenLineTextBefore;
let left = (this._contentLeft + visibleStart.left - this._scrollLeft);
@ -640,16 +640,6 @@ export class TextAreaHandler extends ViewPart {
width = this._contentWidth;
}
this.textArea.domNode.scrollLeft = scrollLeft;
this._renderInsideEditor(
null,
top,
left,
width,
this._lineHeight
);
// Try to render the textarea with the color/font style to match the text under it
const viewLineData = this._context.viewModel.getViewLineData(startPosition.lineNumber);
const startTokenIndex = viewLineData.tokens.findTokenIndexAtOffset(startPosition.column - 1);
@ -667,14 +657,23 @@ export class TextAreaHandler extends ViewPart {
strikethrough: false,
};
}
const color: Color | undefined = (TokenizationRegistry.getColorMap() || [])[presentation.foreground];
this.textArea.domNode.style.color = (color ? Color.Format.CSS.formatHex(color) : 'inherit');
this.textArea.domNode.style.fontStyle = (presentation.italic ? 'italic' : 'inherit');
if (presentation.bold) {
// fontWeight is also set by `applyFontInfo`, so only overwrite it if necessary
this.textArea.domNode.style.fontWeight = 'bold';
}
this.textArea.domNode.style.textDecoration = `${presentation.underline ? ' underline' : ''}${presentation.strikethrough ? ' line-through' : ''}`;
this.textArea.domNode.scrollTop = lineCount * this._lineHeight;
this.textArea.domNode.scrollLeft = scrollLeft;
this._doRender({
lastRenderPosition: null,
top: top,
left: left,
width: width,
height: this._lineHeight,
useCover: false,
color: (TokenizationRegistry.getColorMap() || [])[presentation.foreground],
italic: presentation.italic,
bold: presentation.bold,
underline: presentation.underline,
strikethrough: presentation.strikethrough
});
}
return;
}
@ -704,11 +703,14 @@ export class TextAreaHandler extends ViewPart {
if (platform.isMacintosh) {
// For the popup emoji input, we will make the text area as high as the line height
// We will also make the fontSize and lineHeight the correct dimensions to help with the placement of these pickers
this._renderInsideEditor(
this._primaryCursorPosition,
top, left,
canUseZeroSizeTextarea ? 0 : 1, this._lineHeight
);
this._doRender({
lastRenderPosition: this._primaryCursorPosition,
top: top,
left: left,
width: (canUseZeroSizeTextarea ? 0 : 1),
height: this._lineHeight,
useCover: false
});
// In case the textarea contains a word, we're going to try to align the textarea's cursor
// with our cursor by scrolling the textarea as much as possible
this.textArea.domNode.scrollLeft = this._primaryCursorVisibleRange.left;
@ -717,11 +719,14 @@ export class TextAreaHandler extends ViewPart {
return;
}
this._renderInsideEditor(
this._primaryCursorPosition,
top, left,
canUseZeroSizeTextarea ? 0 : 1, canUseZeroSizeTextarea ? 0 : 1
);
this._doRender({
lastRenderPosition: this._primaryCursorPosition,
top: top,
left: left,
width: (canUseZeroSizeTextarea ? 0 : 1),
height: (canUseZeroSizeTextarea ? 0 : 1),
useCover: false
});
}
private _newlinecount(text: string): number {
@ -737,50 +742,43 @@ export class TextAreaHandler extends ViewPart {
return result;
}
private _renderInsideEditor(renderedPosition: Position | null, top: number, left: number, width: number, height: number): void {
this._lastRenderPosition = renderedPosition;
const ta = this.textArea;
const tac = this.textAreaCover;
applyFontInfo(ta, this._fontInfo);
ta.setTop(top);
ta.setLeft(left);
ta.setWidth(width);
ta.setHeight(height);
tac.setTop(0);
tac.setLeft(0);
tac.setWidth(0);
tac.setHeight(0);
}
private _renderAtTopLeft(): void {
this._lastRenderPosition = null;
const ta = this.textArea;
const tac = this.textAreaCover;
applyFontInfo(ta, this._fontInfo);
ta.setTop(0);
ta.setLeft(0);
tac.setTop(0);
tac.setLeft(0);
if (canUseZeroSizeTextarea) {
ta.setWidth(0);
ta.setHeight(0);
tac.setWidth(0);
tac.setHeight(0);
return;
}
// (in WebKit the textarea is 1px by 1px because it cannot handle input to a 0x0 textarea)
// specifically, when doing Korean IME, setting the textarea to 0x0 breaks IME badly.
this._doRender({
lastRenderPosition: null,
top: 0,
left: 0,
width: (canUseZeroSizeTextarea ? 0 : 1),
height: (canUseZeroSizeTextarea ? 0 : 1),
useCover: true
});
}
ta.setWidth(1);
ta.setHeight(1);
tac.setWidth(1);
tac.setHeight(1);
private _doRender(renderData: IRenderData): void {
this._lastRenderPosition = renderData.lastRenderPosition;
const ta = this.textArea;
const tac = this.textAreaCover;
applyFontInfo(ta, this._fontInfo);
ta.setTop(renderData.top);
ta.setLeft(renderData.left);
ta.setWidth(renderData.width);
ta.setHeight(renderData.height);
ta.setColor(renderData.color ? Color.Format.CSS.formatHex(renderData.color) : '');
ta.setFontStyle(renderData.italic ? 'italic' : '');
if (renderData.bold) {
// fontWeight is also set by `applyFontInfo`, so only overwrite it if necessary
ta.setFontWeight('bold');
}
ta.setTextDecoration(`${renderData.underline ? ' underline' : ''}${renderData.strikethrough ? ' line-through' : ''}`);
tac.setTop(renderData.useCover ? renderData.top : 0);
tac.setLeft(renderData.useCover ? renderData.left : 0);
tac.setWidth(renderData.useCover ? renderData.width : 0);
tac.setHeight(renderData.useCover ? renderData.height : 0);
const options = this._context.configuration.options;
@ -796,6 +794,21 @@ export class TextAreaHandler extends ViewPart {
}
}
interface IRenderData {
lastRenderPosition: Position | null;
top: number;
left: number;
width: number;
height: number;
useCover: boolean;
color?: Color | null;
italic?: boolean;
bold?: boolean;
underline?: boolean;
strikethrough?: boolean;
}
function measureText(text: string, fontInfo: BareFontInfo): number {
if (text.length === 0) {
return 0;

View file

@ -8,6 +8,7 @@ import { Searcher } from 'vs/editor/common/model/textModelSearch';
import * as strings from 'vs/base/common/strings';
import { IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker';
import { assertNever } from 'vs/base/common/types';
import { DEFAULT_WORD_REGEXP, getWordAtText } from 'vs/editor/common/core/wordHelper';
export class UnicodeTextModelHighlighter {
public static computeUnicodeHighlights(model: IUnicodeCharacterSearcherTarget, options: UnicodeHighlighterOptions, range?: IRange): IUnicodeHighlightsResult {
@ -60,7 +61,8 @@ export class UnicodeTextModelHighlighter {
}
}
const str = lineContent.substring(startIndex, endIndex);
const highlightReason = codePointHighlighter.shouldHighlightNonBasicASCII(str);
const word = getWordAtText(startIndex + 1, DEFAULT_WORD_REGEXP, lineContent, 0);
const highlightReason = codePointHighlighter.shouldHighlightNonBasicASCII(str, word ? word.word : null);
if (highlightReason !== SimpleHighlightReason.None) {
if (highlightReason === SimpleHighlightReason.Ambiguous) {
@ -96,7 +98,7 @@ export class UnicodeTextModelHighlighter {
public static computeUnicodeHighlightReason(char: string, options: UnicodeHighlighterOptions): UnicodeHighlighterReason | null {
const codePointHighlighter = new CodePointHighlighter(options);
const reason = codePointHighlighter.shouldHighlightNonBasicASCII(char);
const reason = codePointHighlighter.shouldHighlightNonBasicASCII(char, null);
switch (reason) {
case SimpleHighlightReason.None:
return null;
@ -159,7 +161,9 @@ class CodePointHighlighter {
if (this.options.invisibleCharacters) {
for (const cp of strings.InvisibleCharacters.codePoints) {
set.add(cp);
if (!isAllowedInvisibleCharacter(String.fromCodePoint(cp))) {
set.add(cp);
}
}
}
@ -176,7 +180,7 @@ class CodePointHighlighter {
return set;
}
public shouldHighlightNonBasicASCII(character: string): SimpleHighlightReason {
public shouldHighlightNonBasicASCII(character: string, wordContext: string | null): SimpleHighlightReason {
const codePoint = character.codePointAt(0)!;
if (this.allowedCodePoints.has(codePoint)) {
@ -187,10 +191,27 @@ class CodePointHighlighter {
return SimpleHighlightReason.NonBasicASCII;
}
let hasNonConfusableNonBasicAsciiCharacter = false;
if (wordContext) {
for (let char of wordContext) {
const codePoint = char.codePointAt(0)!;
if (
!strings.isBasicASCII(char) &&
!this.ambiguousCharacters.isAmbiguous(codePoint) &&
!strings.InvisibleCharacters.isInvisibleCharacter(codePoint)
) {
hasNonConfusableNonBasicAsciiCharacter = true;
}
}
}
if (hasNonConfusableNonBasicAsciiCharacter) {
return SimpleHighlightReason.None;
}
if (this.options.invisibleCharacters) {
const isAllowedInvisibleCharacter = character === ' ' || character === '\n' || character === '\t';
// TODO check for emojis
if (!isAllowedInvisibleCharacter && strings.InvisibleCharacters.isInvisibleCharacter(codePoint)) {
if (!isAllowedInvisibleCharacter(character) && strings.InvisibleCharacters.isInvisibleCharacter(codePoint)) {
return SimpleHighlightReason.Invisible;
}
}
@ -205,6 +226,10 @@ class CodePointHighlighter {
}
}
function isAllowedInvisibleCharacter(character: string): boolean {
return character === ' ' || character === '\n' || character === '\t';
}
const enum SimpleHighlightReason {
None,
NonBasicASCII,

View file

@ -107,6 +107,7 @@ export interface NativeParsedArgs {
'allow-insecure-localhost'?: boolean;
'log-net-log'?: string;
'vmodule'?: string;
'disable-dev-shm-usage'?: boolean;
// MS Build command line arg
'ms-enable-electron-run-as-node'?: boolean;

View file

@ -149,6 +149,7 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
'log-net-log': { type: 'string' },
'vmodule': { type: 'string' },
'_urls': { type: 'string[]' },
'disable-dev-shm-usage': { type: 'boolean' },
_: { type: 'string[]' } // main arguments
};

View file

@ -18,6 +18,15 @@ export namespace WebFileSystemAccess {
return false;
}
export function isFileSystemHandle(handle: unknown): handle is FileSystemHandle {
const candidate = handle as FileSystemHandle | undefined;
if (!candidate) {
return false;
}
return typeof candidate.kind === 'string' && typeof candidate.queryPermission === 'function' && typeof candidate.requestPermission === 'function';
}
export function isFileSystemFileHandle(handle: FileSystemHandle): handle is FileSystemFileHandle {
return handle.kind === 'file';
}

View file

@ -155,10 +155,9 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
private async createResourceLock(resource: URI): Promise<IDisposable> {
this.logService.trace(`[Disk FileSystemProvider]: request to acquire resource lock (${this.toFilePath(resource)})`);
// Await pending locks for resource
// It is possible for a new lock being
// added right after opening, so we have
// to loop over locks until no lock remains
// Await pending locks for resource. It is possible for a new lock being
// added right after opening, so we have to loop over locks until no lock
// remains.
let existingLock: Barrier | undefined = undefined;
while (existingLock = this.resourceLocks.get(resource)) {
this.logService.trace(`[Disk FileSystemProvider]: waiting for resource lock to be released (${this.toFilePath(resource)})`);
@ -175,8 +174,12 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
return toDisposable(() => {
this.logService.trace(`[Disk FileSystemProvider]: resource lock disposed (${this.toFilePath(resource)})`);
// Delete and open lock
this.resourceLocks.delete(resource);
// Delete lock if it is still ours
if (this.resourceLocks.get(resource) === newLock) {
this.resourceLocks.delete(resource);
}
// Open lock
newLock.open();
});
}
@ -284,6 +287,7 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
if (isFileOpenForWriteOptions(opts)) {
if (isWindows) {
try {
// On Windows and if the file exists, we use a different strategy of saving the file
// by first truncating the file and then writing with r+ flag. This helps to save hidden files on Windows
// (see https://github.com/microsoft/vscode/issues/931) and prevent removing alternate data streams
@ -299,14 +303,15 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
}
}
// we take opts.create as a hint that the file is opened for writing
// We take opts.create as a hint that the file is opened for writing
// as such we use 'w' to truncate an existing or create the
// file otherwise. we do not allow reading.
if (!flags) {
flags = 'w';
}
} else {
// otherwise we assume the file is opened for reading
// Otherwise we assume the file is opened for reading
// as such we use 'r' to neither truncate, nor create
// the file.
flags = 'r';
@ -329,7 +334,7 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
}
}
// remember this handle to track file position of the handle
// Remember this handle to track file position of the handle
// we init the position to 0 since the file descriptor was
// just created and the position was not moved so far (see
// also http://man7.org/linux/man-pages/man2/open.2.html -
@ -341,21 +346,42 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
this.writeHandles.set(handle, resource);
}
// remember that this handle has an associated lock
if (lock) {
const previousLock = this.mapHandleToLock.get(handle);
// Remember that this handle has an associated lock
this.mapHandleToLock.set(handle, lock);
// There is a slight chance that a resource lock for a
// handle was not yet disposed when we acquire a new
// lock, so we must ensure to dispose the previous lock
// before storing a new one for the same handle, other
// wise we end up in a deadlock situation
// https://github.com/microsoft/vscode/issues/142462
if (previousLock) {
previousLock.dispose();
}
}
return handle;
}
async close(fd: number): Promise<void> {
// It is very important that we keep any associated lock
// for the file handle before attempting to call `fs.close(fd)`
// because of a possible race condition: as soon as a file
// handle is released, the OS may assign the same handle to
// the next `fs.open` call and as such it is possible that our
// lock is getting overwritten
const lockForHandle = this.mapHandleToLock.get(fd);
try {
// remove this handle from map of positions
// Remove this handle from map of positions
this.mapHandleToPos.delete(fd);
// if a handle is closed that was used for writing, ensure
// If a handle is closed that was used for writing, ensure
// to flush the contents to disk if possible.
if (this.writeHandles.delete(fd) && this.canFlush) {
try {
@ -372,9 +398,10 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
} catch (error) {
throw this.toFileSystemProviderError(error);
} finally {
const lockForHandle = this.mapHandleToLock.get(fd);
if (lockForHandle) {
this.mapHandleToLock.delete(fd);
if (this.mapHandleToLock.get(fd) === lockForHandle) {
this.mapHandleToLock.delete(fd); // only delete from map if this is still our lock!
}
lockForHandle.dispose();
}
}
@ -397,7 +424,7 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
private normalizePos(fd: number, pos: number): number | null {
// when calling fs.read/write we try to avoid passing in the "pos" argument and
// When calling fs.read/write we try to avoid passing in the "pos" argument and
// rather prefer to pass in "null" because this avoids an extra seek(pos)
// call that in some cases can even fail (e.g. when opening a file over FTP -
// see https://github.com/microsoft/vscode/issues/73884).
@ -454,7 +481,8 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
}
async write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
// we know at this point that the file to write to is truncated and thus empty
// We know at this point that the file to write to is truncated and thus empty
// if the write now fails, the file remains empty. as such we really try hard
// to ensure the write succeeds by retrying up to three times.
return retry(() => this.doWrite(fd, pos, data, offset, length), 100 /* ms delay */, 3 /* retries */);
@ -518,7 +546,7 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
await Promises.move(fromFilePath, toFilePath);
} catch (error) {
// rewrite some typical errors that can happen especially around symlinks
// Rewrite some typical errors that can happen especially around symlinks
// to something the user can better understand
if (error.code === 'EINVAL' || error.code === 'EBUSY' || error.code === 'ENAMETOOLONG') {
error = new Error(localize('moveError', "Unable to move '{0}' into '{1}' ({2}).", basename(fromFilePath), basename(dirname(toFilePath)), error.toString()));
@ -545,7 +573,7 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
await Promises.copy(fromFilePath, toFilePath, { preserveSymlinks: true });
} catch (error) {
// rewrite some typical errors that can happen especially around symlinks
// Rewrite some typical errors that can happen especially around symlinks
// to something the user can better understand
if (error.code === 'EINVAL' || error.code === 'EBUSY' || error.code === 'ENAMETOOLONG') {
error = new Error(localize('copyError', "Unable to copy '{0}' into '{1}' ({2}).", basename(fromFilePath), basename(dirname(toFilePath)), error.toString()));
@ -569,7 +597,7 @@ export class DiskFileSystemProvider extends AbstractDiskFileSystemProvider imple
throw createFileSystemProviderError(localize('fileCopyErrorPathCase', "'File cannot be copied to same path with different path case"), FileSystemProviderErrorCode.FileExists);
}
// handle existing target (unless this is a case change)
// Handle existing target (unless this is a case change)
if (!isSameResourceWithDifferentPathCase && await Promises.exists(toFilePath)) {
if (!overwrite) {
throw createFileSystemProviderError(localize('fileCopyErrorExists', "File at target already exists"), FileSystemProviderErrorCode.FileExists);

View file

@ -598,7 +598,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe
async kill(code?: number): Promise<void> {
this.logService.trace('Lifecycle#kill()');
// Give main process participants a chance to oderly shutdown
// Give main process participants a chance to orderly shutdown
await this.fireOnWillShutdown();
// From extension tests we have seen issues where calling app.exit()

View file

@ -102,10 +102,11 @@ export const enum TerminalSettingId {
ShowLinkHover = 'terminal.integrated.showLinkHover',
IgnoreProcessNames = 'terminal.integrated.ignoreProcessNames',
AutoReplies = 'terminal.integrated.autoReplies',
EnableShellIntegration = 'terminal.integrated.enableShellIntegration',
ShowShellIntegrationWelcome = 'terminal.integrated.showShellIntegrationWelcome',
CommandIcon = 'terminal.integrated.commandIcon',
CommandIconError = 'terminal.integrated.commandIconError',
ShellIntegrationEnabled = 'terminal.integrated.shellIntegration.enabled',
ShellIntegrationShowWelcome = 'terminal.integrated.shellIntegration.showWelcome',
ShellIntegrationCommandIcon = 'terminal.integrated.shellIntegration.commandIcon',
ShellIntegrationCommandIconError = 'terminal.integrated.shellIntegration.commandIconError',
ShellIntegrationCommandIconSkipped = 'terminal.integrated.shellIntegration.commandIconSkipped',
ShellIntegrationCommandHistory = 'terminal.integrated.shellIntegration.history'
}

View file

@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Schemas } from 'vs/base/common/network';
import { isWeb } from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { IWorkspace } from 'vs/platform/workspace/common/workspace';
@ -27,3 +28,11 @@ export function getVirtualWorkspaceScheme(workspace: IWorkspace): string | undef
export function isVirtualWorkspace(workspace: IWorkspace): boolean {
return getVirtualWorkspaceLocation(workspace) !== undefined;
}
export function isTemporaryWorkspace(workspace: IWorkspace): boolean {
if (!isWeb) {
return false; // this concept only exists in web currently
}
return workspace.configuration?.scheme === Schemas.tmp;
}

View file

@ -21,26 +21,19 @@ import { IProcessEnvironment, isWindows } from 'vs/base/common/platform';
import { logRemoteEntry } from 'vs/workbench/services/extensions/common/remoteConsoleUtil';
import { removeDangerousEnvVariables } from 'vs/base/node/processes';
export async function buildUserEnvironment(startParamsEnv: { [key: string]: string | null } = {}, language: string, isDebug: boolean, environmentService: IServerEnvironmentService, logService: ILogService): Promise<IProcessEnvironment> {
export async function buildUserEnvironment(startParamsEnv: { [key: string]: string | null } = {}, withUserShellEnvironment: boolean, language: string, isDebug: boolean, environmentService: IServerEnvironmentService, logService: ILogService): Promise<IProcessEnvironment> {
const nlsConfig = await getNLSConfiguration(language, environmentService.userDataPath);
let userShellEnv: typeof process.env | undefined = undefined;
try {
userShellEnv = await getResolvedShellEnv(logService, environmentService.args, process.env);
} catch (error) {
logService.error('ExtensionHostConnection#buildUserEnvironment resolving shell environment failed', error);
userShellEnv = {};
let userShellEnv: typeof process.env = {};
if (withUserShellEnvironment) {
try {
userShellEnv = await getResolvedShellEnv(logService, environmentService.args, process.env);
} catch (error) {
logService.error('ExtensionHostConnection#buildUserEnvironment resolving shell environment failed', error);
}
}
const binFolder = environmentService.isBuilt ? join(environmentService.appRoot, 'bin') : join(environmentService.appRoot, 'resources', 'server', 'bin-dev');
const remoteCliBinFolder = join(binFolder, 'remote-cli'); // contains the `code` command that can talk to the remote server
const processEnv = process.env;
let PATH = startParamsEnv['PATH'] || (userShellEnv ? userShellEnv['PATH'] : undefined) || processEnv['PATH'];
if (PATH) {
PATH = remoteCliBinFolder + delimiter + PATH;
} else {
PATH = remoteCliBinFolder;
}
const env: IProcessEnvironment = {
...processEnv,
@ -57,13 +50,23 @@ export async function buildUserEnvironment(startParamsEnv: { [key: string]: stri
},
...startParamsEnv
};
const binFolder = environmentService.isBuilt ? join(environmentService.appRoot, 'bin') : join(environmentService.appRoot, 'resources', 'server', 'bin-dev');
const remoteCliBinFolder = join(binFolder, 'remote-cli'); // contains the `code` command that can talk to the remote server
let PATH = readCaseInsensitive(env, 'PATH');
if (PATH) {
PATH = remoteCliBinFolder + delimiter + PATH;
} else {
PATH = remoteCliBinFolder;
}
setCaseInsensitive(env, 'PATH', PATH);
if (!environmentService.args['without-browser-env-var']) {
env.BROWSER = join(binFolder, 'helpers', isWindows ? 'browser.cmd' : 'browser.sh'); // a command that opens a browser on the local machine
}
setCaseInsensitive(env, 'PATH', PATH);
removeNulls(env);
return env;
}
@ -189,7 +192,7 @@ export class ExtensionHostConnection {
execArgv = [`--inspect${startParams.break ? '-brk' : ''}=${startParams.port}`];
}
const env = await buildUserEnvironment(startParams.env, startParams.language, !!startParams.debugId, this._environmentService, this._logService);
const env = await buildUserEnvironment(startParams.env, true, startParams.language, !!startParams.debugId, this._environmentService, this._logService);
removeDangerousEnvVariables(env);
const opts = {
@ -252,6 +255,12 @@ export class ExtensionHostConnection {
}
}
function readCaseInsensitive(env: { [key: string]: string | undefined }, key: string): string | undefined {
const pathKeys = Object.keys(env).filter(k => k.toLowerCase() === key.toLowerCase());
const pathKey = pathKeys.length > 0 ? pathKeys[0] : key;
return env[pathKey];
}
function setCaseInsensitive(env: { [key: string]: unknown }, key: string, value: string): void {
const pathKeys = Object.keys(env).filter(k => k.toLowerCase() === key.toLowerCase());
const pathKey = pathKeys.length > 0 ? pathKeys[0] : key;

View file

@ -180,13 +180,7 @@ export class RemoteTerminalChannel extends Disposable implements IServerChannel<
};
let baseEnv: platform.IProcessEnvironment;
if (args.shellLaunchConfig.useShellEnvironment) {
this._logService.trace('*');
baseEnv = await buildUserEnvironment(args.resolverEnv, platform.language, false, this._environmentService, this._logService);
} else {
baseEnv = this._getEnvironment();
}
const baseEnv = await buildUserEnvironment(args.resolverEnv, !!args.shellLaunchConfig.useShellEnvironment, platform.language, false, this._environmentService, this._logService);
this._logService.trace('baseEnv', baseEnv);
const reviveWorkspaceFolder = (workspaceData: IWorkspaceFolderData): IWorkspaceFolder => {

View file

@ -279,6 +279,7 @@ export class WebClientServer {
developmentOptions: { enableSmokeTestDriver: this._environmentService.driverHandle === 'web' ? true : undefined },
settingsSyncOptions: !this._environmentService.isBuilt && this._environmentService.args['enable-sync'] ? { enabled: true } : undefined,
productConfiguration: <Partial<IProductConfiguration>>{
embedderIdentifier: 'server-distro',
extensionsGallery: this._webExtensionResourceUrlTemplate ? {
...this._productService.extensionsGallery,
'resourceUrlTemplate': this._webExtensionResourceUrlTemplate.with({

View file

@ -21,7 +21,7 @@ export class MainThreadEditorTabs {
private readonly _dispoables = new DisposableStore();
private readonly _proxy: IExtHostEditorTabsShape;
private _tabGroupModel: IEditorTabGroupDto[] = [];
private readonly _tabModel: Map<number, IEditorTabDto[]> = new Map();
private readonly _groupModel: Map<number, IEditorTabGroupDto> = new Map();
constructor(
extHostContext: IExtHostContext,
@ -55,7 +55,8 @@ export class MainThreadEditorTabs {
resource: editor instanceof SideBySideEditorInput ? EditorResourceAccessor.getCanonicalUri(editor, { supportSideBySide: SideBySideEditor.PRIMARY }) : EditorResourceAccessor.getCanonicalUri(editor),
editorId,
additionalResourcesAndViewIds: [],
isActive: group.isActive(editor)
isActive: group.isActive(editor),
isDirty: editor.isDirty()
};
tab.additionalResourcesAndViewIds.push({ resource: tab.resource, viewId: tab.editorId });
if (editor instanceof SideBySideEditorInput) {
@ -99,7 +100,10 @@ export class MainThreadEditorTabs {
* @param editorIndex The index of the editor within that group
*/
private _onDidTabLabelChange(groupId: number, editorInput: EditorInput, editorIndex: number) {
this._tabGroupModel[groupId].tabs[editorIndex].label = editorInput.getName();
const tabs = this._groupModel.get(groupId)?.tabs;
if (tabs) {
tabs[editorIndex].label = editorInput.getName();
}
}
/**
@ -110,11 +114,18 @@ export class MainThreadEditorTabs {
*/
private _onDidTabOpen(groupId: number, editorInput: EditorInput, editorIndex: number) {
const group = this._editorGroupsService.getGroup(groupId);
if (!group) {
// Even if the editor service knows about the group the group might not exist yet in our model
const groupInModel = this._groupModel.get(groupId) !== undefined;
// Means a new group was likely created so we rebuild the model
if (!group || !groupInModel) {
this._createTabsModel();
return;
}
// Splice tab into group at index editorIndex
this._tabGroupModel[groupId].tabs.splice(editorIndex, 0, this._buildTabObject(editorInput, group));
const tabs = this._groupModel.get(groupId)?.tabs;
if (tabs) {
// Splice tab into group at index editorIndex
tabs.splice(editorIndex, 0, this._buildTabObject(editorInput, group));
}
}
/**
@ -124,15 +135,24 @@ export class MainThreadEditorTabs {
*/
private _onDidTabClose(groupId: number, editorIndex: number) {
const group = this._editorGroupsService.getGroup(groupId);
if (!group) {
const tabs = this._groupModel.get(groupId)?.tabs;
// Something is wrong with the model state so we rebuild
if (!group || !tabs) {
this._createTabsModel();
return;
}
// Splice tab into group at index editorIndex
this._tabGroupModel[groupId].tabs.splice(editorIndex, 1);
tabs.splice(editorIndex, 1);
// If no tabs it's an empty group and gets deleted from the model
// In the future we may want to support empty groups
if (this._tabGroupModel[groupId].tabs.length === 0) {
this._tabGroupModel.splice(groupId, 1);
if (tabs.length === 0) {
for (let i = 0; i < this._tabGroupModel.length; i++) {
if (this._tabGroupModel[i].groupId === group.id) {
this._tabGroupModel.splice(i, 1);
return;
}
}
}
}
@ -142,7 +162,10 @@ export class MainThreadEditorTabs {
* @param editorIndex The index of the tab
*/
private _onDidTabActiveChange(groupId: number, editorIndex: number) {
const tabs = this._tabGroupModel[groupId].tabs;
const tabs = this._groupModel.get(groupId)?.tabs;
if (!tabs) {
return;
}
let activeTab: IEditorTabDto | undefined;
for (let i = 0; i < tabs.length; i++) {
if (i === editorIndex) {
@ -152,7 +175,19 @@ export class MainThreadEditorTabs {
tabs[i].isActive = false;
}
}
this._tabGroupModel[groupId].activeTab = activeTab;
// null assertion is ok here because if tabs is undefined then we would've returned above.
// Therefore there must be a group here.
this._groupModel.get(groupId)!.activeTab = activeTab;
}
private _onDidTabDirty(groupId: number, editorIndex: number, editor: EditorInput) {
const tab = this._groupModel.get(groupId)?.tabs[editorIndex];
// Something wrong with the model staate so we rebuild
if (!tab) {
this._createTabsModel();
return;
}
tab.isDirty = editor.isDirty();
}
/**
@ -160,7 +195,7 @@ export class MainThreadEditorTabs {
*/
private _createTabsModel(): void {
this._tabGroupModel = [];
this._tabModel.clear();
this._groupModel.clear();
let tabs: IEditorTabDto[] = [];
for (const group of this._editorGroupsService.groups) {
const currentTabGroupModel: IEditorTabGroupDto = {
@ -180,7 +215,7 @@ export class MainThreadEditorTabs {
}
currentTabGroupModel.tabs = tabs;
this._tabGroupModel.push(currentTabGroupModel);
this._tabModel.set(group.id, tabs);
this._groupModel.set(group.id, currentTabGroupModel);
tabs = [];
}
}
@ -217,25 +252,30 @@ export class MainThreadEditorTabs {
return;
}
case GroupModelChangeKind.EDITOR_LABEL:
if (event.editor && event.editorIndex) {
if (event.editor !== undefined && event.editorIndex !== undefined) {
this._onDidTabLabelChange(event.groupId, event.editor, event.editorIndex);
break;
}
case GroupModelChangeKind.EDITOR_OPEN:
if (event.editor && event.editorIndex) {
if (event.editor !== undefined && event.editorIndex !== undefined) {
this._onDidTabOpen(event.groupId, event.editor, event.editorIndex);
break;
}
case GroupModelChangeKind.EDITOR_CLOSE:
if (event.editorIndex) {
if (event.editorIndex !== undefined) {
this._onDidTabClose(event.groupId, event.editorIndex);
break;
}
case GroupModelChangeKind.EDITOR_ACTIVE:
if (event.editorIndex) {
if (event.editorIndex !== undefined) {
this._onDidTabActiveChange(event.groupId, event.editorIndex);
break;
}
case GroupModelChangeKind.EDITOR_DIRTY:
if (event.editorIndex !== undefined && event.editor !== undefined) {
this._onDidTabDirty(event.groupId, event.editorIndex, event.editor);
break;
}
default:
// If it's not an optimized case we rebuild the tabs model from scratch
this._createTabsModel();
@ -252,7 +292,7 @@ export class MainThreadEditorTabs {
return;
}
// If group index is out of bounds then we make a new one that's to the right of the last group
if (this._tabModel.get(groupId) === undefined) {
if (this._groupModel.get(groupId) === undefined) {
targetGroup = this._editorGroupsService.addGroup(this._editorGroupsService.groups[this._editorGroupsService.groups.length - 1], GroupDirection.RIGHT, undefined);
} else {
targetGroup = this._editorGroupsService.getGroup(groupId);

View file

@ -631,6 +631,7 @@ export interface IEditorTabDto {
resource?: UriComponents;
editorId?: string;
isActive: boolean;
isDirty: boolean;
additionalResourcesAndViewIds: { resource?: UriComponents; viewId?: string }[];
}

View file

@ -17,6 +17,7 @@ export interface IEditorTab {
resource: vscode.Uri | undefined;
viewId: string | undefined;
isActive: boolean;
isDirty: boolean;
additionalResourcesAndViewIds: { resource: vscode.Uri | undefined; viewId: string | undefined }[];
move(index: number, viewColumn: ViewColumn): Promise<void>;
close(): Promise<void>;
@ -91,6 +92,7 @@ export class ExtHostEditorTabs implements IExtHostEditorTabs {
additionalResourcesAndViewIds: tabDto.additionalResourcesAndViewIds.map(({ resource, viewId }) => ({ resource: URI.revive(resource), viewId })),
viewId: tabDto.editorId,
isActive: tabDto.isActive,
isDirty: tabDto.isDirty,
move: async (index: number, viewColumn: ViewColumn) => {
this._proxy.$moveTab(tabDto, index, typeConverters.ViewColumn.from(viewColumn));
// TODO: Need an on did change tab event at the group level

View file

@ -45,8 +45,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
import { ILogService } from 'vs/platform/log/common/log';
import { DeferredPromise, Promises } from 'vs/base/common/async';
import { IBannerService } from 'vs/workbench/services/banner/browser/bannerService';
import { getVirtualWorkspaceScheme } from 'vs/platform/workspace/common/virtualWorkspace';
import { Schemas } from 'vs/base/common/network';
import { isTemporaryWorkspace } from 'vs/platform/workspace/common/virtualWorkspace';
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
import { ActivitybarPart } from 'vs/workbench/browser/parts/activitybar/activitybarPart';
import { AuxiliaryBarPart } from 'vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart';
@ -459,12 +458,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
const windowInitializationState: IWorkbenchLayoutWindowInitializationState = {
editor: {
restoreEditors: this.shouldRestoreEditors(this.contextService, initialFilesToOpen),
editorsToOpen: this.resolveEditorsToOpen(fileService, this.contextService, initialFilesToOpen),
editorsToOpen: this.resolveEditorsToOpen(fileService, initialFilesToOpen)
},
views: {
defaults: this.getDefaultLayoutViews(this.environmentService, this.storageService),
containerToRestore: {}
},
}
};
// Window Runtime State
@ -551,11 +550,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
private shouldRestoreEditors(contextService: IWorkspaceContextService, initialFilesToOpen: IInitialFilesToOpen | undefined): boolean {
// Restore editors based on a set of rules:
// - never when running in web on `tmp` scheme
// - never when running on temporary workspace
// - not when we have files to open, unless:
// - always when `window.restoreWindows: preserve`
if (isWeb && getVirtualWorkspaceScheme(contextService.getWorkspace()) === Schemas.tmp) {
if (isTemporaryWorkspace(contextService.getWorkspace())) {
return false;
}
@ -567,7 +566,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
return this.windowState.initialization.editor.restoreEditors;
}
private resolveEditorsToOpen(fileService: IFileService, contextService: IWorkspaceContextService, initialFilesToOpen: IInitialFilesToOpen | undefined): Promise<IUntypedEditorInput[]> | IUntypedEditorInput[] {
private resolveEditorsToOpen(fileService: IFileService, initialFilesToOpen: IInitialFilesToOpen | undefined): Promise<IUntypedEditorInput[]> | IUntypedEditorInput[] {
// Files to open, diff or create
if (initialFilesToOpen) {

View file

@ -64,17 +64,17 @@ export const LayoutStateKeys = {
PANEL_WAS_LAST_MAXIMIZED: new RuntimeStateKey<boolean>('panel.wasLastMaximized', StorageScope.WORKSPACE, StorageTarget.USER, false),
// Part Positions
SIDEBAR_POSITON: new RuntimeStateKey<Position>('sideBar.position', StorageScope.GLOBAL, StorageTarget.USER, Position.LEFT),
SIDEBAR_POSITON: new RuntimeStateKey<Position>('sideBar.position', StorageScope.WORKSPACE, StorageTarget.USER, Position.LEFT),
PANEL_POSITION: new RuntimeStateKey<Position>('panel.position', StorageScope.WORKSPACE, StorageTarget.USER, Position.BOTTOM),
PANEL_ALIGNMENT: new RuntimeStateKey<PanelAlignment>('panel.alignment', StorageScope.GLOBAL, StorageTarget.USER, 'center'),
// Part Visibility
ACTIVITYBAR_HIDDEN: new RuntimeStateKey<boolean>('activityBar.hidden', StorageScope.GLOBAL, StorageTarget.USER, false, true),
ACTIVITYBAR_HIDDEN: new RuntimeStateKey<boolean>('activityBar.hidden', StorageScope.WORKSPACE, StorageTarget.USER, false, true),
SIDEBAR_HIDDEN: new RuntimeStateKey<boolean>('sideBar.hidden', StorageScope.WORKSPACE, StorageTarget.USER, false),
EDITOR_HIDDEN: new RuntimeStateKey<boolean>('editor.hidden', StorageScope.WORKSPACE, StorageTarget.USER, false),
PANEL_HIDDEN: new RuntimeStateKey<boolean>('panel.hidden', StorageScope.WORKSPACE, StorageTarget.USER, true),
AUXILIARYBAR_HIDDEN: new RuntimeStateKey<boolean>('auxiliaryBar.hidden', StorageScope.WORKSPACE, StorageTarget.USER, true),
STATUSBAR_HIDDEN: new RuntimeStateKey<boolean>('statusBar.hidden', StorageScope.GLOBAL, StorageTarget.USER, false, true),
STATUSBAR_HIDDEN: new RuntimeStateKey<boolean>('statusBar.hidden', StorageScope.WORKSPACE, StorageTarget.USER, false, true),
} as const;

View file

@ -3,10 +3,11 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { AudioCue, IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService';
import { IDebugService, State } from 'vs/workbench/contrib/debug/common/debug';
import { autorunWithStore } from 'vs/workbench/contrib/audioCues/browser/observable';
import { IDebugService, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
export class AudioCueLineDebuggerContribution
extends Disposable
@ -14,14 +15,50 @@ export class AudioCueLineDebuggerContribution
constructor(
@IDebugService debugService: IDebugService,
@IAudioCueService audioCueService: IAudioCueService,
@IAudioCueService private readonly audioCueService: IAudioCueService,
) {
super();
this._register(debugService.onDidChangeState(e => {
if (e === State.Stopped) {
audioCueService.playAudioCue(AudioCue.executionStopped);
this._register(autorunWithStore((reader, store) => {
if (!audioCueService.isEnabled(AudioCue.debuggerStoppedOnBreakpoint).read(reader)) {
return;
}
}));
const sessionDisposables = new Map<IDebugSession, IDisposable>();
store.add(toDisposable(() => {
sessionDisposables.forEach(d => d.dispose());
sessionDisposables.clear();
}));
store.add(
debugService.onDidNewSession((session) =>
sessionDisposables.set(session, this.handleSession(session))
)
);
store.add(debugService.onDidEndSession(session => {
sessionDisposables.get(session)?.dispose();
sessionDisposables.delete(session);
}));
debugService
.getModel()
.getSessions()
.forEach((session) =>
sessionDisposables.set(session, this.handleSession(session))
);
}, 'subscribe to debug sessions'));
}
private handleSession(session: IDebugSession): IDisposable {
return session.onDidChangeState(e => {
const stoppedDetails = session.getStoppedDetails();
const BREAKPOINT_STOP_REASON = 'breakpoint';
if (stoppedDetails && stoppedDetails.reason === BREAKPOINT_STOP_REASON) {
this.audioCueService.playAudioCue(AudioCue.debuggerStoppedOnBreakpoint);
}
});
}
}

View file

@ -113,7 +113,7 @@ export class AudioCueLineFeatureContribution
this.audioCueService
.isEnabled(this.features[idx].audioCue)
.read(reader) &&
featureResult.read(reader).isActive(lineNumber),
featureResult.read(reader).isPresent(lineNumber),
'isActiveForLine'
)
),
@ -181,7 +181,7 @@ interface LineFeature {
}
interface LineFeatureState {
isActive(lineNumber: number): boolean;
isPresent(lineNumber: number): boolean;
}
class MarkerLineFeature implements LineFeature {
@ -195,12 +195,12 @@ class MarkerLineFeature implements LineFeature {
) { }
getObservableState(editor: ICodeEditor, model: ITextModel): IObservable<LineFeatureState> {
return fromEvent(
return fromEvent<LineFeatureState>(
Event.filter(this.markerService.onMarkerChanged, (changedUris) =>
changedUris.some((u) => u.toString() === model.uri.toString())
),
() => ({
isActive: (lineNumber) => {
isPresent: (lineNumber) => {
const hasMarker = this.markerService
.read({ resource: model.uri })
.some(
@ -223,15 +223,15 @@ class FoldedAreaLineFeature implements LineFeature {
const foldingController = FoldingController.get(editor);
if (!foldingController) {
return constObservable({
isActive: () => false,
isPresent: () => false,
});
}
const foldingModel = fromPromise(
foldingController.getFoldingModel() ?? Promise.resolve(undefined)
);
return foldingModel.map((v) => ({
isActive: (lineNumber) => {
return foldingModel.map<LineFeatureState>((v) => ({
isPresent: (lineNumber) => {
const regionAtLine = v.value?.getRegionAtLine(lineNumber);
const hasFolding = !regionAtLine
? false
@ -249,10 +249,10 @@ class BreakpointLineFeature implements LineFeature {
constructor(@IDebugService private readonly debugService: IDebugService) { }
getObservableState(editor: ICodeEditor, model: ITextModel): IObservable<LineFeatureState> {
return fromEvent(
return fromEvent<LineFeatureState>(
this.debugService.getModel().onDidChangeBreakpoints,
() => ({
isActive: (lineNumber) => {
isPresent: (lineNumber) => {
const breakpoints = this.debugService
.getModel()
.getBreakpoints({ uri: model.uri, lineNumber });
@ -270,8 +270,8 @@ class InlineCompletionLineFeature implements LineFeature {
getObservableState(editor: ICodeEditor, _model: ITextModel): IObservable<LineFeatureState> {
const ghostTextController = GhostTextController.get(editor);
if (!ghostTextController) {
return constObservable({
isActive: () => false,
return constObservable<LineFeatureState>({
isPresent: () => false,
});
}
@ -287,10 +287,10 @@ class InlineCompletionLineFeature implements LineFeature {
: undefined
));
return new LazyDerived(reader => {
return new LazyDerived<LineFeatureState>(reader => {
const ghostText = activeGhostText.read(reader)?.read(reader);
return {
isActive(lineNumber) {
isPresent(lineNumber) {
return ghostText?.lineNumber === lineNumber;
}
};

View file

@ -148,10 +148,10 @@ export class AudioCue {
settingsKey: 'audioCues.lineHasInlineSuggestion',
});
public static readonly executionStopped = AudioCue.register({
name: 'Debugger Execution Paused',
public static readonly debuggerStoppedOnBreakpoint = AudioCue.register({
name: 'Debugger Stopped On Breakpoint',
sound: Sound.break,
settingsKey: 'audioCues.debuggerExecutionPaused',
settingsKey: 'audioCues.debuggerStoppedOnBreakpoint',
});
private constructor(

View file

@ -57,8 +57,8 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
...audioCueFeatureBase,
default: 'off',
},
'audioCues.debuggerExecutionPaused': {
'description': localize('audioCues.debuggerExecutionPaused', "Plays an audio cue when the debugger paused."),
'audioCues.debuggerStoppedOnBreakpoint': {
'description': localize('audioCues.debuggerStoppedOnBreakpoint', "Plays an audio cue when the debugger stopped on a breakpoint."),
...audioCueFeatureBase,
},
}

View file

@ -18,8 +18,8 @@ export class ShowAudioCueHelp extends Action2 {
super({
id: ShowAudioCueHelp.ID,
title: {
value: localize('closeWindow', "Audio Cues Help"),
original: 'Audio Cues Help'
value: localize('audioCues.help', "Help: Audio Cues"),
original: 'Help: Audio Cues'
},
f1: true,
});
@ -36,7 +36,7 @@ export class ShowAudioCueHelp extends Action2 {
audioCue: cue,
buttons: [{
iconClass: Codicon.settingsGear.classNames,
tooltip: localize('showAudioCueHelp.settings', 'Enable/Disable Audio Cue'),
tooltip: localize('audioCues.help.settings', 'Enable/Disable Audio Cue'),
}],
})),
{
@ -46,7 +46,7 @@ export class ShowAudioCueHelp extends Action2 {
onDidTriggerItemButton: (context) => {
preferencesService.openSettings({ query: context.item.audioCue.settingsKey });
},
placeHolder: localize('showAudioCueHelp.placeholder', 'Select an audio cue to play'),
placeHolder: localize('audioCues.help.placeholder', 'Select an audio cue to play'),
}
);

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
export interface IObservable<T> {
/**
@ -215,6 +215,24 @@ export function autorun(
return new AutorunObserver(fn, name);
}
export function autorunWithStore(
fn: (reader: IReader, store: DisposableStore) => void,
name: string
): IDisposable {
let store = new DisposableStore();
const disposable = autorun(
reader => {
store.clear();
fn(reader, store);
},
name
);
return toDisposable(() => {
disposable.dispose();
store.dispose();
});
}
export class AutorunObserver implements IObserver, IReader, IDisposable {
public needsToRun = true;
private updateCount = 0;
@ -475,7 +493,7 @@ export function fromPromise<T>(promise: Promise<T>): IObservable<{ value?: T }>
return observable;
}
export function fromEvent<TArgs, T>(
export function fromEvent<T, TArgs = unknown>(
event: Event<TArgs>,
getValue: (args: TArgs | undefined) => T
): IObservable<T> {

View file

@ -152,7 +152,7 @@ export class CallStackEditorContribution implements IEditorContribution {
stackFrames.forEach(candidateStackFrame => {
if (candidateStackFrame && this.uriIdentityService.extUri.isEqual(candidateStackFrame.source.uri, editor.getModel()?.uri)) {
if (candidateStackFrame.range.startLineNumber > editor.getModel()?.getLineCount()) {
if (candidateStackFrame.range.startLineNumber > editor.getModel()?.getLineCount() || candidateStackFrame.range.startLineNumber < 1) {
this.logService.warn(`CallStackEditorContribution: invalid stack frame line number: ${candidateStackFrame.range.startLineNumber}`);
return;
}

View file

@ -14,7 +14,7 @@ import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { mixin } from 'vs/base/common/objects';
import * as platform from 'vs/base/common/platform';
import * as resources from 'vs/base/common/resources';
import severity from 'vs/base/common/severity';
import Severity from 'vs/base/common/severity';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import { IPosition, Position } from 'vs/editor/common/core/position';
@ -1055,7 +1055,7 @@ export class DebugSession implements IDebugSession {
resolved.forEach((child) => {
// Since we can not display multiple trees in a row, we are displaying these variables one after the other (ignoring their names)
(<any>child).name = null;
this.appendToRepl(child, severity.Info, source);
this.appendToRepl(child, Severity.Info, event.body.category === 'important', source);
});
});
return;
@ -1065,7 +1065,7 @@ export class DebugSession implements IDebugSession {
return;
}
const outputSeverity = event.body.category === 'stderr' ? severity.Error : event.body.category === 'console' ? severity.Warning : severity.Info;
const outputSeverity = event.body.category === 'stderr' ? Severity.Error : event.body.category === 'console' ? Severity.Warning : Severity.Info;
if (event.body.category === 'telemetry') {
// only log telemetry events from debug adapter if the debug extension provided the telemetry key
// and the user opted in telemetry
@ -1104,7 +1104,7 @@ export class DebugSession implements IDebugSession {
}
if (typeof event.body.output === 'string') {
this.appendToRepl(event.body.output, outputSeverity, source);
this.appendToRepl(event.body.output, outputSeverity, event.body.category === 'important', source);
}
});
}));
@ -1297,11 +1297,10 @@ export class DebugSession implements IDebugSession {
this.debugService.getViewModel().updateViews();
}
appendToRepl(data: string | IExpression, severity: severity, source?: IReplElementSource): void {
appendToRepl(data: string | IExpression, severity: Severity, isImportant?: boolean, source?: IReplElementSource): void {
this.repl.appendToRepl(this, data, severity, source);
}
logToRepl(sev: severity, args: any[], frame?: { uri: URI; line: number; column: number }) {
this.repl.logToRepl(this, sev, args, frame);
if (isImportant) {
this.notificationService.notify({ message: data.toString(), severity: severity, source: this.name });
}
}
}

View file

@ -312,8 +312,7 @@ export interface IDebugSession extends ITreeElement {
hasSeparateRepl(): boolean;
removeReplExpressions(): void;
addReplExpression(stackFrame: IStackFrame | undefined, name: string): Promise<void>;
appendToRepl(data: string | IExpression, severity: severity, source?: IReplElementSource): void;
logToRepl(sev: severity, args: any[], frame?: { uri: uri; line: number; column: number }): void;
appendToRepl(data: string | IExpression, severity: severity, isImportant?: boolean, source?: IReplElementSource): void;
// session events
readonly onDidEndAdapter: Event<AdapterEndEvent | undefined>;

View file

@ -3,16 +3,14 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import severity from 'vs/base/common/severity';
import { IReplElement, IStackFrame, IExpression, IReplElementSource, IDebugSession, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug';
import { ExpressionContainer } from 'vs/workbench/contrib/debug/common/debugModel';
import { isString, isUndefinedOrNull, isObject } from 'vs/base/common/types';
import { basenameOrAuthority } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import { Emitter, Event } from 'vs/base/common/event';
import severity from 'vs/base/common/severity';
import { isObject, isString } from 'vs/base/common/types';
import { generateUuid } from 'vs/base/common/uuid';
import * as nls from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IDebugConfiguration, IDebugSession, IExpression, IReplElement, IReplElementSource, IStackFrame } from 'vs/workbench/contrib/debug/common/debug';
import { ExpressionContainer } from 'vs/workbench/contrib/debug/common/debugModel';
const MAX_REPL_LENGTH = 10000;
let topReplElementCounter = 0;
@ -289,79 +287,6 @@ export class ReplModel {
this._onDidChangeElements.fire();
}
logToRepl(session: IDebugSession, sev: severity, args: any[], frame?: { uri: URI; line: number; column: number }) {
let source: IReplElementSource | undefined;
if (frame) {
source = {
column: frame.column,
lineNumber: frame.line,
source: session.getSource({
name: basenameOrAuthority(frame.uri),
path: frame.uri.fsPath
})
};
}
// add output for each argument logged
let simpleVals: any[] = [];
for (let i = 0; i < args.length; i++) {
const a = args[i];
// undefined gets printed as 'undefined'
if (typeof a === 'undefined') {
simpleVals.push('undefined');
}
// null gets printed as 'null'
else if (a === null) {
simpleVals.push('null');
}
// objects & arrays are special because we want to inspect them in the REPL
else if (isObject(a) || Array.isArray(a)) {
// flush any existing simple values logged
if (simpleVals.length) {
this.appendToRepl(session, simpleVals.join(' '), sev, source);
simpleVals = [];
}
// show object
this.appendToRepl(session, new RawObjectReplElement(getUniqueId(), (<any>a).prototype, a, undefined, nls.localize('snapshotObj', "Only primitive values are shown for this object.")), sev, source);
}
// string: watch out for % replacement directive
// string substitution and formatting @ https://developer.chrome.com/devtools/docs/console
else if (typeof a === 'string') {
let buf = '';
for (let j = 0, len = a.length; j < len; j++) {
if (a[j] === '%' && (a[j + 1] === 's' || a[j + 1] === 'i' || a[j + 1] === 'd' || a[j + 1] === 'O')) {
i++; // read over substitution
buf += !isUndefinedOrNull(args[i]) ? args[i] : ''; // replace
j++; // read over directive
} else {
buf += a[j];
}
}
simpleVals.push(buf);
}
// number or boolean is joined together
else {
simpleVals.push(a);
}
}
// flush simple values
// always append a new line for output coming from an extension such that separate logs go to separate lines #23695
if (simpleVals.length) {
this.appendToRepl(session, simpleVals.join(' ') + '\n', sev, source);
}
}
removeReplExpressions(): void {
if (this.replElements.length > 0) {
this.replElements = [];

View file

@ -156,8 +156,6 @@ export class MockDebugService implements IDebugService {
throw new Error('not implemented');
}
logToRepl(session: IDebugSession, value: string): void { }
sourceIsNotAvailable(uri: uri): void { }
tryToAutoFocusStackFrame(thread: IThread): Promise<any> {
@ -245,8 +243,7 @@ export class MockSession implements IDebugSession {
return Promise.resolve(undefined);
}
appendToRepl(data: string | IExpression, severity: Severity, source?: IReplElementSource): void { }
logToRepl(sev: Severity, args: any[], frame?: { uri: uri; line: number; column: number }) { }
appendToRepl(data: string | IExpression, severity: Severity, isImportant?: boolean, source?: IReplElementSource): void { }
configuration: IConfig = { type: 'mock', name: 'mock', request: 'launch' };
unresolvedConfiguration: IConfig = { type: 'mock', name: 'mock', request: 'launch' };

View file

@ -56,6 +56,7 @@ import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration';
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { NOTEBOOK_KERNEL } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
const DECORATION_KEY = 'interactiveInputDecoration';
const INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'InteractiveEditorViewState';
@ -435,8 +436,8 @@ export class InteractiveEditor extends EditorPane {
}
}));
this.#widgetDisposableStore.add(this.#notebookKernelService.onDidChangeNotebookAffinity(this.#updateInputEditorLanguage, this));
this.#widgetDisposableStore.add(this.#notebookKernelService.onDidChangeSelectedNotebooks(this.#updateInputEditorLanguage, this));
this.#widgetDisposableStore.add(this.#notebookKernelService.onDidChangeNotebookAffinity(this.#syncWithKernel, this));
this.#widgetDisposableStore.add(this.#notebookKernelService.onDidChangeSelectedNotebooks(this.#syncWithKernel, this));
this.#widgetDisposableStore.add(this.themeService.onDidColorThemeChange(() => {
if (this.isVisible()) {
@ -491,8 +492,7 @@ export class InteractiveEditor extends EditorPane {
}
}));
this.#updateInputDecoration();
this.#updateInputEditorLanguage();
this.#syncWithKernel();
}
#lastCell: ICellViewModel | undefined = undefined;
@ -592,22 +592,24 @@ export class InteractiveEditor extends EditorPane {
}));
}
#updateInputEditorLanguage() {
#syncWithKernel() {
const notebook = this.#notebookWidget.value?.textModel;
const textModel = this.#codeEditorWidget.getModel();
if (!notebook || !textModel) {
return;
if (notebook && textModel) {
const info = this.#notebookKernelService.getMatchingKernel(notebook);
const selectedOrSuggested = info.selected ?? info.suggestions[0];
if (selectedOrSuggested) {
const language = selectedOrSuggested.supportedLanguages[0];
const newMode = language ? this.#languageService.createById(language).languageId : PLAINTEXT_LANGUAGE_ID;
textModel.setMode(newMode);
NOTEBOOK_KERNEL.bindTo(this.#contextKeyService).set(selectedOrSuggested.id);
}
}
const info = this.#notebookKernelService.getMatchingKernel(notebook);
const selectedOrSuggested = info.selected ?? info.suggestions[0];
if (selectedOrSuggested) {
const language = selectedOrSuggested.supportedLanguages[0];
const newMode = language ? this.#languageService.createById(language).languageId : PLAINTEXT_LANGUAGE_ID;
textModel.setMode(newMode);
}
this.#updateInputDecoration();
}
layout(dimension: DOM.Dimension): void {
@ -661,8 +663,9 @@ export class InteractiveEditor extends EditorPane {
if (model?.getValueLength() === 0) {
const transparentForeground = resolveColorValue(editorForeground, this.themeService.getColorTheme())?.transparent(0.4);
const keybinding = this.#keybindingService.lookupKeybinding('interactive.execute')?.getLabel();
const text = nls.localize('interactiveInputPlaceHolder', "Type code here and press {0} to run", keybinding ?? 'ctrl+enter');
const languageId = model.getLanguageId();
const keybinding = this.#keybindingService.lookupKeybinding('interactive.execute', this.#contextKeyService)?.getLabel();
const text = nls.localize('interactiveInputPlaceHolder', "Type '{0}' code here and press {1} to run", languageId, keybinding ?? 'ctrl+enter');
decorations.push({
range: {
startLineNumber: 0,

View file

@ -22,7 +22,7 @@ export function registerStickyScroll(notebookEditor: INotebookEditor, cell: ICel
const maxTop = cell.layoutInfo.editorHeight + cell.layoutInfo.statusBarHeight - 45; // subtract roughly the height of the execution order label plus padding
const top = maxTop > 20 ? // Don't move the run button if it can only move a very short distance
clamp(min, diff, maxTop) :
0;
min;
element.style.top = `${top}px`;
}
};

View file

@ -42,7 +42,7 @@ import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookS
import { IWebviewElement, IWebviewService, WebviewContentPurpose } from 'vs/workbench/contrib/webview/browser/webview';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { FromWebviewMessage, IAckOutputHeight, IClickedDataUrlMessage, IContentWidgetTopRequest, IControllerPreload, ICreationRequestMessage, IFindMatch, IMarkupCellInitialization, ToWebviewMessage } from './webviewMessages';
import { FromWebviewMessage, IAckOutputHeight, IClickedDataUrlMessage, ICodeBlockHighlightRequest, IContentWidgetTopRequest, IControllerPreload, ICreationRequestMessage, IFindMatch, IMarkupCellInitialization, ToWebviewMessage } from './webviewMessages';
export interface ICachedInset<K extends ICommonCellInfo> {
outputId: string;
@ -816,30 +816,38 @@ var requirejs = (function() {
cell.renderedHtml = data.html;
}
for (const { id, value, lang } of data.codeBlocks) {
// The language id may be a language aliases (e.g.js instead of javascript)
const languageId = this.languageService.getLanguageIdByLanguageName(lang);
if (!languageId) {
continue;
}
tokenizeToString(this.languageService, value, languageId).then((html) => {
if (this._disposed) {
return;
}
this._sendMessageToWebview({
type: 'tokenizedCodeBlock',
html,
codeBlockId: id
});
});
}
this._handleHighlightCodeBlock(data.codeBlocks);
break;
}
case 'renderedCellOutput':
{
this._handleHighlightCodeBlock(data.codeBlocks);
break;
}
}
}));
}
private _handleHighlightCodeBlock(codeBlocks: ReadonlyArray<ICodeBlockHighlightRequest>) {
for (const { id, value, lang } of codeBlocks) {
// The language id may be a language aliases (e.g.js instead of javascript)
const languageId = this.languageService.getLanguageIdByLanguageName(lang);
if (!languageId) {
continue;
}
tokenizeToString(this.languageService, value, languageId).then((html) => {
if (this._disposed) {
return;
}
this._sendMessageToWebview({
type: 'tokenizedCodeBlock',
html,
codeBlockId: id
});
});
}
}
private async _onDidClickDataLink(event: IClickedDataUrlMessage): Promise<void> {
if (typeof event.data !== 'string') {
return;

View file

@ -139,15 +139,22 @@ export interface IInitializedMarkupMessage extends BaseToWebviewMessage {
readonly type: 'initializedMarkup';
}
export interface ICodeBlockHighlightRequest {
readonly id: string;
readonly value: string;
readonly lang: string;
}
export interface IRenderedMarkupMessage extends BaseToWebviewMessage {
readonly type: 'renderedMarkup';
readonly cellId: string;
readonly html: string;
readonly codeBlocks: ReadonlyArray<{
readonly id: string;
readonly value: string;
readonly lang: string;
}>;
readonly codeBlocks: ReadonlyArray<ICodeBlockHighlightRequest>;
}
export interface IRenderedCellOutputMessage extends BaseToWebviewMessage {
readonly type: 'renderedCellOutput';
readonly codeBlocks: ReadonlyArray<ICodeBlockHighlightRequest>;
}
export interface IClearMessage {
@ -418,6 +425,7 @@ export type FromWebviewMessage = WebviewInitialized |
ICellDragEndMessage |
IInitializedMarkupMessage |
IRenderedMarkupMessage |
IRenderedCellOutputMessage |
IDidFindMessage |
IDidFindHighlightMessage;

View file

@ -1145,7 +1145,7 @@ async function webviewPreloads(ctx: PreloadContext) {
}
case 'tokenizedCodeBlock': {
const { codeBlockId, html } = event.data;
MarkupCell.highlightCodeBlock(codeBlockId, html);
MarkdownCodeBlock.highlightCodeBlock(codeBlockId, html);
break;
}
case 'tokenizedStylesChanged': {
@ -1562,12 +1562,11 @@ async function webviewPreloads(ctx: PreloadContext) {
}
}();
class MarkupCell {
class MarkdownCodeBlock {
private static pendingCodeBlocksToHighlight = new Map<string, HTMLElement>();
public static highlightCodeBlock(id: string, html: string) {
const el = MarkupCell.pendingCodeBlocksToHighlight.get(id);
const el = MarkdownCodeBlock.pendingCodeBlocksToHighlight.get(id);
if (!el) {
return;
}
@ -1578,6 +1577,24 @@ async function webviewPreloads(ctx: PreloadContext) {
}
}
public static requestHighlightCodeBlock(root: HTMLElement | ShadowRoot) {
const codeBlocks: Array<{ value: string; lang: string; id: string }> = [];
let i = 0;
for (const el of root.querySelectorAll('.vscode-code-block')) {
const lang = el.getAttribute('data-vscode-code-block-lang');
if (el.textContent && lang) {
const id = `${Date.now()}-${i++}`;
codeBlocks.push({ value: el.textContent, lang: lang, id });
MarkdownCodeBlock.pendingCodeBlocksToHighlight.set(id, el as HTMLElement);
}
}
return codeBlocks;
}
}
class MarkupCell {
public readonly ready: Promise<void>;
public readonly id: string;
@ -1712,16 +1729,7 @@ async function webviewPreloads(ctx: PreloadContext) {
}
}
const codeBlocks: Array<{ value: string; lang: string; id: string }> = [];
let i = 0;
for (const el of root.querySelectorAll('.vscode-code-block')) {
const lang = el.getAttribute('data-vscode-code-block-lang');
if (el.textContent && lang) {
const id = `${Date.now()}-${i++}`;
codeBlocks.push({ value: el.textContent, lang: lang, id });
MarkupCell.pendingCodeBlocksToHighlight.set(id, el as HTMLElement);
}
}
const codeBlocks: Array<{ value: string; lang: string; id: string }> = MarkdownCodeBlock.requestHighlightCodeBlock(root);
postNotebookMessage<webviewMessages.IRenderedMarkupMessage>('renderedMarkup', {
cellId: this.id,
@ -1925,7 +1933,6 @@ async function webviewPreloads(ctx: PreloadContext) {
}
class OutputElement {
public readonly element: HTMLElement;
private _content?: { content: webviewMessages.ICreationContent; preloadsAndErrors: unknown[] };
@ -1987,6 +1994,15 @@ async function webviewPreloads(ctx: PreloadContext) {
init: true,
});
}
const root = this.element.shadowRoot ?? this.element;
const codeBlocks: Array<{ value: string; lang: string; id: string }> = MarkdownCodeBlock.requestHighlightCodeBlock(root);
if (codeBlocks.length > 0) {
postNotebookMessage<webviewMessages.IRenderedCellOutputMessage>('renderedCellOutput', {
codeBlocks
});
}
}
public rerender() {

View file

@ -189,6 +189,7 @@ export class SettingsEditor2 extends EditorPane {
/** Don't spam warnings */
private hasWarnedMissingSettings = false;
/** Persist the search query upon reloads */
private editorMemento: IEditorMemento<ISettingsEditor2State>;
private tocFocusedElement: SettingsTreeGroupElement | null = null;
@ -1548,6 +1549,8 @@ export class SettingsEditor2 extends EditorPane {
if (this.group && this.input) {
this.editorMemento.saveEditorState(this.group, this.input, { searchQuery, target });
}
} else if (this.group && this.input) {
this.editorMemento.clearEditorState(this.input, this.group);
}
super.saveState();

View file

@ -62,6 +62,6 @@ export class TerminalShellIntegrationLinkDetector implements ITerminalLinkDetect
}
private async _hideMessage() {
await this._configurationService.updateValue(TerminalSettingId.ShowShellIntegrationWelcome, false);
await this._configurationService.updateValue(TerminalSettingId.ShellIntegrationShowWelcome, false);
}
}

View file

@ -52,7 +52,6 @@ update_prompt() {
}
precmd() {
local STATUS="$?"
command_complete "$STATUS"
# in command execution
@ -70,19 +69,20 @@ preexec() {
}
update_prompt
export ORIGINAL_PROMPT_COMMAND=$PROMPT_COMMAND
prompt_cmd() {
prompt_cmd_original() {
STATUS="$?"
$ORIGINAL_PROMPT_COMMAND
precmd
}
original_prompt_cmd() {
${ORIGINAL_PROMPT_COMMAND}
prompt_cmd
prompt_cmd() {
STATUS="$?"
precmd
}
if [ -n "$ORIGINAL_PROMPT_COMMAND" ]; then
export PROMPT_COMMAND=original_prompt_cmd
ORIGINAL_PROMPT_COMMAND=$PROMPT_COMMAND
if [[ -n "$ORIGINAL_PROMPT_COMMAND" && "$ORIGINAL_PROMPT_COMMAND" != "prompt_cmd" ]]; then
PROMPT_COMMAND=prompt_cmd_original
else
export PROMPT_COMMAND=prompt_cmd
PROMPT_COMMAND=prompt_cmd
fi
trap 'preexec' DEBUG

View file

@ -425,12 +425,9 @@
.terminal-command-decoration:not(.skipped):hover {
cursor: pointer;
border-radius: 5px;
}
.terminal-command-decoration.skipped {
pointer-events: none;
}
.terminal-command-decoration {
width: 10px;
height: 10px;
}

View file

@ -441,7 +441,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._xtermReadyPromise.then(async () => {
// Wait for a period to allow a container to be ready
await this._containerReadyBarrier.wait();
if (this._configHelper.config.enableShellIntegration && !this.shellLaunchConfig.executable) {
if (this._configHelper.config.shellIntegration.enabled && !this.shellLaunchConfig.executable) {
const os = await this._processManager.getBackendOS();
this.shellLaunchConfig.executable = (await this._terminalProfileResolverService.getDefaultProfile({ remoteAuthority: this.remoteAuthority, os })).path;
}
@ -2518,9 +2518,9 @@ export function parseExitResult(
}
if (shellIntegrationAttempted) {
if (commandLine) {
message = nls.localize('launchFailed.exitCodeAndCommandLineShellIntegration', "The terminal process \"{0}\" failed to launch (exit code: {1}). Disabling shell integration with `terminal.integrated.enableShellIntegration` might help.", commandLine, code);
message = nls.localize('launchFailed.exitCodeAndCommandLineShellIntegration', "The terminal process \"{0}\" failed to launch (exit code: {1}). Disabling shell integration with `terminal.integrated.shellIntegration.enabled` might help.", commandLine, code);
} else {
message = nls.localize('launchFailed.exitCodeOnlyShellIntegration', "The terminal process failed to launch (exit code: {0}). Disabling shell integration with `terminal.integrated.enableShellIntegration` might help.", code);
message = nls.localize('launchFailed.exitCodeOnlyShellIntegration', "The terminal process failed to launch (exit code: {0}). Disabling shell integration with `terminal.integrated.shellIntegration.enabled` might help.", code);
}
} else if (processState === ProcessState.KilledDuringLaunch) {
if (commandLine) {

View file

@ -243,7 +243,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
os: this.os
});
try {
const shellIntegration = terminalEnvironment.injectShellIntegrationArgs(this._logService, this._configurationService, env, this._configHelper.config.enableShellIntegration, shellLaunchConfig, this.os);
const shellIntegration = terminalEnvironment.injectShellIntegrationArgs(this._logService, this._configurationService, env, this._configHelper.config.shellIntegration.enabled, shellLaunchConfig, this.os);
this.shellIntegrationAttempted = shellIntegration.enableShellIntegration;
if (this.shellIntegrationAttempted && shellIntegration.args) {
const remoteEnv = await this._remoteAgentService.getEnvironment();
@ -448,7 +448,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
const env = await this._resolveEnvironment(backend, variableResolver, shellLaunchConfig);
const shellIntegration = terminalEnvironment.injectShellIntegrationArgs(this._logService, this._configurationService, env, this._configHelper.config.enableShellIntegration, shellLaunchConfig, OS);
const shellIntegration = terminalEnvironment.injectShellIntegrationArgs(this._logService, this._configurationService, env, this._configHelper.config.shellIntegration.enabled, shellLaunchConfig, OS);
if (shellIntegration.enableShellIntegration) {
shellLaunchConfig.args = shellIntegration.args;
// Always resolve the injected arguments on local processes

View file

@ -10,7 +10,6 @@ import * as dom from 'vs/base/browser/dom';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { ITerminalCapabilityStore, TerminalCapability } from 'vs/workbench/contrib/terminal/common/capabilities/capabilities';
import { IColorTheme, ICssStyleCollector, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { TERMINAL_COMMAND_DECORATION_DEFAULT_BACKGROUND_COLOR, TERMINAL_COMMAND_DECORATION_ERROR_BACKGROUND_COLOR, TERMINAL_COMMAND_DECORATION_SKIPPED_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IHoverService } from 'vs/workbench/services/hover/browser/hover';
import { IAction } from 'vs/base/common/actions';
@ -22,6 +21,8 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { fromNow } from 'vs/base/common/date';
import { toolbarHoverBackground } from 'vs/platform/theme/common/colorRegistry';
import { TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { editorGutterDeletedBackground, editorGutterModifiedBackground } from 'vs/workbench/contrib/scm/browser/dirtydiffDecorator';
import { TERMINAL_COMMAND_DECORATION_SKIPPED_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
const enum DecorationSelector {
CommandDecoration = 'terminal-command-decoration',
@ -124,14 +125,16 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
target.classList.add(DecorationSelector.Codicon);
if (command.exitCode === undefined) {
target.classList.add(DecorationSelector.SkippedColor);
// TODO: Use outline icon?
target.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.CommandIcon)}`);
target.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationCommandIconSkipped)}`);
} else if (command.exitCode) {
target.classList.add(DecorationSelector.ErrorColor);
target.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.CommandIconError)}`);
target.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationCommandIconError)}`);
} else {
target.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.CommandIcon)}`);
target.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationCommandIcon)}`);
}
// must be inlined to override the inlined styles from xterm
decoration.element!.style.width = '16px';
decoration.element!.style.height = '16px';
}
});
return decoration;
@ -141,6 +144,7 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
// When the xterm Decoration gets disposed of, its element gets removed from the dom
// along with its listeners
return dom.addDisposableListener(target, dom.EventType.CLICK, async () => {
this._hideHover();
const actions = await this._getCommandActions(command);
this._contextMenuService.showContextMenu({ getAnchor: () => target, getActions: () => actions });
});
@ -187,12 +191,15 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
}
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
const commandDecorationDefaultColor = theme.getColor(TERMINAL_COMMAND_DECORATION_DEFAULT_BACKGROUND_COLOR);
collector.addRule(`.${DecorationSelector.CommandDecoration} { color: ${commandDecorationDefaultColor ? commandDecorationDefaultColor.toString() : ''}; } `);
const commandDecorationErrorColor = theme.getColor(TERMINAL_COMMAND_DECORATION_ERROR_BACKGROUND_COLOR);
collector.addRule(`.${DecorationSelector.CommandDecoration}.${DecorationSelector.ErrorColor} { color: ${commandDecorationErrorColor ? commandDecorationErrorColor.toString() : ''}; } `);
const commandDecorationSkippedColor = theme.getColor(TERMINAL_COMMAND_DECORATION_SKIPPED_BACKGROUND_COLOR);
collector.addRule(`.${DecorationSelector.CommandDecoration}.${DecorationSelector.SkippedColor} { color: ${commandDecorationSkippedColor ? commandDecorationSkippedColor.toString() : ''}; } `);
const toolbarHoverBackgroundColor = theme.getColor(toolbarHoverBackground);
collector.addRule(`.${DecorationSelector.CommandDecoration}:not(.${DecorationSelector.SkippedColor}):hover { background-color: ${toolbarHoverBackgroundColor ? toolbarHoverBackgroundColor.toString() : ''}; border-radius: 5px; }`);
const defaultColor = theme.getColor(editorGutterModifiedBackground);
const errorColor = theme.getColor(editorGutterDeletedBackground);
const skippedColor = theme.getColor(TERMINAL_COMMAND_DECORATION_SKIPPED_BACKGROUND_COLOR);
const hoverBackgroundColor = theme.getColor(toolbarHoverBackground);
if (!defaultColor || !errorColor || !skippedColor || !hoverBackgroundColor) {
return;
}
collector.addRule(`.${DecorationSelector.CommandDecoration} { color: ${defaultColor.toString()}; } `);
collector.addRule(`.${DecorationSelector.CommandDecoration}.${DecorationSelector.ErrorColor} { color: ${errorColor.toString()}; } `);
collector.addRule(`.${DecorationSelector.CommandDecoration}.${DecorationSelector.SkippedColor} { color: ${skippedColor.toString()};} `);
collector.addRule(`.${DecorationSelector.CommandDecoration}: not(.${DecorationSelector.SkippedColor}): hover { background-color: ${hoverBackgroundColor.toString()}; }`);
});

View file

@ -290,7 +290,9 @@ export interface ITerminalConfiguration {
persistentSessionReviveProcess: 'onExit' | 'onExitAndWindowClose' | 'never';
ignoreProcessNames: string[];
autoReplies: { [key: string]: string };
enableShellIntegration: boolean;
shellIntegration: {
enabled: boolean;
};
}
export const DEFAULT_LOCAL_ECHO_EXCLUDE: ReadonlyArray<string> = ['vim', 'vi', 'nano', 'tmux'];

View file

@ -27,16 +27,6 @@ export const TERMINAL_SELECTION_BACKGROUND_COLOR = registerColor('terminal.selec
dark: '#FFFFFF40',
hc: '#FFFFFF80'
}, nls.localize('terminal.selectionBackground', 'The selection background color of the terminal.'));
export const TERMINAL_COMMAND_DECORATION_DEFAULT_BACKGROUND_COLOR = registerColor('terminalCommandDecoration.defaultBackground', {
light: '#66afe0',
dark: '#399ee6',
hc: '#399ee6'
}, nls.localize('terminalCommandDecoration.defaultBackground', 'The default terminal command decoration background color for successful commands (zero exit code).'));
export const TERMINAL_COMMAND_DECORATION_ERROR_BACKGROUND_COLOR = registerColor('terminalCommandDecoration.errorBackground', {
light: '#a1260d',
dark: '#be1100',
hc: '#be1100'
}, nls.localize('terminalCommandDecoration.errorBackground', 'The terminal command decoration background color when the command fails (non-zero exit code).'));
export const TERMINAL_COMMAND_DECORATION_SKIPPED_BACKGROUND_COLOR = registerColor('terminalCommandDecoration.skippedBackground', {
light: '#00000040',
dark: '#ffffff40',

View file

@ -6,7 +6,7 @@
import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { localize } from 'vs/nls';
import { DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, DEFAULT_COMMANDS_TO_SKIP_SHELL, SUGGESTIONS_FONT_WEIGHT, MINIMUM_FONT_WEIGHT, MAXIMUM_FONT_WEIGHT, DEFAULT_LOCAL_ECHO_EXCLUDE } from 'vs/workbench/contrib/terminal/common/terminal';
import { TerminalCommandIcon, TerminalCommandIconError, TerminalLocationString, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { TerminalLocationString, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { isMacintosh, isWindows } from 'vs/base/common/platform';
import { Registry } from 'vs/platform/registry/common/platform';
@ -103,26 +103,20 @@ const terminalConfiguration: IConfigurationNode = {
default: 'view',
description: localize('terminal.integrated.defaultLocation', "Controls where newly created terminals will appear.")
},
[TerminalSettingId.CommandIcon]: {
[TerminalSettingId.ShellIntegrationCommandIcon]: {
type: 'string',
enum: [TerminalCommandIcon.ChevronRight, TerminalCommandIcon.TriangleRight],
enumDescriptions: [
localize('terminal.integrated.commandIcon.chevronRight', "A chevron pointed to the right"),
localize('terminal.integrated.commandIcon.triangleRight', "A triangle pointed to the right")
],
default: 'triangle-right',
description: localize('terminal.integrated.commandIcon', "Controls the icon that will be used for each command in terminals with shell integration enabled that do not have an associated exit code.")
default: 'primitive-dot',
description: localize('terminal.integrated.shellIntegration.commandIcon', "Controls the icon that will be used for each command in terminals with shell integration enabled that do not have an associated exit code. Set to '' to hide the icon.")
},
[TerminalSettingId.CommandIconError]: {
[TerminalSettingId.ShellIntegrationCommandIconError]: {
type: 'string',
enum: [TerminalCommandIconError.ChevronRight, TerminalCommandIconError.TriangleRight, TerminalCommandIconError.X],
enumDescriptions: [
localize('terminal.integrated.commandIconError.chevronRight', "A chevron pointed to the right"),
localize('terminal.integrated.commandIconError.triangleRight', "A triangle pointed to the right"),
localize('terminal.integrated.commandIconError.x', "An X"),
],
default: 'x',
description: localize('terminal.integrated.commandIconError', "Controls the icon that will be used for each command in terminals with shell integration enabled that do have an associated exit code.")
default: 'error-small',
description: localize('terminal.integrated.shellIntegration.commandIconError', "Controls the icon that will be used for each command in terminals with shell integration enabled that do have an associated exit code. Set to '' to hide the icon.")
},
[TerminalSettingId.ShellIntegrationCommandIconSkipped]: {
type: 'string',
default: 'circle-outline',
description: localize('terminal.integrated.shellIntegration.commandIconSkipped', "Controls the icon that will be used for skipped/empty commands. Set to '' to hide the icon.")
},
[TerminalSettingId.TabsFocusMode]: {
type: 'string',
@ -537,15 +531,15 @@ const terminalConfiguration: IConfigurationNode = {
},
default: {}
},
[TerminalSettingId.EnableShellIntegration]: {
[TerminalSettingId.ShellIntegrationEnabled]: {
restricted: true,
markdownDescription: localize('terminal.integrated.enableShellIntegration', "Enable the experimental shell integration feature which will turn on certain features like enhanced command tracking and current working directory detection. Shell integration works by injecting a script that is run when the shell is initialized which lets the terminal gain additional insights into what is happening within the terminal, the script injection may not work if you have custom arguments defined in the terminal profile.\n\nSupported shells:\n\n- Linux/macOS: bash, pwsh, zsh\n - Windows: pwsh"),
markdownDescription: localize('terminal.integrated.shellIntegration.enabled', "Enable the experimental shell integration feature which will turn on certain features like enhanced command tracking and current working directory detection. Shell integration works by injecting a script that is run when the shell is initialized which lets the terminal gain additional insights into what is happening within the terminal, the script injection may not work if you have custom arguments defined in the terminal profile.\n\nSupported shells:\n\n- Linux/macOS: bash, pwsh, zsh\n - Windows: pwsh"),
type: 'boolean',
default: false
},
[TerminalSettingId.ShowShellIntegrationWelcome]: {
[TerminalSettingId.ShellIntegrationShowWelcome]: {
restricted: true,
markdownDescription: localize('terminal.integrated.showShellIntegrationWelcome', "Whether to show the shell integration activated welcome message in the terminal when the feature is enabled."),
markdownDescription: localize('terminal.integrated.shellIntegration.showWelcome', "Whether to show the shell integration activated welcome message in the terminal when the feature is enabled."),
type: 'boolean',
default: true
},

View file

@ -452,7 +452,7 @@ export function injectShellIntegrationArgs(
}
}
if (newArgs) {
const showWelcome = configurationService.getValue(TerminalSettingId.ShowShellIntegrationWelcome);
const showWelcome = configurationService.getValue(TerminalSettingId.ShellIntegrationShowWelcome);
const additionalArgs = showWelcome ? '' : ' -HideWelcome';
newArgs = [...newArgs]; // Shallow clone the array to avoid setting the default array
newArgs[newArgs.length - 1] = format(newArgs[newArgs.length - 1], additionalArgs);
@ -466,7 +466,7 @@ export function injectShellIntegrationArgs(
env['VSCODE_SHELL_LOGIN'] = '1';
newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Bash);
}
const showWelcome = configurationService.getValue(TerminalSettingId.ShowShellIntegrationWelcome);
const showWelcome = configurationService.getValue(TerminalSettingId.ShellIntegrationShowWelcome);
if (!showWelcome) {
env['VSCODE_SHELL_HIDE_WELCOME'] = '1';
}
@ -479,7 +479,7 @@ export function injectShellIntegrationArgs(
newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.PwshLogin);
}
if (newArgs) {
const showWelcome = configurationService.getValue(TerminalSettingId.ShowShellIntegrationWelcome);
const showWelcome = configurationService.getValue(TerminalSettingId.ShellIntegrationShowWelcome);
const additionalArgs = showWelcome ? '' : ' -HideWelcome';
newArgs = [...newArgs]; // Shallow clone the array to avoid setting the default array
newArgs[newArgs.length - 1] = format(newArgs[newArgs.length - 1], additionalArgs);
@ -492,7 +492,7 @@ export function injectShellIntegrationArgs(
} else if (areZshBashLoginArgs(originalArgs)) {
newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.ZshLogin);
}
const showWelcome = configurationService.getValue(TerminalSettingId.ShowShellIntegrationWelcome);
const showWelcome = configurationService.getValue(TerminalSettingId.ShellIntegrationShowWelcome);
if (!showWelcome) {
env['VSCODE_SHELL_HIDE_WELCOME'] = '1';
}

View file

@ -8,11 +8,12 @@ import { localize } from 'vs/nls';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { SyncActionDescriptor, MenuRegistry, MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
import { ShowCurrentReleaseNotesAction, ProductContribution, UpdateContribution, CheckForVSCodeUpdateAction, CONTEXT_UPDATE_STATE, SwitchProductQualityContribution } from 'vs/workbench/contrib/update/browser/update';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import product from 'vs/platform/product/common/product';
import { StateType } from 'vs/platform/update/common/update';
import { IUpdateService, StateType } from 'vs/platform/update/common/update';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
const workbench = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
@ -29,6 +30,58 @@ actionRegistry
actionRegistry
.registerWorkbenchAction(SyncActionDescriptor.from(CheckForVSCodeUpdateAction), `${product.nameShort}: Check for Update`, product.nameShort, CONTEXT_UPDATE_STATE.isEqualTo(StateType.Idle));
class DownloadUpdateAction extends Action2 {
constructor() {
super({
id: 'update.downloadUpdate',
title: localize('downloadUpdate', "Download Update"),
category: product.nameShort,
f1: true,
precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.AvailableForDownload)
});
}
async run(accessor: ServicesAccessor): Promise<void> {
await accessor.get(IUpdateService).downloadUpdate();
}
}
class InstallUpdateAction extends Action2 {
constructor() {
super({
id: 'update.installUpdate',
title: localize('installUpdate', "Install Update"),
category: product.nameShort,
f1: true,
precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Downloaded)
});
}
async run(accessor: ServicesAccessor): Promise<void> {
await accessor.get(IUpdateService).applyUpdate();
}
}
class RestartToUpdateAction extends Action2 {
constructor() {
super({
id: 'update.restartToUpdate',
title: localize('restartToUpdate', "Restart to Update"),
category: product.nameShort,
f1: true,
precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Ready)
});
}
async run(accessor: ServicesAccessor): Promise<void> {
await accessor.get(IUpdateService).quitAndInstall();
}
}
registerAction2(DownloadUpdateAction);
registerAction2(InstallUpdateAction);
registerAction2(RestartToUpdateAction);
// Menu
if (ShowCurrentReleaseNotesAction.AVAILABE) {
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {

View file

@ -225,6 +225,9 @@ export class DesktopMain extends Disposable {
const diskFileSystemProvider = this._register(new DiskFileSystemProvider(mainProcessService, sharedProcessWorkerWorkbenchService, logService));
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
// Remote Files
this._register(RemoteFileSystemProviderClient.register(remoteAgentService, fileService, logService));
// User Data Provider
fileService.registerProvider(Schemas.userData, this._register(new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.userData, logService)));
@ -246,9 +249,6 @@ export class DesktopMain extends Disposable {
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// Remote file system
this._register(RemoteFileSystemProviderClient.register(remoteAgentService, fileService, logService));
const payload = this.resolveWorkspaceInitializationPayload(environmentService);
const [configurationService, storageService] = await Promise.all([

View file

@ -46,32 +46,43 @@ export class CommandService extends Disposable implements ICommandService {
return this._starActivation;
}
executeCommand<T>(id: string, ...args: any[]): Promise<T> {
async executeCommand<T>(id: string, ...args: any[]): Promise<T> {
this._logService.trace('CommandService#executeCommand', id);
// we always send an activation event, but
// we don't wait for it when the extension
// host didn't yet start and the command is already registered
const activation: Promise<any> = this._extensionService.activateByEvent(`onCommand:${id}`);
const activationEvent = `onCommand:${id}`;
const commandIsRegistered = !!CommandsRegistry.getCommand(id);
if (!this._extensionHostIsReady && commandIsRegistered) {
return this._tryExecuteCommand(id, args);
} else {
let waitFor = activation;
if (!commandIsRegistered) {
waitFor = Promise.all([
activation,
Promise.race<any>([
// race * activation against command registration
this._activateStar(),
Event.toPromise(Event.filter(CommandsRegistry.onDidRegisterCommand, e => e === id))
]),
]);
if (commandIsRegistered) {
// if the activation event has already resolved (i.e. subsequent call),
// we will execute the registered command immediately
if (this._extensionService.activationEventIsDone(activationEvent)) {
return this._tryExecuteCommand(id, args);
}
return waitFor.then(_ => this._tryExecuteCommand(id, args));
// if the extension host didn't start yet, we will execute the registered
// command immediately and send an activation event, but not wait for it
if (!this._extensionHostIsReady) {
this._extensionService.activateByEvent(activationEvent); // intentionally not awaited
return this._tryExecuteCommand(id, args);
}
// we will wait for a simple activation event (e.g. in case an extension wants to overwrite it)
await this._extensionService.activateByEvent(activationEvent);
return this._tryExecuteCommand(id, args);
}
// finally, if the command is not registered we will send a simple activation event
// as well as a * activation event raced against registration and against 30s
await Promise.all([
this._extensionService.activateByEvent(activationEvent),
Promise.race<any>([
// race * activation against command registration
this._activateStar(),
Event.toPromise(Event.filter(CommandsRegistry.onDidRegisterCommand, e => e === id))
]),
]);
return this._tryExecuteCommand(id, args);
}
private _tryExecuteCommand(id: string, args: any[]): Promise<any> {

View file

@ -175,4 +175,37 @@ suite('CommandService', function () {
disposables.dispose();
});
});
test('issue #142155: execute commands synchronously if possible', async () => {
const actualOrder: string[] = [];
const disposables = new DisposableStore();
disposables.add(CommandsRegistry.registerCommand(`bizBaz`, () => {
actualOrder.push('executing command');
}));
const extensionService = new class extends NullExtensionService {
override activationEventIsDone(_activationEvent: string): boolean {
return true;
}
};
const service = new CommandService(new InstantiationService(), extensionService, new NullLogService());
await extensionService.whenInstalledExtensionsRegistered();
try {
actualOrder.push(`before call`);
const promise = service.executeCommand('bizBaz');
actualOrder.push(`after call`);
await promise;
actualOrder.push(`resolved`);
assert.deepStrictEqual(actualOrder, [
'before call',
'executing command',
'after call',
'resolved'
]);
} finally {
disposables.dispose();
}
});
});

View file

@ -719,6 +719,17 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
return result;
}
public activationEventIsDone(activationEvent: string): boolean {
if (!this._installedExtensionsReady.isOpen()) {
return false;
}
if (!this._registry.containsActivationEvent(activationEvent)) {
// There is no extension that is interested in this activation event
return true;
}
return this._extensionHostManagers.every(manager => manager.activationEventIsDone(activationEvent));
}
public whenInstalledExtensionsRegistered(): Promise<boolean> {
return this._installedExtensionsReady.wait();
}

View file

@ -40,6 +40,7 @@ export interface IExtensionHostManager {
deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise<void>;
activate(extension: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<boolean>;
activateByEvent(activationEvent: string, activationKind: ActivationKind): Promise<void>;
activationEventIsDone(activationEvent: string): boolean;
getInspectPort(tryEnableInspector: boolean): Promise<number>;
resolveAuthority(remoteAuthority: string): Promise<ResolverResult>;
getCanonicalURI(remoteAuthority: string, uri: URI): Promise<URI>;
@ -86,6 +87,7 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager {
* A map of already requested activation events to speed things up if the same activation event is triggered multiple times.
*/
private readonly _cachedActivationEvents: Map<string, Promise<void>>;
private readonly _resolvedActivationEvents: Set<string>;
private _rpcProtocol: RPCProtocol | null;
private readonly _customers: IDisposable[];
private readonly _extensionHost: IExtensionHost;
@ -104,6 +106,7 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager {
) {
super();
this._cachedActivationEvents = new Map<string, Promise<void>>();
this._resolvedActivationEvents = new Set<string>();
this._rpcProtocol = null;
this._customers = [];
@ -325,6 +328,10 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager {
return this._cachedActivationEvents.get(activationEvent)!;
}
public activationEventIsDone(activationEvent: string): boolean {
return this._resolvedActivationEvents.has(activationEvent);
}
private async _activateByEvent(activationEvent: string, activationKind: ActivationKind): Promise<void> {
if (!this._proxy) {
return;
@ -335,7 +342,8 @@ class ExtensionHostManager extends Disposable implements IExtensionHostManager {
// i.e. the extension host could not be started
return;
}
return proxy.activateByEvent(activationEvent, activationKind);
await proxy.activateByEvent(activationEvent, activationKind);
this._resolvedActivationEvents.add(activationEvent);
}
public async getInspectPort(tryEnableInspector: boolean): Promise<number> {
@ -519,6 +527,15 @@ class LazyStartExtensionHostManager extends Disposable implements IExtensionHost
return this._actual.activateByEvent(activationEvent, activationKind);
}
}
public activationEventIsDone(activationEvent: string): boolean {
if (!this._startCalled.isOpen()) {
return false;
}
if (this._actual) {
return this._actual.activationEventIsDone(activationEvent);
}
return true;
}
public async getInspectPort(tryEnableInspector: boolean): Promise<number> {
await this._startCalled.wait();
if (this._actual) {

View file

@ -241,6 +241,13 @@ export interface IExtensionService {
*/
activateByEvent(activationEvent: string, activationKind?: ActivationKind): Promise<void>;
/**
* Determine if `activateByEvent(activationEvent)` has resolved already.
*
* i.e. the activation event is finished and all interested extensions are already active.
*/
activationEventIsDone(activationEvent: string): boolean;
/**
* An promise that resolves when the installed extensions are registered after
* their extension points got handled.
@ -357,6 +364,7 @@ export class NullExtensionService implements IExtensionService {
onWillActivateByEvent: Event<IWillActivateEvent> = Event.None;
onDidChangeResponsiveChange: Event<IResponsiveStateChangeEvent> = Event.None;
activateByEvent(_activationEvent: string): Promise<void> { return Promise.resolve(undefined); }
activationEventIsDone(_activationEvent: string): boolean { return false; }
whenInstalledExtensionsRegistered(): Promise<boolean> { return Promise.resolve(true); }
getExtensions(): Promise<IExtensionDescription[]> { return Promise.resolve([]); }
getExtension() { return Promise.resolve(undefined); }

View file

@ -50,6 +50,11 @@ declare module 'vscode' {
*/
readonly isActive: boolean;
/**
* Whether or not the dirty indicator is present on the tab
*/
readonly isDirty: boolean;
/**
* Moves a tab to the given index within the column.
* If the index is out of range, the tab will be moved to the end of the column.
@ -77,7 +82,7 @@ declare module 'vscode' {
/**
* All the groups within the group container
*/
all: TabGroup[];
readonly all: TabGroup[];
/**
* An {@link Event} which fires when a group changes.
@ -90,21 +95,21 @@ declare module 'vscode' {
/**
* Whether or not the group is currently active
*/
isActive: boolean;
readonly isActive: boolean;
/**
* The view column of the groups
*/
viewColumn: ViewColumn;
readonly viewColumn: ViewColumn;
/**
* The active tab within the group
*/
activeTab: Tab | undefined;
readonly activeTab: Tab | undefined;
/**
* The list of tabs contained within the group
*/
tabs: Tab[];
readonly tabs: Tab[];
}
}

View file

@ -12368,15 +12368,15 @@ xterm-addon-webgl@0.12.0-beta.24:
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.24.tgz#5c17256933991856554c95c9bd1eaab42e9727a0"
integrity sha512-+wZxKReEOlfN9JRHyikoffA6Do61/THR7QY35ajkQo0lLutKr6hTd/TLTuZh0PhFVelgTgudpXqlP++Lc0WFIA==
xterm-headless@4.18.0-beta.5:
version "4.18.0-beta.5"
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.18.0-beta.5.tgz#31abe8053462451b0603e252afdb328bcbb0e674"
integrity sha512-Whf53jmnA8QbuceNieR6dGtvqresnInxLyaFaGHRWwizbjF7OmTN/r0hFxH4ul8codmK+GAZ+GnM1OCyJ0aFfg==
xterm-headless@4.18.0-beta.6:
version "4.18.0-beta.6"
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.18.0-beta.6.tgz#ee4b2a1925603dfb1fb79b259142edb04c5ee4d4"
integrity sha512-yWKFgY7oKz/EoP0blLalcgjnCRxldPRW/SBObBkgrMAmN4ItiDrQV4aUZjXLrhlOJ8BmeJdw4TjaBbUXnvWaBQ==
xterm@4.18.0-beta.5:
version "4.18.0-beta.5"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.18.0-beta.5.tgz#69d7c6255d9245437302d07fac3d070f167d73a2"
integrity sha512-ZpCix34QB+B72F3Ulux3fD/BJ+K5KhJcUBZg2jut9vVVEaydr8xRVic5v9x18L3ejIgnGnSSZci6NPF7uaQiDg==
xterm@4.18.0-beta.6:
version "4.18.0-beta.6"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.18.0-beta.6.tgz#65ebbe50e36eb202c57aa07e76062fc17431a06b"
integrity sha512-3hVi8fJX0S01vuI1RnFOtTyW3L7x/gZU8IfIUrwjbfKlaoQqkny0YFVlLGR0X3OjPFuR0oowsU+P+wysj6P2fQ==
y18n@^3.2.1:
version "3.2.2"