mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-28 01:25:39 -05:00
* Fix initial build breaks from 1.67 merge (#2514) * Update yarn lock files * Update build scripts * Fix tsconfig * Build breaks * WIP * Update yarn lock files * Misc breaks * Updates to package.json * Breaks * Update yarn * Fix breaks * Breaks * Build breaks * Breaks * Breaks * Breaks * Breaks * Breaks * Missing file * Breaks * Breaks * Breaks * Breaks * Breaks * Fix several runtime breaks (#2515) * Missing files * Runtime breaks * Fix proxy ordering issue * Remove commented code * Fix breaks with opening query editor * Fix post merge break * Updates related to setup build and other breaks (#2516) * Fix bundle build issues * Update distro * Fix distro merge and update build JS files * Disable pipeline steps * Remove stats call * Update license name * Make new RPM dependencies a warning * Fix extension manager version checks * Update JS file * Fix a few runtime breaks * Fixes * Fix runtime issues * Fix build breaks * Update notebook tests (part 1) * Fix broken tests * Linting errors * Fix hygiene * Disable lint rules * Bump distro * Turn off smoke tests * Disable integration tests * Remove failing "activate" test * Remove failed test assertion * Disable other broken test * Disable query history tests * Disable extension unit tests * Disable failing tasks
309 lines
11 KiB
TypeScript
309 lines
11 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* 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 * as uri from 'vscode-uri';
|
|
import { MarkdownEngine } from '../markdownEngine';
|
|
import { Slugifier } from '../slugify';
|
|
import { TableOfContents, TocEntry } from '../tableOfContents';
|
|
import { noopToken } from '../test/util';
|
|
import { Disposable } from '../util/dispose';
|
|
import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents';
|
|
import { InternalHref, MdLink, MdLinkProvider } from './documentLinkProvider';
|
|
import { MdWorkspaceCache } from './workspaceCache';
|
|
|
|
|
|
/**
|
|
* A link in a markdown file.
|
|
*/
|
|
export interface MdLinkReference {
|
|
readonly kind: 'link';
|
|
readonly isTriggerLocation: boolean;
|
|
readonly isDefinition: boolean;
|
|
readonly location: vscode.Location;
|
|
|
|
readonly link: MdLink;
|
|
}
|
|
|
|
/**
|
|
* A header in a markdown file.
|
|
*/
|
|
export interface MdHeaderReference {
|
|
readonly kind: 'header';
|
|
|
|
readonly isTriggerLocation: boolean;
|
|
readonly isDefinition: boolean;
|
|
|
|
/**
|
|
* The range of the header.
|
|
*
|
|
* In `# a b c #` this would be the range of `# a b c #`
|
|
*/
|
|
readonly location: vscode.Location;
|
|
|
|
/**
|
|
* The text of the header.
|
|
*
|
|
* In `# a b c #` this would be `a b c`
|
|
*/
|
|
readonly headerText: string;
|
|
|
|
/**
|
|
* The range of the header text itself.
|
|
*
|
|
* In `# a b c #` this would be the range of `a b c`
|
|
*/
|
|
readonly headerTextLocation: vscode.Location;
|
|
}
|
|
|
|
export type MdReference = MdLinkReference | MdHeaderReference;
|
|
|
|
export class MdReferencesProvider extends Disposable implements vscode.ReferenceProvider {
|
|
|
|
private readonly _linkCache: MdWorkspaceCache<readonly MdLink[]>;
|
|
|
|
public constructor(
|
|
private readonly linkProvider: MdLinkProvider,
|
|
private readonly workspaceContents: MdWorkspaceContents,
|
|
private readonly engine: MarkdownEngine,
|
|
private readonly slugifier: Slugifier,
|
|
) {
|
|
super();
|
|
|
|
this._linkCache = this._register(new MdWorkspaceCache(workspaceContents, doc => linkProvider.getAllLinks(doc, noopToken)));
|
|
}
|
|
|
|
async provideReferences(document: SkinnyTextDocument, position: vscode.Position, context: vscode.ReferenceContext, token: vscode.CancellationToken): Promise<vscode.Location[] | undefined> {
|
|
const allRefs = await this.getAllReferencesAtPosition(document, position, token);
|
|
|
|
return allRefs
|
|
.filter(ref => context.includeDeclaration || !ref.isDefinition)
|
|
.map(ref => ref.location);
|
|
}
|
|
|
|
public async getAllReferencesAtPosition(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<MdReference[]> {
|
|
const toc = await TableOfContents.create(this.engine, document);
|
|
if (token.isCancellationRequested) {
|
|
return [];
|
|
}
|
|
|
|
const header = toc.entries.find(entry => entry.line === position.line);
|
|
if (header) {
|
|
return this.getReferencesToHeader(document, header);
|
|
} else {
|
|
return this.getReferencesToLinkAtPosition(document, position, token);
|
|
}
|
|
}
|
|
|
|
private async getReferencesToHeader(document: SkinnyTextDocument, header: TocEntry): Promise<MdReference[]> {
|
|
const links = (await this._linkCache.getAll()).flat();
|
|
|
|
const references: MdReference[] = [];
|
|
|
|
references.push({
|
|
kind: 'header',
|
|
isTriggerLocation: true,
|
|
isDefinition: true,
|
|
location: header.headerLocation,
|
|
headerText: header.text,
|
|
headerTextLocation: header.headerTextLocation
|
|
});
|
|
|
|
for (const link of links) {
|
|
if (link.href.kind === 'internal'
|
|
&& this.looksLikeLinkToDoc(link.href, document.uri)
|
|
&& this.slugifier.fromHeading(link.href.fragment).value === header.slug.value
|
|
) {
|
|
references.push({
|
|
kind: 'link',
|
|
isTriggerLocation: false,
|
|
isDefinition: false,
|
|
link,
|
|
location: new vscode.Location(link.source.resource, link.source.hrefRange),
|
|
});
|
|
}
|
|
}
|
|
|
|
return references;
|
|
}
|
|
|
|
private async getReferencesToLinkAtPosition(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<MdReference[]> {
|
|
const docLinks = await this.linkProvider.getAllLinks(document, token);
|
|
|
|
for (const link of docLinks) {
|
|
if (link.kind === 'definition') {
|
|
// We could be in either the ref name or the definition
|
|
if (link.ref.range.contains(position)) {
|
|
return Array.from(this.getReferencesToLinkReference(docLinks, link.ref.text, { resource: document.uri, range: link.ref.range }));
|
|
} else if (link.source.hrefRange.contains(position)) {
|
|
return this.getReferencesToLink(link, position, token);
|
|
}
|
|
} else {
|
|
if (link.source.hrefRange.contains(position)) {
|
|
return this.getReferencesToLink(link, position, token);
|
|
}
|
|
}
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
private async getReferencesToLink(sourceLink: MdLink, triggerPosition: vscode.Position, token: vscode.CancellationToken): Promise<MdReference[]> {
|
|
const allLinksInWorkspace = (await this._linkCache.getAll()).flat();
|
|
if (token.isCancellationRequested) {
|
|
return [];
|
|
}
|
|
|
|
if (sourceLink.href.kind === 'reference') {
|
|
return Array.from(this.getReferencesToLinkReference(allLinksInWorkspace, sourceLink.href.ref, { resource: sourceLink.source.resource, range: sourceLink.source.hrefRange }));
|
|
}
|
|
|
|
if (sourceLink.href.kind === 'external') {
|
|
const references: MdReference[] = [];
|
|
|
|
for (const link of allLinksInWorkspace) {
|
|
if (link.href.kind === 'external' && link.href.uri.toString() === sourceLink.href.uri.toString()) {
|
|
const isTriggerLocation = sourceLink.source.resource.fsPath === link.source.resource.fsPath && sourceLink.source.hrefRange.isEqual(link.source.hrefRange);
|
|
references.push({
|
|
kind: 'link',
|
|
isTriggerLocation,
|
|
isDefinition: false,
|
|
link,
|
|
location: new vscode.Location(link.source.resource, link.source.hrefRange),
|
|
});
|
|
}
|
|
}
|
|
return references;
|
|
}
|
|
|
|
const targetDoc = await tryFindMdDocumentForLink(sourceLink.href, this.workspaceContents);
|
|
if (token.isCancellationRequested) {
|
|
return [];
|
|
}
|
|
|
|
const references: MdReference[] = [];
|
|
|
|
if (targetDoc && sourceLink.href.fragment && sourceLink.source.fragmentRange?.contains(triggerPosition)) {
|
|
const toc = await TableOfContents.create(this.engine, targetDoc);
|
|
const entry = toc.lookup(sourceLink.href.fragment);
|
|
if (entry) {
|
|
references.push({
|
|
kind: 'header',
|
|
isTriggerLocation: false,
|
|
isDefinition: true,
|
|
location: entry.headerLocation,
|
|
headerText: entry.text,
|
|
headerTextLocation: entry.headerTextLocation
|
|
});
|
|
}
|
|
|
|
for (const link of allLinksInWorkspace) {
|
|
if (link.href.kind !== 'internal' || !this.looksLikeLinkToDoc(link.href, targetDoc.uri)) {
|
|
continue;
|
|
}
|
|
|
|
if (this.slugifier.fromHeading(link.href.fragment).equals(this.slugifier.fromHeading(sourceLink.href.fragment))) {
|
|
const isTriggerLocation = sourceLink.source.resource.fsPath === link.source.resource.fsPath && sourceLink.source.hrefRange.isEqual(link.source.hrefRange);
|
|
references.push({
|
|
kind: 'link',
|
|
isTriggerLocation,
|
|
isDefinition: false,
|
|
link,
|
|
location: new vscode.Location(link.source.resource, link.source.hrefRange),
|
|
});
|
|
}
|
|
}
|
|
} else { // Triggered on a link without a fragment so we only require matching the file and ignore fragments
|
|
references.push(...this.findAllLinksToFile(targetDoc?.uri ?? sourceLink.href.path, allLinksInWorkspace, sourceLink));
|
|
}
|
|
|
|
return references;
|
|
}
|
|
|
|
private looksLikeLinkToDoc(href: InternalHref, targetDoc: vscode.Uri) {
|
|
return href.path.fsPath === targetDoc.fsPath
|
|
|| uri.Utils.extname(href.path) === '' && href.path.with({ path: href.path.path + '.md' }).fsPath === targetDoc.fsPath;
|
|
}
|
|
|
|
public async getAllReferencesToFile(resource: vscode.Uri, _token: vscode.CancellationToken): Promise<MdReference[]> {
|
|
const allLinksInWorkspace = (await this._linkCache.getAll()).flat();
|
|
return Array.from(this.findAllLinksToFile(resource, allLinksInWorkspace, undefined));
|
|
}
|
|
|
|
private * findAllLinksToFile(resource: vscode.Uri, allLinksInWorkspace: readonly MdLink[], sourceLink: MdLink | undefined): Iterable<MdReference> {
|
|
for (const link of allLinksInWorkspace) {
|
|
if (link.href.kind !== 'internal' || !this.looksLikeLinkToDoc(link.href, resource)) {
|
|
continue;
|
|
}
|
|
|
|
// Exclude cases where the file is implicitly referencing itself
|
|
if (link.source.text.startsWith('#') && link.source.resource.fsPath === resource.fsPath) {
|
|
continue;
|
|
}
|
|
|
|
const isTriggerLocation = !!sourceLink && sourceLink.source.resource.fsPath === link.source.resource.fsPath && sourceLink.source.hrefRange.isEqual(link.source.hrefRange);
|
|
const pathRange = this.getPathRange(link);
|
|
yield {
|
|
kind: 'link',
|
|
isTriggerLocation,
|
|
isDefinition: false,
|
|
link,
|
|
location: new vscode.Location(link.source.resource, pathRange),
|
|
};
|
|
}
|
|
}
|
|
|
|
private * getReferencesToLinkReference(allLinks: Iterable<MdLink>, refToFind: string, from: { resource: vscode.Uri; range: vscode.Range }): Iterable<MdReference> {
|
|
for (const link of allLinks) {
|
|
let ref: string;
|
|
if (link.kind === 'definition') {
|
|
ref = link.ref.text;
|
|
} else if (link.href.kind === 'reference') {
|
|
ref = link.href.ref;
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
if (ref === refToFind && link.source.resource.fsPath === from.resource.fsPath) {
|
|
const isTriggerLocation = from.resource.fsPath === link.source.resource.fsPath && (
|
|
(link.href.kind === 'reference' && from.range.isEqual(link.source.hrefRange)) || (link.kind === 'definition' && from.range.isEqual(link.ref.range)));
|
|
|
|
const pathRange = this.getPathRange(link);
|
|
yield {
|
|
kind: 'link',
|
|
isTriggerLocation,
|
|
isDefinition: link.kind === 'definition',
|
|
link,
|
|
location: new vscode.Location(from.resource, pathRange),
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get just the range of the file path, dropping the fragment
|
|
*/
|
|
private getPathRange(link: MdLink): vscode.Range {
|
|
return link.source.fragmentRange
|
|
? link.source.hrefRange.with(undefined, link.source.fragmentRange.start.translate(0, -1))
|
|
: link.source.hrefRange;
|
|
}
|
|
}
|
|
|
|
export async function tryFindMdDocumentForLink(href: InternalHref, workspaceContents: MdWorkspaceContents): Promise<SkinnyTextDocument | undefined> {
|
|
const targetDoc = await workspaceContents.getMarkdownDocument(href.path);
|
|
if (targetDoc) {
|
|
return targetDoc;
|
|
}
|
|
|
|
// We don't think the file exists. If it doesn't already have an extension, try tacking on a `.md` and using that instead
|
|
if (uri.Utils.extname(href.path) === '') {
|
|
const dotMdResource = href.path.with({ path: href.path.path + '.md' });
|
|
return workspaceContents.getMarkdownDocument(dotMdResource);
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|