feat: update editor directly to fix jumping cursor

This commit is contained in:
Dylan Armstrong 2025-05-26 19:57:26 +02:00
parent fbdfcaee0e
commit 5add7ef706
No known key found for this signature in database
6 changed files with 84 additions and 30 deletions

View file

@ -7,5 +7,5 @@
"isDesktopOnly": false,
"minAppVersion": "0.15.0",
"name": "Format Automatically with Prettier",
"version": "0.1.8"
"version": "0.2.0"
}

View file

@ -1,13 +1,16 @@
{
"author": "Dylan Armstrong<dylan@dylan.is>",
"dependencies": {
"diff-match-patch": "^1.0.5",
"obsidian": "^1.8.7",
"prettier": "^3.5.3"
},
"description": "This is a formatting plugin for Obsidian (https://obsidian.md)",
"devDependencies": {
"@codemirror/view": "^6.36.8",
"@dylanarmstrong/eslint-config": "^0.8.0",
"@eslint/js": "^9.27.0",
"@types/diff-match-patch": "^1.0.36",
"@types/node": "^22.15.21",
"builtin-modules": "5.0.0",
"esbuild": "0.25.4",

View file

@ -8,6 +8,9 @@ importers:
.:
dependencies:
diff-match-patch:
specifier: ^1.0.5
version: 1.0.5
obsidian:
specifier: ^1.8.7
version: 1.8.7(@codemirror/state@6.5.2)(@codemirror/view@6.36.8)
@ -15,12 +18,18 @@ importers:
specifier: ^3.5.3
version: 3.5.3
devDependencies:
'@codemirror/view':
specifier: ^6.36.8
version: 6.36.8
'@dylanarmstrong/eslint-config':
specifier: ^0.8.0
version: 0.8.0(eslint@9.27.0)
'@eslint/js':
specifier: ^9.27.0
version: 9.27.0
'@types/diff-match-patch':
specifier: ^1.0.36
version: 1.0.36
'@types/node':
specifier: ^22.15.21
version: 22.15.21
@ -301,6 +310,9 @@ packages:
'@types/codemirror@5.60.8':
resolution: {integrity: sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==}
'@types/diff-match-patch@1.0.36':
resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==}
'@types/estree@1.0.7':
resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
@ -454,6 +466,9 @@ packages:
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
diff-match-patch@1.0.5:
resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==}
electron-to-chromium@1.5.157:
resolution: {integrity: sha512-/0ybgsQd1muo8QlnuTpKwtl0oX5YMlUGbm8xyqgDU00motRkKFFbUJySAQBWcY79rVqNLWIWa87BGVGClwAB2w==}
@ -1036,6 +1051,8 @@ snapshots:
dependencies:
'@types/tern': 0.23.9
'@types/diff-match-patch@1.0.36': {}
'@types/estree@1.0.7': {}
'@types/json-schema@7.0.15': {}
@ -1209,6 +1226,8 @@ snapshots:
deep-is@0.1.4: {}
diff-match-patch@1.0.5: {}
electron-to-chromium@1.5.157: {}
esbuild@0.25.4:

View file

@ -1,4 +1,5 @@
import 'obsidian';
import { EditorView } from '@codemirror/view';
declare module 'obsidian' {
interface App {
@ -12,6 +13,10 @@ declare module 'obsidian' {
};
}
interface Editor {
cm?: EditorView;
}
interface Vault {
getConfig(id: string): boolean;
}

View file

@ -1,3 +1,4 @@
import DiffMatchPatch from 'diff-match-patch';
import { EditorPosition, Plugin } from 'obsidian';
import * as markdownPlugin from 'prettier/plugins/markdown';
@ -17,22 +18,11 @@ const DEFAULT_SETTINGS: PrettierPluginSettings = {
useTabs: true,
};
// Taken from https://github.com/alexgavrusev/obsidian-format-with-prettier/blob/master/src/cursor-position-utils.ts
// If the cursor is on line i, on character j, the offset
// is j plus sum of lengths (incl. the \n at the end) of lines 0..(i - 1)
const toOffset = (position: EditorPosition, text: string): number =>
text
.split('\n')
.slice(0, position.line)
.reduce((accumulator, line) => accumulator + line.length + 1, position.ch);
const fromOffset = (offset: number, text: string): EditorPosition => {
const textUpToOffset = text.slice(0, offset);
const newLines = textUpToOffset.split('\n');
const getPosition = (text: string): EditorPosition => {
const lines = text.split('\n');
return {
ch: newLines?.at(-1)?.length || 0,
line: newLines.length - 1,
ch: lines.at(-1)?.length ?? 0,
line: lines.length - 1,
};
};
@ -43,28 +33,64 @@ export default class PrettierPlugin extends Plugin {
private async format() {
const editor = this.app.workspace.activeEditor?.editor;
if (!editor) {
if (!editor || !editor.cm) {
return;
}
const file = this.app.workspace.getActiveFile();
const text = editor.getValue();
if (text && file) {
const position = editor.getCursor();
const cursorOffset = toOffset(position, text);
const formattedText = await prettier.format(text, {
embeddedLanguageFormatting: 'auto',
filepath: file.path,
parser: 'markdown',
plugins: [markdownPlugin],
tabWidth: this.settings.tabWidth,
useTabs: this.settings.useTabs,
});
const { cursorOffset: formattedCursorOffset, formatted: formattedText } =
await prettier.formatWithCursor(text, {
cursorOffset,
filepath: file.path,
parser: 'markdown',
plugins: [markdownPlugin],
tabWidth: this.settings.tabWidth,
useTabs: this.settings.useTabs,
});
// Switched to using DiffMatchPatch after I saw it here:
// https://github.com/platers/obsidian-linter
// And this style of updating the editor stops my cursor from jumping
// around. Also, it keeps sections toggled if I've closed them.
editor.setValue(formattedText);
editor.setCursor(fromOffset(formattedCursorOffset, formattedText));
// eslint-disable-next-line new-cap
const dmp = new DiffMatchPatch.diff_match_patch();
const changes = dmp.diff_main(text, formattedText);
let currentText = '';
for (const change of changes) {
const [type, value] = change;
if (type === DiffMatchPatch.DIFF_INSERT) {
editor.cm.dispatch({
changes: [
{
from: editor.posToOffset(getPosition(currentText)),
insert: value,
},
],
filter: false,
});
currentText += value;
} else if (type === DiffMatchPatch.DIFF_DELETE) {
const start = getPosition(currentText);
const end = getPosition(currentText + value);
editor.cm.dispatch({
changes: [
{
from: editor.posToOffset(start),
insert: '',
to: editor.posToOffset(end),
},
],
filter: false,
});
} else {
currentText += value;
}
}
}
}

View file

@ -1,6 +1,7 @@
{
"compilerOptions": {
"allowJs": true,
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
"importHelpers": true,
"inlineSourceMap": true,