0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-05-09 20:38:40 +00:00

safe jumps 2

This commit is contained in:
Víctor Losada Hernández
2026-04-13 00:59:33 +02:00
parent 40246d8493
commit 95c0b409b9
2 changed files with 65 additions and 61 deletions
+51 -12
View File
@@ -118,16 +118,45 @@ const CodeEditor = forwardRef(
const docsRef = useRef({}); const docsRef = useRef({});
const prevTabRef = useRef(tab); 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 createExtensions = ({ onChange, language, editorTheme })=>{
const setEventListeners = EditorView.updateListener.of((update)=>{ const setEventListeners = EditorView.updateListener.of((update)=>{
if(update.docChanged) { if(update.docChanged) {
recomputePages(update.state.doc); // CHANGED (added)
onChange(update.state.doc.toString()); onChange(update.state.doc.toString());
} }
if(update.selectionSet) { if(update.selectionSet) {
const pos = update.state.selection.main.head; const pos = update.state.selection.main.head;
const line = update.state.doc.lineAt(pos).number; const page = findPageFromPos(pos);
onCursorChange(page);
onCursorChange(line);
} }
}); });
@@ -184,6 +213,8 @@ const CodeEditor = forwardRef(
extensions : createExtensions({ onChange, language, editorTheme }), extensions : createExtensions({ onChange, language, editorTheme }),
}); });
recomputePages(state.doc);
viewRef.current = new EditorView({ viewRef.current = new EditorView({
state, state,
parent : editorRef.current, parent : editorRef.current,
@@ -198,14 +229,12 @@ const CodeEditor = forwardRef(
ticking = true; ticking = true;
requestAnimationFrame(()=>{ requestAnimationFrame(()=>{
const view = viewRef.current;
if(!view?.scrollDOM) return;
const top = view.scrollDOM.scrollTop; const top = view.scrollDOM.scrollTop;
const block = view.lineBlockAtHeight(top); 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; ticking = false;
}); });
}; };
@@ -320,14 +349,24 @@ const CodeEditor = forwardRef(
viewRef.current.scrollDOM.scrollTo({ top: y }); 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; const view = viewRef.current;
if(!view) return 0; if(!view) return 0;
const line = view.state.doc.line(lineNumber); const pos = pageBreaksRef.current[pageNumber - 1] ?? 0;
return view.coordsAtPos(line.from)?.top ?? 0; return pos;
}, },
setCursorToLine : (lineNumber)=>{ setCursorToLine : (lineNumber)=>{
const view = viewRef.current; const view = viewRef.current;
const line = view.state.doc.line(lineNumber); const line = view.state.doc.line(lineNumber);
+14 -49
View File
@@ -141,18 +141,12 @@ const Editor = createReactClass({
} }
}, },
updateCurrentCursorPage : function(lineNumber) { updateCurrentCursorPage : function(pageNumber) {
const lines = this.props.brew.text.split('\n').slice(0, lineNumber); this.props.onCursorPageChange(pageNumber);
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);
}, },
updateCurrentViewPage : function(topLine) { updateCurrentViewPage : function(pageNumber) {
const lines = this.props.brew.text.split('\n').slice(0, topLine); this.props.onViewPageChange(pageNumber);
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);
}, },
handleInject : function(injectText){ handleInject : function(injectText){
@@ -214,43 +208,14 @@ const Editor = createReactClass({
if(!this.isText() || isJumping) if(!this.isText() || isJumping)
return; 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; const editor = this.codeEditor.current;
if(!editor) return;
let currentY = editor.getScrollTop(); editor.scrollToPage(targetPage);
const targetY = editor.getLineTop(targetLine); const pos = editor.getPagePos(targetPage);
editor.setCursorToPos?.(pos);
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);
}
}, },
//Called when there are changes to the editor's dimensions //Called when there are changes to the editor's dimensions
update : function(){}, update : function(){},
@@ -276,8 +241,8 @@ const Editor = createReactClass({
view={this.state.view} view={this.state.view}
value={this.props.brew.text} value={this.props.brew.text}
onChange={this.props.onBrewChange('text')} onChange={this.props.onBrewChange('text')}
onCursorChange={(line)=>this.updateCurrentCursorPage(line)} onCursorChange={(page)=>this.updateCurrentCursorPage(page)}
onViewChange={(line)=>this.updateCurrentViewPage(line)} onViewChange={(page)=>this.updateCurrentViewPage(page)}
editorTheme={this.state.editorTheme} editorTheme={this.state.editorTheme}
renderer={this.props.brew.renderer} renderer={this.props.brew.renderer}
rerenderParent={this.rerenderParent} rerenderParent={this.rerenderParent}
@@ -348,9 +313,9 @@ const Editor = createReactClass({
return this.codeEditor.current?.undo(); return this.codeEditor.current?.undo();
}, },
foldCode: function() { foldCode: function() {
return this.codeEditor.current?.foldAll(); return this.codeEditor.current?.foldAll();
}, },
unfoldCode : function() { unfoldCode : function() {
return this.codeEditor.current?.unfoldAll(); return this.codeEditor.current?.unfoldAll();