dotfiles/misc/browser/surfingkeys+colemak.js
2025-10-31 21:47:00 +00:00

1040 lines
23 KiB
JavaScript

// vim:foldmethod=marker:foldmarker=#region,#endregion:foldlevel=0
// Paste this into surfingkeys advanced settings
// or use:
// Load settings from: https://raw.githubusercontent.com/js0ny/dotfiles/refs/heads/master/misc/browser/surfingkeys+colemak.js
// Browse to Extension > Surfingkeys > Allow access to file URLs to enable local file access
// Windows: file:///C:/Users/username/.dotfiles/tools/browser/surfingkeys.js
// Linux: file:///home/username/.dotfiles/tools/browser/surfingkeys.js
// macOS: file:///Users/username/.dotfiles/tools/browser/surfingkeys.js
// #region Example
/** Examples
// an example to create a new mapping `ctrl-y`
api.mapkey('<ctrl-y>', 'Show me the money', function() {
Front.showPopup('a well-known phrase uttered by characters in the 1996 film Jerry Maguire (Escape to close).');
});
// an example to replace `T` with `gt`, click `Default mappings` to see how `T` works.
api.map('gt', 'T');
// an example to remove mapkey `Ctrl-i`
api.unmap('<ctrl-i>');
*/
// #endregion
// #region Settings
settings.language = "zh-CN";
settings.showModeStatus = false;
// #endregion
// #region Helper
// import the API so that no need to use `api` prefix
const {
aceVimMap,
addVimMapKey,
mapkey,
imap,
imapkey,
getClickableElements,
vmapkey,
map,
unmap,
cmap,
addSearchAlias,
removeSearchAlias,
tabOpenLink,
readText,
Clipboard,
Front,
Hints,
Visual,
RUNTIME,
} = api;
// Keymap, reference https://github.com/texiwustion/colemak_config_for_surfingkeys/tree/main
const forward = {
add: function (key) {
// 转发即将被 unmap 的键
return api.map(`for${key}`, key);
},
cancel: function (key) {
// 删除转发生成的键
api.unmap(`for${key}`);
api.unmap(key);
},
use: function (key) {
return `for${key}`;
},
};
const colemak = {
forward: function (key) {
// 转发即将被 unmap 的键
api.map(key, `col${key}`);
api.unmap(`col${key}`);
},
use: function (key) {
return `col${key}`;
},
map: function (a, b) {
api.map(colemak.use(a), forward.use(b));
},
};
const vForward = {
add: function (key) {
// 转发即将被 unmap 的键
return api.vmap(`vfor${key}`, key);
},
cancel: function (key) {
// 删除转发生成的键
api.vunmap(`vfor${key}`);
api.vunmap(key);
},
use: function (key) {
return `vfor${key}`;
},
};
const vColemak = {
forward: function (key) {
// 转发即将被 unmap 的键
api.vmap(key, `vcol${key}`);
api.vunmap(`vcol${key}`);
},
use: function (key) {
return `vcol${key}`;
},
map: function (a, b) {
api.vmap(vColemak.use(a), vForward.use(b));
},
};
const forwardFactory = {
push: function (mapLists) {
// forward original keys
for (const key in mapLists) {
// `const` better than `let`
forward.add(mapLists[key]);
}
},
map: function (mapLists) {
for (const key in mapLists) {
colemak.map(key, mapLists[key]);
}
},
pull: function (mapLists) {
for (const key in mapLists) {
forward.cancel(mapLists[key]);
}
for (const key in mapLists) {
colemak.forward(key);
}
},
};
const vForwardFactory = {
push: function (mapLists) {
// forward original keys
for (const key in mapLists) {
vForward.add(mapLists[key]);
}
},
map: function (mapLists) {
for (const key in mapLists) {
vColemak.map(key, mapLists[key]);
}
},
pull: function (mapLists) {
for (const key in mapLists) {
vForward.cancel(mapLists[key]);
}
for (const key in mapLists) {
vColemak.forward(key);
}
},
};
// TODO: Add more search completion source (with json)
const parseSearchResponse = function (response) {
const res = JSON.parse(response.text);
return res.map((r) => r.phrase);
};
const _addSearchAlias = function (
alias,
name,
searchUrl,
acUrl = "https://duckduckgo.com/ac/?q=",
searchPrefix = "s",
parseResponse = parseSearchResponse,
) {
api.addSearchAlias(
alias,
name,
searchUrl,
searchPrefix,
acUrl,
parseResponse,
);
};
// Shortcut for querySelector
const q = (selector) => document.querySelector(selector);
const qs = (selector) => document.querySelectorAll(selector);
// #endregion
// #region Keymap
// Normal Mode Keymap
const mapLists = {
/// scroll page
// Arrow
n: "j",
e: "k",
i: "l",
// l <-> i
l: "gi", // Focus on first input box by default
L: "I",
// k <-> n
k: "n",
K: "N",
// j <-> e
j: "e",
// PrevTab < H - I > NextTab
H: "E",
I: "R",
// E,N -> Up/Down HalfPage
N: "d",
E: "e",
// F -> Open Link in New Tab
F: "af",
// oH -> Tab History
oH: "H",
// gh/gi -> Prev/Next History
gh: "S",
gi: "D",
gl: "i", // Use `gl` to search and focus on input box
// t -> Open Link in New Tab
t: "gf",
// 缩放
zu: "zi",
zo: "ze",
zz: "zr",
};
// Visual Mode Keymap
const vMapLists = {
n: "j",
N: "J",
e: "k",
E: "K",
i: "l",
I: "L",
j: "e",
J: "E",
k: "n",
K: "N",
};
forwardFactory.push(mapLists);
forwardFactory.map(mapLists);
vForwardFactory.push(vMapLists);
vForwardFactory.map(vMapLists);
// All other unmapped keys should be defined here
// TODO: Add more mouse click keymap
api.unmap("gi"); // conflict with `gi` in `mapLists`
api.unmap("C"); // Use `F` instead (Open Link in New Tab)
api.map("g/", "gU"); // Goto Root Domain
// TODO: Add SPC keymap as leader (maybe change `,` to `SPC`)
api.unmap("<space>"); // Leader Key
forwardFactory.pull(mapLists);
vForwardFactory.pull(vMapLists);
api.map("gH", "g/");
// #endregion
// #region Omnibar NOTE: Dosn't work
// api.cmap("<Ctrl-a>", "<Ctrl-ArrowUp>");
// api.cmap("<Ctrl-e>", "<Ctrl-ArrowDown>");
// api.cmap("<Ctrl-f>", "<ArrowRight>");
// api.cmap("<Ctrl-b>", "<ArrowLeft>");
// api.cmap("<Alt-f>", "<Ctrl-ArrowRight>");
// api.cmap("<Alt-b>", "<Ctrl-ArrowLeft>");
// api.cmap("<Ctrl-h>", "<Backspace>");
// api.cmap("<Ctrl-d>", "<Delete>");
// #endregion
// #region Search Alias
const removedSearchAlias = [
"b", // Baidu
"d", // DuckDuckGo
"e", // Wikipedia
"g", // Google
"s", // StackOverflow
"w", // Bing
"y", // YouTube
];
removedSearchAlias.forEach((alias) => removeSearchAlias(alias));
const searchAliases = [
["a2", "AlternativeTo", "https://alternativeto.net/browse/search/?q="],
["ap", "APT", "https://packages.ubuntu.com/search?keywords="],
["au", "AUR", "https://aur.archlinux.org/packages?K="],
["aw", "ArchWiki", "https://wiki.archlinux.org/index.php?search="],
["bd", "Baidu", "https://www.baidu.com/s?wd="],
["bi", "Bing", "https://www.bing.com/search?q="],
["bl", "Bilibili", "https://search.bilibili.com/all?keyword="],
["br", "HomeBrew", "https://duckduckgo.com/?q=!brew "],
["cg", "ChatGPT", "https://chat.openai.com/?q="],
["cr", "Chrome Web Store", "https://chrome.google.com/webstore/search/"],
["dd", "DuckDuckGo", "https://duckduckgo.com/?q="],
["de", "Thesaurus", "https://www.onelook.com/?w="],
["eb", "ebay", "https://www.ebay.co.uk/sch/i.html?kw="],
["fe", "Felo", "https://felo.ai/search?q="],
["gh", "GitHub", "https://github.com/search?type=repositories&q="],
["gg", "Google", "https://www.google.com/search?q="],
["mc", "Metacritic", "https://www.metacritic.com/search/"],
["nx", "NixPackages", "https://search.nixos.org/packages?query="],
["ng", "NuGet", "https://www.nuget.org/packages?q="],
["np", "npm", "https://www.npmjs.com/search?q="],
["pa", "Pacman", "https://archlinux.org/packages/?q="],
["pp", "Perplexity", "https://www.perplexity.ai/?q="],
["py", "pypi", "https://pypi.org/search/?q="],
["re", "Reddit", "https://www.reddit.com/search?q="],
["sc", "Scoop", "https://scoop.sh/#/apps?q="],
["se", "StackExchange", "https://stackexchange.com/search?q="],
["so", "StackOverflow", "https://stackoverflow.com/search?q="],
["st", "Steam", "https://store.steampowered.com/search/?term="],
["tw", "X", "https://twitter.com/search?q="],
["ud", "UrbanDictionary", "https://www.urbandictionary.com/define.php?term="],
["wa", "WolframAlpha", "https://www.wolframalpha.com/input/?i="],
["wg", "winget", "https://winget.ragerworks.com/search/all/"],
[
"wk",
"Wikipedia",
"https://en.wikipedia.org/w/index.php?title=Special:Search&search=",
],
[
"ww",
"WantWords",
"https://www.shenyandayi.com/wantWordsResult?lang=zh&query=",
],
["yt", "YouTube", "https://www.youtube.com/results?search_query="],
];
api.unmap("on");
// Add all search aliases
searchAliases.forEach(([alias, name, url]) => {
_addSearchAlias(alias, name, url);
});
// #endregion
// #region Site-specific
// This is a global keymap
mapkey("yY", "yank link without parameter", function () {
const url = new URL(window.location.href);
Clipboard.write(url.origin + url.pathname);
});
unmap("yma");
unmap("ymc");
unmap("ymv");
mapkey("ym", "yank link as markdown", function () {
const url = new URL(window.location.href);
const title = document.title;
Clipboard.write(`[${title}](${url.origin + url.pathname})`);
});
// #region bilibili.com
mapkey(
",n",
"[n]ext Video",
function () {
window.location.href = q("div.next-play").querySelector("a").href;
},
{ domain: /bilibili.com/ },
);
// #endregion
// #region chatgpt.com
const chatgptNewChat = function () {
var btn = q(
"div.no-draggable:nth-child(3) > span:nth-child(1) > button:nth-child(1)",
);
btn.click();
};
const chatgptStartStop = function () {
var btn = q("button.h-8:nth-child(2)");
btn.click();
};
mapkey(",n", "New Chat", chatgptNewChat, { domain: /chatgpt.com/ });
mapkey(",s", "Start/Stop Generating", chatgptStartStop, {
domain: /chatgpt.com/,
});
// #endregion
// #region chat.deepseek.com
mapkey(
",s",
"Toggle Sidebar",
function () {
var btn = qs("div.ds-icon-button");
btn[0].click();
},
{ domain: /chat.deepseek.com/ },
);
mapkey(
",e",
"[e]dit last input",
function () {
var btn = qs("div.ds-icon-button");
btn[btn.length - 5].click();
},
{ domain: /chat.deepseek.com/ },
);
mapkey(
",y",
"[y]ank last oupput",
function () {
var btn = qs("div.ds-icon-button");
btn[btn.length - 4].click();
},
{ domain: /chat.deepseek.com/ },
);
mapkey(
",r",
"[r]egenerate last output",
function () {
var btn = qs("div.ds-icon-button");
btn[btn.length - 3].click();
},
{ domain: /chat.deepseek.com/ },
);
mapkey(
",n",
"[n]ew Chat",
function () {
window.location.href = "https://chat.deepseek.com/";
},
{ domain: /chat.deepseek.com/ },
);
mapkey(
",t",
"Toggle co[t](R1)",
function () {
var btns = qs("div.ds-button");
btns[0].click();
},
{ domain: /chat.deepseek.com/ },
);
mapkey(
",w",
"Toggle [w]eb Search",
function () {
var btns = qs("div.ds-button");
btns[1].click();
},
{ domain: /chat.deepseek.com/ },
);
// #endregion
//#region dropbox.com
//https://www.dropbox.com/scl/fi/u58c2qmqbwq672y3hwmfn/setup.sh?rlkey=d3figouv5eqk1xfwdtyzfr7ua&e=1&st=ehttmy2r&dl=0
//https://dl.dropboxusercontent.com/scl/fi/u58c2qmqbwq672y3hwmfn/setup.sh?rlkey=d3figouv5eqk1xfwdtyzfr7ua&e=1&st=ehttmy2r
mapkey(
",r",
"Extract [r]aw link",
function () {
const url = new URL(window.location.href);
if (url.href.endsWith("&dl=0")) {
url.searchParams.delete("dl");
url.hostname = "dl.dropboxusercontent.com";
Clipboard.write(url.href);
}
},
{ domain: /dropbox.com/ },
);
mapkey(
",d",
"Extract [d]ownload link",
function () {
const url = new URL(window.location.href);
if (url.href.endsWith("&dl=0")) {
url.searchParams.set("dl", "1");
Clipboard.write(url.href);
}
},
{ domain: /dropbox.com/ },
);
//#endregion
// #region app.follow.is
mapkey(
",t",
"Toggle ",
function () {
var btn = qs("button.no-drag-region");
btn[btn.length - 4].click();
},
{ domain: /app.follow.is/ },
);
mapkey(
",a",
"Toggle AI Summary",
function () {
var btn = qs("button.no-drag-region");
btn[btn.length - 3].click();
},
{ domain: /app.follow.is/ },
);
mapkey(
",o",
"Toggle Original Website",
function () {
var btn = qs("button.no-drag-region");
btn[btn.length - 4].click();
},
{ domain: /app.follow.is/ },
);
// #endregion
// #region GitHub
// utils
const gh = {};
gh.repoLink = (owner, repo) => `https://github.com/${owner}/${repo}`;
gh.pageLink = (owner, repo) => `https://${owner}.github.io/${repo}/`;
gh.sourceLink = (owner, repo, path) =>
`${gh.repoLink(owner, repo)}/tree/${path}`;
gh.rawToSource = (url) => {
const ps = url.split("/").slice(3);
return gh.sourceLink(ps[0], ps[1], ps.slice(4).join("/"));
};
// github.com
mapkey(
",e",
"Use Web Editor",
function () {
const url = new URL(window.location.href);
url.hostname = "github.dev";
window.location.href = url.href;
},
{ domain: /github.com/ },
);
mapkey(
",E",
"Use Web Editor (New Page)",
function () {
const url = new URL(window.location.href);
url.hostname = "github.dev";
tabOpenLink(url.href);
},
{ domain: /github.com/ },
);
mapkey(
",p",
"Switch to GitHub Page",
function () {
href = window.location.href;
owner = href.split("/")[3];
repo = href.split("/")[4];
window.location.href = gh.pageLink(owner, repo);
},
{ domain: /github.com/ },
);
/// This might be useful for Vim plugins
mapkey(
",y",
"[y]ank short refeference owner/repo",
function () {
const href = window.location.href;
owner = href.split("/")[3];
repo = href.split("/")[4];
Clipboard.write(`${owner}/${repo}`);
},
{ domain: /github.com/ },
);
// github.dev
mapkey(
",r",
"Switch to GitHub Repo",
function () {
const url = new URL(window.location.href);
url.hostname = "github.com";
window.location.href = url.href;
},
{ domain: /github.dev/ },
);
// github.io
mapkey(
",r",
"Switch to GitHub Repo",
function () {
const href = window.location.href;
owner = href.split("/")[2].split(".")[0];
repo = href.split("/")[3];
tabOpenLink(gh.repoLink(owner, repo));
},
{ domain: /github.io/ },
);
mapkey(
",R",
"Go to GitHub Repo (New tab)",
function () {
const href = window.location.href;
owner = href.split("/")[2].split(".")[0];
repo = href.split("/")[3];
tabOpenLink(gh.repoLink(owner, repo));
},
{ domain: /github.io/ },
);
// raw.githubusercontent.com
mapkey(
",r",
"Switch to GitHub Repo",
function () {
const url = new URL(window.location.href);
var owner, repo;
(owner, (repo = url.pathname.split("/").slice(1, 3)));
window.location.href = gh.repoLink(owner, repo);
},
{ domain: /raw.githubusercontent.com/ },
);
mapkey(
",R",
"Switch to GitHub Repo",
function () {
const url = new URL(window.location.href);
var owner, repo;
(owner, (repo = url.pathname.split("/").slice(1, 3)));
tabOpenLink(gh.repoLink(owner, repo));
},
{ domain: /raw.githubusercontent.com/ },
);
mapkey(
",s",
"Open Source in GitHub",
function () {
window.location.href = gh.rawToSource(window.location.href);
},
{ domain: /raw.githubusercontent.com/ },
);
mapkey(
",S",
"Open Source in GitHub (New Page)",
function () {
tabOpenLink(gh.rawToSource(window.location.href));
},
{ domain: /raw.githubusercontent.com/ },
);
// #endregion GitHub
//#region app.microsoft.com
// https://apps.microsoft.com/detail/9nl6kd1h33v3?hl=en-GB&gl=GB
// This is useful in `winget` (Windows Package Manager)
mapkey(
",y",
"[y]ank app id",
function () {
const url = new URL(window.location.href);
const id = url.pathname.split("/")[2];
Clipboard.write(id);
},
{ domain: /apps.microsoft.com/ },
);
//#endregion
// #region perplexity.ai
/**
* 0 - 网络
* 1 - 学术
* 2 - 社交
*/
unmap("<Ctrl-i>", /perplexity.ai/); // allows to use perplexity web keybindings
mapkey(
",b",
"Add Perplexity [b]ookmark",
function () {
// button.border:nth-child(2)
q("div.sticky.left-0").querySelectorAll("button")[2].click();
},
{ domain: /perplexity.ai/ },
);
mapkey(
",M",
"Toggle [M]odel switching",
function () {
q("div.rounded-md").querySelectorAll("span")[2].click();
//setTimeout(() => {
// // Wait for the DOM to update
// qs("div.shadow-subtle div.group\\/item")[0].click();
//}, 100);
},
{ domain: /perplexity.ai/ },
);
mapkey(
",m",
"Toggle default [m]odel (Claude 3.7 Sonnet)",
function () {
q("div.rounded-md").querySelectorAll("span")[1].click();
setTimeout(() => {
// Wait for the DOM to update
qs("div.shadow-subtle div.group\\/item")[3].click();
}, 100);
},
{ domain: /perplexity.ai/ },
);
mapkey(
",w",
"Toggle [w]riting/[w]eb Search",
function () {
q("div.rounded-md").querySelectorAll("span")[2].click();
setTimeout(() => {
// Wait for the DOM to update
qs("div.shadow-subtle div.group\\/item")[0].click();
}, 100);
},
{ domain: /perplexity.ai/ },
);
mapkey(
",s",
"[s]tart Generating",
function () {
var btns = qs("span.grow button");
btns[btns.length - 1].click();
},
{ domain: /perplexity.ai/ },
);
mapkey(
",y",
"[y]ank Last Output",
function () {
var toolbars = qs("div.mt-sm");
var last = toolbars[toolbars.length - 1];
var btns = last.querySelectorAll("button");
btns[5].click();
},
{ domain: /perplexity.ai/ },
);
mapkey(
",R",
"Change model to [R]egenerate last output",
function () {
var toolbars = qs("div.mt-sm");
var last = toolbars[toolbars.length - 1];
var btns = last.querySelectorAll("button");
btns[1].click();
},
{ domain: /perplexity.ai/ },
);
mapkey(
",r",
"Toggle [r]easoning",
function () {
q("div.rounded-md").querySelectorAll("span")[0].click();
setTimeout(() => {
// Wait for the DOM to update
qs("div.shadow-subtle div.group\\/item")[2].click();
}, 100);
},
{ domain: /perplexity.ai/ },
);
// #endregion
// #region sspai.com
unmap("[[", /sspai.com/);
unmap("]]", /sspai.com/);
unmap(",", /sspai.com/);
mapkey(
"[[",
"Previous Page",
function () {
q("button.btn-prev").click();
},
{ domain: /sspai.com/ },
);
mapkey(
"]]",
"Next Page",
function () {
q("button.btn-next").click();
},
{ domain: /sspai.com/ },
);
// #endregion
// #region pixiv.net
// Use site-specific paging method
unmap("[[", /pixiv.net/);
unmap("]]", /pixiv.net/);
unmap(",", /pixiv.net/);
const isArtwork = (url) => /pixiv.net\/artworks/.test(url.href);
mapkey(
"[[",
"Previous Page",
function () {
const url = new URL(window.location.href);
if (url.href === url.origin) {
return;
}
const page = url.searchParams.get("p");
const newPage = page ? parseInt(page) - 1 : 1;
url.searchParams.set("p", newPage);
window.location.href = url.href;
},
{ domain: /pixiv.net/ },
);
mapkey(
"]]",
"Next Page",
function () {
const url = new URL(window.location.href);
if (url.href === url.origin) {
return;
}
const page = url.searchParams.get("p");
const newPage = page ? parseInt(page) + 1 : 2;
url.searchParams.set("p", newPage);
window.location.href = url.href;
},
{ domain: /pixiv.net/ },
);
mapkey(
",b",
"Add to [b]ookmark",
function () {
const url = new URL(window.location.href);
if (!isArtwork(url)) {
return;
}
const toolbar = q('section [class$="Toolbar"]');
toolbar.querySelectorAll("div")[2].querySelector("button").click();
},
{ domain: /pixiv.net/ },
);
mapkey(
",B",
"Add to private [B]ookmark",
function () {
const url = new URL(window.location.href);
if (!isArtwork(url)) {
return;
}
const toolbar = q('section [class$="Toolbar"]');
toolbar.querySelectorAll("div")[0].querySelector("button").click();
setTimeout(() => {
// Wait for the DOM to update
q("div[role=menu]").querySelector("li").click();
}, 100);
},
{ domain: /pixiv.net/ },
);
mapkey(
",v",
"Up[v]ote Artwork",
function () {
const url = new URL(window.location.href);
if (!isArtwork(url)) {
return;
}
const toolbar = q('section [class$="Toolbar"]');
toolbar.querySelectorAll("div")[3].querySelector("button").click();
},
{ domain: /pixiv.net/ },
);
mapkey(
",f",
"Toggle [f]ollow the author",
function () {
const url = new URL(window.location.href);
if (!isArtwork(url)) {
return;
}
q("aside").querySelector("section").querySelector("button").click();
},
{ domain: /pixiv.net/ },
);
// #endregion
// #region youtube.com
mapkey(
",n",
"[n]ext Video",
function () {
window.location.href = q("ytd-compact-video-renderer").querySelector(
"a",
).href;
},
{ domain: /youtube.com/ },
);
mapkey(
",v",
"Up[v]ote Video",
function () {
qs("like-button-view-model")[0].querySelector("button").click();
},
{ domain: /youtube.com/ },
);
mapkey(
",V",
"Down[v]ote Video",
function () {
qs("dislike-button-view-model")[0].querySelector("button").click();
},
{ domain: /youtube.com/ },
);
// class="ytp-subtitles-button ytp-button"
mapkey(
",c",
"toggle [c]aptions",
function () {
q("button.ytp-subtitles-button").click();
},
{ domain: /youtube.com/ },
);
// #endregion
//#region zhihu.com
mapkey(
",d",
"Toggle [d]ark mode",
function () {
const url = new URL(window.location.href);
if (url.searchParams.get("theme") === "dark") {
url.searchParams.set("theme", "light");
} else {
url.searchParams.set("theme", "dark");
}
window.location.href = url.href;
},
{ domain: /zhihu.com/ },
);
//#endregion
// #endregion
// #region ACE Editor
addVimMapKey(
// Navigation
{
keys: "k",
type: "motion",
motion: "findNext",
motionArgs: { forward: true, toJumplist: true },
},
{
keys: "K",
type: "motion",
motion: "findNext",
motionArgs: { forward: false, toJumplist: true },
},
// Word movement
{
keys: "j",
type: "motion",
motion: "moveByWords",
motionArgs: { forward: true, wordEnd: true, inclusive: true },
},
{
keys: "J",
type: "motion",
motion: "moveByWords",
motionArgs: {
forward: true,
wordEnd: true,
bigWord: true,
inclusive: true,
},
},
// Insert mode entries
{
keys: "l",
type: "action",
action: "enterInsertMode",
isEdit: true,
actionArgs: { insertAt: "inplace" },
context: "normal",
},
{
keys: "gl",
type: "action",
action: "enterInsertMode",
isEdit: true,
actionArgs: { insertAt: "lastEdit" },
context: "normal",
},
{
keys: "L",
type: "action",
action: "enterInsertMode",
isEdit: true,
actionArgs: { insertAt: "firstNonBlank" },
context: "normal",
},
{
keys: "gL",
type: "action",
action: "enterInsertMode",
isEdit: true,
actionArgs: { insertAt: "bol" },
context: "normal",
},
{
keys: "L",
type: "action",
action: "enterInsertMode",
isEdit: true,
actionArgs: { insertAt: "startOfSelectedArea" },
context: "visual",
},
{
keys: "n",
type: "motion",
motion: "moveByLines",
motionArgs: { forward: true, linewise: true },
},
{
keys: "e",
type: "motion",
motion: "moveByLines",
motionArgs: { forward: false, linewise: true },
},
{
keys: "i",
type: "motion",
motion: "moveByCharacters",
motionArgs: { forward: true },
},
{
keys: "H",
type: "keyToKey",
toKeys: "^",
},
{
keys: "I",
type: "keyToKey",
toKeys: "$",
},
{
keys: "Y",
type: "keyToKey",
toKeys: "y$",
},
);
// #endregion
// #region Hints
api.Hints.setCharacters("qwfpgarstdcv"); // Left-hand keys
// #endregion