Skip to content

Commit

Permalink
refactor: add editor-md-syntax plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
purocean committed Mar 10, 2022
1 parent 7f3ee5f commit 82456b4
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 128 deletions.
2 changes: 2 additions & 0 deletions src/renderer/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -71,6 +72,7 @@ export default [
editorPaste,
editorAttachment,
editorMarkdown,
editorMdSyntax,
editorEmoji,
editorWords,
copyText,
Expand Down
129 changes: 1 addition & 128 deletions src/renderer/plugins/editor-markdown.ts
Original file line number Diff line number Diff line change
@@ -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<!-- --applet-- ${1:DEMO} -->\n<button onclick="ctx.ui.useToast().show(`info`, `HELLOWORLD!`)">TEST</button>\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'
Expand Down Expand Up @@ -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 => {
Expand Down
146 changes: 146 additions & 0 deletions src/renderer/plugins/editor-md-syntax.ts
Original file line number Diff line number Diff line change
@@ -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<!-- --applet-- ${1:DEMO} -->\n<button onclick="ctx.ui.useToast().show(`info`, `HELLOWORLD!`)">TEST</button>\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<Monaco.languages.CompletionList | undefined> {
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

0 comments on commit 82456b4

Please sign in to comment.