Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 Rewrite heading processing #877

Merged
merged 32 commits into from
Jan 17, 2021
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c1e27e5
🐛 Rewrite `getAllRootHeading` (formerly `buildToc`)
Lemmingh Dec 16, 2020
c92ce6b
🐛 Type check `util.ts`, correct `slugify`
Lemmingh Dec 19, 2020
28916e1
✨ Add `getAllRootHeading`
Lemmingh Dec 19, 2020
8a5cfa8
Optimize regexp
Lemmingh Dec 19, 2020
02b5a85
Adapt `generateTocText`
Lemmingh Dec 21, 2020
438ee83
Rename
Lemmingh Dec 21, 2020
aed62ed
🐛 Rewrite `getProjectExcludedHeadings`
Lemmingh Dec 21, 2020
93f355a
Recursive omitting only applies to project level
Lemmingh Dec 21, 2020
616ec72
Adapt `addSectionNumbers`
Lemmingh Dec 21, 2020
ddada03
Adapt `removeSectionNumbers`
Lemmingh Dec 21, 2020
bd85cb7
Adapt `completion`
Lemmingh Dec 21, 2020
35a228e
No optional parameter in `getAllRootHeading`
Lemmingh Dec 21, 2020
da6c777
🐛 Correct `REGEX_FENCED_CODE_BLOCK`, `isInFencedCodeBlock`
Lemmingh Dec 27, 2020
86923b5
🐛 Ensure the TOC text from `generateTocText` always ends with an EOL
Lemmingh Dec 27, 2020
754b955
🐛 Rewrite `detectTocRanges` ... ( ̄_ ̄|||)
Lemmingh Dec 27, 2020
3513bd5
Align with GitLab 13.8
Lemmingh Dec 27, 2020
8ca33e3
✅ Correct test cases
Lemmingh Dec 27, 2020
9467ab5
🐛 GitHub slugify function
Lemmingh Jan 4, 2021
b81eac5
Right, multiline comment only.
Lemmingh Jan 5, 2021
dc77659
✅ Your test cases themselves were ILLEGAL!
Lemmingh Jan 5, 2021
7f188a6
Preserve magic comment when removing comments
Lemmingh Jan 6, 2021
b7cec91
Reorder setext heading conditions
Lemmingh Jan 6, 2021
9244921
Format code
Lemmingh Jan 6, 2021
bfb8f13
reorganized the comment to have a good overview
Jan 6, 2021
8e97569
* Avoid normal comments being recognized as JSDoc.
Lemmingh Jan 16, 2021
64acdba
Allow optional parameter (revert 35a228ea16714eaf50ba422ed6ce2178eab6…
Lemmingh Jan 16, 2021
780e01b
Rename `isInToc` -> `canInToc`
Lemmingh Jan 16, 2021
fe99a2e
Roll back TOC link style to `[text](uri)`
Lemmingh Jan 16, 2021
0743c06
Reorganize the signature of `getAllTocEntry()`
Lemmingh Jan 16, 2021
de6680a
Typo
Lemmingh Jan 16, 2021
1563f30
Rename `/typing/` -> `/contract/` to avoid confusion
Lemmingh Jan 17, 2021
98302f7
Consistent style
Lemmingh Jan 17, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"version": "3.4.0",
"publisher": "yzhang",
"engines": {
"vscode": "^1.38.0"
"vscode": "^1.49.0"
},
"categories": [
"Programming Languages",
Expand Down Expand Up @@ -461,7 +461,7 @@
"@types/markdown-it": "^10.0.2",
"@types/mocha": "^8.0.3",
"@types/node": "^12.12.58",
"@types/vscode": "1.38.0",
"@types/vscode": "^1.49.0",
"glob": "^7.1.6",
"mocha": "^8.1.3",
"ts-loader": "^8.0.3",
Expand Down
21 changes: 10 additions & 11 deletions src/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import * as fs from 'fs';
import * as sizeOf from 'image-size';
import * as path from 'path';
import { CancellationToken, CompletionContext, CompletionItem, CompletionItemKind, CompletionItemProvider, CompletionList, ExtensionContext, languages, MarkdownString, Position, ProviderResult, Range, SnippetString, TextDocument, workspace } from 'vscode';
import { buildToc } from './toc';
import { mathEnvCheck, mdDocSelector, slugify } from './util';
import { getAllTocEntry, IHeading } from './toc';
import { mathEnvCheck, mdDocSelector } from './util';

export function activate(context: ExtensionContext) {
context.subscriptions.push(languages.registerCompletionItemProvider(mdDocSelector, new MdCompletionItemProvider(), '(', '\\', '/', '[', '#'));
Expand All @@ -17,7 +17,7 @@ class MdCompletionItemProvider implements CompletionItemProvider {
// \cmd -> 0
// \cmd{$1} -> 1
// \cmd{$1}{$2} -> 2
//
//
// Use linebreak to mimic the structure of the KaTeX [Support Table](https://katex.org/docs/supported.html)
accents1 = [
'tilde', 'mathring',
Expand Down Expand Up @@ -559,20 +559,19 @@ class MdCompletionItemProvider implements CompletionItemProvider {
const range = new Range(position.with({ character: startIndex + 1 }), endPosition);

return new Promise((res, _) => {
const toc = buildToc(document);
const toc: readonly Readonly<IHeading>[] = getAllTocEntry(document, { respectMagicCommentOmit: false, respectProjectLevelOmit: false });

const headingCompletions = toc.reduce((prev, curr) => {
let item = new CompletionItem('#' + slugify(curr.text), CompletionItemKind.Reference);
const headingCompletions = toc.map<CompletionItem>(heading => {
const item = new CompletionItem('#' + heading.slug, CompletionItemKind.Reference);

if (addClosingParen) {
item.insertText = item.label + ')';
}

item.documentation = curr.text;
item.documentation = heading.rawContent;
item.range = range;
prev.push(item);
return prev;
}, []);
return item;
});

res(headingCompletions);
});
Expand Down Expand Up @@ -608,7 +607,7 @@ class MdCompletionItemProvider implements CompletionItemProvider {
}

/**
* @param doc
* @param doc
* @param dir The dir already typed in the src field, e.g. `[alt text](dir_here|)`
*/
function getBasepath(doc: TextDocument, dir: string): string {
Expand Down
14 changes: 14 additions & 0 deletions src/contract/LanguageIdentifier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"use strict";

/**
* Well-known language identifiers.
* @see <https://code.visualstudio.com/docs/languages/identifiers>
*/
const enum LanguageIdentifier {
Html = "html",
Json = "json",
Markdown = "markdown",
PlainText = "plaintext",
}

export default LanguageIdentifier;
28 changes: 28 additions & 0 deletions src/contract/MarkdownSpec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use strict";

// The name of types here begins with `Markdown`.

/**
* CommonMark bullet list marker.
* https://spec.commonmark.org/0.29/#list-items
*/
export const enum MarkdownBulletListMarker {
Asterisk = "*",
Hyphen = "-",
Plus = "+",
}

/**
* CommonMark emphasis indicator.
* https://spec.commonmark.org/0.29/#emphasis-and-strong-emphasis
*/
export const enum MarkdownEmphasisIndicator {
Asterisk = "*",
Underscore = "_",
}

/**
* The heading level allowed by the CommonMark Spec.
* https://spec.commonmark.org/0.29/#atx-headings
*/
export type MarkdownHeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
29 changes: 29 additions & 0 deletions src/contract/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Top-level contracts and constants

## Conventions

### General

Very few things are allowed to be under this directory. They are not scoped to a few specific modules, instead, must be globally recognized and used across the whole product, and well-known outside our codebase.

Currently, here are:

* Well-known constants.
* Public API definitions, aka public contracts.

### Naming

* The name of files, types, enum members, constants must match the [StrictPascalCase](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/naming-convention.md#format) format.
* Names here are globally uniquely recognized, that is, once a name is assigned to a type here, no other identifier can have the same name in our codebase.
* If a file holds only one type, then it may only provide the [default export](https://www.typescriptlang.org/docs/handbook/modules.html#default-exports), and the type must be of the same name as the file.
* If a file holds multiple types, then the types must be under the same topic, which is the file name.

### Organization

* Each file must be a module.
* Only the following are allowed:
* [Const enum](https://www.typescriptlang.org/docs/handbook/enums.html#const-enums).
* [Interface](https://www.typescriptlang.org/docs/handbook/interfaces.html).
* [Literal](https://www.typescriptlang.org/docs/handbook/literal-types.html).
* [Type alias](https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-aliases).
* Sort in alphabetical order whenever possible.
15 changes: 15 additions & 0 deletions src/contract/SlugifyMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use strict";

/**
* Slugify mode.
*/
const enum SlugifyMode {
AzureDevOps = "azureDevops",
BitbucketCloud = "bitbucketCloud",
Gitea = "gitea",
GitHub = "github",
GitLab = "gitlab",
VisualStudioCode = "vscode",
}

export default SlugifyMode;
139 changes: 65 additions & 74 deletions src/test/suite/slugify.test.ts
Original file line number Diff line number Diff line change
@@ -1,87 +1,78 @@
import * as assert from 'assert';
import * as util from '../../util';
import SlugifyMode from "../../contract/SlugifyMode";

type ICase = readonly [string, string];

suite("Slugify function.", () => {
const headings = {
"foo _italic_ bar": "foo-italic-bar",
// "foo_foo_bar": "foo_foo_bar",
"`a.b` c": "ab-c",
"Via [remark-cli][]": "via-remark-cli",
"1. not a list": "1-not-a-list"
}

const headings_github = {
"foo _italic_ bar": "foo-italic-bar",
"foo_foo_bar": "foo_foo_bar",
"`a.b` c": "ab-c",
"Via [remark-cli][]": "via-remark-cli",
"1. not a list": "1-not-a-list",
"1) not a list": "1-not-a-list",
"foo & < >  \"foo\"": "foo---foo",
"$\\LaTeX equations$": "latex-equations"
}

const headings_gitlab = {
"foo _italic_ bar": "foo-italic-bar",
"foo_foo_bar": "foo_foo_bar",
"`a.b` c": "ab-c",
"Via [remark-cli][]": "via-remark-cli",
"1. not a list": "1-not-a-list",
"1) not a list": "1-not-a-list",
"foo & < >  \"foo\"": "foo-foo",
"1": "anchor-1" // GitLab adds "anchor-" before digit-only IDs
}
/**
* `mode -> [rawContent, slug]`
*/
const cases: Readonly<Record<SlugifyMode, readonly ICase[]>> = {
[SlugifyMode.AzureDevOps]: [
["A !\"#$%&'()*+,-./:;<=>?@[\\\\]^_`{|}~", "a-%21%22%23%24%25%26%27%28%29%2a%2B%2C-.%2F%3A%3B%3C%3D%3E%3F%40%5B%5C%5C%5D%5E_%60%7B%7C%7D~"],
["W\u{0020}\u{00A0}\u{2003}\u{202F}\u{205F}\u{3000}\u{1680}S", "w-------s"],
],

const headings_gitea = {
"foo _italic_ bar": "foo-italic-bar",
"foo_foo_bar": "foo-foo-bar",
"`a.b` c": "a-b-c",
"Via [remark-cli][]": "via-remark-cli",
"1. not a list": "1-not-a-list",
"1) not a list": "1-not-a-list",
"foo & < >  \"foo\"": "foo-foo",
"$\\LaTeX equations$": "latex-equations",
":checkered_flag: with emoji shortname": "checkered-flag-with-emoji-shortname"
}
[SlugifyMode.BitbucketCloud]: [],

const headings_azureDevops = {
"A !\"#$%&'()*+,-./:;<=>?@[\\\\]^_`{|}~": "a-%21%22%23%24%25%26%27%28%29%2a%2B%2C-.%2F%3A%3B%3C%3D%3E%3F%40%5B%5C%5C%5D%5E_%60%7B%7C%7D~",
"W\u{0020}\u{00A0}\u{2003}\u{202F}\u{205F}\u{3000}\u{1680}S": "w-------s"
};
[SlugifyMode.GitHub]: [
["foo _italic_ bar", "foo-italic-bar"],
["foo_foo_bar", "foo_foo_bar"],
["`a.b` c", "ab-c"],
["Via [remark-cli][]", "via-remark-cli"],
["1. not a list", "1-not-a-list"],
["1) not a list", "1-not-a-list"],
["foo & < >  \"foo\"", "foo---foo"],
["$\\LaTeX equations$", "latex-equations"],
],

for (const heading of Object.keys(headings)) {
const slug = headings[heading];
test(`(VSCode) ${heading}${slug}`, () => {
assert.strictEqual(util.slugify(heading, "vscode"), slug);
});
}
[SlugifyMode.GitLab]: [
["foo _italic_ bar", "foo-italic-bar"],
["foo_foo_bar", "foo_foo_bar"],
["`a.b` c", "ab-c"],
["Via [remark-cli][]", "via-remark-cli"],
["1. not a list", "1-not-a-list"],
["1) not a list", "1-not-a-list"],
["foo & < >  \"foo\"", "foo-foo"],
["1", "anchor-1"], // GitLab adds "anchor-" before digit-only IDs
],

for (const heading of Object.keys(headings_github)) {
const slug = headings_github[heading];
test(`(GitHub) ${heading}${slug}`, () => {
assert.strictEqual(util.slugify(heading, "github"), slug);
});
}
[SlugifyMode.Gitea]: [
["foo _italic_ bar", "foo-italic-bar"],
["foo_foo_bar", "foo-foo-bar"],
["`a.b` c", "a-b-c"],
["Via [remark-cli][]", "via-remark-cli"],
["1. not a list", "1-not-a-list"],
["1) not a list", "1-not-a-list"],
["foo & < >  \"foo\"", "foo-foo"],
["$\\LaTeX equations$", "latex-equations"],
[":checkered_flag: with emoji shortname", "checkered-flag-with-emoji-shortname"],
],

for (const heading of Object.keys(headings_gitlab)) {
const slug = headings_gitlab[heading];
test(`(GitLab) ${heading}${slug}`, () => {
assert.strictEqual(util.slugify(heading, "gitlab"), slug);
});
}
[SlugifyMode.VisualStudioCode]: [
["foo _italic_ bar", "foo-italic-bar"],
["`a.b` c", "ab-c"],
["Via [remark-cli][]", "via-remark-cli"],
["1. not a list", "1-not-a-list"],
],
};

for (const heading of Object.keys(headings_gitea)) {
const slug = headings_gitea[heading];
test(`(Gitea) ${heading}${slug}`, () => {
assert.strictEqual(util.slugify(heading, "gitea"), slug);
});
}
const modeName: Readonly<Record<SlugifyMode, string>> = {
[SlugifyMode.AzureDevOps]: "Azure DevOps",
[SlugifyMode.BitbucketCloud]: "Bitbucket Cloud",
[SlugifyMode.GitHub]: "GitHub",
[SlugifyMode.GitLab]: "GitLab",
[SlugifyMode.Gitea]: "Gitea",
[SlugifyMode.VisualStudioCode]: "VS Code",
};

for (const heading of Object.keys(headings_azureDevops)) {
const slug = headings_azureDevops[heading];
test(`(Azure DevOps) ${heading}${slug}`, () => {
assert.strictEqual(util.slugify(heading, "azureDevops"), slug);
});
suite("Slugify function.", () => {
for (const group of Object.keys(cases) as SlugifyMode[]) {
for (const testCase of cases[group]) {
const [rawContent, slug] = testCase;
globalThis.test(`(${modeName[group]}) ${rawContent}${slug}`, () => {
assert.strictEqual(util.slugify(rawContent, group), slug);
});
}
}
});
Loading