@@ -132,6 +132,8 @@ import {
132132} from '../lib/cm-wikilinks'
133133import { linkRangeAtCursor , markdownLinkAt } from '../lib/internal-links'
134134import { setBlockType , toggleWrap , wrapLink } from '../lib/cm-format'
135+ import { shouldShowSelectionToolbar } from '../lib/cm-selection-toolbar'
136+ import { editorCursorPosition } from '../lib/editor-cursor-position'
135137import { EditorSelectionToolbar } from './EditorSelectionToolbar'
136138import { appMarkdownSnippetExtension } from '../lib/markdown-snippets-config'
137139import { LazyDiagramTabView , LazyPreview as Preview } from './LazyPreview'
@@ -197,7 +199,7 @@ import {
197199 recallTabScroll ,
198200 type TabScrollPosition
199201} from '../lib/tab-scroll-memory'
200- import { parseOutline } from '../lib/outline'
202+ import { activeOutlineLineForCursor , parseOutline } from '../lib/outline'
201203import {
202204 findRenderedHeadingForOutlineLine ,
203205 nextOutlinePreviewSyncLockUntil ,
@@ -750,6 +752,7 @@ function selectionEdgeCoords(view: EditorView): {
750752}
751753
752754function getSelectionCommentAction ( view : EditorView ) : SelectionCommentAction {
755+ if ( ! shouldShowSelectionToolbar ( view , useStore . getState ( ) . vimMode ) ) return null
753756 const sel = view . state . selection . main
754757 const active = document . activeElement
755758 // Keep the toolbar up while the editor holds the selection OR while the user
@@ -842,6 +845,9 @@ export function EditorPane({ pane }: { pane: PaneLeaf }): JSX.Element {
842845 const setActiveCommentId = useStore ( ( s ) => s . setActiveCommentId )
843846
844847 const setEditorViewRef = useStore ( ( s ) => s . setEditorViewRef )
848+ const activeEditorCursorPosition = useStore ( ( s ) =>
849+ isActive ? s . editorCursorPosition : null
850+ )
845851 const sidebarOpen = useStore ( ( s ) => s . sidebarOpen )
846852 const zenMode = useStore ( ( s ) => s . zenMode )
847853 const toggleSidebar = useStore ( ( s ) => s . toggleSidebar )
@@ -1627,23 +1633,8 @@ export function EditorPane({ pane }: { pane: PaneLeaf }): JSX.Element {
16271633 setActiveOutlineLineSafely ( null )
16281634 return
16291635 }
1630- // Probe ~25% down the viewport (capped) so a heading is considered
1631- // active once it scrolls into the upper portion of the visible area
1632- // — not only after it has scrolled past the very top edge.
1633- const rect = view . scrollDOM . getBoundingClientRect ( )
1634- const probeY = rect . top + Math . min ( 140 , rect . height * 0.25 )
1635- const pos = view . posAtCoords ( { x : rect . left + 8 , y : probeY } )
1636- if ( pos == null ) {
1637- setActiveOutlineLineSafely ( null )
1638- return
1639- }
1640- const probeLine = view . state . doc . lineAt ( pos ) . number
1641- let activeLine : number | null = null
1642- for ( const item of outlineItems ) {
1643- if ( item . line <= probeLine ) activeLine = item . line
1644- else break
1645- }
1646- setActiveOutlineLineSafely ( activeLine )
1636+ const cursorLine = view . state . doc . lineAt ( view . state . selection . main . head ) . number
1637+ setActiveOutlineLineSafely ( activeOutlineLineForCursor ( outlineItems , cursorLine ) )
16471638 } , [ outlineItems , setActiveOutlineLineSafely ] )
16481639
16491640 const computeActiveFromPreview = useCallback ( ( ) => {
@@ -1974,6 +1965,12 @@ export function EditorPane({ pane }: { pane: PaneLeaf }): JSX.Element {
19741965 if ( upd . viewportChanged || upd . geometryChanged ) {
19751966 schedulePreviewSyncFromEditorViewport ( )
19761967 }
1968+ if (
1969+ ( upd . selectionSet || upd . docChanged ) &&
1970+ useStore . getState ( ) . editorViewRef === upd . view
1971+ ) {
1972+ useStore . getState ( ) . setEditorCursorPosition ( editorCursorPosition ( upd . state ) )
1973+ }
19771974 if ( ! upd . docChanged ) return
19781975 if ( upd . transactions . some ( ( tr : Transaction ) => tr . annotation ( programmatic ) ) ) return
19791976 const path = viewPathRef . current
@@ -3488,10 +3485,8 @@ export function EditorPane({ pane }: { pane: PaneLeaf }): JSX.Element {
34883485 focusEditorNormalMode ( )
34893486 } , [ applyPaneMode , mode ] )
34903487
3491- // Track the topmost-visible heading and surface it as the active
3492- // outline item. We listen on whichever surface is the user's scroll
3493- // target for the current mode — split mode follows the editor since
3494- // that's where typing happens.
3488+ // Editing follows the cursor so keyboard motion updates the Outline even
3489+ // when the viewport barely moves. Preview mode remains scroll-driven.
34953490 useEffect ( ( ) => {
34963491 if ( ! outlineOpen ) {
34973492 setActiveOutlineLineSafely ( null )
@@ -3512,6 +3507,7 @@ export function EditorPane({ pane }: { pane: PaneLeaf }): JSX.Element {
35123507 }
35133508 }
35143509 } , [
3510+ activeEditorCursorPosition ?. line ,
35153511 computeActiveFromEditor ,
35163512 content ?. path ,
35173513 mode ,
0 commit comments