vscode-markdown-languageservice
Advanced tools
Comparing version
@@ -53,3 +53,3 @@ "use strict"; | ||
const definitionsProvider = new definitions_1.MdDefinitionProvider(config, init.workspace, tocProvider, linkCache); | ||
const renameProvider = new rename_1.MdRenameProvider(config, init.workspace, referencesProvider, init.parser.slugifier, logger); | ||
const renameProvider = new rename_1.MdRenameProvider(config, init.workspace, init.parser, referencesProvider, tocProvider, init.parser.slugifier, logger); | ||
const fileRenameProvider = new fileRename_1.MdFileRenameProvider(config, init.workspace, linkCache, referencesProvider); | ||
@@ -56,0 +56,0 @@ const diagnosticsComputer = new diagnostics_1.DiagnosticComputer(config, init.workspace, linkProvider, tocProvider, logger); |
@@ -53,3 +53,3 @@ "use strict"; | ||
const toc = await this.#tocProvider.get(resolvedResource); | ||
return toc.lookup(sourceLink.href.fragment)?.headerLocation; | ||
return toc?.lookup(sourceLink.href.fragment)?.headerLocation; | ||
} | ||
@@ -56,0 +56,0 @@ #getDefinitionOfRef(ref, allLinksInFile) { |
@@ -11,2 +11,3 @@ "use strict"; | ||
const lsp = require("vscode-languageserver-protocol"); | ||
const logging_1 = require("../logging"); | ||
const position_1 = require("../types/position"); | ||
@@ -21,3 +22,2 @@ const range_1 = require("../types/range"); | ||
const documentLinks_1 = require("./documentLinks"); | ||
const logging_1 = require("../logging"); | ||
/** | ||
@@ -297,3 +297,3 @@ * The severity at which diagnostics are reported | ||
} | ||
if (!toc.lookup(link.fragment) && !this.#isIgnoredLink(options, link.source.pathText) && !this.#isIgnoredLink(options, link.source.hrefText)) { | ||
if (!toc?.lookup(link.fragment) && !this.#isIgnoredLink(options, link.source.pathText) && !this.#isIgnoredLink(options, link.source.hrefText)) { | ||
const range = (link.source.fragmentRange && (0, range_1.modifyRange)(link.source.fragmentRange, (0, position_1.translatePosition)(link.source.fragmentRange.start, { characterDelta: -1 }), undefined)) ?? link.source.hrefRange; | ||
@@ -300,0 +300,0 @@ diagnostics.push({ |
@@ -18,2 +18,3 @@ "use strict"; | ||
const file_1 = require("../util/file"); | ||
const mdLinks_1 = require("../util/mdLinks"); | ||
const path_2 = require("../util/path"); | ||
@@ -25,3 +26,2 @@ const schemes_1 = require("../util/schemes"); | ||
const documentLinks_1 = require("./documentLinks"); | ||
const mdLinks_1 = require("../util/mdLinks"); | ||
var CompletionContextKind; | ||
@@ -28,0 +28,0 @@ (function (CompletionContextKind) { |
@@ -81,3 +81,3 @@ "use strict"; | ||
&& (0, documentLinks_1.looksLikeLinkToResource)(this.#configuration, link.href, (0, textDocument_1.getDocUri)(document)) | ||
&& this.#parser.slugifier.fromHeading(link.href.fragment).value === header.slug.value) { | ||
&& this.#parser.slugifier.fromFragment(link.href.fragment).equals(header.slug)) { | ||
references.push({ | ||
@@ -149,3 +149,3 @@ kind: MdReferenceKind.Link, | ||
const toc = await this.#tocProvider.get(resolvedResource); | ||
const entry = toc.lookup(sourceLink.href.fragment); | ||
const entry = toc?.lookup(sourceLink.href.fragment); | ||
if (entry) { | ||
@@ -152,0 +152,0 @@ references.push({ |
@@ -10,5 +10,8 @@ "use strict"; | ||
const path = require("path"); | ||
const lsp = require("vscode-languageserver-protocol"); | ||
const vscode_uri_1 = require("vscode-uri"); | ||
const config_1 = require("../config"); | ||
const logging_1 = require("../logging"); | ||
const tableOfContents_1 = require("../tableOfContents"); | ||
const inMemoryDocument_1 = require("../types/inMemoryDocument"); | ||
const position_1 = require("../types/position"); | ||
@@ -38,10 +41,14 @@ const range_1 = require("../types/range"); | ||
#workspace; | ||
#parser; | ||
#referencesProvider; | ||
#tableOfContentProvider; | ||
#slugifier; | ||
#logger; | ||
constructor(configuration, workspace, referencesProvider, slugifier, logger) { | ||
constructor(configuration, workspace, parser, referencesProvider, tableOfContentProvider, slugifier, logger) { | ||
super(); | ||
this.#configuration = configuration; | ||
this.#workspace = workspace; | ||
this.#parser = parser; | ||
this.#referencesProvider = referencesProvider; | ||
this.#tableOfContentProvider = tableOfContentProvider; | ||
this.#slugifier = slugifier; | ||
@@ -108,3 +115,3 @@ this.#logger = logger; | ||
else if (triggerRef.kind === references_1.MdReferenceKind.Header || (triggerRef.kind === references_1.MdReferenceKind.Link && triggerRef.link.source.fragmentRange && (0, range_1.rangeContains)(triggerRef.link.source.fragmentRange, position) && (triggerRef.link.kind === documentLinks_1.MdLinkKind.Definition || triggerRef.link.kind === documentLinks_1.MdLinkKind.Link && triggerRef.link.href.kind === documentLinks_1.HrefKind.Internal))) { | ||
return this.#renameFragment(allRefsInfo, newName); | ||
return this.#renameFragment(allRefsInfo, newName, token); | ||
} | ||
@@ -154,12 +161,73 @@ else if (triggerRef.kind === references_1.MdReferenceKind.Link && !(triggerRef.link.source.fragmentRange && (0, range_1.rangeContains)(triggerRef.link.source.fragmentRange, position)) && (triggerRef.link.kind === documentLinks_1.MdLinkKind.Link || triggerRef.link.kind === documentLinks_1.MdLinkKind.Definition) && triggerRef.link.href.kind === documentLinks_1.HrefKind.Internal) { | ||
} | ||
#renameFragment(allRefsInfo, newName) { | ||
const slug = this.#slugifier.fromHeading(newName).value; | ||
async #renameFragment(allRefsInfo, newHeaderText, token) { | ||
const builder = new editBuilder_1.WorkspaceEditBuilder(); | ||
let newSlug = this.#slugifier.fromHeading(newHeaderText); | ||
const existingHeader = allRefsInfo.references.find(x => x.kind === references_1.MdReferenceKind.Header); | ||
if (existingHeader) { | ||
// If there's a real header we're renaming, we need to handle cases where there are duplicate header ids. | ||
// There are two cases of this to consider: | ||
// | ||
// - The new name duplicates an existing header. In this case, we need to use the unique slug of the new header | ||
// but also potentially update links to the other duplicated headers. | ||
// | ||
// - The old header was duplicated. This may result in links to other instances of the duplicated headers changing | ||
// | ||
// In both cases, there could be a cascading effect where multiple headers/links are updated. | ||
// For instance: | ||
// | ||
// `` | ||
// # Header | ||
// # Header <- rename here | ||
// # Header | ||
// ``` | ||
// | ||
// In this case we need to rename the third header as well plus all reference to it. | ||
const doc = await this.#workspace.openMarkdownDocument(vscode_uri_1.URI.parse(existingHeader.location.uri)); | ||
if (token.isCancellationRequested) { | ||
return; | ||
} | ||
if (doc) { | ||
const editedDoc = new inMemoryDocument_1.InMemoryDocument(vscode_uri_1.URI.parse(existingHeader.location.uri), doc.getText()); | ||
editedDoc.updateContent(editedDoc.applyEdits([lsp.TextEdit.replace(existingHeader.location.range, '# ' + newHeaderText)])); | ||
const [oldToc, newToc] = await Promise.all([ | ||
this.#tableOfContentProvider.getForDocument(doc), | ||
tableOfContents_1.TableOfContents.create(this.#parser, editedDoc, token) // Don't use cache for new temp doc | ||
]); | ||
if (token.isCancellationRequested) { | ||
return; | ||
} | ||
const changedHeaders = []; | ||
oldToc.entries.forEach((oldEntry, index) => { | ||
const newEntry = newToc.entries[index]; | ||
if (!newEntry) { | ||
return; | ||
} | ||
if (oldEntry.headerLocation.range.start.line === existingHeader.location.range.start.line) { | ||
newSlug = newEntry.slug; // Take the new slug from the edited document | ||
return; | ||
} | ||
if (newEntry && !oldEntry.slug.equals(newEntry.slug)) { | ||
changedHeaders.push(newEntry); | ||
} | ||
}); | ||
for (const changedHeader of changedHeaders) { | ||
const refs = await this.#getAllReferences(doc, changedHeader.headerLocation.range.start, token); | ||
if (token.isCancellationRequested) { | ||
return; | ||
} | ||
for (const ref of refs?.references ?? []) { | ||
if (ref.kind === references_1.MdReferenceKind.Link) { | ||
builder.replace(ref.link.source.resource, ref.link.source.fragmentRange ?? ref.location.range, changedHeader.slug.value); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
for (const ref of allRefsInfo.references) { | ||
switch (ref.kind) { | ||
case references_1.MdReferenceKind.Header: | ||
builder.replace(vscode_uri_1.URI.parse(ref.location.uri), ref.headerTextLocation.range, newName); | ||
builder.replace(vscode_uri_1.URI.parse(ref.location.uri), ref.headerTextLocation.range, newHeaderText); | ||
break; | ||
case references_1.MdReferenceKind.Link: | ||
builder.replace(ref.link.source.resource, ref.link.source.fragmentRange ?? ref.location.range, !ref.link.source.fragmentRange || ref.link.href.kind === documentLinks_1.HrefKind.External ? newName : slug); | ||
builder.replace(ref.link.source.resource, ref.link.source.fragmentRange ?? ref.location.range, !ref.link.source.fragmentRange || ref.link.href.kind === documentLinks_1.HrefKind.External ? newHeaderText : newSlug.value); | ||
break; | ||
@@ -166,0 +234,0 @@ } |
@@ -10,4 +10,4 @@ "use strict"; | ||
const dispose_1 = require("../util/dispose"); | ||
const string_1 = require("../util/string"); | ||
const workspaceCache_1 = require("../workspaceCache"); | ||
const string_1 = require("../util/string"); | ||
class MdWorkspaceSymbolProvider extends dispose_1.Disposable { | ||
@@ -14,0 +14,0 @@ #cache; |
@@ -7,4 +7,4 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.githubSlugifier = exports.Slug = void 0; | ||
class Slug { | ||
exports.githubSlugifier = exports.GithubSlug = void 0; | ||
class GithubSlug { | ||
value; | ||
@@ -15,6 +15,6 @@ constructor(value) { | ||
equals(other) { | ||
return this.value === other.value; | ||
return other instanceof GithubSlug && this.value.toLowerCase() === other.value.toLowerCase(); | ||
} | ||
} | ||
exports.Slug = Slug; | ||
exports.GithubSlug = GithubSlug; | ||
/** | ||
@@ -33,5 +33,23 @@ * A {@link ISlugifier slugifier} that approximates how GitHub's slugifier works. | ||
); | ||
return new Slug(slugifiedHeading); | ||
return new GithubSlug(slugifiedHeading); | ||
} | ||
fromFragment(fragmentText) { | ||
return new GithubSlug(fragmentText.toLowerCase()); | ||
} | ||
createBuilder() { | ||
const entries = new Map(); | ||
return { | ||
add: (heading) => { | ||
const slug = this.fromHeading(heading); | ||
const existingSlugEntry = entries.get(slug.value); | ||
if (existingSlugEntry) { | ||
++existingSlugEntry.count; | ||
return this.fromHeading(slug.value + '-' + existingSlugEntry.count); | ||
} | ||
entries.set(slug.value, { count: 0 }); | ||
return slug; | ||
} | ||
}; | ||
} | ||
}; | ||
//# sourceMappingURL=slugify.js.map |
@@ -9,3 +9,2 @@ "use strict"; | ||
const logging_1 = require("./logging"); | ||
const slugify_1 = require("./slugify"); | ||
const range_1 = require("./types/range"); | ||
@@ -42,3 +41,3 @@ const textDocument_1 = require("./types/textDocument"); | ||
} | ||
const existingSlugEntries = new Map(); | ||
const slugBuilder = parser.slugifier.createBuilder(); | ||
const headers = []; | ||
@@ -70,11 +69,3 @@ let currentHeader; | ||
const bodyText = TableOfContents.#getHeaderTitleAsPlainText(body); | ||
let slug = parser.slugifier.fromHeading(bodyText); | ||
const existingSlugEntry = existingSlugEntries.get(slug.value); | ||
if (existingSlugEntry) { | ||
++existingSlugEntry.count; | ||
slug = parser.slugifier.fromHeading(slug.value + '-' + existingSlugEntry.count); | ||
} | ||
else { | ||
existingSlugEntries.set(slug.value, { count: 0 }); | ||
} | ||
const slug = slugBuilder.add(bodyText); | ||
const headerLocation = { | ||
@@ -147,3 +138,2 @@ uri: docUri.toString(), | ||
} | ||
static empty = new TableOfContents([], slugify_1.githubSlugifier); | ||
#slugifier; | ||
@@ -175,4 +165,4 @@ constructor(entries, slugifier) { | ||
} | ||
async get(resource) { | ||
return await this.#cache.get(resource) ?? TableOfContents.empty; | ||
get(resource) { | ||
return this.#cache.get(resource); | ||
} | ||
@@ -179,0 +169,0 @@ getForDocument(doc) { |
@@ -9,13 +9,13 @@ "use strict"; | ||
const lsp = require("vscode-languageserver-protocol"); | ||
const config_1 = require("../../config"); | ||
const extractLinkDef_1 = require("../../languageFeatures/codeActions/extractLinkDef"); | ||
const documentLinks_1 = require("../../languageFeatures/documentLinks"); | ||
const extractLinkDef_1 = require("../../languageFeatures/codeActions/extractLinkDef"); | ||
const tableOfContents_1 = require("../../tableOfContents"); | ||
const inMemoryDocument_1 = require("../../types/inMemoryDocument"); | ||
const range_1 = require("../../types/range"); | ||
const cancellation_1 = require("../../util/cancellation"); | ||
const engine_1 = require("../engine"); | ||
const inMemoryDocument_1 = require("../inMemoryDocument"); | ||
const inMemoryWorkspace_1 = require("../inMemoryWorkspace"); | ||
const nulLogging_1 = require("../nulLogging"); | ||
const util_1 = require("../util"); | ||
const range_1 = require("../../types/range"); | ||
const config_1 = require("../../config"); | ||
async function getActions(store, doc, pos) { | ||
@@ -22,0 +22,0 @@ const workspace = store.add(new inMemoryWorkspace_1.InMemoryWorkspace([doc])); |
@@ -14,6 +14,6 @@ "use strict"; | ||
const tableOfContents_1 = require("../../tableOfContents"); | ||
const inMemoryDocument_1 = require("../../types/inMemoryDocument"); | ||
const range_1 = require("../../types/range"); | ||
const cancellation_1 = require("../../util/cancellation"); | ||
const engine_1 = require("../engine"); | ||
const inMemoryDocument_1 = require("../inMemoryDocument"); | ||
const inMemoryWorkspace_1 = require("../inMemoryWorkspace"); | ||
@@ -20,0 +20,0 @@ const nulLogging_1 = require("../nulLogging"); |
@@ -46,3 +46,3 @@ "use strict"; | ||
} | ||
async getForDocument(document) { | ||
getForDocument(document) { | ||
const existing = this.#cache.get((0, textDocument_1.getDocUri)(document)); | ||
@@ -49,0 +49,0 @@ if (existing) { |
{ | ||
"name": "vscode-markdown-languageservice", | ||
"description": "Markdown language service", | ||
"version": "0.5.0-alpha.1", | ||
"version": "0.5.0-alpha.2", | ||
"author": "Microsoft Corporation", | ||
@@ -22,2 +22,3 @@ "license": "MIT", | ||
"vscode-languageserver-protocol": "^3.17.1", | ||
"vscode-languageserver-textdocument": "^1.0.11", | ||
"vscode-uri": "^3.0.7" | ||
@@ -37,4 +38,4 @@ }, | ||
"mocha": "^10.0.0", | ||
"typescript": "^5.4.2", | ||
"vscode-languageserver-textdocument": "^1.0.8" | ||
"source-map-support": "^0.5.21", | ||
"typescript": "^5.4.2" | ||
}, | ||
@@ -47,3 +48,3 @@ "scripts": { | ||
"prepublishOnly": "npm run compile && npm run api-extractor", | ||
"test": "mocha 'out/test/**/*.test.js' --ui=tdd --timeout=2000 --exit" | ||
"test": "mocha -r source-map-support/register 'out/test/**/*.test.js' --ui=tdd --timeout=2000 --exit" | ||
}, | ||
@@ -50,0 +51,0 @@ "repository": { |
@@ -374,2 +374,7 @@ import { Event as Event_2 } from 'vscode-languageserver-protocol'; | ||
export declare interface ISlug { | ||
readonly value: string; | ||
equals(other: ISlug): boolean; | ||
} | ||
/** | ||
@@ -379,3 +384,22 @@ * Generates unique ids for headers in the Markdown. | ||
export declare interface ISlugifier { | ||
fromHeading(heading: string): Slug; | ||
/** | ||
* Create a new slug from the text of a markdown heading. | ||
* | ||
* For a heading such as `# Header`, this will be called with `Header` | ||
*/ | ||
fromHeading(headingText: string): ISlug; | ||
/** | ||
* Create a slug from a link fragment. | ||
* | ||
* For a link such as `[text](#header)`, this will be called with `header` | ||
*/ | ||
fromFragment(fragmentText: string): ISlug; | ||
/** | ||
* Creates a stateful object that can be used to build slugs incrementally. | ||
* | ||
* This should be used when getting all slugs in a document as it handles duplicate headings | ||
*/ | ||
createBuilder(): { | ||
add(headingText: string): ISlug; | ||
}; | ||
} | ||
@@ -605,8 +629,2 @@ | ||
declare class Slug { | ||
readonly value: string; | ||
constructor(value: string); | ||
equals(other: Slug): boolean; | ||
} | ||
export declare interface Token { | ||
@@ -613,0 +631,0 @@ readonly type: string; |
269594
2.35%47
2.17%5618
2.39%6
20%+ Added