Skip to content

Commit

Permalink
Allow overriding <C-w>
Browse files Browse the repository at this point in the history
Closes #103
  • Loading branch information
glacambre committed Mar 22, 2020
1 parent 3ddf69c commit 8c73600
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 21 deletions.
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,22 @@ au TextChangedI * ++nested call Delay_My_Write()

## Drawbacks

The main issue with Firenvim is that some keybindings (e.g. `<C-w>`) are not overridable. I circumvent this issue by running a [patched](https://github.com/glacambre/firefox-patches) version of Firefox.
Some keybindings, such as `<C-n>`, `<C-t>` and `<C-w>` are not overridable through usual means. This means that you have to tell your browser to let Firenvim override them by using [the shortcuts menu in `about://addons`](https://support.mozilla.org/en-US/kb/manage-extension-shortcuts-firefox) on Firefox and `chrome://extensions/shortcuts` in Chrome.

When it is possible to do so, if you press one of these keyboard shortcuts while not in a Firenvim frame, Firenvim will attempt to emulate the expected behavior of the shortcut. For example, pressing `<C-w>` in a Firenvim frame will tell neovim you pressed `<C-w>`, but outside of it it will tell the browser to close the current tab.

Controlling whether Firenvim should attempt to emulate the browser's default behavior can be done with global settings. The following snippet will tell Firenvim to simulate `<C-n>`'s default behavior while never simulating `<C-w>`'s:

```vim
let g:firenvim_config = {
'globalSettings': {
'<C-w>': 'noop',
'<C-n>': 'default',
}
}
```

Note that on Firefox on Linux some keyboard shortcuts might not be overridable. I circumvent this issue by running a [patched](https://github.com/glacambre/firefox-patches) version of Firefox (note: once Firefox is patched, you won't need to setup webextension keyboard shortcuts).

## You might also like

Expand Down
13 changes: 13 additions & 0 deletions src/FirenvimElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,19 @@ export class FirenvimElement {
}
}

sendKey (key: string) {
return browser.runtime.sendMessage({
args: {
frameId: this.frameId,
message: {
args: [key],
funcName: ["sendKey"],
}
},
funcName: ["messageFrame"],
});
}

setPageElementContent (text: string) {
this.editor.setContent(text);
[
Expand Down
4 changes: 3 additions & 1 deletion src/NeovimFrame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ window.addEventListener("load", async () => {

let resizeReqId = 0;
browser.runtime.onMessage.addListener((request: any, sender: any, sendResponse: any) => {
if (request.funcName[0] === "resize" && request.args[0] > resizeReqId) {
if (request.funcName[0] === "sendKey") {
nvim.input(request.args.join(""));
} else if (request.funcName[0] === "resize" && request.args[0] > resizeReqId) {
const [id, width, height] = request.args;
resizeReqId = id;
// We need to put the keyHandler at the origin in order to avoid
Expand Down
90 changes: 86 additions & 4 deletions src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,24 @@ function applySettings(settings: any) {
}

makeDefaults(settings, "globalSettings", {});
// "<KEY>": "default" | "noop"
// #103: When using the browser's command API to allow sending `<C-w>` to
// firenvim, whether the default action should be performed if no neovim
// frame is focused.
makeDefaults(settings.globalSettings, "<C-n>", "default");
makeDefaults(settings.globalSettings, "<C-t>", "default");
makeDefaults(settings.globalSettings, "<C-w>", "default");
// Note: <CS-*> are currently disabled because of
// https://github.com/neovim/neovim/issues/12037
// Note: <CS-n> doesn't match the default behavior on firefox because this
// would require the sessions API. Instead, Firefox's behavior matches
// Chrome's.
makeDefaults(settings.globalSettings, "<CS-n>", "default");
// Note: <CS-t> is there for completeness sake's but can't be emulated in
// Chrome and Firefox because this would require the sessions API.
makeDefaults(settings.globalSettings, "<CS-t>", "default");
makeDefaults(settings.globalSettings, "<CS-w>", "default");

// "server": "persistent" | "ephemeral"
// #197: Allow multiple firenvim instances to connect to a single server
makeDefaults(settings.globalSettings, "server", "ephemeral");
Expand Down Expand Up @@ -236,6 +254,7 @@ Object.assign(window, {
// We need to stick the browser polyfill in `window` if we want the `exec`
// call to be able to find it on Chrome
browser,
closeOwnTab: (sender: any) => browser.tabs.remove(sender.tab.id),
exec: (sender: any, args: any) => args.funcName.reduce((acc: any, cur: string) => acc[cur], window)(...(args.args)),
getError,
getNeovimInstance: (sender: any, args: any) => {
Expand Down Expand Up @@ -289,27 +308,90 @@ browser.windows.onFocusChanged.addListener(async (windowId: number) => {
updateIcon();

browser.commands.onCommand.addListener(async (command: string) => {
const tab = (await browser.tabs.query({ active: true, currentWindow: true }))[0];
let p;
switch (command) {
case "focus_input":
browser.tabs.sendMessage(
(await browser.tabs.query({ active: true, currentWindow: true }))[0].id,
tab.id,
{ args: [], funcName: ["focusInput"] },
{ frameId: 0 },
);
break;
case "focus_page":
browser.tabs.sendMessage(
(await browser.tabs.query({ active: true, currentWindow: true }))[0].id,
tab.id,
{ args: [], funcName: ["focusPage"] },
{ frameId: 0 },
);
break;
case "nvimify":
browser.tabs.sendMessage(
(await browser.tabs.query({ active: true, currentWindow: true }))[0].id,
tab.id,
{ args: [], funcName: ["forceNvimify"] },
{ frameId: 0 }
);
break;
case "send_C-n":
p = browser.tabs.sendMessage(
tab.id,
{ args: ["<C-n>"], funcName: ["sendKey"] },
{ frameId: 0 }
);
if (getGlobalConf()["<C-n>"] === "default") {
p.catch(() => browser.windows.create());
}
break;
case "send_C-t":
p = browser.tabs.sendMessage(
tab.id,
{ args: ["<C-t>"], funcName: ["sendKey"] },
{ frameId: 0 }
);
if (getGlobalConf()["<C-t>"] === "default") {
p.catch(() => browser.tabs.create({ "windowId": tab.windowId }));
}
break;
case "send_C-w":
p = browser.tabs.sendMessage(
tab.id,
{ args: ["<C-w>"], funcName: ["sendKey"] },
{ frameId: 0 }
);
if (getGlobalConf()["<C-w>"] === "default") {
p.catch(() => browser.tabs.remove(tab.id));
}
break;
case "send_CS-n":
p = browser.tabs.sendMessage(
tab.id,
{ args: ["<CS-n>"], funcName: ["sendKey"] },
{ frameId: 0 }
);
if (getGlobalConf()["<CS-n>"] === "default") {
p.catch(() => browser.windows.create({ "incognito": true }));
}
break;
case "send_CS-t":
// <CS-t> can't be emulated without the sessions API.
browser.tabs.sendMessage(
tab.id,
{ args: ["<CS-t>"], funcName: ["sendKey"] },
{ frameId: 0 }
);
break;
case "send_CS-w":
p = browser.tabs.sendMessage(
tab.id,
{ args: ["<CS-w>"], funcName: ["sendKey"] },
{ frameId: 0 }
);
if (getGlobalConf()["<CS-w>"] === "default") {
p.catch(() => browser.windows.remove(tab.windowId));
}
break;
case "toggle_firenvim":
await toggleDisabled();
toggleDisabled();
break;
}
});
9 changes: 9 additions & 0 deletions src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@
"default": "Ctrl+E"
}
},
"send_C-n": {
"description": "Send <C-n> to firenvim."
},
"send_C-t": {
"description": "Send <C-t> to firenvim."
},
"send_C-w": {
"description": "Send <C-w> to firenvim."
},
"toggle_firenvim": {
"description": "Toggle Firenvim in the current tab."
}
Expand Down
43 changes: 28 additions & 15 deletions src/page/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ interface IGlobalState {
disabled: boolean | Promise<boolean>;
}

function _focusInput(global: IGlobalState, frameId: number, addListener: boolean) {
const firenvim = global.firenvimElems.get(frameId);
function _focusInput(global: IGlobalState, firenvim: FirenvimElement, addListener: boolean) {
if (addListener) {
// Only re-add event listener if input's selector matches the ones
// that should be autonvimified
Expand All @@ -25,20 +24,22 @@ function _focusInput(global: IGlobalState, frameId: number, addListener: boolean
firenvim.focusOriginalElement(addListener);
}

function getFocusedElement (firenvimElems: Map<number, FirenvimElement>) {
return Array
.from(firenvimElems.values())
.find(instance => instance.getSpan() === document.activeElement);
}

export function getFunctions(global: IGlobalState) {
return {
focusInput: (frameId: number) => {
let firenvimElement;
if (frameId === undefined) {
const pair = Array.from(global.firenvimElems.entries())
.find(([id, instance]) =>
instance.getSpan() === document.activeElement);
if (pair !== undefined) {
frameId = pair[0];
}
}
if (frameId !== undefined) {
_focusInput(global, frameId, true);
firenvimElement = getFocusedElement(global.firenvimElems);
} else {
firenvimElement = global.firenvimElems.get(frameId);
}
_focusInput(global, firenvimElement, true);
},
focusPage: () => {
(document.activeElement as any).blur();
Expand Down Expand Up @@ -72,13 +73,15 @@ export function getFunctions(global: IGlobalState) {
.get(frameId)
.getPageElementContent(),
hideEditor: (frameId: number) => {
global.firenvimElems.get(frameId).hide();
_focusInput(global, frameId, true);
const firenvim = global.firenvimElems.get(frameId);
firenvim.hide();
_focusInput(global, firenvim, true);
},
killEditor: (frameId: number) => {
global.firenvimElems.get(frameId).detachFromPage();
const firenvim = global.firenvimElems.get(frameId);
firenvim.detachFromPage();
const conf = getConf();
_focusInput(global, frameId, conf.takeover !== "once");
_focusInput(global, firenvim, conf.takeover !== "once");
global.firenvimElems.delete(frameId);
},
pressKeys: (frameId: number, keys: string[]) => {
Expand All @@ -90,6 +93,16 @@ export function getFunctions(global: IGlobalState) {
elem.putEditorCloseToInputOriginAfterResizeFromFrame();
},
registerNewFrameId: (frameId: number) => global.registerNewFrameId(frameId),
sendKey: (key: string) => {
const firenvim = getFocusedElement(global.firenvimElems);
if (firenvim !== undefined) {
firenvim.sendKey(key);
} else {
// It's important to throw this error as the background script
// will execute a fallback
throw new Error("No firenvim frame selected");
}
},
setDisabled: (disabled: boolean) => {
global.disabled = disabled;
},
Expand Down
6 changes: 6 additions & 0 deletions src/utils/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ export interface IConfig {
alt: "alphanum" | "all",
server: "persistent" | "ephemeral",
server_url: string,
"<C-n>": "default" | "noop",
"<C-t>": "default" | "noop",
"<C-w>": "default" | "noop",
"<CS-n>": "default" | "noop",
"<CS-t>": "default" | "noop",
"<CS-w>": "default" | "noop",
};
localSettings: { [key: string]: ISiteConfig };
}
Expand Down

0 comments on commit 8c73600

Please sign in to comment.