@@ -14,7 +14,12 @@ import {
1414
1515import { indentUnit } from "@codemirror/language" ;
1616import { search } from "@codemirror/search" ;
17- import { Compartment , EditorState , Prec , StateEffect } from "@codemirror/state" ;
17+ import {
18+ Compartment ,
19+ EditorState ,
20+ Prec ,
21+ StateEffect ,
22+ } from "@codemirror/state" ;
1823import { oneDark } from "@codemirror/theme-one-dark" ;
1924import {
2025 EditorView ,
@@ -139,6 +144,23 @@ async function EditorManager($header, $body) {
139144 ".cm-scroller" : { height : "100%" , overflow : "auto" } ,
140145 } ) ;
141146
147+ const pointerCursorVisibilityExtension = EditorView . updateListener . of (
148+ ( update ) => {
149+ if ( ! update . selectionSet ) return ;
150+ const pointerTriggered = update . transactions . some ( ( tr ) =>
151+ tr . isUserEvent ( "pointer" ) ||
152+ tr . isUserEvent ( "select.pointer" ) ||
153+ tr . isUserEvent ( "touch" ) ||
154+ tr . isUserEvent ( "select.touch" ) ,
155+ ) ;
156+ if ( ! pointerTriggered ) return ;
157+ if ( isCursorVisible ( ) ) return ;
158+ requestAnimationFrame ( ( ) => {
159+ if ( ! isCursorVisible ( ) ) scrollCursorIntoView ( { behavior : "instant" } ) ;
160+ } ) ;
161+ } ,
162+ ) ;
163+
142164 // Compartment to swap editor theme dynamically
143165 const themeCompartment = new Compartment ( ) ;
144166 // Compartments to control indentation, tab width, and font styling dynamically
@@ -605,6 +627,7 @@ async function EditorManager($header, $body) {
605627 // Default theme
606628 themeCompartment . of ( oneDark ) ,
607629 fixedHeightTheme ,
630+ pointerCursorVisibilityExtension ,
608631 search ( ) ,
609632 // Ensure read-only can be toggled later via compartment
610633 readOnlyCompartment . of ( EditorState . readOnly . of ( false ) ) ,
@@ -889,6 +912,7 @@ async function EditorManager($header, $body) {
889912 // keep compartment in the state to allow dynamic theme changes later
890913 themeCompartment . of ( oneDark ) ,
891914 fixedHeightTheme ,
915+ pointerCursorVisibilityExtension ,
892916 search ( ) ,
893917 // Keep dynamic compartments across state swaps
894918 ...getBaseExtensionsFromOptions ( ) ,
@@ -1433,15 +1457,12 @@ async function EditorManager($header, $body) {
14331457 scroller ?. addEventListener ( "scroll" , handleEditorScroll , { passive : true } ) ;
14341458 handleEditorScroll ( ) ;
14351459
1436- // TODO: Implement focus event for CodeMirror
1437- // editor.on("focus", async () => {
1438- // const { activeFile } = manager;
1439- // activeFile.focused = true;
1440- // keyboardHandler.on("keyboardShow", scrollCursorIntoView);
1441- // if (isScrolling) return;
1442- // $hScrollbar.hide();
1443- // $vScrollbar.hide();
1444- // });
1460+ keyboardHandler . on ( "keyboardShowStart" , ( ) => {
1461+ requestAnimationFrame ( ( ) => {
1462+ scrollCursorIntoView ( { behavior : "instant" } ) ;
1463+ } ) ;
1464+ } ) ;
1465+ keyboardHandler . on ( "keyboardShow" , scrollCursorIntoView ) ;
14451466
14461467 // TODO: Implement blur event for CodeMirror
14471468 // editor.on("blur", async () => {
@@ -1547,34 +1568,54 @@ async function EditorManager($header, $body) {
15471568 /**
15481569 * Scrolls the cursor into view if it is not currently visible.
15491570 */
1550- // TODO: Implement cursor scrolling for CodeMirror
1551- function scrollCursorIntoView ( ) {
1552- // keyboardHandler.off("keyboardShow", scrollCursorIntoView);
1553- // if (isCursorVisible()) return;
1554- // const { teardropSize } = appSettings.value;
1555- // editor.renderer.scrollCursorIntoView();
1556- // editor.renderer.scrollBy(0, teardropSize + 10);
1557- // editor._emit("scroll-intoview");
1571+ function scrollCursorIntoView ( options = { } ) {
1572+ const view = editor ;
1573+ const scroller = view ?. scrollDOM ;
1574+ if ( ! view || ! scroller ) return ;
1575+
1576+ const { behavior = "smooth" } = options ;
1577+ const { head } = view . state . selection . main ;
1578+ const caret = view . coordsAtPos ( head ) ;
1579+ if ( ! caret ) return ;
1580+
1581+ const scrollerRect = scroller . getBoundingClientRect ( ) ;
1582+ const relativeTop = caret . top - scrollerRect . top + scroller . scrollTop ;
1583+ const relativeBottom =
1584+ caret . bottom - scrollerRect . top + scroller . scrollTop ;
1585+ const topMargin = 16 ;
1586+ const bottomMargin =
1587+ ( appSettings . value ?. teardropSize || 24 ) + 12 ;
1588+
1589+ const scrollTop = scroller . scrollTop ;
1590+ const visibleTop = scrollTop + topMargin ;
1591+ const visibleBottom = scrollTop + scroller . clientHeight - bottomMargin ;
1592+
1593+ if ( relativeTop < visibleTop ) {
1594+ const nextTop = Math . max ( relativeTop - topMargin , 0 ) ;
1595+ scroller . scrollTo ( { top : nextTop , behavior } ) ;
1596+ } else if ( relativeBottom > visibleBottom ) {
1597+ const delta = relativeBottom - visibleBottom ;
1598+ scroller . scrollTo ( { top : scrollTop + delta , behavior } ) ;
1599+ }
15581600 }
15591601
15601602 /**
1561- * Checks if the cursor is visible within the Ace editor .
1603+ * Checks if the cursor is visible within the CodeMirror viewport .
15621604 * @returns {boolean } - True if the cursor is visible, false otherwise.
15631605 */
1564- // TODO: Implement cursor visibility check for CodeMirror
15651606 function isCursorVisible ( ) {
1566- // const { editor, container } = manager ;
1567- // const { teardropSize } = appSettings.value ;
1568- // const cursorPos = editor.getCursorPosition() ;
1569- // const contentTop = container.getBoundingClientRect().top;
1570- // const contentBottom = contentTop + container.clientHeight ;
1571- // const cursorTop = editor.renderer.textToScreenCoordinates(
1572- // cursorPos.row,
1573- // cursorPos.column,
1574- // ).pageY ;
1575- // const cursorBottom = cursorTop + teardropSize + 10;
1576- // return cursorTop >= contentTop && cursorBottom <= contentBottom;
1577- return true ; // Placeholder
1607+ const view = editor ;
1608+ const scroller = view ?. scrollDOM ;
1609+ if ( ! view || ! scroller ) return true ;
1610+
1611+ const { head } = view . state . selection . main ;
1612+ const caret = view . coordsAtPos ( head ) ;
1613+ if ( ! caret ) return true ;
1614+
1615+ const scrollerRect = scroller . getBoundingClientRect ( ) ;
1616+ return (
1617+ caret . top >= scrollerRect . top && caret . bottom <= scrollerRect . bottom
1618+ ) ;
15781619 }
15791620
15801621 /**
0 commit comments