Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ doc/
temp/
logs/
*.min.json
.claude/settings.local.json
61 changes: 40 additions & 21 deletions src/main/filesystem/markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,15 @@ export const writeMarkdownFile = (pathname, content, options) => {
* @param {string} preferredEol The preferred EOL.
* @param {boolean} autoGuessEncoding Whether we should try to auto guess encoding.
* @param {*} trimTrailingNewline The trim trailing newline option.
* @param {boolean} autoNormalizeMarkdownOnOpen Whether to automatically normalize line endings and detect trailing newlines on open.
* @returns {IMarkdownDocumentRaw} Returns a raw markdown document.
*/
export const loadMarkdownFile = async (
pathname,
preferredEol,
autoGuessEncoding = true,
trimTrailingNewline = 2
trimTrailingNewline = 2,
autoNormalizeMarkdownOnOpen = false
) => {
// TODO: Use streams to not buffer the file multiple times and only guess
// encoding on the first 256/512 bytes.
Expand Down Expand Up @@ -108,33 +110,50 @@ export const loadMarkdownFile = async (
}

let adjustLineEndingOnSave = false
if (isMixedLineEndings || isUnknownEnding || lineEnding !== 'lf') {
adjustLineEndingOnSave = lineEnding !== 'lf'
// Convert to LF for internal use.
markdown = convertLineEndings(markdown, 'lf')
}

// Detect final newline
if (trimTrailingNewline === 2) {
if (!markdown) {
// Use default value
trimTrailingNewline = 3
} else {
const lastIndex = markdown.length - 1
if (lastIndex >= 1 && markdown[lastIndex] === '\n' && markdown[lastIndex - 1] === '\n') {
// Disabled
trimTrailingNewline = 2
} else if (markdown[lastIndex] === '\n') {
// Ensure single trailing newline
trimTrailingNewline = 1
// Only auto-normalize if the preference is enabled
if (autoNormalizeMarkdownOnOpen) {
if (isMixedLineEndings || isUnknownEnding || lineEnding !== 'lf') {
adjustLineEndingOnSave = lineEnding !== 'lf'
// Convert to LF for internal use.
markdown = convertLineEndings(markdown, 'lf')
}

// Detect final newline
if (trimTrailingNewline === 2) {
if (!markdown) {
// Use default value
trimTrailingNewline = 3
} else {
// Trim trailing newlines
trimTrailingNewline = 0
const lastIndex = markdown.length - 1
if (lastIndex >= 1 && markdown[lastIndex] === '\n' && markdown[lastIndex - 1] === '\n') {
// Disabled
trimTrailingNewline = 2
} else if (markdown[lastIndex] === '\n') {
// Ensure single trailing newline
trimTrailingNewline = 1
} else {
// Trim trailing newlines
trimTrailingNewline = 0
}
}
}
} else {
// When not auto-normalizing, preserve the original line ending format
// but still convert to LF internally for the editor (required by MarkText)
if (lineEnding !== 'lf') {
adjustLineEndingOnSave = true
markdown = convertLineEndings(markdown, 'lf')
}
// When not auto-normalizing and trimTrailingNewline is set to auto-detect (2),
// we should use "disabled" (3) to preserve the file exactly as-is
if (trimTrailingNewline === 2) {
trimTrailingNewline = 3
}
}

const filename = path.basename(pathname)

return {
// document information
markdown,
Expand Down
18 changes: 10 additions & 8 deletions src/main/filesystem/watcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const EVENT_NAME = {
file: 'mt::update-file'
}

const add = async (win, pathname, type, endOfLine, autoGuessEncoding, trimTrailingNewline) => {
const add = async (win, pathname, type, endOfLine, autoGuessEncoding, trimTrailingNewline, autoNormalizeMarkdownOnOpen) => {
const stats = await fsPromises.stat(pathname)
const birthTime = stats.birthtime
const isMarkdown = hasMarkdownExtension(pathname)
Expand All @@ -37,7 +37,8 @@ const add = async (win, pathname, type, endOfLine, autoGuessEncoding, trimTraili
pathname,
endOfLine,
autoGuessEncoding,
trimTrailingNewline
trimTrailingNewline,
autoNormalizeMarkdownOnOpen
)
file.data = data
} catch (err) {
Expand Down Expand Up @@ -66,7 +67,7 @@ const unlink = (win, pathname, type) => {
})
}

const change = async (win, pathname, type, endOfLine, autoGuessEncoding, trimTrailingNewline) => {
const change = async (win, pathname, type, endOfLine, autoGuessEncoding, trimTrailingNewline, autoNormalizeMarkdownOnOpen) => {
// No need to update the tree view if the file content has changed.
if (type === 'dir') return

Expand All @@ -79,7 +80,8 @@ const change = async (win, pathname, type, endOfLine, autoGuessEncoding, trimTra
pathname,
endOfLine,
autoGuessEncoding,
trimTrailingNewline
trimTrailingNewline,
autoNormalizeMarkdownOnOpen
)
const file = {
pathname,
Expand Down Expand Up @@ -194,16 +196,16 @@ class Watcher {
if (!await this._shouldIgnoreEvent(win.id, pathname, type, usePolling)) {
const { _preferences } = this
const eol = _preferences.getPreferredEol()
const { autoGuessEncoding, trimTrailingNewline } = _preferences.getAll()
add(win, pathname, type, eol, autoGuessEncoding, trimTrailingNewline)
const { autoGuessEncoding, trimTrailingNewline, autoNormalizeMarkdownOnOpen } = _preferences.getAll()
add(win, pathname, type, eol, autoGuessEncoding, trimTrailingNewline, autoNormalizeMarkdownOnOpen)
}
})
.on('change', async pathname => {
if (!await this._shouldIgnoreEvent(win.id, pathname, type, usePolling)) {
const { _preferences } = this
const eol = _preferences.getPreferredEol()
const { autoGuessEncoding, trimTrailingNewline } = _preferences.getAll()
change(win, pathname, type, eol, autoGuessEncoding, trimTrailingNewline)
const { autoGuessEncoding, trimTrailingNewline, autoNormalizeMarkdownOnOpen } = _preferences.getAll()
change(win, pathname, type, eol, autoGuessEncoding, trimTrailingNewline, autoNormalizeMarkdownOnOpen)
}
})
.on('unlink', pathname => unlink(win, pathname, type))
Expand Down
5 changes: 5 additions & 0 deletions src/main/preferences/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,11 @@
"type": "boolean",
"default": false
},
"autoNormalizeMarkdownOnOpen": {
"description": "Editor--Automatically normalize line endings and detect trailing newline settings when opening files. When disabled, files are opened as-is and only normalized when saved.",
"type": "boolean",
"default": false
},
"preferLooseListItem": {
"description": "Markdown--The preferred list type",
"type": "boolean",
Expand Down
4 changes: 2 additions & 2 deletions src/main/windows/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,10 +258,10 @@ class EditorWindow extends BaseWindow {
const { browserWindow } = this
const { preferences } = this._accessor
const eol = preferences.getPreferredEol()
const { autoGuessEncoding, trimTrailingNewline } = preferences.getAll()
const { autoGuessEncoding, trimTrailingNewline, autoNormalizeMarkdownOnOpen } = preferences.getAll()

for (const { filePath, options, selected } of fileList) {
loadMarkdownFile(filePath, eol, autoGuessEncoding, trimTrailingNewline)
loadMarkdownFile(filePath, eol, autoGuessEncoding, trimTrailingNewline, autoNormalizeMarkdownOnOpen)
.then((rawDocument) => {
if (this.lifecycle === WindowLifecycle.READY) {
this._doOpenTab(rawDocument, options, selected)
Expand Down
6 changes: 6 additions & 0 deletions src/renderer/src/prefComponents/editor/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@
:bool="autoCheck"
:on-change="(value) => onSelectChange('autoCheck', value)"
></bool>
<bool
:description="t('preferences.editor.misc.autoNormalizeMarkdownOnOpen')"
:bool="autoNormalizeMarkdownOnOpen"
:on-change="(value) => onSelectChange('autoNormalizeMarkdownOnOpen', value)"
></bool>
<bool
:description="t('preferences.editor.misc.wrapCodeBlocks')"
:bool="wrapCodeBlocks"
Expand Down Expand Up @@ -209,6 +214,7 @@ const {
hideQuickInsertHint,
hideLinkPopup,
autoCheck,
autoNormalizeMarkdownOnOpen,
wrapCodeBlocks,
editorLineWidth,
defaultEncoding,
Expand Down
6 changes: 6 additions & 0 deletions src/renderer/src/store/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -1036,6 +1036,12 @@ export const useEditorStore = defineStore('editor', {
this.toc = listToTree(toc)
}

// Ignore the first content change after file load (Muya normalizes content)
if (this.currentFile.isFirstContentChange) {
this.currentFile.isFirstContentChange = false
return
}

if (markdown !== oldMarkdown) {
this.currentFile.isSaved = false
if (pathname && autoSave) {
Expand Down
4 changes: 3 additions & 1 deletion src/renderer/src/store/help.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ export const defaultFileState = {
value: ''
},
// Per tab notifications
notifications: []
notifications: [],
// Track whether this is the first content change after loading (to ignore Muya normalization)
isFirstContentChange: true
}

export const getOptionsFromState = (file) => {
Expand Down
1 change: 1 addition & 0 deletions static/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@
"items": {
"autoSave": "Den bearbeiteten Inhalt automatisch speichern",
"autoSaveDelay": "Die Zeit in ms nach einer Änderung, bis die Datei gespeichert wird",
"autoNormalizeMarkdownOnOpen": "Zeilenenden beim Öffnen automatisch normalisieren und Einstellungen für nachfolgende Zeilenumbrüche erkennen. Bei Deaktivierung werden Dateien unverändert geöffnet und nur beim Speichern normalisiert",
"titleBarStyle": "Der Titelleisten-Stil (nur Windows und Linux-System)",
"openFilesInNewWindow": "Dateien in einem neuen Fenster öffnen",
"openFolderInNewWindow": "Ordner über Menü in einem neuen Fenster öffnen",
Expand Down
1 change: 1 addition & 0 deletions static/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@
"hideQuickInsertHint": "Hide quick insert hint",
"hideLinkPopup": "Hide link popup",
"autoCheck": "Auto check",
"autoNormalizeMarkdownOnOpen": "Auto-normalize line endings on open",
"wrapCodeBlocks": "Wrap code blocks"
}
},
Expand Down
1 change: 1 addition & 0 deletions static/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@
"items": {
"autoSave": "Guardar automáticamente el contenido que se está editando",
"autoSaveDelay": "El tiempo en ms después de un cambio que se guarda el archivo",
"autoNormalizeMarkdownOnOpen": "Normalizar automáticamente los finales de línea y detectar la configuración de salto de línea final al abrir archivos. Cuando está deshabilitado, los archivos se abren tal cual y solo se normalizan al guardar",
"titleBarStyle": "El estilo de la barra de título (solo sistema Windows y Linux)",
"openFilesInNewWindow": "Abrir archivos en una nueva ventana",
"openFolderInNewWindow": "Abrir carpeta vía menú en una nueva ventana",
Expand Down
1 change: 1 addition & 0 deletions static/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@
"items": {
"autoSave": "Sauvegarder automatiquement le contenu en cours d'édition",
"autoSaveDelay": "Le temps en ms après un changement avant que le fichier soit sauvegardé",
"autoNormalizeMarkdownOnOpen": "Normaliser automatiquement les fins de ligne et détecter les paramètres de saut de ligne final lors de l'ouverture des fichiers. Lorsque désactivé, les fichiers sont ouverts tels quels et normalisés uniquement lors de l'enregistrement",
"titleBarStyle": "Le style de la barre de titre (systèmes Windows et Linux uniquement)",
"openFilesInNewWindow": "Ouvrir les fichiers dans une nouvelle fenêtre",
"openFolderInNewWindow": "Ouvrir le dossier via le menu dans une nouvelle fenêtre",
Expand Down
1 change: 1 addition & 0 deletions static/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@
"items": {
"autoSave": "編集中のコンテンツを自動保存",
"autoSaveDelay": "変更後にファイルが保存されるまでの時間(ミリ秒)",
"autoNormalizeMarkdownOnOpen": "ファイルを開く際に行末を自動的に正規化し、末尾の改行設定を検出します。無効にすると、ファイルはそのまま開かれ、保存時にのみ正規化されます",
"titleBarStyle": "タイトルバーのスタイル(WindowsおよびLinuxシステムのみ)",
"openFilesInNewWindow": "新しいウィンドウでファイルを開く",
"openFolderInNewWindow": "メニューから新しいウィンドウでフォルダを開く",
Expand Down
1 change: 1 addition & 0 deletions static/locales/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@
"items": {
"autoSave": "편집 중인 내용을 자동으로 저장",
"autoSaveDelay": "변경 후 파일이 저장되기까지의 시간(밀리초)",
"autoNormalizeMarkdownOnOpen": "파일을 열 때 줄 끝을 자동으로 정규화하고 후행 줄바꿈 설정을 감지합니다. 비활성화하면 파일이 그대로 열리고 저장할 때만 정규화됩니다",
"titleBarStyle": "제목 표시줄 스타일(Windows 및 Linux 시스템만)",
"openFilesInNewWindow": "새 창에서 파일 열기",
"openFolderInNewWindow": "메뉴를 통해 새 창에서 폴더 열기",
Expand Down
1 change: 1 addition & 0 deletions static/locales/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@
"items": {
"autoSave": "Salvar automaticamente o conteúdo sendo editado",
"autoSaveDelay": "O tempo em ms após uma mudança que o arquivo é salvo",
"autoNormalizeMarkdownOnOpen": "Normalizar automaticamente as terminações de linha e detectar as configurações de nova linha final ao abrir arquivos. Quando desabilitado, os arquivos são abertos como estão e normalizados apenas ao salvar",
"titleBarStyle": "O estilo da barra de título (apenas sistemas Windows e Linux)",
"openFilesInNewWindow": "Abrir arquivos em uma nova janela",
"openFolderInNewWindow": "Abrir pasta via menu em uma nova janela",
Expand Down
1 change: 1 addition & 0 deletions static/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@
"items": {
"autoSave": "自动保存正在编辑的内容",
"autoSaveDelay": "文件保存前的延迟时间(毫秒)",
"autoNormalizeMarkdownOnOpen": "打开文件时自动规范化行尾并检测尾随换行符设置。禁用时,文件按原样打开,仅在保存时规范化",
"titleBarStyle": "标题栏样式(仅限 Windows 和 Linux 系统)",
"openFilesInNewWindow": "在新窗口中打开文件",
"openFolderInNewWindow": "通过菜单在新窗口中打开文件夹",
Expand Down
1 change: 1 addition & 0 deletions static/locales/zh-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@
"items": {
"autoSave": "自動儲存正在編輯的內容",
"autoSaveDelay": "檔案儲存前的延遲時間(毫秒)",
"autoNormalizeMarkdownOnOpen": "開啟檔案時自動規範化行尾並檢測尾隨換行符設定。停用時,檔案按原樣開啟,僅在儲存時規範化",
"titleBarStyle": "標題欄樣式(僅限 Windows 和 Linux 系統)",
"openFilesInNewWindow": "在新視窗中開啟檔案",
"openFolderInNewWindow": "透過選單在新視窗中開啟資料夾",
Expand Down