Skip to content

Instantly share code, notes, and snippets.

@lynn
Created September 30, 2025 12:54
Show Gist options
  • Select an option

  • Save lynn/18c1f4dc41749054380fb1ab72046b73 to your computer and use it in GitHub Desktop.

Select an option

Save lynn/18c1f4dc41749054380fb1ab72046b73 to your computer and use it in GitHub Desktop.
// ==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