Skip to content

Instantly share code, notes, and snippets.

@sairajchouhan
Last active November 29, 2025 04:14
Show Gist options
  • Select an option

  • Save sairajchouhan/8086c05939b7409761526bc0b1b4b2bb to your computer and use it in GitHub Desktop.

Select an option

Save sairajchouhan/8086c05939b7409761526bc0b1b4b2bb to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name TUF Vim Mode
// @namespace http://tampermonkey.net/
// @version 12.0
// @description Vim support for takeUforward code editor
// @author You
// @match https://*.takeuforward.org/*
// @connect unpkg.com
// @connect cdn.jsdelivr.net
// @grant GM_xmlhttpRequest
// @grant unsafeWindow
// ==/UserScript==
(function() {
'use strict';
const pageWindow = unsafeWindow;
const LOG_PREFIX = '%c[TUF VIM]';
const LOG_STYLE = 'background: #000; color: #0f0; font-weight: bold; padding: 2px 4px; border-radius: 2px;';
const URLS = [
'https://unpkg.com/monaco-vim/dist/monaco-vim.min.js',
'https://cdn.jsdelivr.net/npm/monaco-vim/dist/monaco-vim.min.js'
];
const fetchScript = (url) => {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: url,
onload: (res) => (res.status === 200 ? resolve(res.responseText) : reject(`Status ${res.status}`)),
onerror: (err) => reject(err)
});
});
};
const loadVimEngine = async () => {
let vimCode = null;
for (const url of URLS) {
try {
vimCode = await fetchScript(url);
break;
} catch (err) { console.warn(`Failed ${url}`, err); }
}
if (!vimCode) throw new Error("Download failed.");
const mockModule = { exports: {} };
const mockRequire = (dependency) => {
if (dependency.includes('monaco-editor')) return pageWindow.monaco;
return {};
};
const runner = new Function('module', 'exports', 'require', 'define', vimCode);
runner(mockModule, mockModule.exports, mockRequire, undefined);
return mockModule.exports || window.MonacoVim;
};
const createPhantomNode = () => {
let statusNode = document.getElementById('vim-phantom-node');
if (statusNode) return statusNode;
statusNode = document.createElement('div');
statusNode.id = 'vim-phantom-node';
statusNode.style.cssText = 'display: none !important; width: 0; height: 0; overflow: hidden;';
document.body.appendChild(statusNode);
return statusNode;
};
const initVim = async () => {
try {
const MonacoVim = await loadVimEngine();
const phantomNode = createPhantomNode();
console.log(LOG_PREFIX, LOG_STYLE, 'Engine Loaded. Silent Mode Active.');
setInterval(() => {
if (!pageWindow.monaco || !pageWindow.monaco.editor) return;
const editors = pageWindow.monaco.editor.getEditors();
editors.forEach(editor => {
if (editor._vimAttached) return;
if (editor.getOption(pageWindow.monaco.editor.EditorOption.readOnly)) return;
if (!pageWindow.monaco.Range || !pageWindow.monaco.KeyCode) return;
try {
const vimMode = MonacoVim.initVimMode(editor, phantomNode);
editor._vimAttached = true;
editor.layout();
} catch (e) {
console.error('Vim Attach Error:', e);
}
});
}, 1000);
} catch (err) {
console.error('Vim Load Error:', err);
}
};
let attempts = 0;
const checkLoader = setInterval(() => {
attempts++;
if (pageWindow.monaco) {
clearInterval(checkLoader);
initVim();
} else if (attempts > 20) {
clearInterval(checkLoader);
}
}, 500);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment