- Clone and copy (or symlink) this repo inside your userscripts dir (See Dir structure below).
- Make
quterofi/open
,quterofi/quteromarks
,quterofi/switch_engine
andquterofi/common
executable if needed. - Declare your engines and quteromarks in
quterofi.toml
(See Quterofi.toml section below). - Update your
config.py
file (See Config section below).
See the Dequterofi section for a script you can use to translate quterofi.toml
files into regular python search engines. This is useful if you decide to switch back after trying quterofi for a while. It can also be used to declare search engines generated from a quterofi.toml
file, without having to clone/download/install/use quterofi at all.
.
├── config.py
├── quickmarks
├── quterofi.toml
└── userscripts
└── quterofi
├── common
├── open
├── quteromarks
└── switch_engine
Quterofi provides users with the open, quteromarks and switch_engine userscripts (common
is a module providing utility functions to the other scripts)
quterofi/open
is a replacement for the :open
menu
quterofi/open [--newtab] [--string <arg>] [--invert] [--engines] [--history] [--quteromarks] [--autoaccept]
-
Call with
--newtab
to use:open -t
by default (It's also possible to switch later betweenopen
andopen -t
using -kb-custom-2) -
Call with
--string <arg>
to start writing with string ARG already set -
Call with
--invert
to treat the last word as a search engine alias. If your search string ishello world ddg
, the underlying command will be:open ddg hello world
, if and only if there is a search engine with aliasddg
declared in your quterofi.toml file. This behavior is for -kb-accept-entry, you can also accept your entry with -kb-custom-2 for your string to be interpreted verbatim (:open hello world ddg
). -
Call with
--engines
to open the engines menu directly (You can also enter this menu using -kb-custom-5 in the main menu) -
Call with
--history
to open the history menu directly (You can also enter this menu using -kb-custom-6 in the main menu) -
Call with
--quteromarks
to open the quteromarks menu directly (You can also enter this menu using -kb-custom-7 in the main menu). -
Call with
--autoaccept
for the search engine menu to automatically submit the:open
comand instead of setting the search engine alias in the search string of the main menu.
-
-kb-accept-entry (Any of
Ctrl+j
,Ctrl+m
,Return
,KP_Enter
):- If you call
quterofi/open
with--invert
, the last word in the search string will be used as a search engine (if and only if there is one with such alias). For example, if your search string ishello world ddg
, the underlying open command will be:open ddg hello world
. Alternatively, you can accept your entry with -kb-custom-1 for your string to be interpreted verbatim (:open hello world ddg
) - If you call
quterofi/open
without--invert
, -kb-accept-entry will:open
your search string, verbatim.
- If you call
-
-kb-cancel (Any of
Escape
,Control+g
,Control+bracketleft
):- Exit menu
-
-kb-custom-1 (
Alt+j
):- Accept entry as normal, without inversion.
ddg hello
->:open ddg hello
- Accept entry as normal, without inversion.
-
-kb-custom-2 (
Ctrl+o
):- Switch between
:open
and:open -t
without closing the menu or clearing user input.
- Switch between
-
-kb-custom-3 (
Alt+o
):- Set search string to be the current url. This is the same as calling
spawn -u quterofi/open --string {url:pretty}
- Set search string to be the current url. This is the same as calling
-
-kb-custom-4 (
Alt+u
):- Close menu, open the regular
:open
menu and set search string::open <search_string>
(search string is set verbatim, without inversion)
- Close menu, open the regular
-
-kb-custom-5 (
Alt+e
):- Open submenu listing all search engines, pick one to be set in your search string. The choosen alias will be set at the end or beginning of your search string, depending on whether you are using
--invert
or not, and indepently of cursor position. If there is already a valid search engine set, quterofi will replace it instead of stacking a new one.
- Open submenu listing all search engines, pick one to be set in your search string. The choosen alias will be set at the end or beginning of your search string, depending on whether you are using
-
-kb-custom-6 (
Alt+h
):- Open submenu listing history items.
-
-kb-custom-7 (Any of
Alt+q
,Alt+m
):- Open submenu listing quteromarks.
Call quterofi/quteromarks
to open a menu allowing to use, create, update and delete quteromarks.
quterofi/quteromarks [--delete_no_confirmation] [--string <arg>]
-
Call with
--delete_no_confirmation
to proceed deleting quteromarks without asking the user for confirmation. -
Call with
--string <arg>
to start writing with string ARG already set
The ^
automatically set on the input field is for the item list to filter out any item not starting with the text provided by the user after ^
. You can leave it or remove it if you want the item list to include any item having your input present in any position of the item's name or url. If you keep it, quterofi/quteromarks
will later ignore it, so using any of ^hello
or hello
will create a quteromark named hello
.
-
-kb-accept-entry (Any of
Ctrl+j
,Ctrl+m
,Return
,KP_Enter
):- Open the selected/highlited quteromark .
-
-kb-custom-1 (
Alt+d
)- Delete the selected/highlighted item in the quteromark list. Text written in the user input field is irrelevant when using this menu to delete existing quteromarks.
-
-kb-custom-2 (
Alt+r
)- Rename the selected/highlighted item in the quteromark list. Text written in the user input field is irrelevant when using this menu to rename existing quteromarks.
-
-kb-custom-3 (
Alt+c
)- Create a new quteromark for the current url, using the written user input (the
filter
, in rofi terminology) as name/alias. If there is no filter/written user input, the selected/highlighted item is used to overwrite/update the existing quteromark.
- Create a new quteromark for the current url, using the written user input (the
Python's standard toml lib (tomllib
) does not supports writing [source]. For this reason, quterofi/quteromarks
uses regexes to delete, rename and overwrite quteromarks. This supports some weird cases like in the following example, but it's not meant to be bulletproof. Inside each individual [[quteromarks]]
block empty lines are supported but lines with nothing but comments are not.
[[quteromarks]]
alias = "foo" # fooalias
url = "https://foo.com/" # foourl
[[quteromarks]]
url = 'https://bar.com/' # barurl
# A COMMENT HERE IS NOT SUPPORTED!!!
alias = 'bar' # baralias
In all cases (delete, rename, overwrite), the following expressions do the job:
(first_case_text, first_case_count) = re.subn(r'\[\[quteromarks\]\].*\n+ *alias *= *[\'"]' + alias + '[\'"].*\n+ *url *\=.*', "", text)
(second_case_text, second_case_count) = re.subn(r'\[\[quteromarks\]\].*\n+ *url *\=.*\n+ *alias *= *[\'"]' + alias + '[\'"].*', "", first_case_text)
Call quterofi/switch_engine
to open a menu asking for the alias of a new search engine to open the search string present in your current url, if there is a matching engine for your current url. See examples below.
quterofi/switch_engine [--newtab] [--edit] [--main_invert] [--main_autoaccept]
- Call with
--newtab
to use:open -t
instead of:open
. - Call with
--edit
to edit the search string and engine alias in the main menue. - Call with
--main_invert
if you want--edit
to call the main menu with--invert
. - Call with
--main_autoaccept
if you want--edit
to call the main menu with--autoaccept
.
- Current url:
https://www.google.com/search?q=hello world
- New engine selected by calling
quterofi/switch_engine
:ghi.qr
- Automagically redirects to:
https://github.com/cortsf/quterofi/issues?q=hello world
- Current url:
https://www.google.com/search?q=hello world
- Calling
quterofi/switch_engine
with--edit
will callquterofi/open
with the stringgl hello world
orhello world gl
if you use--main_invert
in addition to--edit
. This allows you to edit both the search engine and the search string.
- -kb-accept-entry (Any of
Ctrl+j
,Ctrl+m
,Return
,KP_Enter
) - -kb-cancel' (Any of
Escape
,Control+g
,Control+bracketleft
)
Declare your search engines and quteromarks in quterofi.toml
(See Dir structure) using this format.
This format allows users to create custom "templates" (quite an abstract concept in this context..) instructing quterofi how to generate search engines and quteromarks. Note that you can declare/define any variable to construct urls, except for alias
which is reserved to be used exclusively to construct aliases, and it's in fact, mandatory to declare for every engine/quteromark declaration, and it's also the only variable available to construct aliases (This may change in the future allowing any variable name and number to be used both for urls and aliases).
- Rules declare templates (Think of templates as the set of column names in an imaginary table, each
{variable}
used on a given rule declaring a column name, and each rule declaring a new template or imaginary table with a name). Multiple rules/templates can use the same template name. - Each individual engine/quteromark declaration make use of one or many rule/template to insert a row on each of these imaginary table/s. This is done by providing a
[[template_name]]
which (as stated before) can be shared by many rules/templates. - The information on each of these imaginary tables is used by quterofi to generate a list of engines and quteromarks, by replacing variables (
{variables}
in the toml rules, or column names on this imaginary table) with values (fields on this imaginary table)
Trivial example:
# Declare a single template/table:
[[engine_rules]]
er_template = "github_repos" # <-- Give this template a (not necessarily unique) name (mandatory)
er_alias = "gh.{alias}" # <-- Give this template the means to create aliases (mandatory)
er_url = "https://github.com/search?q=repo%3A{user}%2F{repo}+{}&type=issues" # <-- Give this template the means to create urls by later replacing any number of user-provided `{variables}` (mandatory).
# Insert a new row on this imaginary table:
[[github_repos]] # <-- Use the previously declared template name (mandatory)
alias = "lnx" # <-- Mandatory
user = "torvalds" # <-- Mandatory for this particular `[[engine_rules]]`
repo = "linux" # <-- Mandatory for this particular `[[engine_rules]]`
# Insert another row on this imaginary table:
[[github_repos]] # same
alias = "qr" # same
user = "cortsf" # same
repo = "quterofi" #same
Imaginary table for this example:
{alias} | {user} | {repo} |
---|---|---|
lnx | torvalds | linux |
qr | cortsf | quterofi |
Resulting engines after replacing all {variables}
with the information on the fields of this imaginary table:
{"gh.lnx": "https://github.com/search?q=repo%3Atorvalds%2Flinux+{}&type=issues"}
{"gh.qr": "https://github.com/search?q=repo%3Acortsf%2Fquterofi+{}&type=issues"}
This rule is needed for quterofi/quteromarks
to manage quteromarks using the provided UI
[[quteromark_rules]]
qr_template = "quteromarks"
qr_alias ="{alias}"
qr_url = "{url}"
Using the quterofi/quteromarks
menu to create a new quteromark, will simply append the following code block, which depends on the previous [[quteromark_rules]]
to work.
[[quteromarks]]
alias="new_quoteromark"
url="https://new_quteromark.com/"
Brief example skipping many useful rules like ghic
, ghpc
or ghd
, for github closed issues, closed pulls and discussions, respectively. For brevity. See more github rules on this gist
## Engine rules
####################
[[engine_rules]]
er_template = "search_engines"
er_alias = "{alias}"
er_url = "{url}"
[[engine_rules]]
er_template = "wikipedia_languages"
er_alias ="wp.{alias}"
er_url = "https://{lang}.wikipedia.org/wiki/{}"
[[engine_rules]]
er_template = "github_repos"
er_alias = "gh.{alias}"
er_url = "https://github.com/search?q=repo%3A{user}%2F{repo}+{}&type=issues"
[[engine_rules]]
er_template = "github_repos"
er_alias ="ghi.{alias}"
er_url = "https://github.com/{user}/{repo}/issues?q={}"
[[engine_rules]] # Use this one to open individual issues, directly, by number.
er_template = "github_repos"
er_alias ="ghin.{alias}"
er_url = "https://github.com/{user}/{repo}/issues/{}"
[[engine_rules]]
er_template = "github_repos"
er_alias ="ghp.{alias}"
er_url = "https://github.com/{user}/{repo}/pulls?q={}"
[[engine_rules]] # Use this one to open individual pr's, directly, by number.
er_template = "github_repos"
er_alias ="ghpn.{alias}"
er_url = "https://github.com/{user}/{repo}/pull/{}"
## Quteromark rules
####################
[[quteromark_rules]]
qr_template = "quteromarks"
qr_alias ="{alias}"
qr_url = "{url}"
[[quteromark_rules]]
qr_template = "github_repos"
qr_alias ="gh.{alias}"
qr_url = "https://github.com/{user}/{repo}"
[[quteromark_rules]]
qr_template = "github_repos"
qr_alias ="ghi.{alias}"
qr_url = "https://github.com/{user}/{repo}/issues"
[[quteromark_rules]]
qr_template = "github_repos"
qr_alias ="ghp.{alias}"
qr_url = "https://github.com/{user}/{repo}/pulls"
## Search engines
####################
[[search_engines]]
alias = "ddg"
url = "https://duckduckgo.com/?q={}&ia=web"
[[search_engines]]
alias = "gl"
url = "https://www.google.com/search?q={}"
## Github repos
####################
[[quteromarks]]
alias="gh"
url="https://github.com/"
[[github_repos]]
alias = "qr"
user = "cortsf"
repo = "quterofi"
[[github_repos]]
alias = "lnx"
user = "torvalds"
repo = "linux"
## Wikipedia
####################
[[wikipedia_languages]]
alias = "en"
lang = "en"
[[wikipedia_languages]]
alias = "es"
lang = "es"
{"ddg": "https://duckduckgo.com/?q={}&ia=web"}
{"gl": "https://www.google.com/search?q={}"}
{"wp.en": "https://en.wikipedia.org/wiki/{}"}
{"wp.es": "https://es.wikipedia.org/wiki/{}"}
{"gh.qr": "https://github.com/search?q=repo%3Acortsf%2Fquterofi+{}&type=issues"}
{"gh.lnx": "https://github.com/search?q=repo%3Atorvalds%2Flinux+{}&type=issues"}
{"ghi.qr": "https://github.com/cortsf/quterofi/issues?q={}"}
{"ghi.lnx": "https://github.com/torvalds/linux/issues?q={}"}
{"ghin.qr": "https://github.com/cortsf/quterofi/issues/{}"}
{"ghin.lnx": "https://github.com/torvalds/linux/issues/{}"}
{"ghp.qr": "https://github.com/cortsf/quterofi/pulls?q={}"}
{"ghp.lnx": "https://github.com/torvalds/linux/pulls?q={}"}
{"ghpn.qr": "https://github.com/cortsf/quterofi/pulls/{}"}
{"ghpn.lnx": "https://github.com/torvalds/linux/pulls/{}"}
{"gh": "https://github.com/"}
{"gh.qr": "https://github.com/cortsf/quterofi"}
{"ghi.qr": "https://github.com/cortsf/quterofi/issues"}
{"ghp.qr": "https://github.com/cortsf/quterofi/pulls"}
{"gh.lnx": "https://github.com/torvalds/linux"}
{"ghi.lnx": "https://github.com/torvalds/linux/issues"}
{"ghp.lnx": "https://github.com/torvalds/linux/pulls"}
In this particular example, the more [[github_repos]]
you declare, the more you benefit since you'll have for free all of those rules using er_template = "github_repos"
generating many (uniformly prefixed) engines and quteromarks for you.
In your config.py include code below. Be sure to set <username>
.
qbdir = "/home/<username>/.config/qutebrowser"
exec(open(qbdir + '/userscripts/quterofi/common').read())
with open(qbdir + "/quterofi.toml", "rb") as f:
all_engines = parse_engines(tomllib.load(f))
for alias, url in all_engines.items():
c.url.searchengines[alias] = url
config.bind('o', 'spawn -u quterofi/open --invert --autoaccept')
config.bind('<Ctrl-o>', 'spawn -u quterofi/open --invert --newtab --autoaccept')
config.bind('<Shift-o>', 'spawn -u quterofi/open --invert --newtab --autoaccept')
config.bind('<Alt-o>', 'spawn -u quterofi/open --invert --string {url:pretty}')
config.bind('<Alt-Shift-o>', 'spawn -u quterofi/open --invert --newtab --string {url:pretty}')
config.bind('<Alt-h>', 'spawn -u quterofi/open --history')
config.bind('<Alt-Shift-h>', 'spawn -u quterofi/open --history --newtab')
config.bind('<Alt-q>', 'spawn -u quterofi/open --quteromarks')
config.bind('<Alt-Shift-q>', 'spawn -u quterofi/open --quteromarks --newtab')
config.bind(',o', 'spawn -u -m quterofi/switch_engine')
config.bind(',O', 'spawn -u -m quterofi/switch_engine --newtab')
config.bind(',e', 'spawn -u -m quterofi/switch_engine --edit --main_autoaccept')
config.bind(',E', 'spawn -u -m quterofi/switch_engine --edit --newtab --main_autoaccept')
config.bind(',y', 'mode-enter caret;;yank selection;;cmd-later 40 spawn -u -m quterofi/open --engines --autoaccept --string {clipboard}') # Use when searching text
config.bind(',y', 'yank selection;;cmd-later 40 spawn -u -m quterofi/open --engines --autoaccept --string {clipboard}', mode="caret")
These are standard non-quterofi bindings that play well with the above keybindings.
config.bind("e", "cmd-set-text -s :open ")
config.bind("E", "cmd-set-text -s :open -t ")
config.bind("<Alt-e>", "cmd-set-text -s :open {url:pretty}")
config.bind("<Alt-Shift-e>", "cmd-set-text -s :open -t {url:pretty}")
#!/usr/bin/env bash
export QUTE_CONFIG_DIR="$HOME/.config/qutebrowser"
export QUTE_FIFO="/tmp/quterofi_launcher_fifo"
> "$QUTE_FIFO"
"$QUTE_CONFIG_DIR"/userscripts/quterofi/open "$@"
[[ -n "$(cat "$QUTE_FIFO")" ]] && qutebrowser "$(cat "$QUTE_FIFO")"
rm "$QUTE_FIFO"
You may want to add some (system-dependent..) code to switch window focus automatically.
This function opens the quterofi/open
script showing the list of search engines. As soon as you pick one, it will use this engine to search the text inside the emacs region, or the word-at-point
, if there is no active region.
This function needs the External launcher.
(defun quterofi () (interactive)
(shell-command (format "/path/to/quterofi_launcher.sh --newtab --engines --autoaccept --string %s"
(if (use-region-p)
(buffer-substring (mark) (point))
(word-at-point)
)
)
)
)
Use this script to translate quterofi.toml files into python code you can source or paste in your config.py
. Needs toml2json
and jq
available on $PATH
to work.
#!/usr/bin/env bash
# USAGE: decuterofi.sh /path/to/quterofi.toml > engines.py
# DEPS: toml2json, jq
json="$(toml2json "$1")"
echo "$json" | jq -r '.engine_rules.[] | .er_template + " " + .er_alias + " " + .er_url' | while IFS=' ' read -r er_template er_alias er_url; do
url_variables="$(echo "$er_url" | grep -o '{[a-zA-Z0-9]*}' | grep -v "{}" | tr -d "{}")"
while read -r engine_item; do
if [ -n "$engine_item" ]; then
new_url="$er_url"
new_alias="$(echo "${er_alias}" | sed "s/{alias}/$(echo "$engine_item" | jq -r .alias)/")"
while read -r url_var; do
new_url="$(echo "${new_url}" | sed "s|{${url_var}}|$(echo "$engine_item" | jq -r ."${url_var}" | sed 's/&/\\\&/g')|")"
done <<<"$url_variables"
echo "c.url.searchengines[\"$new_alias\"] = \"$new_url\""
fi
done <<<"$(echo "$json" | jq -c ".${er_template}[]?")"
done
For the above Example, this program produces the following engines:
c.url.searchengines["ddg"] = "https://duckduckgo.com/?q={}&ia=web"
c.url.searchengines["gl"] = "https://www.google.com/search?q={}"
c.url.searchengines["wp.en"] = "https://en.wikipedia.org/wiki/{}"
c.url.searchengines["wp.es"] = "https://es.wikipedia.org/wiki/{}"
c.url.searchengines["gh.qr"] = "https://github.com/search?q=repo%3Acortsf%2Fquterofi+{}&type=issues"
c.url.searchengines["gh.lnx"] = "https://github.com/search?q=repo%3Atorvalds%2Flinux+{}&type=issues"
c.url.searchengines["ghi.qr"] = "https://github.com/cortsf/quterofi/issues?q={}"
c.url.searchengines["ghi.lnx"] = "https://github.com/torvalds/linux/issues?q={}"
c.url.searchengines["ghin.qr"] = "https://github.com/cortsf/quterofi/issues/{}"
c.url.searchengines["ghin.lnx"] = "https://github.com/torvalds/linux/issues/{}"
c.url.searchengines["ghp.qr"] = "https://github.com/cortsf/quterofi/pulls?q={}"
c.url.searchengines["ghp.lnx"] = "https://github.com/torvalds/linux/pulls?q={}"
c.url.searchengines["ghpn.qr"] = "https://github.com/cortsf/quterofi/pull/{}"
c.url.searchengines["ghpn.lnx"] = "https://github.com/torvalds/linux/pull/{}"
Source from engines.py
with:
config.source('./engines.py')
Or just copy the contents inside your config.py
#!/usr/bin/env bash
cat "$1" | while read -r line; do
echo -e "\n[[quteromarks]]" >> quteromarks.toml
echo -e "alias=\"$(echo "$line" | sed 's/ [^ ]*//')\"" >> quteromarks.toml
echo -e "url=\"$(echo "$line" | awk '{print $NF}')\"" >> quteromarks.toml
done
This script will write quteromarks into quteromarks.toml
, to prevent user from (potentially..) making accidental mistakes. Once the conversion is done, verify and copy the contents of this file into quterofi.toml
, since quterofi (for now) only supports this file. See Dir structure.