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": "",