mirror of
https://github.com/Microsoft/vscode
synced 2024-08-26 20:39:37 +00:00
Merge remote-tracking branch 'origin/main' into tyriar/141006
This commit is contained in:
commit
7de1a818a8
7
.github/subscribers.json
vendored
7
.github/subscribers.json
vendored
|
@ -1,9 +1,2 @@
|
|||
{
|
||||
"notebook": [
|
||||
"rchiodo",
|
||||
"greazer",
|
||||
"donjayamanne",
|
||||
"jilljac",
|
||||
"IanMatthewHuff"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
|
|
@ -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
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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%"
|
||||
],
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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([
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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==
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
Binary file not shown.
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
38
src/vs/base/common/observableValue.ts
Normal file
38
src/vs/base/common/observableValue.ts
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -631,6 +631,7 @@ export interface IEditorTabDto {
|
|||
resource?: UriComponents;
|
||||
editorId?: string;
|
||||
isActive: boolean;
|
||||
isDirty: boolean;
|
||||
additionalResourcesAndViewIds: { resource?: UriComponents; viewId?: string }[];
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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'),
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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 = [];
|
||||
|
|
|
@ -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' };
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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`;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()}; }`);
|
||||
});
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
},
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -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([
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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); }
|
||||
|
|
15
src/vscode-dts/vscode.proposed.tabs.d.ts
vendored
15
src/vscode-dts/vscode.proposed.tabs.d.ts
vendored
|
@ -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[];
|
||||
}
|
||||
}
|
||||
|
|
16
yarn.lock
16
yarn.lock
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue