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

✨ Add decoration manager #897

Merged
merged 5 commits into from
Feb 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
69 changes: 67 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,35 @@
],
"main": "./dist/extension",
"contributes": {
"colors": [
{
"id": "markdown.extension.editor.codeSpan.border",
"description": "Border color of code spans in the Markdown editor.",
"defaults": {
"dark": "editor.selectionBackground",
"light": "editor.selectionBackground",
"highContrast": "editor.selectionBackground"
}
},
{
"id": "markdown.extension.editor.formattingMark.foreground",
"description": "Color of formatting marks (paragraphs, hard line breaks, links, etc.) in the Markdown editor.",
"defaults": {
"dark": "editorWhitespace.foreground",
"light": "editorWhitespace.foreground",
"highContrast": "diffEditor.insertedTextBorder"
}
},
{
"id": "markdown.extension.editor.trailingSpace.background",
"description": "Background color of trailing space (U+0020) characters in the Markdown editor.",
"defaults": {
"dark": "diffEditor.diagonalFill",
"light": "diffEditor.diagonalFill",
"highContrast": "editorWhitespace.foreground"
}
}
],
"commands": [
{
"command": "markdown.extension.toc.create",
Expand Down Expand Up @@ -325,8 +354,8 @@
},
"markdown.extension.syntax.decorations": {
"type": "boolean",
"default": true,
"description": "%config.syntax.decorations.description%"
"default": null,
"markdownDeprecationMessage": "%config.syntax.decorations.description%"
},
"markdown.extension.syntax.decorationFileSizeLimit": {
"type": "number",
Expand All @@ -348,6 +377,42 @@
"default": false,
"markdownDescription": "%config.tableFormatter.normalizeIndentation.description%"
},
"markdown.extension.theming.decoration.renderCodeSpan": {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guess we can directly use ...decoration.codeSpan and so on

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The verb "render" leaves room for a bunch of other settings that I'm going to add to this section.

Besides, I see many people use verb-noun statement as toggle label, and feel the form makes intention clear.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm okay with that. But I was wondering whether we should have used an object-type configuration, otherwise there are too many trivial options (esp. if there will be more). The disadvantage is it becomes more complex for both us and users, but I guess this is the price to have an advanced configuration 😥.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is a toggle, then it is supposed to be a toggle.

If it is a leaf, then it is supposed to be a leaf.

IMO, wrapping a group of settings inside an object would introduce an unnecessary layer, harming accessibility, usability, clarity, and validation.

I don't consider "trivial" items as a problem. Users can benefit a lot from visually and directly editing configuration right in Settings UI. And you can see other products also show many "trivial" settings for fine-grained control.

Copy link
Collaborator Author

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The real problem is VS Code doesn't provide setting groups. Ideally it should have a tree-like structure, but what we have now is a big flat list (visually).

(I have a bad experience when I search "python lint" in the Settings UI: there are 41 results from the Python extension, including different providers: Bandit, Flake8, Mypy, Prospector, Pydocstyle etc., each of which further has several options: enabled, path, args etc. This looks really "noisy" and horrible.)

image

I agree that object-type configuration has many disadvantages as you mentioned. To me it is just a workaround and that's why I'm hesitant. If we only have 5 new settings, it won't be a problem. But if each of which will have, e.g. 2 more settings in the future, it becomes more like the Python case 😓.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VS Code doesn't provide setting groups

IMHO, it is VS Code's fault. microsoft/vscode#70589

We should not make it worse by introducing custom objects.


I have a bad experience when I search "python lint"

TypeScript's settings are also a beast. 😂

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VS Code's fault. microsoft/vscode#70589

Upvoted 👍

We should not make it worse by introducing custom objects.

Fine, let's just use the simple way (the flat list).

"type": "boolean",
"default": true,
"markdownDescription": "%config.theming.decoration.renderCodeSpan.description%",
"scope": "application"
},
"markdown.extension.theming.decoration.renderHardLineBreak": {
"type": "boolean",
"default": false,
"markdownDescription": "%config.theming.decoration.renderHardLineBreak.description%",
"scope": "application"
},
"markdown.extension.theming.decoration.renderLink": {
"type": "boolean",
"default": false,
"markdownDescription": "%config.theming.decoration.renderLink.description%",
"scope": "application"
},
"markdown.extension.theming.decoration.renderParagraph": {
"type": "boolean",
"default": false,
"markdownDescription": "%config.theming.decoration.renderParagraph.description%",
"scope": "application"
},
"markdown.extension.theming.decoration.renderStrikethrough": {
"type": "boolean",
"default": true,
"markdownDescription": "%config.theming.decoration.renderStrikethrough.description%",
"scope": "application"
},
"markdown.extension.theming.decoration.renderTrailingSpace": {
"type": "boolean",
"default": false,
"markdownDescription": "%config.theming.decoration.renderTrailingSpace.description%",
"scope": "application"
},
"markdown.extension.toc.downcaseLink": {
"type": "boolean",
"default": true,
Expand Down
10 changes: 8 additions & 2 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,17 @@
"config.print.onFileSave.description": "Print current document to HTML when file is saved.",
"config.print.theme": "Theme of the exported HTML. Only affects code blocks.",
"config.print.validateUrls.description": "Enable/disable URL validation when printing.",
"config.syntax.decorations.description": "Add syntax decorations in editors. (e.g. ~~strikethrough~~, `code span`)",
"config.syntax.decorations.description": "(**Deprecated**) Use `#markdown.extension.theming.decoration.renderCodeSpan#` instead. See <https://github.com/yzhang-gh/vscode-markdown/issues/888> for details.",
"config.syntax.decorationFileSizeLimit.description": "If a file is larger than this size (in byte/B), we won't attempt to render syntax decorations.",
"config.syntax.plainTheme.description": "(**Experimental**) Report issue at <https://github.com/yzhang-gh/vscode-markdown/issues/185>.\n\nOnly take effect when `#markdown.extension.syntax.decorations#` is enabled.",
"config.syntax.plainTheme.description": "(**Experimental**) Report issue at <https://github.com/yzhang-gh/vscode-markdown/issues/185>.",
"config.tableFormatter.enabled.description": "Enable [GitHub Flavored Markdown](https://github.github.com/gfm/) table formatter.",
"config.tableFormatter.normalizeIndentation.description": "Normalize table indentation to closest multiple of configured editor tab size.",
"config.theming.decoration.renderCodeSpan.description": "Apply a border around a [code span](https://spec.commonmark.org/0.29/#code-spans).",
"config.theming.decoration.renderHardLineBreak.description": "(**Experimental**)",
"config.theming.decoration.renderLink.description": "(**Experimental**)",
"config.theming.decoration.renderParagraph.description": "(**Experimental**)",
"config.theming.decoration.renderStrikethrough.description": "Show a line through the middle of a [strikethrough](https://github.github.com/gfm/#strikethrough-extension-).",
"config.theming.decoration.renderTrailingSpace.description": "Shade the background of trailing space (U+0020) characters on a [line](https://spec.commonmark.org/0.29/#line).",
"config.toc.downcaseLink.description": "Whether to **force** to downcase TOC links.",
"config.toc.levels.description": "Range of levels for table of contents. Use `x..y` for level `x` to `y`.",
"config.toc.omittedFromToc.description": "Lists of headings to omit by project file.\nExample:\n{ \"README.md\": [\"# Introduction\"] }",
Expand Down
6 changes: 4 additions & 2 deletions package.nls.zh-cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
"config.print.absoluteImgPath.description": "将图片路径转换为绝对路径",
"config.print.imgToBase64.description": "在打印到 HTML 时将图片转换为 base64",
"config.print.onFileSave.description": "Markdown 文档保存后自动打印为 HTML。",
"config.syntax.decorations.description": "在编辑器中添加语法装饰。例如,~~strikethrough~~ 和 `code span`。",
"config.syntax.plainTheme.description": "(**实验性**)请在 <https://github.com/yzhang-gh/vscode-markdown/issues/185> 报告问题。\n\n只有当 `#markdown.extension.syntax.decorations#` 启用时才生效。",
"config.syntax.plainTheme.description": "(**实验性**)请在 <https://github.com/yzhang-gh/vscode-markdown/issues/185> 报告问题。",
"config.tableFormatter.enabled.description": "启用 [GitHub Flavored Markdown](https://github.github.com/gfm/) 表格格式化。",
"config.tableFormatter.normalizeIndentation.description": "使位于列表内的表格的缩进长度为制表符长度(`tabSize`)。",
"config.theming.decoration.renderCodeSpan.description": "在[行内代码 (code span)](https://spec.commonmark.org/0.29/#code-spans) 周围显示边框。",
"config.theming.decoration.renderStrikethrough.description": "显示[删除线 (strikethrough)](https://github.github.com/gfm/#strikethrough-extension-)。",
"config.theming.decoration.renderTrailingSpace.description": "为[行](https://spec.commonmark.org/0.29/#line)末端的空格 (U+0020) 字符添加底纹背景。",
"config.toc.downcaseLink.description": "是否**强制**将目录中的链接转为小写。",
"config.toc.levels.description": "目录级别的范围。例如 `2..5` 表示在目录中只包含 2 到 5 级标题。",
"config.toc.omittedFromToc.description": "在指定文件的目录中省略这些标题。\n示例:\n{ \"README.md\": [\"# Introduction\"] }",
Expand Down
43 changes: 43 additions & 0 deletions src/configuration/KnownKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"use strict";

/**
* Configuration keys that this product contributes.
* These values are relative to `markdown.extension`.
*/
type KnownKey =
| "completion.respectVscodeSearchExclude"
| "completion.root"
| "italic.indicator"
| "katex.macros"
| "list.indentationSize"
| "math.enabled"
| "orderedList.autoRenumber"
| "orderedList.marker"
| "preview.autoShowPreviewToSide"
| "print.absoluteImgPath"
| "print.imgToBase64"
| "print.includeVscodeStylesheets"
| "print.onFileSave"
| "print.theme"
| "print.validateUrls"
| "syntax.decorationFileSizeLimit" // To be superseded.
| "syntax.plainTheme" // To be superseded.
| "tableFormatter.enabled"
| "tableFormatter.normalizeIndentation"
| "theming.decoration.renderCodeSpan" // <- "syntax.decorations"
| "theming.decoration.renderHardLineBreak"
| "theming.decoration.renderLink"
| "theming.decoration.renderParagraph"
| "theming.decoration.renderStrikethrough" // <- "syntax.decorations"
| "theming.decoration.renderTrailingSpace"
| "toc.downcaseLink"
| "toc.levels"
| "toc.omittedFromToc" // To be superseded.
| "toc.orderedList"
| "toc.plaintext"
| "toc.slugifyMode"
| "toc.unorderedList.marker"
| "toc.updateOnSave"
;

export default KnownKey;
35 changes: 35 additions & 0 deletions src/configuration/defaultFallback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"use strict";

import * as vscode from "vscode";
import { IConfigurationFallbackMap } from "./manager";

/**
* Configuration keys that are no longer supported,
* and will be removed in the next major version.
*/
export const deprecated: readonly string[] = [
"syntax.decorations",
];

export const fallbackMap: IConfigurationFallbackMap = {

"theming.decoration.renderCodeSpan": (scope): boolean => {
const config = vscode.workspace.getConfiguration("markdown.extension", scope);
const old = config.get<boolean | null>("syntax.decorations");
if (old === null || old === undefined) {
return config.get<boolean>("theming.decoration.renderCodeSpan")!;
} else {
return old;
}
},

"theming.decoration.renderStrikethrough": (scope): boolean => {
const config = vscode.workspace.getConfiguration("markdown.extension", scope);
const old = config.get<boolean | null>("syntax.decorations");
if (old === null || old === undefined) {
return config.get<boolean>("theming.decoration.renderStrikethrough")!;
} else {
return old;
}
},
};
76 changes: 76 additions & 0 deletions src/configuration/manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"use strict";

import * as vscode from "vscode";
import type IDisposable from "../IDisposable";
import type KnownKey from "./KnownKey";
import { deprecated, fallbackMap } from "./defaultFallback";

export type IConfigurationFallbackMap = { readonly [key in KnownKey]?: (scope?: vscode.ConfigurationScope) => any; };

export interface IConfigurationManager extends IDisposable {

/**
* Gets the value that an our own key denotes.
* @param key The configuration key.
* @param scope The scope, for which the configuration is asked.
*/
get<T>(key: KnownKey, scope?: vscode.ConfigurationScope): T;

/**
* Gets the value that an absolute identifier denotes.
* @param section The dot-separated identifier (usually a setting ID).
* @param scope The scope, for which the configuration is asked.
*/
getByAbsolute<T>(section: string, scope?: vscode.ConfigurationScope): T | undefined;
}

/**
* This is currently just a proxy that helps mapping our configuration keys.
*/
class ConfigurationManager implements IConfigurationManager {

private readonly _fallback: IConfigurationFallbackMap;

constructor(fallback: IConfigurationFallbackMap, deprecatedKeys: readonly string[]) {
this._fallback = Object.assign<IConfigurationFallbackMap, IConfigurationFallbackMap>(Object.create(null), fallback);
this.showWarning(deprecatedKeys);
}

public dispose(): void { }

/**
* Shows an error message for each deprecated key, to help user migrate.
* This is async to avoid blocking instance creation.
*/
private async showWarning(deprecatedKeys: readonly string[]): Promise<void> {
for (const key of deprecatedKeys) {
const value = vscode.workspace.getConfiguration("markdown.extension").get(key);
if (value !== undefined && value !== null) {
// We are not able to localize this string for now.
// Our NLS module needs to be configured before using, which is done in the extension entry point.
// This module may be directly or indirectly imported by the entry point.
// Thus, this module may be loaded before the NLS module is available.
vscode.window.showErrorMessage(`The setting 'markdown.extension.${key}' has been deprecated.`);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is too "aggressive", I mean everyone (millions) will see the pop-up.

I prefer to do a config transition in the background (without proactively notifying the users). (Of course we will say it in the changelog.)

e.g. https://github.com/James-Yu/LaTeX-Workshop/blob/8eb80f46794233c868223e99f5c206449bab5587/src/config.ts#L67

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

everyone (millions) will see the pop-up.

No.

"markdown.extension.syntax.decorations": {
"type": "boolean",
"default": null,
"markdownDeprecationMessage": "%config.syntax.decorations.description%"
},

I've set the default value of the deprecated setting to null. Thus, only those that changed the setting will see the notification.


I prefer to do a config transition in the background

But it's the user that knows the state of settings best. A short message to the user can saves you tens of line of code.

As you can see from KnownKey.ts, defaultFallback.ts, and #888, the changes will be complex, involving names, formats, and scopes. You can hardly make it correct programmatically.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only those that changed the setting will see the notification.

Then this is good.

But it's the user that knows the state of settings best.

From my experience most of the users know little about the settings... But for options like syntaxDecorations we can assume they know about it.
I was concerned someday we might change a configuration which affects majority of the users, although it isn't very likely to happen.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

most of the users know little about the settings

I mean the settings are under the control of the user. They remember what they did, and what those changes actually mean.

If we tried to act on behalf of them, we would have to inspect() and analyze the possibly huge result. But it still cannot solve all problems. For example, in my plan, decorationFileSizeLimit (binary size) will be superseded by maxDocumentLength (the number of UTF-16 code units). I guess the factor is 1.5 (UTF-8, only BMP), but who knows, what if a user prefers Windows-1252 or Shift-JIS.


someday we might change a configuration which affects majority of the users

Er ... Very likely.

I've identified a good number of flawed configuration keys, but only marked those that I've found a solution for as "To be superseded" in KnownKey.ts.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

most of the users know little about the settings

I mean the settings are under the control of the user. They remember what they did, and what those changes actually mean.

Ideally, they should. The reality (from my observation) is, even many CS majoring students (my friends) have few concepts about the settings. It is just because they never pay attention to it.
I believe the fact "Markdown is rendered to HTML" will filter out 80% of the users. (Many users don't know what "decoration" means as it is a VS Code concept. And many many users never read the README.)
For average users, (probably) they just searched somewhere, changed the settings and totally forgot about it several months later. They are not interested in how it should be configured, they want it just works.

The "Windows-1252 and Shift-JIS" thing is a typical example. I would say more than 90% of the users don't know about it, as I have never seen it before 🤣.

Two of my own "rules":

  • Only show pop-up message if we really want the users to know about it (e.g., exciting new features). And make sure the message is attractive (not like a regular non-exciting message)
  • Do not assume the users {know well about / are interested in} how {Markdown / VS Code} works. (curse of knowledge)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many users don't know what "decoration" means as it is a VS Code concept.

Do you think changing theming to editorTheming or editing.theming will help? E.g.

markdown.extension.editorTheming.decoration.renderCodeSpan

Markdown › Extension › Editor Theming › Decoration: Render Code Span

markdown.extension.editing.theming.decoration.renderCodeSpan

Markdown › Extension › Editing › Theming › Decoration: Render Code Span


Windows-1252 and Shift-JIS

Windows-1252 was popular in Western Europe.
Shift-JIS is still common in Japan.


curse of knowledge

OK. I'll keep it in mind when writing docs.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think changing theming to editorTheming or editing.theming will help? E.g.

Ah, I didn't notice that. I like (just) theming or theme as it is very clear 👍, especially after markdown and extension. (It is much better than syntax that we had before)
The problem is at decoration, but I cannot think of a better one... Then I'd rather keep it aligned with VS Code (and "teach" users about it).

Windows-1252 and Shift-JIS

Many thanks. #TodayILearned

}
}
}

public get<T>(key: KnownKey, scope?: vscode.ConfigurationScope): T {
const fallback = this._fallback[key];
if (fallback) {
return fallback(scope);
} else {
return vscode.workspace.getConfiguration("markdown.extension", scope).get<T>(key)!;
}
}

public getByAbsolute<T>(section: string, scope?: vscode.ConfigurationScope): T | undefined {
if (section.startsWith("markdown.extension.")) {
return this.get<T>(section.slice(19) as KnownKey, scope);
} else {
return vscode.workspace.getConfiguration(undefined, scope).get<T>(section);
}
}
}

export const configManager = new ConfigurationManager(fallbackMap, deprecated);
19 changes: 14 additions & 5 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
'use strict';

import { ExtensionContext, languages, Uri, window, workspace } from 'vscode';
import type { KatexOptions } from "katex";
import { configManager } from "./configuration/manager";
import { decorationManager } from "./theming/decorationManager";
import * as completion from './completion';
import * as formatting from './formatting';
import * as listEditing from './listEditing';
import { commonMarkEngine, MarkdownIt, mdEngine } from "./markdownEngine";
import { config as configNls, localize } from './nls';
import resolveResource from "./nls/resolveResource";
import * as preview from './preview';
Expand All @@ -14,29 +18,34 @@ import * as toc from './toc';

export function activate(context: ExtensionContext) {
configNls({ extensionContext: context });

context.subscriptions.push(
configManager, decorationManager, commonMarkEngine, mdEngine
);

activateMdExt(context);

if (workspace.getConfiguration('markdown.extension.math').get<boolean>('enabled')) {
// Make a deep copy as `macros` will be modified by KaTeX during initialization
let userMacros = JSON.parse(JSON.stringify(workspace.getConfiguration('markdown.extension.katex').get<object>('macros')));
let katexOptions = { throwOnError: false };
const katexOptions: KatexOptions = { throwOnError: false };
if (Object.keys(userMacros).length !== 0) {
katexOptions['macros'] = userMacros;
}

return {
extendMarkdownIt(md) {
extendMarkdownIt(md: MarkdownIt): MarkdownIt {
require('katex/contrib/mhchem/mhchem');
return md.use(require('markdown-it-task-lists'))
.use(require('@neilsustc/markdown-it-katex'), katexOptions);
}
}
};
} else {
return {
extendMarkdownIt(md) {
extendMarkdownIt(md: MarkdownIt): MarkdownIt {
return md.use(require('markdown-it-task-lists'));
}
}
};
}
}

Expand Down
Loading