Created
September 30, 2025 12:54
-
-
Save lynn/18c1f4dc41749054380fb1ab72046b73 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Ctrl+E to End | |
| // @version 1.0.0 | |
| // @description Make Ctrl+E move the caret to the end of the line in input and textarea fields (Emacs-style) | |
| // @match https://*.github.com/* | |
| // @grant none | |
| // @run-at document-start | |
| // ==/UserScript== | |
| (function () { | |
| "use strict"; | |
| /** | |
| * Compute end index of current line within a string, given a cursor index. | |
| * Handles both \n and \r\n inputs gracefully. | |
| */ | |
| function getEndOfCurrentLine(text, cursorIndex) { | |
| const len = text.length; | |
| if (len === 0) return 0; | |
| const after = text.indexOf('\n', cursorIndex); | |
| if (after === -1) return len; | |
| // End-of-line is the character just before the newline. If that char is \r, stop before it. | |
| const eol = after; | |
| if (eol > 0 && text[eol - 1] === '\r') return eol - 1; | |
| return eol; | |
| } | |
| /** | |
| * Move caret to end of content (inputs) or end of current line (textareas). | |
| */ | |
| function moveCaretToEnd(element) { | |
| try { | |
| if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) { | |
| if (element.readOnly || element.disabled) return; | |
| // focus first to ensure selection can be changed | |
| if (document.activeElement !== element) element.focus(); | |
| const text = element.value ?? ""; | |
| if (element instanceof HTMLTextAreaElement) { | |
| const cursor = typeof element.selectionStart === 'number' ? element.selectionStart : text.length; | |
| const endOfLine = getEndOfCurrentLine(text, cursor); | |
| element.setSelectionRange(endOfLine, endOfLine); | |
| return; | |
| } | |
| // Single-line input types: go to absolute end | |
| const end = text.length; | |
| if (typeof element.setSelectionRange === "function") { | |
| element.setSelectionRange(end, end); | |
| } else { | |
| const prev = element.value; | |
| element.value = prev; | |
| } | |
| } | |
| } catch (_) { | |
| // Best-effort only; ignore errors for unsupported input types | |
| } | |
| } | |
| /** | |
| * Key handler: intercept Ctrl+E on inputs and textareas only. | |
| */ | |
| function onKeyDown(event) { | |
| // Only care about Ctrl+E without other modifiers | |
| if (!event.ctrlKey || event.shiftKey || event.altKey || event.metaKey) return; | |
| // Prefer standardized 'key' comparison. Some layouts may report 'E'/'e' | |
| const key = event.key; | |
| if (!(key === "e" || key === "E")) return; | |
| const target = event.target; | |
| if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement)) return; | |
| // Let content-editable and code editors handle their own bindings by narrowing scope | |
| // Only act for common text-like inputs | |
| const input = target; | |
| const type = (input instanceof HTMLInputElement) ? (input.type || "text").toLowerCase() : "textarea"; | |
| const nonTextTypes = new Set(["button", "submit", "reset", "checkbox", "radio", "range", "color", "file", "image", "hidden"]); | |
| if (input instanceof HTMLInputElement && nonTextTypes.has(type)) return; | |
| // Prevent default browser/website handlers and stop propagation | |
| event.preventDefault(); | |
| event.stopPropagation(); | |
| moveCaretToEnd(input); | |
| } | |
| // Capture phase to win over site scripts that listen in bubbling phase | |
| document.addEventListener("keydown", onKeyDown, true); | |
| })(); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment