import { Editor, EditorRange, EditorSelection } from 'obsidian' // ============================================================ // Selection Utilities // ============================================================ /** * Get the main (primary) selection from the editor */ export function getMainSelection(editor: Editor): EditorSelection { return { anchor: editor.getCursor('anchor'), head: editor.getCursor('head'), } } /** * Convert a selection to a range (from/to with sorted positions) */ export function selectionToRange(selection: EditorSelection): EditorRange { const sortedPositions = [selection.anchor, selection.head].sort((a, b) => { if (a.line !== b.line) return a.line - b.line return a.ch - b.ch }) return { from: sortedPositions[0], to: sortedPositions[1], } } /** * Convert a selection to encompass full lines */ export function selectionToLine(editor: Editor, selection: EditorSelection): EditorSelection { const range = selectionToRange(selection) return { anchor: { line: range.from.line, ch: 0 }, head: { line: range.to.line, ch: editor.getLine(range.to.line).length }, } } // ============================================================ // Text Utilities // ============================================================ /** * Get leading whitespace (indentation) from a line */ export function getLeadingWhitespace(lineContent: string): string { const indentation = lineContent.match(/^\s+/) return indentation ? indentation[0] : '' } /** * Check if a character is a letter or digit */ export function isLetterOrDigit(char: string): boolean { return /\p{L}\p{M}*/u.test(char) || /\d/.test(char) } /** * Get the word range at a given position */ export function wordRangeAtPos( pos: { line: number; ch: number }, lineContent: string ): { anchor: { line: number; ch: number }; head: { line: number; ch: number } } { let start = pos.ch let end = pos.ch while (start > 0 && isLetterOrDigit(lineContent.charAt(start - 1))) start-- while (end < lineContent.length && isLetterOrDigit(lineContent.charAt(end))) end++ return { anchor: { line: pos.line, ch: start }, head: { line: pos.line, ch: end } } } // ============================================================ // Case Transformation Utilities // ============================================================ const LOWERCASE_ARTICLES = ['the', 'a', 'an'] /** * Convert text to title case */ export function toTitleCase(text: string): string { return text .split(/(\s+)/) .map((w, i, all) => { if (i > 0 && i < all.length - 1 && LOWERCASE_ARTICLES.includes(w.toLowerCase())) { return w.toLowerCase() } return w.charAt(0).toUpperCase() + w.slice(1).toLowerCase() }) .join(' ') }