{"id":24527717,"url":"https://github.com/jdtsmith/ultra-scroll","last_synced_at":"2025-06-14T10:35:10.648Z","repository":{"id":209949411,"uuid":"725337512","full_name":"jdtsmith/ultra-scroll","owner":"jdtsmith","description":"scroll Emacs like lightning","archived":false,"fork":false,"pushed_at":"2025-03-10T22:37:32.000Z","size":210,"stargazers_count":272,"open_issues_count":4,"forks_count":5,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-03-10T23:29:00.733Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Emacs Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jdtsmith.png","metadata":{"files":{"readme":"README.md","changelog":"NEWS.org","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-11-29T23:44:05.000Z","updated_at":"2025-03-10T22:37:36.000Z","dependencies_parsed_at":"2024-02-18T23:32:39.288Z","dependency_job_id":"cc17ad73-579b-45b5-b335-9e6617c28c14","html_url":"https://github.com/jdtsmith/ultra-scroll","commit_stats":null,"previous_names":["jdtsmith/ultra-scroll-mac"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jdtsmith%2Fultra-scroll","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jdtsmith%2Fultra-scroll/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jdtsmith%2Fultra-scroll/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jdtsmith%2Fultra-scroll/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jdtsmith","download_url":"https://codeload.github.com/jdtsmith/ultra-scroll/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243760551,"owners_count":20343650,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-01-22T06:24:18.547Z","updated_at":"2025-06-14T10:35:10.641Z","avatar_url":"https://github.com/jdtsmith.png","language":"Emacs Lisp","funding_links":[],"categories":["Emacs Lisp"],"sub_categories":[],"readme":"# ultra-scroll: scroll emacs like lightning ⚡🖱️⚡\n\n`ultra-scroll`[^1] is a smooth-scrolling package for emacs, with native\nsupport for standard builds as well as\n[emacs-mac](https://bitbucket.org/mituharu/emacs-mac). It provides\nhighly optimized, pixel-precise smooth scrolling which can readily keep\nup with the *very* high event rates of modern track-pads and\nhigh-precision wheel mice.\n\nYou move your fingers, the page responds, *instantly*:\n\n\u003chttps://github-production-user-asset-6210df.s3.amazonaws.com/93749/290018933-ed5cf414-eab5-4ba8-b077-30cac0c5ace0.mov\u003e\n\nImportantly, `ultra-scroll` can cleanly *scroll right across* tall\nimages and other jumbo lines – a perennial problem with scrolling\npackages to date. As a bonus, it enables relatively smooth scrolling\neven with dumb third party mice on some systems.\n\nNote, the `previous-buffer` animation above is from two-finger track-pad\nswiping, and is an `emacs-mac` exclusive.\n\n\u003e [!NOTE]\n\u003e **Do you need this?**\n\u003e\n\u003e If you don't scroll with a high-speed device (modern mouse or\n\u003e track-pad), no. If you do, but aren't sure, here's a good test to try:\n\u003e\n\u003e Open a heavy emacs buffer full screen on your largest monitor. While\n\u003e scrolling smoothly such that lines would move across your window's\n\u003e full height in about 5 seconds, *can you easily read the text you\n\u003e see*, without stopping, in both directions? Now, try this exercise\n\u003e again with your browser – I bet it's *very* readable there. Shouldn't\n\u003e emacs be like this?\n\u003e\n\u003e If you scroll buffers with tall images visible, this is also a good\n\u003e reason to give `ultra-scroll` a try.\n\n## Release Information\n\nSee the [NEWS](./NEWS.org).\n\n## Compatibility\n\n`ultra-scroll` should work across all systems that provide pixel-level\nscrolling information for your input hardware. If you don't think\n`ultra-scroll` is working for you, run `M-x ultra-scroll-check`, and\nfollow the directions. If it reports:\n\n- **Normal pixel scroll data**: you are good to go, everything is\n  working.\n- **No real pixel scroll data**: your system and hardware are delivering\n  pixel scrolling data, but they *never change*. This is equivalent to\n  line-by-line scrolling. `ultra-scroll` will work fine for you, but\n  *without* any smooth scrolling. You can use it for the improved large\n  image scrolling behavior, or see below for another option.\n- The *error* **Malformed wheel event**: your system does not deliver\n  *any* pixel-level scroll data. Either upgrade your hardware/system to\n  a known working config (see [this issue](../../issues/18) for user\n  experiences), or see below.\n\nFor systems which do *not* provide normal pixel scroll data, you can try\nthe built-in `pixel-scroll-precision-mode` with\n`pixel-scroll-precision-interpolate-mice` (which \"creates\" events by\ninterpolation) instead.\n\n\u003e [!IMPORTANT]\n\u003e **Your Help Needed!** While `ultra-scroll` works out of the box for\n\u003e most people, it's impossible to test all combinations of systems and\n\u003e hardware, so please take a moment to [report your smooth-scrolling\n\u003e experiences](../../issues/18) for the benefit of others.\n\n## Installation\n\nNot yet in a package archive. For Emacs 29, use `package-vc-install`. In\nthe `*scratch*` buffer, enter\n\n``` commonlisp\n(package-vc-install '(ultra-scroll :vc-backend Git :url  \"https://github.com/jdtsmith/ultra-scroll\"))\n```\n\nmove to the final paren, and `C-x C-e`. For Emacs 30, you can use the\nnew `:vc` keyword. Configuration is then simple:\n\n``` commonlisp\n(use-package ultra-scroll\n  ;:load-path \"~/code/emacs/ultra-scroll\" ; if you git cloned\n  ;:vc (:url \"https://github.com/jdtsmith/ultra-scroll\") ; For Emacs\u003e=30\n  :init\n  (setq scroll-conservatively 3 ; or whatever value you prefer, since v0.4\n        scroll-margin 0)        ; important: scroll-margin\u003e0 not yet supported\n  :config\n  (ultra-scroll-mode 1))\n```\n\n## Usage\n\nJust start scrolling :).\n\n\u003e [!TIP]\n\u003e For best performance, use a build with native-compilation (see\n\u003e [Speed](#Speed)).\n\n## Configuration\n\nThere is little to no configuration.\n\n### Altering dumb mice behavior on emacs-mac\n\nIf desired for use with dumb mice on `emacs-mac`, the variable\n`ultra-scroll-mac-multiplier` can be set to a number smaller or larger\nthan `1.0` to decrease/increase mouse-wheel scrolling speed. Note that\nmany fancier wheeled mice have drivers that *simulate* track-pads, so\nthis variable will have no effect on them. For these, and for track-pads\ngenerally, scrolling speed should be configured in system settings.\n\n\u003e [!NOTE]\n\u003e Only certain systems provide real variable pixel scroll offset data\n\u003e (`PIXEL-DELTA`) for older/wheeled (\"dumb\") mice. Use\n\u003e `M-x ultra-scroll-check` to see if yours does. If not, it's\n\u003e recommended to upgrade hardware, or stick with\n\u003e `pixel-scroll-precision-mode`.\n\n### Mitigating garbage collection pauses\n\nTo reduce the likelihood of garbage collection during scroll, which can\nintroduce slight pauses, the value of `gc-cons-percentage` is\ntemporarily increased, and reset during idle time. The defaults should\nwork well for most situations, but if necessary, can be configured using\n`ultra-scroll-gc-percentage` and `ultra-scroll-gc-idle-time`.\n\n### Hiding cursor and disabling other modes during scroll\n\nBy default, `ultra-scroll` hides the cursor (and a `hl-line` if active)\nonce it reaches the window edge, to prevent \"bouncing cursor\" behavior.\nThis can be disabled, or the time delay to restore the cursor set, with\n`ultra-scroll-hide-cursor`.\n\nIn addition to the cursor, it is sometimes useful to temporarily disable\nother modes during the scroll. The special hook variable\n`ultra-scroll-hide-functions` can be used for this, e.g.:\n\n``` commonlisp\n(add-hook 'ultra-scroll-hide-functions 'hl-line-mode)\n```\n\nBy default, the hook contains `hl-line-mode`.\n\n## `pixel-scroll-precision` comparison and interoperability\n\nEmacs has a built-in smooth scrolling system called\n`pixel-scroll-precision-mode`. In fact, by design, `ultra-scroll`\n*activates* the builtin `pixel-scroll-precision-mode`, remapping its\nscrolling function with its own. The latter also has the capability of\n*faking* smooth scrolling using interpolation. It can do this for\nnon-mouse movements, like `scroll-up/down-command` (usually on `PgUp` /\n`PgDown`). To use these additional capabilities, simply set the relevant\nvariables, like `pixel-scroll-precision-interpolate-page`, and they\nshould \"just work\".\n\nNote that `ultra-scroll` disables `pixel-scroll-precision-use-momentum`,\nsince it may not handle tall image scrolling well. Some systems (MacOS)\nget momentum scrolling \"for free\" from the OS, independent of this\nsetting. If you experiment with re-enabling\n`pixel-scroll-precision-use-momentum` on other systems like Linux,\nplease open an issue to report your findings.\n\n\u003e [!WARNING]\n\u003e `ultra-scroll` activates `pixel-scroll-precision-mode` by side effect.\n\u003e If you are experimenting with both modes during a single session,\n\u003e always disable `ultra-scroll-mode` first and then re-enable\n\u003e `pixel-scroll-precision-mode`.\n\n### A comparison between ultra-scroll and pixel-scroll-precision\n\nSee also [this\nquestion](#how-does-this-compare-to-the-built-in-smooth-scrolling).\n\n`pixel-scroll-precision-mode`:\n\n- Supports smooth scrolling even on systems which do *not* provide pixel\n  scroll data, using interpolation (see\n  `pixel-scroll-precision-interpolate-mice`).\n- Can simulate a \"momentum\" scrolling phase on systems which do not\n  provide this capability (see `pixel-scroll-precision-use-momentum`).\n- Has occasional issues scrolling tall images.\n\n`ultra-scroll`:\n\n- Fully supports *only* those system and hardware combos that deliver\n  *real* pixel scroll data (see [Compatibility](#Compatibility)).\n- Provides \"momentum\" scrolling only on systems which provide this\n  themselves.\n- Is somewhat faster (see [Speed](#Speed)).\n- Handles tall image scrolling without issue.\n\n## Related packages and functionality\n\nemacs-mac's own builtin `mac-mwheel-scroll`  \nThis venerable code was introduced with\n[emacs-mac](https://bitbucket.org/mituharu/emacs-mac/) more than a\ndecade ago, and was the first to provide smooth scrolling in emacs.\n\n`pixel-scroll-precision-mode`  \nA fast pixel scrolling by Po Lu, built in to Emacs as of v29.1 (see\n`pixel-scroll.el`). Does not support `emacs-mac`. `ultra-scroll` was\ninitially based on its design, but many design elements have changed.\n\n`pixel-scroll-mode`  \nA simpler line-by-line pixel scrolling mode, also found in the file\n`pixel-scroll.el`.\n\n[good-scroll](https://github.com/io12/good-scroll.el)  \nAn update to `pixel-scroll-mode` with variable speed.\n\n[sublimity](https://github.com/zk-phi/sublimity)  \nIncludes smooth scrolling based on sublime editor.\n\n## Questions\n\n### What was the motivation behind this?\n\nPicture it: a fast new laptop and 5K monitor with a large heavy-duty,\nfull-screen buffer in `python-ts-mode`. Scrolling line-by-line with a\ndecent mouse is mostly OK, but smooth pixel scrolling with the track-pad\nis just… *painful*. Repeated attempts to rationalize this fail,\nespecially because it's notably worse in one direction than the other.\nScrolling Emacs feels like moving through (light) molasses. *No bueno*.\n\nChecking into it, the smooth scroll event callback takes 15-20ms\nscrolling in one direction, and 3–5x longer in the other. This\nperformance is perfectly fine for normal mice which deliver a few\nscrolling events a second. *But track-pad and fancy mouse scroll events\nare arriving every 10ms, or less*! The code just couldn't keep up.\nHence: molasses.\n\nI also wanted to be able to scroll through image-rich documents without\nworrying about jumpy/loopy scrolling behavior. And my extra dumb mouse\ndidn't work well either: small scrolls did nothing: you'd have scroll\npretty aggressively to get any movement at all.\n\nHow hard could it be to fix this? And the adventure began…\n\n### Why was this initially for emacs-mac only?\n\nThis packaged used to be called `ultra-scroll-mac`. The `emacs-mac` port\nof emacs exposes pixel-level scrolling event stream of Mac track-pads\n(and other fancy mice) in a distinct way, which is not supported by\n`pixel-scroll-precision-mode`. And unfortunately the default\nsmooth-scrolling library included in `emacs-mac` is quite low\nperformance (see above).\n\n### How does this compare to the built-in smooth scrolling?\n\nOn the `emacs-mac` build, there is no comparison, because\n`pixel-scroll-precision-mode` doesn't work there. On other builds, they\nare fairly comparable. Compared to `pixel-scroll-precision-mode`,\n`ultra-scroll` obviously works with `emacs-mac`, but is also even\n[faster](#Speed), and can cleanly scroll past images taller than the\nwindow.\n\nIn addition to fast scrolling, the built-in\n`pixel-scroll-precision-mode` (new in Emacs v29.1) can simulate a\n*feature-complete track-pad driver* in elisp for older mice which do not\nsupply pixel scroll information. This comes complete with elisp-based\nscroll interpolation, a timer-based *momentum* phase, etc.\n\n### Why are there so many smooth scrolling modes? Why is this so hard? It's just *scrolling*…\n\nEmacs was designed long before mice were common, not to mention modern\nhigh-resolution track-pads and mice which send rapid micro-updates\n(\"move up one pixel!\") 60-120 times per second. Unlike other programs,\nEmacs *insists* on keeping the cursor (point) visible at all times. Deep\nin its re-display code, Emacs tracks where point is, and works\ndiligently to ensure it never falls outside the visible window. It does\nthis not by moving point (that's the user's job), but by moving the\n*window* (visible range of lines) surrounding point.\n\nOnce you are used to this behavior, it's actually pretty nice for\nnavigating with `C-n` / `C-p` and friends. But for smooth scrolling with\na track-pad or mouse, it is *very problematic* – nothing screams \"janky\nscrolling\" like the window lurching back or forth half a page during a\nscroll. Or worse: getting caught in an endless loop of\nscroll-in-one-direction/jump-back-in-the-other.\n\nSo what should be done? The elisp info manual (`Textual Scrolling` /\n`set-window-start`) helpfully mentions:\n\n\u003e …for reliable results Lisp programs that call this function should\n\u003e always move point to be inside the window whose display starts at\n\u003e POSITION.\n\nWhich is all well and good, but *where* do you find such a point, in\nadvance, safely *inside the window*? Often this isn't terribly hard, but\nthere is one common case where this admonition falls comically flat:\nscrolling past an image or other content which is *taller than the\nwindow* – what I call **jumbo lines**. Where can I place point *inside\nthe window* when a jumbo line occupies the entire window height?\n\nAs a result of these types of difficulties, pixel scrolling codes and\npackages are often quite involved, with much of the logic boiling down\nto a stalwart and increasingly heroic pile of interwoven attempts to\n*keep the damn point on screen* and prevent juddering and looping as you\nscroll.\n\n### What should I know about developing scrolling modes for Emacs?\n\nFor posterity, some things I discovered in my own mostly-victorious\nbattle against unwanted re-centering during smooth scroll, including\nacross jumbo lines:\n\n- `scroll-conservatively=101` is very helpful, since with this Emacs\n  will \"scroll just enough text to bring point into view, even if you\n  move far away\". It does not defeat re-centering, but makes it… more\n  manageable.\n- You cannot let-bind `scroll-conservatively` for effect, as it comes\n  into play only on re-display (after your event handler returns). But\n  you *can* set it temporarily and restore it in idle time without ill\n  effect.\n- `scroll-margin\u003e0` is a no-no. This setting always moves point at least\n  that many lines from the window boundaries, which, unless you can\n  reliably place point there during the scroll (even in the presence of\n  jumbo lines; see below), will cause loop-back. See \\#3.\n- Virtual Scroll:\n  - `vscroll` – a virtual rendered scrolling window hiding *below* the\n    current window – is key to smooth scrolling, and altering `vscroll`\n    to move the view-port is incredibly fast.\n  - There is plenty of `vscroll` room available, including the entirety\n    of any tall lines (as for displayed images) in view.\n  - `vscroll` can sometimes place the point off the visible window (I\n    know, sacrilege), but more often triggers re-centering.\n- Scrolling asymmetry:\n  - Sadly `vscroll` is purely *one-sided*: you can only access a\n    `vscroll` area *beneath* the current window view; *there is no\n    negative `vscroll`*.\n  - Unlike `window-start`, `window-end` does not get updated promptly\n    between re-displays and cannot always be trusted. Computing it is\n    expensive, so should be avoided during re-display.\n  - For these two reasons, smooth scrolling up and scrolling down are\n    *not symmetric* with each other, and will likely never be. You need\n    different approaches for each.\n  - If the two approaches for scrolling up and down perform quite\n    differently, the user will definitely feel this difference.\n- For avoiding re-centering, naive movement doesn't work well. You need\n  to learn the basic layout of lines on the window *before re-display*\n  has occurred.\n- The \"usable window height\" deducts any header and the old-fashioned\n  tab-bar, but *not* the tab-bar-mode bar.\n- Jumbo lines (lines taller than the window's height):\n  - Scrolling towards buffer end:\n    - When scrolling with jumbo lines towards the buffer's end (with\n      `vscroll`), simply keep *point on the jumbo line* until it fully\n      disappears from view. As a special case, Emacs will not re-center\n      when this happens.\n    - This is *not* true for lines that are shorter than the usable\n      window height. In this case, you must *avoid* placing point on any\n      line which falls partially out of view.\n  - Scrolling towards buffer start:\n    - When scrolling up past jumbo lines towards the buffer's start\n      using `set-window-start` (lines of content move down), you must\n      keep point on the jumbo, but *only until it clears the top of the\n      window area* (even by one pixel).\n    - After this, you must move the point to the line above it (and had\n      better insist that `scroll-conservatively\u003e0` during the scroll to\n      prevent re-centering).\n    - In some cases (depending on truncation/visual-line-mode/etc.),\n      this movement must occur from a position beyond the first full\n      height object (which may not be at the line's start). E.g. one\n      before the visual line end.\n- `pos-visible-in-window` doesn't always give correct results near the\n  window boundaries. Better to use the first line at the window's top or\n  directly identify the final line (both via `pos-at-x-y`) and adjust\n  from there.\n- Display bugs\n  - There are\n    [display](https://debbugs.gnu.org/cgi/bugreport.cgi?bug=67533)\n    [bugs](https://debbugs.gnu.org/cgi/bugreport.cgi?bug=67604) with\n    inline images that cause them to misreport pixel measurements and\n    positions sometimes.\n  - These lead to slightly staccato scrolling in such buffers and\n    `height=0` gets erroneously reported, so can't be used to find\n    beginning of buffer. Best to guard against these.\n  - **Update:** Two display bugs have been fixed in master as of Dec,\n    2023, so scrolling with lots of inline images will soon be even\n    smoother. [One\n    bug](https://debbugs.gnu.org/cgi/bugreport.cgi?bug=67604) related to\n    motion skipping visual-wrapped lines with images at line start\n    remains.\n\nSo all in all, it's quite complicated to get something that works as\nyou'd hope. The cutting room floor is littered with literally dozens of\nalmost-but-not-quite-working versions of `ultra-scroll`. I'm sure there\nare many more corner cases, but the current design gets most things\nright in my usage.\n\n## Speed\n\nI often wonder how many people who claim \"emacs is laggy\" form that\nimpression from scrolling. Scrolling at 60-120Hz or faster with modern\nmice and track-pads puts a lot of stress on systems, and is often the\nfirst place lag appears. So `ultra-scroll` is fast *by design*. I made\nsome observations about its speed using `ELP` to measure the average\ncall duration of individual scroll functions (`ultra-scroll-up/down`)\nwith various buffer and window sizes[^2].\n\n### Take-aways\n\n1.  Very large window sizes and buffers with \"extra\" processing going\n    on, like treesitter, LSP modes, elaborate font-locking, tons of\n    overlays, etc. can slow down scrolling.\n2.  If the scroll command does its work in \\\u003c10ms, you do not notice it.\n    You can definitely start feeling it when scroll commands take more\n    than 15ms.\n3.  The underlying scroll primitives need to leave some overhead in\n    time, so that all the other emacs commands that occur when new\n    content is brought into view (font-lock) can run without causing\n    scroll lag, for all your different modes. **Faster is better**: 3ms\n    or less in a light buffer would be *ideal*.\n4.  Building `--with-native-comp` is *essential* for ultra-smooth\n    scrolling. It increases the speed of each individual scroll commands\n    by **\\\u003e3x**, which is important since these commands are called so\n    frequently.\n5.  On the same build (NS, v29.4, with native-comp), `ultra-scroll` is\n    about **40% faster** than `pixel-scroll-precision-mode`. Except on\n    slower machines, or in very heavy buffers and/or on large window\n    sizes where your performance is right on the edge, this shouldn't be\n    too noticeable.\n6.  On the same system (an M2 mac), `ultra-scroll` on `emacs-mac` is\n    10-15% faster than on NS builds like `emacs-plus`. Very likely not\n    noticeable.\n7.  The mode-line gets updated *very often* during smooth scrolls (and\n    in general), and poorly written fancy modeline add-ons are a common\n    source of slow-down. Good modeline modes will *rate-limit* their\n    updates behind timers and/or cache results in local/global\n    variables. If your scrolling (or any other aspect of Emacs) \"lags\",\n    try `(setq mode-line-format \"NADA\")` and see if that solves it. If\n    so, suspect your fancy modeline.\n\n[^1]: Formerly `ultra-scroll-mac`.\n\n[^2]: To try this yourself, `M-x elp-instrument-function` on both\n    `ultra-scroll-up/down`, scroll around (both directions) in a big\n    buffer with a large window, then `M-x elp-results`. The last column\n    gives average time in seconds. Less than 0.003s (i.e. 3ms) is ideal,\n    8ms is still perfectly usable, 15ms you'll feel a bit, 50ms will be\n    very frustrating. `scroll-down` is always faster than `scroll-up`\n    due to an asymmetry in Emacs' `vscroll` buffer.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjdtsmith%2Fultra-scroll","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjdtsmith%2Fultra-scroll","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjdtsmith%2Fultra-scroll/lists"}