From 857c98c345bd7013b57c5717a386b1b4f5232364 Mon Sep 17 00:00:00 2001 From: barraIhsan <57800056+barraIhsan@users.noreply.github.com> Date: Fri, 18 Oct 2024 17:19:48 +0700 Subject: [PATCH] feat: add vertical vim movement support This includes j to scroll down and k to scroll up, also supports Ctrl-D and Ctrl-U for even faster scroll, and support gg (scroll to top) and G (scroll to bottom) Yes, by supporting Ctrl-D and Ctrl-U, will prevent the browser defaults. Ctrl-U by default view the page source (tested on chromium & firefox) and Ctrl-D by default bookmark the page. I want this to be disabled by default, and can be enabled by the user (will be notified if the user hasn't set one) using the `:set` command, and store it into `localStorage`. So a TODO for me ig This was a lot of fun --- src/scripts/vim.ts | 64 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/src/scripts/vim.ts b/src/scripts/vim.ts index 7b8e0b3..7f189eb 100644 --- a/src/scripts/vim.ts +++ b/src/scripts/vim.ts @@ -1,16 +1,20 @@ let vimCmdActive: Boolean = false; let vimCmdAllowInput: Boolean = false; +let vimCmdWaitNextInput: Boolean = false; const vimCmdHidden: Array = ["hidden"]; +const vimCmdRight: Array = ["text-right"]; const vimCmdAllowInputClass: Array = ["after:content-['█']"]; const vimCmdError: Array = ["italic", "text-red-400"]; -function enterVimCmd(vimCmd: HTMLParagraphElement) { +function enterVimCmd(vimCmd: HTMLParagraphElement, readOnly: Boolean = false) { vimCmdActive = true; - vimCmdAllowInput = true; vimCmd.textContent = ""; - vimCmd.classList.remove(...vimCmdHidden, ...vimCmdError); - vimCmd.classList.add(...vimCmdAllowInputClass); + vimCmd.classList.remove(...vimCmdHidden, ...vimCmdError, ...vimCmdRight); + if (!readOnly) { + vimCmdAllowInput = true; + vimCmd.classList.add(...vimCmdAllowInputClass); + } } function exitVimCmd(vimCmd: HTMLParagraphElement) { @@ -30,8 +34,15 @@ function echoVimCmd( text: string, error: Boolean, vimCmd: HTMLParagraphElement, + right: Boolean = false, ) { + // enter vim cmd mode with readonly + // just incase it's not active + // and clear the right text and error + enterVimCmd(vimCmd, true); + if (error) vimCmd.classList.add(...vimCmdError); + if (right) vimCmd.classList.add(...vimCmdRight); vimCmd.innerHTML = text; // using `innerHTML` here, so we can put links / html tag vimCmdAllowInput = false; vimCmd.classList.remove(...vimCmdAllowInputClass); @@ -166,14 +177,57 @@ function executeVimCmd(vimCmd: HTMLParagraphElement) { } } +function scroll(amount: number) { + // if the amount is 0, it will scroll to the top + // else, it will "add" to the scroll + window.scrollTo({ + top: parseInt(`${amount == 0 ? 0 : window.scrollY + amount}`), + behavior: "smooth", + }); +} + const vimCmd = document.querySelector("#vimCmd") as HTMLParagraphElement; window.addEventListener("keydown", (e) => { - if ((!vimCmdActive || !vimCmdAllowInput) && e.key == ":") enterVimCmd(vimCmd); + // vim cmd + if (!vimCmdActive || !vimCmdAllowInput) { + if (e.key == ":") enterVimCmd(vimCmd); + + // pressing g once, will display `g` in the bottom right + // pressing g again, will clear the displayed `g` and scroll to the top + // if g is pressed once and not pressed again, + // will clear the displayed `g` and do nothing + if (!vimCmdWaitNextInput) { + if (e.key == "g") { + vimCmdWaitNextInput = true; + echoVimCmd("g", false, vimCmd, true); + } + } else { + // if the next key is g again + if (e.key == "g") scroll(0); + + exitVimCmd(vimCmd); + vimCmdWaitNextInput = false; + } + } if (vimCmdActive || !vimCmdAllowInput) { if (e.key == "Escape" || (e.key == "[" && e.ctrlKey)) { exitVimCmd(vimCmd); } + // vim motion + else if (e.key == "j") { + scroll(200); + } else if (e.key == "k") { + scroll(-200); + } else if (e.key == "d" && e.ctrlKey) { + scroll(700); + e.preventDefault(); + } else if (e.key == "u" && e.ctrlKey) { + scroll(-700); + e.preventDefault(); + } else if (e.key == "G") { + scroll(document.body.scrollHeight); + } } if (vimCmdActive && !vimCmdAllowInput) {