Skip to content

Instantly share code, notes, and snippets.

@milo-minderbinder
Last active April 25, 2026 05:43
Show Gist options
  • Select an option

  • Save milo-minderbinder/2f8f084e3a6ab3adfbf91f0d5b79b2c4 to your computer and use it in GitHub Desktop.

Select an option

Save milo-minderbinder/2f8f084e3a6ab3adfbf91f0d5b79b2c4 to your computer and use it in GitHub Desktop.
Tampermonkey user script that adds collapsable links for each heading, subheading, and tag (e.g. API methods, subtopics, etc.) in the right-hand navigation section.
// ==UserScript==
// @name neovim-docs-helper
// @namespace https://gist.github.com/milo-minderbinder/
// @version 0.2
// @description adds collapsable links for each heading, subheading, and tag (e.g. API methods, subtopics, etc.) in neovim user doc pages.
// @author Chris Passarello
// @match https://neovim.io/doc/user/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=neovim.io
// @downloadURL https://gist.github.com/milo-minderbinder/2f8f084e3a6ab3adfbf91f0d5b79b2c4/raw/neovim-docs-helper.user.js
// @updateURL https://gist.github.com/milo-minderbinder/2f8f084e3a6ab3adfbf91f0d5b79b2c4/raw/neovim-docs-helper.user.js
// @run-at document-idle
// @grant none
// ==/UserScript==
(function() {
'use strict';
console.log('neovim-docs-helper');
function waitForElement(selector, f, delay = 500) {
console.log(`waiting ${delay}ms for element with selector: ${selector}`);
setTimeout(() => {
const e = document.querySelector(selector);
if (e != null) {
console.log(`got element with selector "${selector}": ${e}`);
return f(e);
}
waitForElement(selector, f, delay);
}, delay);
}
function showHide(show, querySelectorRoot, selectors) {
querySelectorRoot.querySelectorAll(selectors).forEach((e) => {
if (show === true) {
e.removeAttribute('hidden');
} else {
e.setAttribute('hidden', true);
}
});
}
/**
* Create & append buttons to container that will toggle the 'hidden' attribute for matching Nodes.
*
* @param container {Element} to append the buttons to
* @param querySelectorRoot {Element|Document} to match the selectors in
* @param selectors {string} the CSS selectors to match the nodes which will be toggled by the buttons
* @returns {Element}
*/
function createShowHideButtons(container, querySelectorRoot = document, selectors = 'ul.collapsable') {
if (container == null) {
container = document.createElement('div');
}
for (const v of ['+', '-']) {
const btn = document.createElement('a');
const id = v === '+' ? 'expandAll' : 'collapseAll';
// btn.setAttribute('id', id);
btn.setAttribute('name', id);
// btn.setAttribute('href', `#${btn.id}`);
btn.setAttribute('href', document.location.href);
btn.text = v;
btn.addEventListener('click', (event) => {
btn.setAttribute('href', document.location.href);
// btn.text = btn.text + v;
showHide(v === '+', querySelectorRoot, selectors);
});
container.appendChild(btn);
}
return container;
}
function run() {
const runSelector = 'div.col-narrow.toc';
waitForElement(runSelector, (_) => {
const navDiv = document.querySelector(runSelector);
const expandCollapseDiv = createShowHideButtons(null);
navDiv.firstElementChild.before(expandCollapseDiv);
const docLinks = Object.create(null);
let headingList;
let subheadingList;
document.querySelectorAll(
'div.help-para>h2.help-heading[id], div.help-para>h3.help-heading[id], div.help-para .help-tag-right')
.forEach((e) => {
if (e.className === 'help-heading' && e.tagName === 'H2') {
subheadingList = null;
headingList = document.createElement('ul');
headingList.classList.add('collapsable');
docLinks[e.id] = headingList;
//console.log(`${e.id}`);
} else {
const a = document.createElement('a');
a.href = `#${e.id}`;
a.text = decodeURIComponent(e.id);
const li = document.createElement('li');
li.appendChild(a);
if (e.className === 'help-heading' && e.tagName === 'H3') {
a.text = e.innerText;
subheadingList = document.createElement('ul');
subheadingList.classList.add('collapsable');
li.appendChild(subheadingList);
headingList.appendChild(li);
//console.log(`\t${e.id}`);
} else if (subheadingList != null) {
subheadingList.appendChild(li);
} else {
headingList.appendChild(li);
//console.log(`\t${e.id}`);
}
}
});
document.querySelectorAll('div.help-toc-h1>a').forEach((e) => {
const k = decodeURIComponent(e.hash).slice(1);
if (docLinks[k] == null) {
console.log(k, 'no docLinks for heading:', e);
} else {
e.after(docLinks[k]);
e.parentElement.querySelectorAll('ul.collapsable').forEach((ul) => {
const listToggles = createShowHideButtons(document.createElement('span'), ul.parentElement);
ul.before(listToggles);
listToggles.before(' ');
});
}
});
});
}
run();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment