diff --git a/docs/PREFERENCES.md b/docs/PREFERENCES.md index 48c5f252..848b122f 100644 --- a/docs/PREFERENCES.md +++ b/docs/PREFERENCES.md @@ -31,6 +31,7 @@ Preferences can be controlled and modified in the settings window or via the `pr | textDirection | String | ltr | The writing text direction, optional value: `ltr` or `rtl` | | codeFontSize | Number | 14 | Font size on code block, the range is 12 ~ 28 | | codeFontFamily | String | `DejaVu Sans Mono` | Code font family | +| codeBlockLineNumbers | Boolean | true | Whether to show the line numbers in code block | | trimUnnecessaryCodeBlockEmptyLines | Boolean | true | Whether to trim the beginning and end empty line in Code block | | hideQuickInsertHint | Boolean | false | Hide hint for quickly creating paragraphs | | imageDropAction | String | folder | The default behavior after paste or drag the image to Mark Text, upload it to the image cloud (if configured), move to the specified folder, insert the path | diff --git a/src/main/preferences/schema.json b/src/main/preferences/schema.json index 03755007..4f026f6a 100644 --- a/src/main/preferences/schema.json +++ b/src/main/preferences/schema.json @@ -94,6 +94,11 @@ "type": "string", "pattern": "^[_A-z0-9]+((-|\\s)*[_A-z0-9])*$" }, + "codeBlockLineNumbers": { + "description": "Editor--Whether to show the line numbers", + "type": "boolean", + "default": true + }, "trimUnnecessaryCodeBlockEmptyLines": { "description": "Editor--Trim the beginning and ending empty lines in code block", "type": "boolean" diff --git a/src/muya/lib/assets/styles/index.css b/src/muya/lib/assets/styles/index.css index 3dcf2f98..ad9c542b 100644 --- a/src/muya/lib/assets/styles/index.css +++ b/src/muya/lib/assets/styles/index.css @@ -1107,3 +1107,49 @@ span.ag-reference-link { .vega-embed { padding-right: 0; } + +pre[class*="language-"].line-numbers { + position: relative; + padding-left: 2.5em; + counter-reset: linenumber; +} + +pre[class*="language-"].line-numbers > code { + position: relative; + white-space: inherit; +} + +figure:not(.ag-active) pre[class*="language-"].line-numbers { + display: none; +} + +.line-numbers .line-numbers-rows { + position: absolute; + pointer-events: none; + top: 0; + font-size: 100%; + left: -2.5em; + width: 2.5em; /* works for line-numbers below 1000 lines */ + letter-spacing: -1px; + + user-select: none; + +} + +.line-numbers-rows > span { + pointer-events: none; + display: block; + counter-increment: linenumber; +} + +.line-numbers-rows > span:before { + content: counter(linenumber); + color: var(--editorColor30); + display: block; + padding-right: .8em; + text-align: right; + transform: scale(.8); + position: relative; + top: .05em; +} + diff --git a/src/muya/lib/config/index.js b/src/muya/lib/config/index.js index 22eb6db1..3b2c1a97 100644 --- a/src/muya/lib/config/index.js +++ b/src/muya/lib/config/index.js @@ -254,6 +254,7 @@ export const MUYA_DEFAULT_OPTION = { bulletListMarker: '-', orderListDelimiter: '.', tabSize: 4, + codeBlockLineNumbers: true, // bullet/list marker width + listIndentation, tab or Daring Fireball Markdown (4 spaces) --> list indentation listIndentation: 1, frontmatterType: '-', diff --git a/src/muya/lib/parser/render/renderBlock/renderContainerBlock.js b/src/muya/lib/parser/render/renderBlock/renderContainerBlock.js index 5f16112d..551980ac 100644 --- a/src/muya/lib/parser/render/renderBlock/renderContainerBlock.js +++ b/src/muya/lib/parser/render/renderBlock/renderContainerBlock.js @@ -1,6 +1,7 @@ import { CLASS_OR_ID } from '../../../config' import { renderTableTools } from './renderToolBar' import { renderEditIcon } from './renderContainerEditIcon' +import renderLineNumberRows from './renderLineNumber' import { renderLeftBar, renderBottomBar } from './renderTableDargBar' import { h } from '../snabbdom' @@ -45,8 +46,17 @@ export default function renderContainerBlock (parent, block, activeBlocks, match }) } - if (/code|pre/.test(type) && typeof lang === 'string' && !!lang) { - selector += `.language-${lang.replace(/[#.]{1}/g, '')}` + if (/code|pre/.test(type)) { + if (typeof lang === 'string' && !!lang) { + selector += `.language-${lang.replace(/[#.]{1}/g, '')}` + } + if (this.muya.options.codeBlockLineNumbers) { + if (type === 'pre') { + selector += '.line-numbers' + } else { + children.unshift(renderLineNumberRows(block.children[0])) + } + } Object.assign(data.attrs, { spellcheck: 'false' }) } diff --git a/src/muya/lib/parser/render/renderBlock/renderLineNumber.js b/src/muya/lib/parser/render/renderBlock/renderLineNumber.js new file mode 100644 index 00000000..c0ddb7b0 --- /dev/null +++ b/src/muya/lib/parser/render/renderBlock/renderLineNumber.js @@ -0,0 +1,22 @@ +import { h } from '../snabbdom' + +const NEW_LINE_EXP = /\n(?!$)/g + +const renderLineNumberRows = codeContent => { + const { text } = codeContent + const match = text.match(NEW_LINE_EXP) + let linesNum = match ? match.length + 1 : 1 + if (text.endsWith('\n')) { + linesNum++ + } + const data = { + attrs: { + 'aria-hidden': true + } + } + const children = [...new Array(linesNum)].map(() => h('span')) + + return h('span.line-numbers-rows', data, children) +} + +export default renderLineNumberRows diff --git a/src/renderer/components/editorWithTabs/editor.vue b/src/renderer/components/editorWithTabs/editor.vue index fa432767..091cffeb 100644 --- a/src/renderer/components/editorWithTabs/editor.vue +++ b/src/renderer/components/editorWithTabs/editor.vue @@ -140,6 +140,7 @@ export default { fontSize: state => state.preferences.fontSize, codeFontSize: state => state.preferences.codeFontSize, codeFontFamily: state => state.preferences.codeFontFamily, + codeBlockLineNumbers: state => state.preferences.codeBlockLineNumbers, trimUnnecessaryCodeBlockEmptyLines: state => state.preferences.trimUnnecessaryCodeBlockEmptyLines, editorFontFamily: state => state.preferences.editorFontFamily, hideQuickInsertHint: state => state.preferences.hideQuickInsertHint, @@ -312,6 +313,12 @@ export default { }) } }, + codeBlockLineNumbers: function (value, oldValue) { + const { editor } = this + if (value !== oldValue && editor) { + editor.setOptions({ codeBlockLineNumbers: value }, true) + } + }, codeFontFamily: function (value, oldValue) { if (value !== oldValue) { addCommonStyle({ @@ -443,6 +450,7 @@ export default { tabSize, fontSize, lineHeight, + codeBlockLineNumbers, listIndentation, frontmatterType, superSubScript, @@ -485,6 +493,7 @@ export default { tabSize, fontSize, lineHeight, + codeBlockLineNumbers, listIndentation, frontmatterType, superSubScript, diff --git a/src/renderer/prefComponents/editor/index.vue b/src/renderer/prefComponents/editor/index.vue index 28edac3c..033e080b 100644 --- a/src/renderer/prefComponents/editor/index.vue +++ b/src/renderer/prefComponents/editor/index.vue @@ -39,6 +39,11 @@ :value="codeFontFamily" :onChange="value => onSelectChange('codeFontFamily', value)" > + state.preferences.textDirection, codeFontSize: state => state.preferences.codeFontSize, codeFontFamily: state => state.preferences.codeFontFamily, + codeBlockLineNumbers: state => state.preferences.codeBlockLineNumbers, trimUnnecessaryCodeBlockEmptyLines: state => state.preferences.trimUnnecessaryCodeBlockEmptyLines, hideQuickInsertHint: state => state.preferences.hideQuickInsertHint, hideLinkPopup: state => state.preferences.hideLinkPopup, diff --git a/src/renderer/store/preferences.js b/src/renderer/store/preferences.js index c0bbef20..32ab0c46 100644 --- a/src/renderer/store/preferences.js +++ b/src/renderer/store/preferences.js @@ -19,6 +19,7 @@ const state = { lineHeight: 1.6, codeFontSize: 14, codeFontFamily: 'DejaVu Sans Mono', + codeBlockLineNumbers: true, trimUnnecessaryCodeBlockEmptyLines: true, editorLineWidth: '', diff --git a/static/preference.json b/static/preference.json index 1364bf47..39a1a720 100644 --- a/static/preference.json +++ b/static/preference.json @@ -16,6 +16,7 @@ "lineHeight": 1.6, "codeFontSize": 14, "codeFontFamily": "DejaVu Sans Mono", + "codeBlockLineNumbers": true, "trimUnnecessaryCodeBlockEmptyLines": true, "editorLineWidth": "",