Merge from vscode merge-base (#22780)

* Revert "Revert "Merge from vscode merge-base (#22769)" (#22779)"

This reverts commit 47a1745180.

* Fix notebook download task

* Remove done call from extensions-ci
This commit is contained in:
Karl Burtram
2023-04-19 21:48:46 -07:00
committed by GitHub
parent decbe8dded
commit e7d3d047ec
2389 changed files with 92155 additions and 42602 deletions

View File

@@ -25,6 +25,10 @@ export class Delayer<T> {
this.task = null;
}
dispose() {
this.cancelTimeout();
}
public trigger(task: ITask<T>, delay: number = this.defaultDelay): Promise<T | null> {
this.task = task;
if (delay >= 0) {

View File

@@ -0,0 +1,13 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
export const noopToken = new class implements vscode.CancellationToken {
_onCancellationRequestedEmitter = new vscode.EventEmitter<void>();
onCancellationRequested = this._onCancellationRequestedEmitter.event;
get isCancellationRequested() { return false; }
};

View File

@@ -5,13 +5,36 @@
import * as vscode from 'vscode';
export function disposeAll(disposables: vscode.Disposable[]) {
while (disposables.length) {
const item = disposables.pop();
item?.dispose();
export class MultiDisposeError extends Error {
constructor(
public readonly errors: any[]
) {
super(`Encountered errors while disposing of store. Errors: [${errors.join(', ')}]`);
}
}
export function disposeAll(disposables: Iterable<vscode.Disposable>) {
const errors: any[] = [];
for (const disposable of disposables) {
try {
disposable.dispose();
} catch (e) {
errors.push(e);
}
}
if (errors.length === 1) {
throw errors[0];
} else if (errors.length > 1) {
throw new MultiDisposeError(errors);
}
}
export interface IDisposable {
dispose(): void;
}
export abstract class Disposable {
private _isDisposed = false;
@@ -25,7 +48,7 @@ export abstract class Disposable {
disposeAll(this._disposables);
}
protected _register<T extends vscode.Disposable>(value: T): T {
protected _register<T extends IDisposable>(value: T): T {
if (this._isDisposed) {
value.dispose();
} else {
@@ -38,3 +61,22 @@ export abstract class Disposable {
return this._isDisposed;
}
}
export class DisposableStore extends Disposable {
private readonly items = new Set<IDisposable>();
public override dispose() {
super.dispose();
disposeAll(this.items);
this.items.clear();
}
public add<T extends IDisposable>(item: T): T {
if (this.isDisposed) {
console.warn('Adding to disposed store. Item will be leaked');
}
this.items.add(item);
return item;
}
}

View File

@@ -0,0 +1,18 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
export function escapeAttribute(value: string | vscode.Uri): string {
return value.toString().replace(/"/g, '&quot;');
}
export function getNonce() {
let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < 64; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}

View File

@@ -4,7 +4,24 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as URI from 'vscode-uri';
export const markdownFileExtensions = Object.freeze<string[]>([
'md',
'mkd',
'mdwn',
'mdown',
'markdown',
'markdn',
'mdtxt',
'mdtext',
'workbook',
]);
export function isMarkdownFile(document: vscode.TextDocument) {
return document.languageId === 'markdown';
}
export function looksLikeMarkdownPath(resolvedHrefPath: vscode.Uri) {
return markdownFileExtensions.includes(URI.Utils.extname(resolvedHrefPath).toLowerCase().replace('.', ''));
}

View File

@@ -5,14 +5,12 @@
import * as vscode from 'vscode';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { SkinnyTextDocument, SkinnyTextLine } from '../workspaceContents';
import { ITextDocument } from '../types/textDocument';
export class InMemoryDocument implements SkinnyTextDocument {
export class InMemoryDocument implements ITextDocument {
private readonly _doc: TextDocument;
private lines: SkinnyTextLine[] | undefined;
constructor(
public readonly uri: vscode.Uri, contents: string,
public readonly version = 0,
@@ -25,16 +23,6 @@ export class InMemoryDocument implements SkinnyTextDocument {
return this._doc.lineCount;
}
lineAt(index: any): SkinnyTextLine {
if (!this.lines) {
this.lines = this._doc.getText().split(/\r?\n/).map(text => ({
text,
get isEmptyOrWhitespace() { return /^\s*$/.test(text); }
}));
}
return this.lines[index];
}
positionAt(offset: number): vscode.Position {
const pos = this._doc.positionAt(offset);
return new vscode.Position(pos.line, pos.character);

View File

@@ -6,8 +6,9 @@
import * as path from 'path';
import * as vscode from 'vscode';
import * as uri from 'vscode-uri';
import { MarkdownEngine } from '../markdownEngine';
import { TableOfContents } from '../tableOfContents';
import { MdTableOfContentsProvider } from '../tableOfContents';
import { ITextDocument } from '../types/textDocument';
import { IMdWorkspace } from '../workspace';
import { isMarkdownFile } from './file';
export interface OpenDocumentLinkArgs {
@@ -22,7 +23,7 @@ enum OpenMarkdownLinks {
}
export function resolveDocumentLink(href: string, markdownFile: vscode.Uri): vscode.Uri {
let [hrefPath, fragment] = href.split('#').map(c => decodeURIComponent(c));
const [hrefPath, fragment] = href.split('#').map(c => decodeURIComponent(c));
if (hrefPath[0] === '/') {
// Absolute path. Try to resolve relative to the workspace
@@ -37,10 +38,10 @@ export function resolveDocumentLink(href: string, markdownFile: vscode.Uri): vsc
return vscode.Uri.joinPath(dirnameUri, hrefPath).with({ fragment });
}
export async function openDocumentLink(engine: MarkdownEngine, targetResource: vscode.Uri, fromResource: vscode.Uri): Promise<void> {
export async function openDocumentLink(tocProvider: MdTableOfContentsProvider, targetResource: vscode.Uri, fromResource: vscode.Uri): Promise<void> {
const column = getViewColumn(fromResource);
if (await tryNavigateToFragmentInActiveEditor(engine, targetResource)) {
if (await tryNavigateToFragmentInActiveEditor(tocProvider, targetResource)) {
return;
}
@@ -58,7 +59,7 @@ export async function openDocumentLink(engine: MarkdownEngine, targetResource: v
try {
const stat = await vscode.workspace.fs.stat(dotMdResource);
if (stat.type === vscode.FileType.File) {
await tryOpenMdFile(engine, dotMdResource, column);
await tryOpenMdFile(tocProvider, dotMdResource, column);
return;
}
} catch {
@@ -69,25 +70,33 @@ export async function openDocumentLink(engine: MarkdownEngine, targetResource: v
return vscode.commands.executeCommand('revealInExplorer', targetResource);
}
await tryOpenMdFile(engine, targetResource, column);
await tryOpenMdFile(tocProvider, targetResource, column);
}
async function tryOpenMdFile(engine: MarkdownEngine, resource: vscode.Uri, column: vscode.ViewColumn): Promise<boolean> {
async function tryOpenMdFile(tocProvider: MdTableOfContentsProvider, resource: vscode.Uri, column: vscode.ViewColumn): Promise<boolean> {
await vscode.commands.executeCommand('vscode.open', resource.with({ fragment: '' }), column);
return tryNavigateToFragmentInActiveEditor(engine, resource);
return tryNavigateToFragmentInActiveEditor(tocProvider, resource);
}
async function tryNavigateToFragmentInActiveEditor(engine: MarkdownEngine, resource: vscode.Uri): Promise<boolean> {
async function tryNavigateToFragmentInActiveEditor(tocProvider: MdTableOfContentsProvider, resource: vscode.Uri): Promise<boolean> {
const notebookEditor = vscode.window.activeNotebookEditor;
if (notebookEditor?.notebook.uri.fsPath === resource.fsPath) {
if (await tryRevealLineInNotebook(tocProvider, notebookEditor, resource.fragment)) {
return true;
}
}
const activeEditor = vscode.window.activeTextEditor;
if (activeEditor?.document.uri.fsPath === resource.fsPath) {
if (isMarkdownFile(activeEditor.document)) {
if (await tryRevealLineUsingTocFragment(engine, activeEditor, resource.fragment)) {
if (await tryRevealLineUsingTocFragment(tocProvider, activeEditor, resource.fragment)) {
return true;
}
}
tryRevealLineUsingLineFragment(activeEditor, resource.fragment);
return true;
}
return false;
}
@@ -103,8 +112,26 @@ function getViewColumn(resource: vscode.Uri): vscode.ViewColumn {
}
}
async function tryRevealLineUsingTocFragment(engine: MarkdownEngine, editor: vscode.TextEditor, fragment: string): Promise<boolean> {
const toc = await TableOfContents.create(engine, editor.document);
async function tryRevealLineInNotebook(tocProvider: MdTableOfContentsProvider, editor: vscode.NotebookEditor, fragment: string): Promise<boolean> {
const toc = await tocProvider.createForNotebook(editor.notebook);
const entry = toc.lookup(fragment);
if (!entry) {
return false;
}
const cell = editor.notebook.getCells().find(cell => cell.document.uri.toString() === entry.sectionLocation.uri.toString());
if (!cell) {
return false;
}
const range = new vscode.NotebookRange(cell.index, cell.index);
editor.selection = range;
editor.revealRange(range);
return true;
}
async function tryRevealLineUsingTocFragment(tocProvider: MdTableOfContentsProvider, editor: vscode.TextEditor, fragment: string): Promise<boolean> {
const toc = await tocProvider.getForDocument(editor.document);
const entry = toc.lookup(fragment);
if (entry) {
const lineStart = new vscode.Range(entry.line, 0, entry.line, 0);
@@ -129,9 +156,9 @@ function tryRevealLineUsingLineFragment(editor: vscode.TextEditor, fragment: str
return false;
}
export async function resolveUriToMarkdownFile(resource: vscode.Uri): Promise<vscode.TextDocument | undefined> {
export async function resolveUriToMarkdownFile(workspace: IMdWorkspace, resource: vscode.Uri): Promise<ITextDocument | undefined> {
try {
const doc = await tryResolveUriToMarkdownFile(resource);
const doc = await workspace.getOrLoadMarkdownDocument(resource);
if (doc) {
return doc;
}
@@ -141,21 +168,8 @@ export async function resolveUriToMarkdownFile(resource: vscode.Uri): Promise<vs
// If no extension, try with `.md` extension
if (uri.Utils.extname(resource) === '') {
return tryResolveUriToMarkdownFile(resource.with({ path: resource.path + '.md' }));
return workspace.getOrLoadMarkdownDocument(resource.with({ path: resource.path + '.md' }));
}
return undefined;
}
async function tryResolveUriToMarkdownFile(resource: vscode.Uri): Promise<vscode.TextDocument | undefined> {
let document: vscode.TextDocument;
try {
document = await vscode.workspace.openTextDocument(resource);
} catch {
return undefined;
}
if (isMarkdownFile(document)) {
return document;
}
return undefined;
}

View File

@@ -0,0 +1,68 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
type ResourceToKey = (uri: vscode.Uri) => string;
const defaultResourceToKey = (resource: vscode.Uri): string => resource.toString();
export class ResourceMap<T> {
private readonly map = new Map<string, { readonly uri: vscode.Uri; readonly value: T }>();
private readonly toKey: ResourceToKey;
constructor(toKey: ResourceToKey = defaultResourceToKey) {
this.toKey = toKey;
}
public set(uri: vscode.Uri, value: T): this {
this.map.set(this.toKey(uri), { uri, value });
return this;
}
public get(resource: vscode.Uri): T | undefined {
return this.map.get(this.toKey(resource))?.value;
}
public has(resource: vscode.Uri): boolean {
return this.map.has(this.toKey(resource));
}
public get size(): number {
return this.map.size;
}
public clear(): void {
this.map.clear();
}
public delete(resource: vscode.Uri): boolean {
return this.map.delete(this.toKey(resource));
}
public *values(): IterableIterator<T> {
for (const entry of this.map.values()) {
yield entry.value;
}
}
public *keys(): IterableIterator<vscode.Uri> {
for (const entry of this.map.values()) {
yield entry.uri;
}
}
public *entries(): IterableIterator<[vscode.Uri, T]> {
for (const entry of this.map.values()) {
yield [entry.uri, entry.value];
}
}
public [Symbol.iterator](): IterableIterator<[vscode.Uri, T]> {
return this.entries();
}
}

View File

@@ -3,44 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
export const Schemes = {
http: 'http:',
https: 'https:',
file: 'file:',
export const Schemes = Object.freeze({
file: 'file',
untitled: 'untitled',
mailto: 'mailto:',
data: 'data:',
vscode: 'vscode:',
'vscode-insiders': 'vscode-insiders:',
};
const knownSchemes = [
...Object.values(Schemes),
`${vscode.env.uriScheme}:`
];
export function getUriForLinkWithKnownExternalScheme(link: string): vscode.Uri | undefined {
if (knownSchemes.some(knownScheme => isOfScheme(knownScheme, link))) {
return vscode.Uri.parse(link);
}
return undefined;
}
mailto: 'mailto',
vscode: 'vscode',
'vscode-insiders': 'vscode-insiders',
notebookCell: 'vscode-notebook-cell',
});
export function isOfScheme(scheme: string, link: string): boolean {
return link.toLowerCase().startsWith(scheme);
return link.toLowerCase().startsWith(scheme + ':');
}
export const MarkdownFileExtensions: readonly string[] = [
'.md',
'.mkd',
'.mdwn',
'.mdown',
'.markdown',
'.markdn',
'.mdtxt',
'.mdtext',
'.workbook',
];

View File

@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function isEmptyOrWhitespace(str: string): boolean {
return /^\s*$/.test(str);
}

View File

@@ -0,0 +1,116 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { ITextDocument } from '../types/textDocument';
import { IMdWorkspace } from '../workspace';
import { Disposable } from './dispose';
import { Lazy, lazy } from './lazy';
import { ResourceMap } from './resourceMap';
class LazyResourceMap<T> {
private readonly _map = new ResourceMap<Lazy<Promise<T>>>();
public has(resource: vscode.Uri): boolean {
return this._map.has(resource);
}
public get(resource: vscode.Uri): Promise<T> | undefined {
return this._map.get(resource)?.value;
}
public set(resource: vscode.Uri, value: Lazy<Promise<T>>) {
this._map.set(resource, value);
}
public delete(resource: vscode.Uri) {
this._map.delete(resource);
}
public entries(): Promise<Array<[vscode.Uri, T]>> {
return Promise.all(Array.from(this._map.entries(), async ([key, entry]) => {
return [key, await entry.value] as [vscode.Uri, T]; // {{SQL CARBON EDIT}} lewissanchez - Added strict typing
}));
}
}
/**
* Cache of information per-document in the workspace.
*
* The values are computed lazily and invalidated when the document changes.
*/
export class MdDocumentInfoCache<T> extends Disposable {
private readonly _cache = new LazyResourceMap<T>();
private readonly _loadingDocuments = new ResourceMap<Promise<ITextDocument | undefined>>();
public constructor(
private readonly workspace: IMdWorkspace,
private readonly getValue: (document: ITextDocument) => Promise<T>,
) {
super();
this._register(this.workspace.onDidChangeMarkdownDocument(doc => this.invalidate(doc)));
this._register(this.workspace.onDidDeleteMarkdownDocument(this.onDidDeleteDocument, this));
}
public async get(resource: vscode.Uri): Promise<T | undefined> {
let existing = this._cache.get(resource);
if (existing) {
return existing;
}
const doc = await this.loadDocument(resource);
if (!doc) {
return undefined;
}
// Check if we have invalidated
existing = this._cache.get(resource);
if (existing) {
return existing;
}
return this.resetEntry(doc)?.value;
}
public async getForDocument(document: ITextDocument): Promise<T> {
const existing = this._cache.get(document.uri);
if (existing) {
return existing;
}
return this.resetEntry(document).value;
}
private loadDocument(resource: vscode.Uri): Promise<ITextDocument | undefined> {
const existing = this._loadingDocuments.get(resource);
if (existing) {
return existing;
}
const p = this.workspace.getOrLoadMarkdownDocument(resource);
this._loadingDocuments.set(resource, p);
p.finally(() => {
this._loadingDocuments.delete(resource);
});
return p;
}
private resetEntry(document: ITextDocument): Lazy<Promise<T>> {
const value = lazy(() => this.getValue(document));
this._cache.set(document.uri, value);
return value;
}
private invalidate(document: ITextDocument): void {
if (this._cache.has(document.uri)) {
this.resetEntry(document);
}
}
private onDidDeleteDocument(resource: vscode.Uri) {
this._cache.delete(resource);
}
}