diff --git a/client/components/codeEditor/codeEditor.jsx b/client/components/codeEditor/codeEditor.jsx index 6c16b6f75..36bd23e57 100644 --- a/client/components/codeEditor/codeEditor.jsx +++ b/client/components/codeEditor/codeEditor.jsx @@ -118,16 +118,45 @@ const CodeEditor = forwardRef( const docsRef = useRef({}); const prevTabRef = useRef(tab); + // page map + const pageBreaksRef = useRef([]); + + const recomputePages = (doc)=>{ + const pages = [0]; + const text = doc.toString(); + let offset = 0; + + for (const line of text.split('\n')) { + if(/^(?=\\page(?:break)?(?: *{[^\n{}]*})?$)/m.test(line)) { + pages.push(offset); + } + offset += line.length + 1; + } + + pageBreaksRef.current = pages; + }; + + const findPageFromPos = (pos)=>{ + const pages = pageBreaksRef.current; + let page = 1; + + for (let i = 1; i < pages.length; i++) { + if(pos >= pages[i]) page = i + 1; + } + + return page; + }; + const createExtensions = ({ onChange, language, editorTheme })=>{ const setEventListeners = EditorView.updateListener.of((update)=>{ if(update.docChanged) { + recomputePages(update.state.doc); // CHANGED (added) onChange(update.state.doc.toString()); } if(update.selectionSet) { const pos = update.state.selection.main.head; - const line = update.state.doc.lineAt(pos).number; - - onCursorChange(line); + const page = findPageFromPos(pos); + onCursorChange(page); } }); @@ -184,6 +213,8 @@ const CodeEditor = forwardRef( extensions : createExtensions({ onChange, language, editorTheme }), }); + recomputePages(state.doc); + viewRef.current = new EditorView({ state, parent : editorRef.current, @@ -198,14 +229,12 @@ const CodeEditor = forwardRef( ticking = true; requestAnimationFrame(()=>{ - const view = viewRef.current; - if(!view?.scrollDOM) return; - const top = view.scrollDOM.scrollTop; const block = view.lineBlockAtHeight(top); - const line = view.state.doc.lineAt(block.from).number; - onViewChange(line); + const page = findPageFromPos(block.from); // CHANGED + onViewChange(page); + ticking = false; }); }; @@ -320,14 +349,24 @@ const CodeEditor = forwardRef( viewRef.current.scrollDOM.scrollTo({ top: y }); }, - getLineTop : (lineNumber)=>{ + scrollToPage : (pageNumber, smooth = true)=>{ + const view = viewRef.current; + if(!view) return; + + const pos = pageBreaksRef.current[pageNumber - 1] ?? 0; + + view.dispatch({ + effects : EditorView.scrollIntoView(pos, { y: 'start' }) + }); + + }, + getPagePos : (pageNumber)=>{ const view = viewRef.current; if(!view) return 0; - const line = view.state.doc.line(lineNumber); - return view.coordsAtPos(line.from)?.top ?? 0; + const pos = pageBreaksRef.current[pageNumber - 1] ?? 0; + return pos; }, - setCursorToLine : (lineNumber)=>{ const view = viewRef.current; const line = view.state.doc.line(lineNumber); diff --git a/client/homebrew/editor/editor.jsx b/client/homebrew/editor/editor.jsx index e0781054f..5fb53b8d6 100644 --- a/client/homebrew/editor/editor.jsx +++ b/client/homebrew/editor/editor.jsx @@ -141,18 +141,12 @@ const Editor = createReactClass({ } }, - updateCurrentCursorPage : function(lineNumber) { - const lines = this.props.brew.text.split('\n').slice(0, lineNumber); - const pageRegex = this.props.brew.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/; - const currentPage = lines.reduce((count, line)=>count + (pageRegex.test(line) ? 1 : 0), 1); - this.props.onCursorPageChange(currentPage); + updateCurrentCursorPage : function(pageNumber) { + this.props.onCursorPageChange(pageNumber); }, - updateCurrentViewPage : function(topLine) { - const lines = this.props.brew.text.split('\n').slice(0, topLine); - const pageRegex = this.props.brew.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/; - const currentPage = lines.reduce((count, line)=>count + (pageRegex.test(line) ? 1 : 0), 1); - this.props.onViewPageChange(currentPage); + updateCurrentViewPage : function(pageNumber) { + this.props.onViewPageChange(pageNumber); }, handleInject : function(injectText){ @@ -214,43 +208,14 @@ const Editor = createReactClass({ if(!this.isText() || isJumping) return; - const textSplit = this.props.renderer == 'V3' ? PAGEBREAK_REGEX_V3 : /\\page/; - const textString = this.props.brew.text.split(textSplit).slice(0, targetPage-1).join(textSplit); - const targetLine = textString.match('\n') ? textString.split('\n').length : 1; - const editor = this.codeEditor.current; + if(!editor) return; - let currentY = editor.getScrollTop(); - const targetY = editor.getLineTop(targetLine); - - let scrollingTimeout; - const checkIfScrollComplete = ()=>{ // Prevent interrupting a scroll in progress if user clicks multiple times - clearTimeout(scrollingTimeout); // Reset the timer every time a scroll event occurs - scrollingTimeout = setTimeout(()=>{ - isJumping = false; - }, 150); // If 150 ms pass without a scroll event, assume scrolling is done - }; - - isJumping = true; - checkIfScrollComplete(); - - if(smooth) { - //Scroll 1/10 of the way every 10ms until 1px off. - const incrementalScroll = setInterval(()=>{ - currentY += (targetY - currentY) / 10; - editor.scrollToY(currentY); - - if(Math.abs(targetY - currentY) < 1) { - editor.scrollToY(targetY); - editor.setCursorToLine(targetLine); - clearInterval(incrementalScroll); - } - }, 10); - } else { - editor.scrollToY(targetY); - editor.setCursorToLine(targetLine); - } + editor.scrollToPage(targetPage); + const pos = editor.getPagePos(targetPage); + editor.setCursorToPos?.(pos); }, + //Called when there are changes to the editor's dimensions update : function(){}, @@ -276,8 +241,8 @@ const Editor = createReactClass({ view={this.state.view} value={this.props.brew.text} onChange={this.props.onBrewChange('text')} - onCursorChange={(line)=>this.updateCurrentCursorPage(line)} - onViewChange={(line)=>this.updateCurrentViewPage(line)} + onCursorChange={(page)=>this.updateCurrentCursorPage(page)} + onViewChange={(page)=>this.updateCurrentViewPage(page)} editorTheme={this.state.editorTheme} renderer={this.props.brew.renderer} rerenderParent={this.rerenderParent} @@ -348,9 +313,9 @@ const Editor = createReactClass({ return this.codeEditor.current?.undo(); }, -foldCode: function() { - return this.codeEditor.current?.foldAll(); -}, + foldCode: function() { + return this.codeEditor.current?.foldAll(); + }, unfoldCode : function() { return this.codeEditor.current?.unfoldAll();