The Editor API provides a unified interface for working with the Obsidian editor, whether in Source mode or Live Preview. This guide covers the essential operations for reading and manipulating editor content.
Accessing the Editor
Get Active Editor
Access the current editor through the workspace:
import { Editor , MarkdownView , Plugin } from 'obsidian' ;
export default class EditorPlugin extends Plugin {
async onload () {
this . addCommand ({
id: 'example-editor-command' ,
name: 'Example editor command' ,
editorCallback : ( editor : Editor , view : MarkdownView ) => {
// Work with editor here
console . log ( editor . getValue ());
}
});
}
}
Get Editor from Active Leaf
const view = this . app . workspace . getActiveViewOfType ( MarkdownView );
if ( view ) {
const editor = view . editor ;
// Use editor
}
The editorCallback in commands automatically provides the editor when a markdown view is active.
Reading Content
Get All Content
// Get entire document
const content = editor . getValue ();
// Get line count
const lineCount = editor . lineCount ();
// Get last line number
const lastLine = editor . lastLine ();
Get Line Content
// Get specific line (0-indexed)
const line = editor . getLine ( 5 );
// Get line at cursor
const cursor = editor . getCursor ();
const currentLine = editor . getLine ( cursor . line );
Get Range Content
import { EditorPosition } from 'obsidian' ;
const from : EditorPosition = { line: 0 , ch: 0 };
const to : EditorPosition = { line: 5 , ch: 10 };
const rangeContent = editor . getRange ( from , to );
Get Selection
// Get selected text
const selection = editor . getSelection ();
// Check if anything is selected
if ( editor . somethingSelected ()) {
console . log ( 'Selection:' , editor . getSelection ());
}
// Get all selections (for multi-cursor)
const selections = editor . listSelections ();
selections . forEach ( sel => {
console . log ( 'From:' , sel . anchor , 'To:' , sel . head );
});
Writing Content
Replace Entire Content
editor . setValue ( 'New content for the entire document' );
Replacing all content will clear the undo history. Use carefully.
Replace Line
// Replace line 5
editor . setLine ( 5 , 'New line content' );
Replace Range
const from : EditorPosition = { line: 2 , ch: 0 };
const to : EditorPosition = { line: 2 , ch: 10 };
editor . replaceRange ( 'replacement text' , from , to );
Replace Selection
// Replace current selection
editor . replaceSelection ( 'New text' );
// Insert at cursor if nothing selected
if ( ! editor . somethingSelected ()) {
editor . replaceSelection ( 'Inserted text' );
}
Cursor and Selection Management
Get Cursor Position
// Get cursor position
const cursor = editor . getCursor ();
console . log ( `Line: ${ cursor . line } , Column: ${ cursor . ch } ` );
// Get cursor from selection start
const from = editor . getCursor ( 'from' );
const to = editor . getCursor ( 'to' );
Set Cursor Position
// Set cursor to position
editor . setCursor ({ line: 10 , ch: 5 });
// Alternative syntax
editor . setCursor ( 10 , 5 );
Set Selection
const anchor : EditorPosition = { line: 5 , ch: 0 };
const head : EditorPosition = { line: 5 , ch: 20 };
// Select range
editor . setSelection ( anchor , head );
// Select entire line
const lineLength = editor . getLine ( 5 ). length ;
editor . setSelection (
{ line: 5 , ch: 0 },
{ line: 5 , ch: lineLength }
);
Multiple Selections
import { EditorSelectionOrCaret } from 'obsidian' ;
const selections : EditorSelectionOrCaret [] = [
{ anchor: { line: 1 , ch: 0 }, head: { line: 1 , ch: 5 } },
{ anchor: { line: 3 , ch: 0 }, head: { line: 3 , ch: 5 } },
{ anchor: { line: 5 , ch: 0 }, head: { line: 5 , ch: 5 } }
];
editor . setSelections ( selections );
Position and Offset Conversion
Convert Position to Offset
const pos : EditorPosition = { line: 5 , ch: 10 };
const offset = editor . posToOffset ( pos );
console . log ( `Position ${ pos . line } : ${ pos . ch } is at offset ${ offset } ` );
Convert Offset to Position
const offset = 150 ;
const pos = editor . offsetToPos ( offset );
console . log ( `Offset ${ offset } is at line ${ pos . line } , column ${ pos . ch } ` );
Offsets are useful when working with regex matches or processing the entire document as a single string.
Editor Commands
Execute Built-in Commands
import { EditorCommandName } from 'obsidian' ;
// Available commands
editor . exec ( 'goUp' );
editor . exec ( 'goDown' );
editor . exec ( 'goLeft' );
editor . exec ( 'goRight' );
editor . exec ( 'goStart' );
editor . exec ( 'goEnd' );
editor . exec ( 'goWordLeft' );
editor . exec ( 'goWordRight' );
editor . exec ( 'indentMore' );
editor . exec ( 'indentLess' );
editor . exec ( 'newlineAndIndent' );
editor . exec ( 'swapLineUp' );
editor . exec ( 'swapLineDown' );
editor . exec ( 'deleteLine' );
editor . exec ( 'toggleFold' );
editor . exec ( 'foldAll' );
editor . exec ( 'unfoldAll' );
Editor Transactions
Perform multiple changes atomically:
import { EditorTransaction } from 'obsidian' ;
const transaction : EditorTransaction = {
changes: [
{
from: { line: 0 , ch: 0 },
to: { line: 0 , ch: 5 },
text: 'Hello'
},
{
from: { line: 2 , ch: 0 },
to: { line: 2 , ch: 10 },
text: 'World'
}
]
};
editor . transaction ( transaction );
Transaction with Selection
const transaction : EditorTransaction = {
changes: [
{
from: { line: 0 , ch: 0 },
text: '# '
}
],
selection: {
from: { line: 0 , ch: 2 }
}
};
editor . transaction ( transaction );
Working with Words
Get Word at Position
const cursor = editor . getCursor ();
const wordRange = editor . wordAt ( cursor );
if ( wordRange ) {
const word = editor . getRange ( wordRange . from , wordRange . to );
console . log ( 'Word at cursor:' , word );
}
const scrollInfo = editor . getScrollInfo ();
console . log ( 'Top:' , scrollInfo . top );
console . log ( 'Left:' , scrollInfo . left );
// Scroll to position
editor . scrollTo ( 0 , 100 );
// Scroll horizontally only
editor . scrollTo ( 50 , null );
// Scroll vertically only
editor . scrollTo ( null , 200 );
const range = {
from: { line: 10 , ch: 0 },
to: { line: 10 , ch: 20 }
};
// Scroll range into view
editor . scrollIntoView ( range );
// Center the range
editor . scrollIntoView ( range , true );
Process Lines
Process multiple lines efficiently:
editor . processLines (
// Read function
( lineNum , lineText ) => {
// Return value to pass to write function
if ( lineText . startsWith ( 'TODO:' )) {
return lineText . replace ( 'TODO:' , 'DONE:' );
}
return null ; // No changes
},
// Write function
( lineNum , lineText , value ) => {
if ( value ) {
return {
from: { line: lineNum , ch: 0 },
to: { line: lineNum , ch: lineText . length },
text: value
};
}
}
);
Focus Management
// Focus editor
editor . focus ();
// Remove focus
editor . blur ();
// Check if focused
if ( editor . hasFocus ()) {
console . log ( 'Editor is focused' );
}
Undo/Redo
// Undo last change
editor . undo ();
// Redo
editor . redo ();
Refresh Editor
// Force editor refresh
editor . refresh ();
Practical Examples
Insert Text at Cursor
function insertAtCursor ( editor : Editor , text : string ) {
const cursor = editor . getCursor ();
editor . replaceRange ( text , cursor );
editor . setCursor ({
line: cursor . line ,
ch: cursor . ch + text . length
});
}
insertAtCursor ( editor , '**bold text**' );
Wrap Selection
function wrapSelection ( editor : Editor , before : string , after : string ) {
const selection = editor . getSelection ();
const wrapped = before + selection + after ;
editor . replaceSelection ( wrapped );
}
wrapSelection ( editor , '**' , '**' ); // Bold
wrapSelection ( editor , '[[' , ']]' ); // Link
Toggle Line Prefix
function toggleLinePrefix ( editor : Editor , prefix : string ) {
const cursor = editor . getCursor ();
const line = editor . getLine ( cursor . line );
if ( line . startsWith ( prefix )) {
editor . setLine ( cursor . line , line . slice ( prefix . length ));
} else {
editor . setLine ( cursor . line , prefix + line );
}
}
toggleLinePrefix ( editor , '- [ ] ' ); // Toggle checkbox
Find and Replace
function findAndReplace ( editor : Editor , find : string , replace : string ) {
const content = editor . getValue ();
const newContent = content . replaceAll ( find , replace );
editor . setValue ( newContent );
}
findAndReplace ( editor , 'old text' , 'new text' );
Best Practices
Prefer editorCallback in commands for automatic editor access:
this . addCommand ({
id: 'my-command' ,
name: 'My Command' ,
editorCallback : ( editor , view ) => {
// Editor is already available
}
});
Batch Changes with Transactions
Use transactions for multiple changes to maintain undo history:
// Good: Single transaction
editor . transaction ({
changes: [ change1 , change2 , change3 ]
});
// Avoid: Multiple separate operations
editor . replaceRange ( ... );
editor . replaceRange ( ... );
editor . replaceRange ( ... );
Restore cursor after modifications:
const cursor = editor . getCursor ();
// Make changes
editor . setCursor ( cursor );
Working with Files Read and write files in the vault
Settings and Commands Create plugin commands