diff --git a/src/renderer/plugins.ts b/src/renderer/plugins.ts
index c9532dc96..4b9a9b2e1 100644
--- a/src/renderer/plugins.ts
+++ b/src/renderer/plugins.ts
@@ -17,6 +17,7 @@ import statusBarGet from '@fe/plugins/status-bar-get'
import editorPaste from '@fe/plugins/editor-paste'
import editorAttachment from '@fe/plugins/editor-attachment'
import editorMarkdown from '@fe/plugins/editor-markdown'
+import editorMdSyntax from '@fe/plugins/editor-md-syntax'
import editorWords from '@fe/plugins/editor-words'
import editorEmoji from '@fe/plugins/editor-emoji'
import copyText from '@fe/plugins/copy-text'
@@ -71,6 +72,7 @@ export default [
editorPaste,
editorAttachment,
editorMarkdown,
+ editorMdSyntax,
editorEmoji,
editorWords,
copyText,
diff --git a/src/renderer/plugins/editor-markdown.ts b/src/renderer/plugins/editor-markdown.ts
index a1433f5b6..2c0a30eba 100644
--- a/src/renderer/plugins/editor-markdown.ts
+++ b/src/renderer/plugins/editor-markdown.ts
@@ -1,74 +1,12 @@
/* eslint-disable no-template-curly-in-string */
import dayjs from 'dayjs'
import type * as Monaco from 'monaco-editor'
-import { deleteLine, getEditor, getLineContent, getMonaco, getOneIndent, insert, replaceLine, whenEditorReady } from '@fe/services/editor'
+import { deleteLine, getEditor, getLineContent, getOneIndent, insert, replaceLine, whenEditorReady } from '@fe/services/editor'
import type { Plugin } from '@fe/context'
import { t } from '@fe/services/i18n'
import { getSetting } from '@fe/services/setting'
import { isKeydown } from '@fe/core/command'
-function createDependencyProposals (range: Monaco.IRange): Monaco.languages.CompletionItem[] {
- const monaco = getMonaco()
-
- const replaceRange = { ...range, endColumn: range.startColumn + 1 }
-
- const result: Monaco.languages.CompletionItem[] = []
-
- ;[
- { name: '/ ![]() Image', insertText: '![${2:Img}]($1)' },
- { name: '/ []() Link', insertText: '[${2:Link}]($1)' },
- { name: '/ # Head', insertText: '# $1' },
- { name: '/ ## Head', insertText: '## $1' },
- { name: '/ ### Head', insertText: '### $1' },
- { name: '/ #### Head', insertText: '#### $1' },
- { name: '/ ##### Head', insertText: '##### $1' },
- { name: '/ ###### Head', insertText: '###### $1' },
- { name: '/ + List', insertText: '+ ' },
- { name: '/ - List', insertText: '- ' },
- { name: '/ ` Code', insertText: '`$1`' },
- { name: '/ * Italic', insertText: '*$1*' },
- { name: '/ _ Italic', insertText: '_$1_' },
- { name: '/ ~ Sub', insertText: '~$1~' },
- { name: '/ ^ Sup', insertText: '^$1^' },
- { name: '/ ** Bold', insertText: '**$1**' },
- { name: '/ __ Bold', insertText: '__$1__' },
- { name: '/ ~~ Delete', insertText: '~~$1~~' },
- { name: '/ == Mark', insertText: '==$1==' },
- { name: '/ + [ ] TODO List', insertText: '+ [ ] ' },
- { name: '/ - [ ] TODO List', insertText: '- [ ] ' },
- { name: '/ ```', insertText: '```$1\n```\n' },
- { name: '/ [toc]', insertText: '[toc]{type: "${1|ul,ol|}", level: [2,3]}' },
- { name: '/ + MindMap', insertText: '+ ${1:Subject}{.mindmap}\n + ${2:Topic}' },
- { name: '/ $ Inline KaTeX', insertText: '$$1$' },
- { name: '/ $$ Block KaTeX', insertText: '$$$1$$\n' },
- { name: '/ ``` ECharts', insertText: '```js\n// --echarts-- \nchart => chart.setOption({\n xAxis: {\n type: "category",\n data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]\n },\n yAxis: {\n type: "value"\n },\n series: [\n {\n data: [150, 230, 224, 218, 135, 147, 260],\n type: "line"\n }\n ]\n}, true)\n```\n' },
- { name: '/ ``` Run Code', insertText: '```js\n// --run--\n${1:await new Promise(r => setTimeout(r, 500))\nctx.ui.useToast().show("info", "HELLOWORLD!")\nconsole.log("hello world!")}\n```\n' },
- { name: '/ ``` Applet', insertText: '```html\n\n\n```\n' },
- { name: '/ ``` Mermaid', insertText: '```mermaid\ngraph LR\n${1:A[Hard] --> |Text| B(Round)}\n```\n' },
- { name: '/ @startuml Plantuml', insertText: '@startuml\n${1:a -> b}\n@enduml\n' },
- { name: '/ []() Drawio Link', insertText: '[${2:Drawio}]($1){link-type="drawio"}' },
- { name: '/ []() Luckysheet Link', insertText: '[${2:Luckysheet}]($1){link-type="luckysheet"}' },
- { name: '/ ||| Table', insertText: '| ${1:--} | ${2:--} | ${3:--} |\n| -- | -- | -- |\n| -- | -- | -- |' },
- { name: '/ ||| Small Table', insertText: '| ${1:--} | ${2:--} | ${3:--} |\n| -- | -- | -- |\n| -- | -- | -- |\n{.small}' },
- { name: '/ [= Macro', insertText: '[= ${1:1+1} =]' },
- { name: '/ --- Horizontal Line', insertText: '---\n' },
- { name: '/ --- Front Matter', insertText: '---\nheadingNumber: true\nenableMacro: true\nmdOptions: { linkify: true, breaks: true }\ndefine:\n APP_NAME: Yank Note\n---\n' },
- { name: '/ ::: Container', insertText: '${1|:::,::::,:::::|} ${2|tip,warning,danger,details,group,group-item|} ${3:Title}\n${4:Content}\n${1|:::,::::,:::::|}\n' },
- { name: '/ ::: Group Container', insertText: ':::: group ${1:Title}\n::: group-item Tab 1\ntest 1\n:::\n::: group-item *Tab 2\ntest 2\n:::\n::: group-item Tab 3\ntest 3\n:::\n::::\n' },
- ].forEach((item, i) => {
- result.push({
- label: { label: item.name },
- kind: monaco.languages.CompletionItemKind.Keyword,
- insertText: item.insertText,
- insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
- range: replaceRange,
- sortText: (20000 + i).toString()
- })
- })
-
- return result
-}
-
function processCursorChange (source: string, position: Monaco.Position) {
const isEnter = source === 'keyboard' && isKeydown('ENTER')
const isTab = source === 'tab'
@@ -206,71 +144,6 @@ export default {
editor.onDidCompositionEnd(() => {
ctx.store.commit('setInComposition', false)
})
-
- monaco.languages.setLanguageConfiguration('markdown', {
- surroundingPairs: [
- { open: '{', close: '}' },
- { open: '[', close: ']' },
- { open: '(', close: ')' },
- { open: '<', close: '>' },
- { open: '`', close: '`' },
- { open: "'", close: "'" },
- { open: '"', close: '"' },
- { open: '*', close: '*' },
- { open: '_', close: '_' },
- { open: '=', close: '=' },
- { open: '~', close: '~' },
- { open: '^', close: '^' },
- { open: '#', close: '#' },
- { open: '$', close: '$' },
- { open: '《', close: '》' },
- { open: '【', close: '】' },
- { open: '「', close: '」' },
- { open: '(', close: ')' },
- { open: '“', close: '”' },
- ],
- onEnterRules: [
- { beforeText: /^\s*> .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '> ' } },
- { beforeText: /^\s*\+ \[ \] .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '+ [ ] ' } },
- { beforeText: /^\s*- \[ \] .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '- [ ] ' } },
- { beforeText: /^\s*\* \[ \] .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '* [ ] ' } },
- { beforeText: /^\s*\+ \[x\] .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '+ [ ] ' } },
- { beforeText: /^\s*- \[x\] .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '- [ ] ' } },
- { beforeText: /^\s*\* \[x\] .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '* [ ] ' } },
- { beforeText: /^\s*\+ .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '+ ' } },
- { beforeText: /^\s*- .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '- ' } },
- { beforeText: /^\s*\* .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '* ' } },
- { beforeText: /^\s*\d+\. .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '1. ' } },
- { beforeText: /^\s*\d+\) .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '1) ' } },
- ]
- })
-
- monaco.languages.registerCompletionItemProvider('markdown', {
- triggerCharacters: Array(93).fill(undefined).map((_, i) => String.fromCharCode(i + 33)).concat(['~']),
- provideCompletionItems: (model, position) => {
- const lineContent = model.getLineContent(position.lineNumber)
- let startColumn = lineContent.substring(0, position.column).lastIndexOf(' ') + 1
- if (startColumn === position.column) {
- startColumn = 0
- }
-
- let endColumn = lineContent.substring(position.column - 1).indexOf(' ') + position.column
- if (endColumn < position.column) {
- endColumn = lineContent.length + 1
- }
-
- const range: Monaco.IRange = {
- startLineNumber: position.lineNumber,
- endLineNumber: position.lineNumber,
- startColumn: startColumn + 1,
- endColumn: endColumn
- }
-
- return {
- suggestions: createDependencyProposals(range, model, position)
- }
- }
- })
})
ctx.statusBar.tapMenus(menus => {
diff --git a/src/renderer/plugins/editor-md-syntax.ts b/src/renderer/plugins/editor-md-syntax.ts
new file mode 100644
index 000000000..2f45d5af1
--- /dev/null
+++ b/src/renderer/plugins/editor-md-syntax.ts
@@ -0,0 +1,146 @@
+/* eslint-disable no-template-curly-in-string */
+import type * as Monaco from 'monaco-editor'
+import type { Ctx, Plugin } from '@fe/context'
+
+const surroundingPairs = [
+ { open: '{', close: '}' },
+ { open: '[', close: ']' },
+ { open: '(', close: ')' },
+ { open: '<', close: '>' },
+ { open: '`', close: '`' },
+ { open: "'", close: "'" },
+ { open: '"', close: '"' },
+ { open: '*', close: '*' },
+ { open: '_', close: '_' },
+ { open: '=', close: '=' },
+ { open: '~', close: '~' },
+ { open: '^', close: '^' },
+ { open: '#', close: '#' },
+ { open: '$', close: '$' },
+ { open: '《', close: '》' },
+ { open: '【', close: '】' },
+ { open: '「', close: '」' },
+ { open: '(', close: ')' },
+ { open: '“', close: '”' },
+]
+
+class MdSyntaxCompletionProvider implements Monaco.languages.CompletionItemProvider {
+ triggerCharacters = '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'.split('')
+
+ private readonly monaco: typeof Monaco
+ private readonly ctx: Ctx
+
+ private readonly list = [
+ { name: '/ ![]() Image', insertText: '![${2:Img}]($1)' },
+ { name: '/ []() Link', insertText: '[${2:Link}]($1)' },
+ { name: '/ # Head 1', insertText: '# $1' },
+ { name: '/ ## Head 2', insertText: '## $1' },
+ { name: '/ ### Head 3', insertText: '### $1' },
+ { name: '/ #### Head 4', insertText: '#### $1' },
+ { name: '/ ##### Head 5', insertText: '##### $1' },
+ { name: '/ ###### Head 6', insertText: '###### $1' },
+ { name: '/ + List', insertText: '+ ' },
+ { name: '/ - List', insertText: '- ' },
+ { name: '/ ` Code', insertText: '`$1`' },
+ { name: '/ * Italic', insertText: '*$1*' },
+ { name: '/ _ Italic', insertText: '_$1_' },
+ { name: '/ ~ Sub', insertText: '~$1~' },
+ { name: '/ ^ Sup', insertText: '^$1^' },
+ { name: '/ ** Bold', insertText: '**$1**' },
+ { name: '/ __ Bold', insertText: '__$1__' },
+ { name: '/ ~~ Delete', insertText: '~~$1~~' },
+ { name: '/ == Mark', insertText: '==$1==' },
+ { name: '/ + [ ] TODO List', insertText: '+ [ ] ' },
+ { name: '/ - [ ] TODO List', insertText: '- [ ] ' },
+ { name: '/ ```', insertText: '```$1\n```\n' },
+ { name: '/ [toc]', insertText: '[toc]{type: "${1|ul,ol|}", level: [2,3]}' },
+ { name: '/ + MindMap', insertText: '+ ${1:Subject}{.mindmap}\n + ${2:Topic}' },
+ { name: '/ $ Inline KaTeX', insertText: '$$1$' },
+ { name: '/ $$ Block KaTeX', insertText: '$$$1$$\n' },
+ { name: '/ ``` ECharts', insertText: '```js\n// --echarts-- \nchart => chart.setOption({\n xAxis: {\n type: "category",\n data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]\n },\n yAxis: {\n type: "value"\n },\n series: [\n {\n data: [150, 230, 224, 218, 135, 147, 260],\n type: "line"\n }\n ]\n}, true)\n```\n' },
+ { name: '/ ``` Run Code', insertText: '```js\n// --run--\n${1:await new Promise(r => setTimeout(r, 500))\nctx.ui.useToast().show("info", "HELLOWORLD!")\nconsole.log("hello world!")}\n```\n' },
+ { name: '/ ``` Applet', insertText: '```html\n\n\n```\n' },
+ { name: '/ ``` Mermaid', insertText: '```mermaid\ngraph LR\n${1:A[Hard] --> |Text| B(Round)}\n```\n' },
+ { name: '/ @startuml Plantuml', insertText: '@startuml\n${1:a -> b}\n@enduml\n' },
+ { name: '/ []() Drawio Link', insertText: '[${2:Drawio}]($1){link-type="drawio"}' },
+ { name: '/ []() Luckysheet Link', insertText: '[${2:Luckysheet}]($1){link-type="luckysheet"}' },
+ { name: '/ ||| Table', insertText: '| ${1:--} | ${2:--} | ${3:--} |\n| -- | -- | -- |\n| -- | -- | -- |' },
+ { name: '/ ||| Small Table', insertText: '| ${1:--} | ${2:--} | ${3:--} |\n| -- | -- | -- |\n| -- | -- | -- |\n{.small}' },
+ { name: '/ [= Macro', insertText: '[= ${1:1+1} =]' },
+ { name: '/ --- Horizontal Line', insertText: '---\n' },
+ { name: '/ --- Front Matter', insertText: '---\nheadingNumber: true\nenableMacro: true\nmdOptions: { linkify: true, breaks: true }\ndefine:\n APP_NAME: Yank Note\n---\n' },
+ { name: '/ ::: Container', insertText: '${1|:::,::::,:::::|} ${2|tip,warning,danger,details,group,group-item|} ${3:Title}\n${4:Content}\n${1|:::,::::,:::::|}\n' },
+ { name: '/ ::: Group Container', insertText: ':::: group ${1:Title}\n::: group-item Tab 1\ntest 1\n:::\n::: group-item *Tab 2\ntest 2\n:::\n::: group-item Tab 3\ntest 3\n:::\n::::\n' },
+ ]
+
+ private readonly pairsMap = new Map(surroundingPairs.map(x => [x.open, x.close]))
+
+ constructor (monaco: typeof Monaco, ctx: Ctx) {
+ this.monaco = monaco
+ this.ctx = ctx
+ }
+
+ public async provideCompletionItems (model: Monaco.editor.IModel, position: Monaco.Position): Promise {
+ const line = model.getLineContent(position.lineNumber)
+ const cursor = position.column - 1
+ const linePrefixText = line.slice(0, cursor)
+
+ let startColumn = linePrefixText.lastIndexOf(' ') + 1
+ if (startColumn === position.column) {
+ startColumn = 0
+ }
+
+ const range = new this.monaco.Range(
+ position.lineNumber,
+ startColumn,
+ position.lineNumber,
+ // remove auto surrounding pairs
+ this.pairsMap.get(line.charAt(cursor - 1)) === line.charAt(cursor)
+ ? position.column + 1
+ : position.column,
+ )
+
+ const result: Monaco.languages.CompletionItem[] = this.list.map((item, i) => (
+ {
+ label: { label: item.name },
+ kind: this.monaco.languages.CompletionItemKind.Keyword,
+ insertText: item.insertText,
+ insertTextRules: this.monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
+ range,
+ sortText: i.toString().padStart(7),
+ }
+ ))
+
+ return { suggestions: result }
+ }
+}
+
+export default {
+ name: 'editor-md-syntax',
+ register: (ctx) => {
+ ctx.editor.whenEditorReady().then(({ monaco }) => {
+ monaco.languages.registerCompletionItemProvider(
+ 'markdown',
+ new MdSyntaxCompletionProvider(monaco, ctx)
+ )
+
+ monaco.languages.setLanguageConfiguration('markdown', {
+ surroundingPairs,
+ onEnterRules: [
+ { beforeText: /^\s*> .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '> ' } },
+ { beforeText: /^\s*\+ \[ \] .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '+ [ ] ' } },
+ { beforeText: /^\s*- \[ \] .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '- [ ] ' } },
+ { beforeText: /^\s*\* \[ \] .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '* [ ] ' } },
+ { beforeText: /^\s*\+ \[x\] .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '+ [ ] ' } },
+ { beforeText: /^\s*- \[x\] .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '- [ ] ' } },
+ { beforeText: /^\s*\* \[x\] .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '* [ ] ' } },
+ { beforeText: /^\s*\+ .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '+ ' } },
+ { beforeText: /^\s*- .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '- ' } },
+ { beforeText: /^\s*\* .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '* ' } },
+ { beforeText: /^\s*\d+\. .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '1. ' } },
+ { beforeText: /^\s*\d+\) .*$/, action: { indentAction: monaco.languages.IndentAction.None, appendText: '1) ' } },
+ ]
+ })
+ })
+ }
+} as Plugin