Merge branch 'main' into tyriar/188173_term_selection

This commit is contained in:
Daniel Imms
2023-07-19 11:07:37 -07:00
committed by GitHub
225 changed files with 6351 additions and 4075 deletions
@@ -21,7 +21,8 @@ module.exports = withBrowserDefaults({
'uuid': path.resolve(__dirname, 'node_modules/uuid/dist/esm-browser/index.js'),
'./node/authServer': path.resolve(__dirname, 'src/browser/authServer'),
'./node/crypto': path.resolve(__dirname, 'src/browser/crypto'),
'./node/fetch': path.resolve(__dirname, 'src/browser/fetch')
'./node/fetch': path.resolve(__dirname, 'src/browser/fetch'),
'./node/buffer': path.resolve(__dirname, 'src/browser/buffer'),
}
}
});
@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function base64Encode(text: string): string {
return btoa(text);
}
@@ -26,4 +26,7 @@ export class Log {
this.output.error(message);
}
public warn(message: string): void {
this.output.warn(message);
}
}
@@ -201,7 +201,8 @@ const allFlows: IFlow[] = [
supportsGitHubEnterpriseServer: false,
supportsHostedGitHubEnterprise: true,
supportsRemoteExtensionHost: true,
supportsWebWorkerExtensionHost: true,
// Web worker can't open a port to listen for the redirect
supportsWebWorkerExtensionHost: false,
// exchanging a code for a token requires a client secret
supportsNoClientSecret: false,
supportsSupportedClients: true,
@@ -363,6 +363,7 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid
sessions.splice(sessionIndex, 1);
await this.storeSessions(sessions);
await this._githubServer.logout(session);
this._sessionChangeEmitter.fire({ added: [], removed: [session], changed: [] });
} else {
@@ -12,6 +12,8 @@ import { crypto } from './node/crypto';
import { fetching } from './node/fetch';
import { ExtensionHost, GitHubTarget, getFlows } from './flows';
import { NETWORK_ERROR, USER_CANCELLATION_ERROR } from './common/errors';
import { Config } from './config';
import { base64Encode } from './node/buffer';
// This is the error message that we throw if the login was cancelled for any reason. Extensions
// calling `getSession` can handle this error to know that the user cancelled the login.
@@ -22,6 +24,7 @@ const REDIRECT_URL_INSIDERS = 'https://insiders.vscode.dev/redirect';
export interface IGitHubServer {
login(scopes: string): Promise<string>;
logout(session: vscode.AuthenticationSession): Promise<void>;
getUserInfo(token: string): Promise<{ id: string; accountName: string }>;
sendAdditionalTelemetryInfo(session: vscode.AuthenticationSession): Promise<void>;
friendlyName: string;
@@ -78,9 +81,14 @@ export class GitHubServer implements IGitHubServer {
}
// TODO@joaomoreno TODO@TylerLeonhardt
private _isNoCorsEnvironment: boolean | undefined;
private async isNoCorsEnvironment(): Promise<boolean> {
if (this._isNoCorsEnvironment !== undefined) {
return this._isNoCorsEnvironment;
}
const uri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/dummy`));
return (uri.scheme === 'https' && /^((insiders\.)?vscode|github)\./.test(uri.authority)) || (uri.scheme === 'http' && /^localhost/.test(uri.authority));
this._isNoCorsEnvironment = (uri.scheme === 'https' && /^((insiders\.)?vscode|github)\./.test(uri.authority)) || (uri.scheme === 'http' && /^localhost/.test(uri.authority));
return this._isNoCorsEnvironment;
}
public async login(scopes: string): Promise<string> {
@@ -144,6 +152,58 @@ export class GitHubServer implements IGitHubServer {
throw new Error(userCancelled ? CANCELLATION_ERROR : 'No auth flow succeeded.');
}
public async logout(session: vscode.AuthenticationSession): Promise<void> {
this._logger.trace(`Deleting session (${session.id}) from server...`);
if (!Config.gitHubClientSecret) {
this._logger.warn('No client secret configured for GitHub authentication. The token has been deleted with best effort on this system, but we are unable to delete the token on server without the client secret.');
return;
}
// Only attempt to delete OAuth tokens. They are always prefixed with `gho_`.
// https://docs.github.com/en/rest/apps/oauth-applications#about-oauth-apps-and-oauth-authorizations-of-github-apps
if (!session.accessToken.startsWith('gho_')) {
this._logger.warn('The token being deleted is not an OAuth token. It has been deleted locally, but we cannot delete it on server.');
return;
}
if (!isSupportedTarget(this._type, this._ghesUri)) {
this._logger.trace('GitHub.com and GitHub hosted GitHub Enterprise are the only options that support deleting tokens on the server. Skipping.');
return;
}
const authHeader = 'Basic ' + base64Encode(`${Config.gitHubClientId}:${Config.gitHubClientSecret}`);
const uri = this.getServerUri(`/applications/${Config.gitHubClientId}/token`);
try {
// Defined here: https://docs.github.com/en/rest/apps/oauth-applications?apiVersion=2022-11-28#delete-an-app-token
const result = await fetching(uri.toString(true), {
method: 'DELETE',
headers: {
Accept: 'application/vnd.github+json',
Authorization: authHeader,
'X-GitHub-Api-Version': '2022-11-28',
'User-Agent': `${vscode.env.appName} (${vscode.env.appHost})`
},
body: JSON.stringify({ access_token: session.accessToken }),
});
if (result.status === 204) {
this._logger.trace(`Successfully deleted token from session (${session.id}) from server.`);
return;
}
try {
const body = await result.text();
throw new Error(body);
} catch (e) {
throw new Error(`${result.status} ${result.statusText}`);
}
} catch (e) {
this._logger.warn('Failed to delete token from server.' + e.message ?? e);
}
}
private getServerUri(path: string = '') {
const apiUri = this.baseUri;
// github.com and Hosted GitHub Enterprise instances
@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function base64Encode(text: string): string {
return Buffer.from(text, 'binary').toString('base64');
}
+2
View File
@@ -191,6 +191,8 @@ export function getVscodeDevHost(): string {
}
export async function ensurePublished(repository: Repository, file: vscode.Uri) {
await repository.status();
if ((repository.state.HEAD?.type === RefType.Head || repository.state.HEAD?.type === RefType.Tag)
// If HEAD is not published, make sure it is
&& !repository?.state.HEAD?.upstream
@@ -176,42 +176,39 @@ export const activate: ActivationFunction<void> = (ctx) => {
hr {
border: 0;
height: 2px;
border-bottom: 2px solid;
}
h2, h3, h4, h5, h6 {
font-weight: normal;
height: 1px;
border-bottom: 1px solid;
}
h1 {
font-size: 2.3em;
font-size: 2em;
margin-top: 0;
padding-bottom: 0.3em;
border-bottom-width: 1px;
border-bottom-style: solid;
}
h2 {
font-size: 2em;
}
h3 {
font-size: 1.7em;
}
h3 {
font-size: 1.5em;
padding-bottom: 0.3em;
border-bottom-width: 1px;
border-bottom-style: solid;
}
h3 {
font-size: 1.25em;
}
h4 {
font-size: 1.3em;
font-size: 1em;
}
h5 {
font-size: 1.2em;
font-size: 0.875em;
}
h1,
h2,
h3 {
font-weight: normal;
h6 {
font-size: 0.85em;
}
div {
@@ -229,12 +226,38 @@ export const activate: ActivationFunction<void> = (ctx) => {
}
/* Removes bottom margin when only one item exists in markdown cell */
#preview > *:only-child,
#preview > *:last-child {
#preview > *:not(h1):not(h2):only-child,
#preview > *:not(h1):not(h2):last-child {
margin-bottom: 0;
padding-bottom: 0;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 600;
margin-top: 24px;
margin-bottom: 16px;
line-height: 1.25;
}
.vscode-light h1,
.vscode-light h2,
.vscode-light hr,
.vscode-light td {
border-color: rgba(0, 0, 0, 0.18);
}
.vscode-dark h1,
.vscode-dark h2,
.vscode-dark hr,
.vscode-dark td {
border-color: rgba(255, 255, 255, 0.18);
}
/* makes all markdown cells consistent */
div {
min-height: var(--notebook-markdown-min-height);
@@ -114,6 +114,7 @@ export class MarkdownItEngine implements IMdParser {
_contributionProvider.onContributionsChanged(() => {
// Markdown plugin contributions may have changed
this._md = undefined;
this._tokenCache.clean();
});
}
@@ -95,7 +95,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
}
this._register(_contributionProvider.onContributionsChanged(() => {
setTimeout(() => this.refresh(), 0);
setTimeout(() => this.refresh(true), 0);
}));
this._register(vscode.workspace.onDidChangeTextDocument(event => {
+11 -20
View File
@@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import type { ActivationFunction, OutputItem, RendererContext } from 'vscode-notebook-renderer';
import { createOutputContent, scrollableClass } from './textHelper';
import { HtmlRenderingHook, IDisposable, IRichRenderContext, JavaScriptRenderingHook, RenderOptions } from './rendererTypes';
import { createOutputContent, appendOutput, scrollableClass } from './textHelper';
import { HtmlRenderingHook, IDisposable, IRichRenderContext, JavaScriptRenderingHook, OutputWithAppend, RenderOptions } from './rendererTypes';
import { ttPolicy } from './htmlHelper';
function clearContainer(container: HTMLElement) {
@@ -152,7 +152,7 @@ function renderError(
outputInfo: OutputItem,
outputElement: HTMLElement,
ctx: IRichRenderContext,
trustHTML: boolean
trustHtml: boolean
): IDisposable {
const disposableStore = createDisposableStore();
@@ -172,7 +172,7 @@ function renderError(
outputElement.classList.add('traceback');
const outputScrolling = scrollingEnabled(outputInfo, ctx.settings);
const content = createOutputContent(outputInfo.id, [err.stack ?? ''], ctx.settings.lineLimit, outputScrolling, trustHTML);
const content = createOutputContent(outputInfo.id, err.stack ?? '', { linesLimit: ctx.settings.lineLimit, scrollable: outputScrolling, trustHtml });
const contentParent = document.createElement('div');
contentParent.classList.toggle('word-wrap', ctx.settings.outputWordWrap);
disposableStore.push(ctx.onDidChangeSettings(e => {
@@ -270,19 +270,13 @@ function scrollingEnabled(output: OutputItem, options: RenderOptions) {
// div.output.output-stream <-- outputElement parameter
// div.scrollable? tabindex="0" <-- contentParent
// div output-item-id="{guid}" <-- content from outputItem parameter
function renderStream(outputInfo: OutputItem, outputElement: HTMLElement, error: boolean, ctx: IRichRenderContext): IDisposable {
function renderStream(outputInfo: OutputWithAppend, outputElement: HTMLElement, error: boolean, ctx: IRichRenderContext): IDisposable {
const disposableStore = createDisposableStore();
const outputScrolling = scrollingEnabled(outputInfo, ctx.settings);
const outputOptions = { linesLimit: ctx.settings.lineLimit, scrollable: outputScrolling, trustHtml: false, error };
outputElement.classList.add('output-stream');
const text = outputInfo.text();
const newContent = createOutputContent(outputInfo.id, [text], ctx.settings.lineLimit, outputScrolling, false);
newContent.setAttribute('output-item-id', outputInfo.id);
if (error) {
newContent.classList.add('error');
}
const scrollTop = outputScrolling ? findScrolledHeight(outputElement) : undefined;
const previousOutputParent = getPreviousMatchingContentGroup(outputElement);
@@ -290,9 +284,9 @@ function renderStream(outputInfo: OutputItem, outputElement: HTMLElement, error:
if (previousOutputParent) {
const existingContent = previousOutputParent.querySelector(`[output-item-id="${outputInfo.id}"]`) as HTMLElement | null;
if (existingContent) {
existingContent.replaceWith(newContent);
appendOutput(outputInfo, existingContent, outputOptions);
} else {
const newContent = createOutputContent(outputInfo.id, outputInfo.text(), outputOptions);
previousOutputParent.appendChild(newContent);
}
previousOutputParent.classList.toggle('scrollbar-visible', previousOutputParent.scrollHeight > previousOutputParent.clientHeight);
@@ -301,12 +295,9 @@ function renderStream(outputInfo: OutputItem, outputElement: HTMLElement, error:
const existingContent = outputElement.querySelector(`[output-item-id="${outputInfo.id}"]`) as HTMLElement | null;
let contentParent = existingContent?.parentElement;
if (existingContent && contentParent) {
existingContent.replaceWith(newContent);
while (newContent.nextSibling) {
// clear out any stale content if we had previously combined streaming outputs into this one
newContent.nextSibling.remove();
}
appendOutput(outputInfo, existingContent, outputOptions);
} else {
const newContent = createOutputContent(outputInfo.id, outputInfo.text(), outputOptions);
contentParent = document.createElement('div');
contentParent.appendChild(newContent);
while (outputElement.firstChild) {
@@ -333,7 +324,7 @@ function renderText(outputInfo: OutputItem, outputElement: HTMLElement, ctx: IRi
const text = outputInfo.text();
const outputScrolling = scrollingEnabled(outputInfo, ctx.settings);
const content = createOutputContent(outputInfo.id, [text], ctx.settings.lineLimit, outputScrolling, false);
const content = createOutputContent(outputInfo.id, text, { linesLimit: ctx.settings.lineLimit, scrollable: outputScrolling, trustHtml: false });
content.classList.add('output-plaintext');
if (ctx.settings.outputWordWrap) {
content.classList.add('word-wrap');
@@ -35,3 +35,14 @@ export interface RenderOptions {
}
export type IRichRenderContext = RendererContext<void> & { readonly settings: RenderOptions; readonly onDidChangeSettings: Event<RenderOptions> };
export type OutputElementOptions = {
linesLimit: number;
scrollable?: boolean;
error?: boolean;
trustHtml?: boolean;
};
export interface OutputWithAppend extends OutputItem {
appendedText?(): string | undefined;
}
@@ -5,8 +5,8 @@
import * as assert from 'assert';
import { activate } from '..';
import { OutputItem, RendererApi } from 'vscode-notebook-renderer';
import { IDisposable, IRichRenderContext, RenderOptions } from '../rendererTypes';
import { RendererApi } from 'vscode-notebook-renderer';
import { IDisposable, IRichRenderContext, OutputWithAppend, RenderOptions } from '../rendererTypes';
import { JSDOM } from "jsdom";
const dom = new JSDOM();
@@ -116,10 +116,13 @@ suite('Notebook builtin output renderer', () => {
}
}
function createOutputItem(text: string, mime: string, id: string = '123'): OutputItem {
function createOutputItem(text: string, mime: string, id: string = '123', appendedText?: string): OutputWithAppend {
return {
id: id,
mime: mime,
appendedText() {
return appendedText;
},
text() {
return text;
},
@@ -177,9 +180,9 @@ suite('Notebook builtin output renderer', () => {
assert.ok(renderer, 'Renderer not created');
const outputElement = new OutputHtml().getFirstOuputElement();
const outputItem = createOutputItem('content', 'text/plain');
const outputItem = createOutputItem('content', mimeType);
await renderer!.renderOutputItem(outputItem, outputElement);
const outputItem2 = createOutputItem('replaced content', 'text/plain');
const outputItem2 = createOutputItem('replaced content', mimeType);
await renderer!.renderOutputItem(outputItem2, outputElement);
const inserted = outputElement.firstChild as HTMLElement;
@@ -189,6 +192,87 @@ suite('Notebook builtin output renderer', () => {
});
test('Append streaming output', async () => {
const context = createContext({ outputWordWrap: false, outputScrolling: true });
const renderer = await activate(context);
assert.ok(renderer, 'Renderer not created');
const outputElement = new OutputHtml().getFirstOuputElement();
const outputItem = createOutputItem('content', stdoutMimeType, '123', 'ignoredAppend');
await renderer!.renderOutputItem(outputItem, outputElement);
const outputItem2 = createOutputItem('content\nappended', stdoutMimeType, '123', '\nappended');
await renderer!.renderOutputItem(outputItem2, outputElement);
const inserted = outputElement.firstChild as HTMLElement;
assert.ok(inserted.innerHTML.indexOf('>content</') !== -1, `Previous content should still exist: ${outputElement.innerHTML}`);
assert.ok(inserted.innerHTML.indexOf('ignoredAppend') === -1, `Append value should not be used on first render: ${outputElement.innerHTML}`);
assert.ok(inserted.innerHTML.indexOf('>appended</') !== -1, `Content was not appended to output element: ${outputElement.innerHTML}`);
assert.ok(inserted.innerHTML.indexOf('>content</') === inserted.innerHTML.lastIndexOf('>content</'), `Original content should not be duplicated: ${outputElement.innerHTML}`);
});
test(`Appending multiple streaming outputs`, async () => {
const context = createContext({ outputScrolling: true });
const renderer = await activate(context);
assert.ok(renderer, 'Renderer not created');
const outputHtml = new OutputHtml();
const firstOutputElement = outputHtml.getFirstOuputElement();
const outputItem1 = createOutputItem('first stream content', stdoutMimeType, '1');
const outputItem2 = createOutputItem(JSON.stringify(error), errorMimeType, '2');
const outputItem3 = createOutputItem('second stream content', stdoutMimeType, '3');
await renderer!.renderOutputItem(outputItem1, firstOutputElement);
const secondOutputElement = outputHtml.appendOutputElement();
await renderer!.renderOutputItem(outputItem2, secondOutputElement);
const thirdOutputElement = outputHtml.appendOutputElement();
await renderer!.renderOutputItem(outputItem3, thirdOutputElement);
const appendedItem1 = createOutputItem('', stdoutMimeType, '1', ' appended1');
await renderer!.renderOutputItem(appendedItem1, firstOutputElement);
const appendedItem3 = createOutputItem('', stdoutMimeType, '3', ' appended3');
await renderer!.renderOutputItem(appendedItem3, thirdOutputElement);
assert.ok(firstOutputElement.innerHTML.indexOf('>first stream content') > -1, `Content was not added to output element: ${outputHtml.cellElement.innerHTML}`);
assert.ok(firstOutputElement.innerHTML.indexOf('appended1') > -1, `Content was not appended to output element: ${outputHtml.cellElement.innerHTML}`);
assert.ok(secondOutputElement.innerHTML.indexOf('>NameError</') > -1, `Content was not added to output element: ${outputHtml.cellElement.innerHTML}`);
assert.ok(thirdOutputElement.innerHTML.indexOf('>second stream content') > -1, `Content was not added to output element: ${outputHtml.cellElement.innerHTML}`);
assert.ok(thirdOutputElement.innerHTML.indexOf('appended3') > -1, `Content was not appended to output element: ${outputHtml.cellElement.innerHTML}`);
});
test('Append large streaming outputs', async () => {
const context = createContext({ outputWordWrap: false, outputScrolling: true });
const renderer = await activate(context);
assert.ok(renderer, 'Renderer not created');
const outputElement = new OutputHtml().getFirstOuputElement();
const lotsOfLines = new Array(4998).fill('line').join('\n');
const firstOuput = lotsOfLines + 'expected1';
const outputItem = createOutputItem(firstOuput, stdoutMimeType, '123');
await renderer!.renderOutputItem(outputItem, outputElement);
const appended = '\n' + lotsOfLines + 'expectedAppend';
const outputItem2 = createOutputItem(firstOuput + appended, stdoutMimeType, '123', appended);
await renderer!.renderOutputItem(outputItem2, outputElement);
const inserted = outputElement.firstChild as HTMLElement;
assert.ok(inserted.innerHTML.indexOf('expected1') !== -1, `Last bit of previous content should still exist`);
assert.ok(inserted.innerHTML.indexOf('expectedAppend') !== -1, `Content was not appended to output element`);
});
test('Streaming outputs larger than the line limit are truncated', async () => {
const context = createContext({ outputWordWrap: false, outputScrolling: true });
const renderer = await activate(context);
assert.ok(renderer, 'Renderer not created');
const outputElement = new OutputHtml().getFirstOuputElement();
const lotsOfLines = new Array(11000).fill('line').join('\n');
const firstOuput = 'shouldBeTruncated' + lotsOfLines + 'expected1';
const outputItem = createOutputItem(firstOuput, stdoutMimeType, '123');
await renderer!.renderOutputItem(outputItem, outputElement);
const inserted = outputElement.firstChild as HTMLElement;
assert.ok(inserted.innerHTML.indexOf('expected1') !== -1, `Last bit of content should exist`);
assert.ok(inserted.innerHTML.indexOf('shouldBeTruncated') === -1, `Beginning content should be truncated`);
});
test(`Render with wordwrap and scrolling for error output`, async () => {
const context = createContext({ outputWordWrap: true, outputScrolling: true });
const renderer = await activate(context);
@@ -268,6 +352,29 @@ suite('Notebook builtin output renderer', () => {
assert.ok(inserted.innerHTML.indexOf('>second stream content</') === -1, `Content was not replaced in output element: ${outputHtml.cellElement.innerHTML}`);
});
test(`Consolidated streaming outputs should append matching outputs correctly`, async () => {
const context = createContext({ outputScrolling: true });
const renderer = await activate(context);
assert.ok(renderer, 'Renderer not created');
const outputHtml = new OutputHtml();
const outputElement = outputHtml.getFirstOuputElement();
const outputItem1 = createOutputItem('first stream content', stdoutMimeType, '1');
const outputItem2 = createOutputItem('second stream content', stdoutMimeType, '2');
await renderer!.renderOutputItem(outputItem1, outputElement);
const secondOutput = outputHtml.appendOutputElement();
await renderer!.renderOutputItem(outputItem2, secondOutput);
const appendingOutput = createOutputItem('', stdoutMimeType, '2', ' appended');
await renderer!.renderOutputItem(appendingOutput, secondOutput);
const inserted = outputElement.firstChild as HTMLElement;
assert.ok(inserted, `nothing appended to output element: ${outputElement.innerHTML}`);
assert.ok(inserted.innerHTML.indexOf('>first stream content</') > -1, `Content was not added to output element: ${outputHtml.cellElement.innerHTML}`);
assert.ok(inserted.innerHTML.indexOf('>second stream content') > -1, `Second content was not added to ouptut element: ${outputHtml.cellElement.innerHTML}`);
assert.ok(inserted.innerHTML.indexOf('appended') > -1, `Content was not appended to ouptut element: ${outputHtml.cellElement.innerHTML}`);
});
test(`Streaming outputs interleaved with other mime types will produce separate outputs`, async () => {
const context = createContext({ outputScrolling: false });
const renderer = await activate(context);
+61 -10
View File
@@ -4,9 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import { handleANSIOutput } from './ansi';
import { OutputElementOptions, OutputWithAppend } from './rendererTypes';
export const scrollableClass = 'scrollable';
const softScrollableLineLimit = 5000;
const hardScrollableLineLimit = 8000;
/**
* Output is Truncated. View as a [scrollable element] or open in a [text editor]. Adjust cell output [settings...]
*/
@@ -91,22 +94,70 @@ function truncatedArrayOfString(id: string, buffer: string[], linesLimit: number
function scrollableArrayOfString(id: string, buffer: string[], trustHtml: boolean) {
const element = document.createElement('div');
if (buffer.length > 5000) {
if (buffer.length > softScrollableLineLimit) {
element.appendChild(generateNestedViewAllElement(id));
}
element.appendChild(handleANSIOutput(buffer.slice(-5000).join('\n'), trustHtml));
element.appendChild(handleANSIOutput(buffer.slice(-1 * softScrollableLineLimit).join('\n'), trustHtml));
return element;
}
export function createOutputContent(id: string, outputs: string[], linesLimit: number, scrollable: boolean, trustHtml: boolean): HTMLElement {
const outputLengths: Record<string, number> = {};
const buffer = outputs.join('\n').split(/\r\n|\r|\n/g);
if (scrollable) {
return scrollableArrayOfString(id, buffer, trustHtml);
} else {
return truncatedArrayOfString(id, buffer, linesLimit, trustHtml);
function appendScrollableOutput(element: HTMLElement, id: string, appended: string, trustHtml: boolean) {
if (!outputLengths[id]) {
outputLengths[id] = 0;
}
const buffer = appended.split(/\r\n|\r|\n/g);
const appendedLength = buffer.length + outputLengths[id];
// Only append outputs up to the hard limit of lines, then replace it with the last softLimit number of lines
if (appendedLength > hardScrollableLineLimit) {
return false;
}
else {
element.appendChild(handleANSIOutput(buffer.join('\n'), trustHtml));
outputLengths[id] = appendedLength;
}
return true;
}
export function createOutputContent(id: string, outputText: string, options: OutputElementOptions): HTMLElement {
const { linesLimit, error, scrollable, trustHtml } = options;
const buffer = outputText.split(/\r\n|\r|\n/g);
outputLengths[id] = outputLengths[id] = Math.min(buffer.length, softScrollableLineLimit);
let outputElement: HTMLElement;
if (scrollable) {
outputElement = scrollableArrayOfString(id, buffer, !!trustHtml);
} else {
outputElement = truncatedArrayOfString(id, buffer, linesLimit, !!trustHtml);
}
outputElement.setAttribute('output-item-id', id);
if (error) {
outputElement.classList.add('error');
}
return outputElement;
}
export function appendOutput(outputInfo: OutputWithAppend, existingContent: HTMLElement, options: OutputElementOptions) {
const appendedText = outputInfo.appendedText?.();
// appending output only supported for scrollable ouputs currently
if (appendedText && options.scrollable) {
if (appendScrollableOutput(existingContent, outputInfo.id, appendedText, false)) {
return;
}
}
const newContent = createOutputContent(outputInfo.id, outputInfo.text(), options);
existingContent.replaceWith(newContent);
while (newContent.nextSibling) {
// clear out any stale content if we had previously combined streaming outputs into this one
newContent.nextSibling.remove();
}
}
@@ -152,7 +152,7 @@
"typescript.preferences.includePackageJsonAutoImports.auto": "Search dependencies based on estimated performance impact.",
"typescript.preferences.includePackageJsonAutoImports.on": "Always search dependencies.",
"typescript.preferences.includePackageJsonAutoImports.off": "Never search dependencies.",
"typescript.preferences.autoImportFileExcludePatterns": "Specify glob patterns of files to exclude from auto imports. Requires using TypeScript 4.8 or newer in the workspace.",
"typescript.preferences.autoImportFileExcludePatterns": "Specify glob patterns of files to exclude from auto imports. Relative paths are resolved relative to the workspace root. Patterns are evaluated using tsconfig.json [`exclude`](https://www.typescriptlang.org/tsconfig#exclude) semantics. Requires using TypeScript 4.8 or newer in the workspace.",
"typescript.updateImportsOnFileMove.enabled": "Enable/disable automatic updating of import paths when you rename or move a file in VS Code.",
"typescript.updateImportsOnFileMove.enabled.prompt": "Prompt on each rename.",
"typescript.updateImportsOnFileMove.enabled.always": "Always update paths automatically.",
@@ -188,10 +188,10 @@ function convertLinkTags(
if (/^https?:/.test(text)) {
const parts = text.split(' ');
if (parts.length === 1) {
out.push(parts[0]);
out.push(`<${parts[0]}>`);
} else if (parts.length > 1) {
const linkText = escapeMarkdownSyntaxTokensForCode(parts.slice(1).join(' '));
out.push(`[${currentLink.linkcode ? '`' + linkText + '`' : linkText}](${parts[0]})`);
const linkText = parts.slice(1).join(' ');
out.push(`[${currentLink.linkcode ? '`' + escapeMarkdownSyntaxTokensForCode(linkText) + '`' : linkText}](${parts[0]})`);
}
} else {
out.push(escapeMarkdownSyntaxTokensForCode(text));
@@ -29,6 +29,7 @@ import { PluginManager, TypeScriptServerPlugin } from './tsServer/plugins';
import { TelemetryProperties, TelemetryReporter, VSCodeTelemetryReporter } from './logging/telemetry';
import Tracer from './logging/tracer';
import { ProjectType, inferredProjectCompilerOptions } from './tsconfig';
import { Schemes } from './configuration/schemes';
export interface TsDiagnostics {
@@ -762,6 +763,18 @@ export default class TypeScriptServiceClient extends Disposable implements IType
return undefined;
}
// For notebook cells, we need to use the notebook document to look up the workspace
if (resource.scheme === Schemes.notebookCell) {
for (const notebook of vscode.workspace.notebookDocuments) {
for (const cell of notebook.getCells()) {
if (cell.document.uri.toString() === resource.toString()) {
resource = notebook.uri;
break;
}
}
}
}
for (const root of roots.sort((a, b) => a.uri.fsPath.length - b.uri.fsPath.length)) {
if (root.uri.scheme === resource.scheme && root.uri.authority === resource.authority) {
if (resource.fsPath.startsWith(root.uri.fsPath + path.sep)) {
@@ -770,7 +783,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType
}
}
return undefined;
return vscode.workspace.getWorkspaceFolder(resource)?.uri;
}
public execute(command: keyof TypeScriptRequests, args: any, token: vscode.CancellationToken, config?: ExecConfig): Promise<ServerResponse.Response<Proto.Response>> {
-1
View File
@@ -46,7 +46,6 @@
"treeItemCheckbox",
"treeViewActiveItem",
"treeViewReveal",
"testInvalidateResults",
"workspaceTrust",
"telemetry",
"windowActivity",