CodeMirror 6-based code file editor plugin for Obsidian. Opens registered file extensions in a CM6 editor view instead of Obsidian's default text view.
src/main.ts— Plugin entry point. Registers the view, settings tab, commands, and file menu items.src/view.ts—CodeEditorViewextendsTextFileView. Creates and manages the CM6EditorView. Handles file loading, saving, Ctrl+scroll zoom, and search hotkeys via ObsidianScope.src/extensions.ts— CM6 extension builder. Uses Compartments for dynamic reconfiguration of: language, line numbers, folding, wrap, font, theme, tab size, indent guides.src/settings.ts—PluginSettingsinterface, defaults, theme options map, and settings tab usinggetSettingDefinitions()(Obsidian 1.13+ declarative API).src/languages.ts— Maps file extensions to CM6 language support. Includes modern language packages and legacy stream-based languages (shell, ruby, lua, toml, r, powershell, dockerfile, swift, csharp).src/folder-suggest.ts—FolderSuggestextendingAbstractInputSuggestfor fuzzy folder search in settings.src/create-modal.ts— Modal for creating new code files with extension dropdown.src/rename-modal.ts— Modal for renaming a file including its extension (Obsidian's inline title and explorer rename only edit the basename). Reopens affected leaves when the new extension belongs to a different view.styles.css— Obsidian CSS variable mappings for.tok-*syntax classes (used when theme is "Obsidian default").
- Compartments allow live-updating settings without rebuilding editor state. Each dynamic setting has a compartment in
EditorCompartments, a function that returns anExtension, and is wired into bothbuildExtensions()andapplySettings(). - Theme switching: when theme is
''(Obsidian default), the theme compartment containssyntaxHighlighting(classHighlighter)which applies.tok-*CSS classes styled bystyles.css. When a CM6 theme is selected,classHighlighteris swapped out and the theme extension takes over. - Settings tab uses
getSettingDefinitions()(notdisplay()). Custom controls like folder suggest useSettingDefinitionRenderwith arendercallback. - Search hotkeys (Cmd+F, Cmd+G, F3) use
this.scope = new Scope(this.app.scope)on the view, which overrides Obsidian's default hotkeys (e.g. graph view's Cmd+G) when the code editor is active. - Tab indentation uses a custom Tab/Shift-Tab keybinding that reads
EditorState.tabSize(shared facet from@codemirror/state) instead ofindentWithTabfrom@codemirror/commands, becauseindentWithTabreadsindentUnitfrom Obsidian's@codemirror/language— a different facet instance than the one bundled in this plugin. - Tab title vs. header title:
getDisplayText()returns the full file name for the tab, butsetState()resets the view header title (titleEl) tofile.basename. Obsidian's inline title rename appends the extension to the typed text, so a full name there producesa.md.txt. - Delete handling:
FileView.onDeleteswaps in the empty view vialeaf.open(null), which does not refresh the tab header.CodeEditorView.onDeletecallssetViewState({ type: 'empty' })afterwards to force the update. - Extension registration:
plugin.registerExtension()tracks what this plugin claimed inregisteredExtensions. Startup registers every extension in settings. The rename modal registers any added since then when they are used. - Internal APIs (declared via
declare module 'obsidian'augmentation, not in the public typings):FileView.titleEl,FileView.onDelete(view.ts);App.viewRegistry.isExtensionRegistered,App.setting.open/openTabById(rename-modal.ts, both optional with fallbacks). Recheck these after Obsidian updates. @codemirror/languagemust NOT be external in esbuild. It is intentionally bundled because legacy stream languages (shell, ruby, lua, toml, etc.) useStreamLanguagewhich Obsidian's runtime may not expose. The tradeoff is a splitindentUnitfacet, which is why Tab uses the custom keybinding above.
npm run build # tsc + esbuild
npm run dev # esbuild watch mode
npm run lint # eslint with eslint-plugin-obsidianmd@uiw/codemirror-themes-all— 45+ CM6 syntax themes (requires@babel/runtime)@replit/codemirror-indentation-markers— indent guide lines@codemirror/stateand@codemirror/vieware pinned viaoverridesinpackage.json
- Uses Obsidian CSS variables for all styling (rule 32)
- Uses
AbstractInputSuggestfor folder suggest (rule 26) - Uses
registerEvent()/registerDomEvent()for cleanup (rule 6) - Settings use sentence case (rule 11)
- No default hotkeys (rule 16)
- Uses
normalizePath()for user paths (rule 22)