Wednesday, 12 January 2011

Conkeror - browsing the web, emacs-style

As noted below, I've switched from Icecat/Firefox+Pentadactyl/Vimperator to Conkeror. Trying to retool the essentially vi-ish shortcuts to emacs-like shortcuts is rather difficult, and in the end I wasn't able to change some of the shortcuts I wanted to change.  Anyway, Conkeror is simply more interesting: more of a from-the-ground-up approach than an add-on approach.

This post isn't intending as a general intro to Conkeror; if that's what you're looking for, see here or here, or, if you're running Conkeror, press C-h t (that's Ctrl and h at the same time, release, then t) for a tutorial.

Rather like Emacs, Conkeror gets much better than more one personalises/configures it. Some good places to start are on the Conkeror site itself, on Tips from Users page and also the emacs-fu article "conkeror web browsing the emacs way".

In the same spirit, I offer a number of useful customisations I've adopted, some from the two above-mentioned sources, some from other places (including from suggestions on the Conkeror mailling list), and some of my own.

Some initial things:
//allow for 'contrib' stuff
load_paths.unshift("chrome://conkeror-contrib/content/");

// Mode-line
mode_line_mode(true);

// auto completion in the minibuffer
minibuffer_auto_complete_default = true;
url_completion_use_history = true; // should work since bf05c87405
url_completion_use_bookmarks = true;

// display the url before going to it in hints mode
hints_display_url_panel = true;

These are settings taken from here and there. Turning on the mode-line, allowing the url completion (very handy) to access history and bookmarks, and displaying the url of a link (in hints mode).

Customising the mode-line:
// FAVICONS
 require("favicon.js");
add_hook("mode_line_hook", mode_line_adder(buffer_icon_widget), true);
read_buffer_show_icons = true;

// we'd like to see the # of buffers being loaded 
add_hook("mode_line_hook", mode_line_adder(loading_count_widget), true);

// but really we'd also like to know how many buffers are present and which is the current
add_hook("mode_line_hook", mode_line_adder(buffer_count_widget), true);

// remove the clock
remove_hook("mode_line_hook", mode_line_adder(clock_widget));

The first allows for favicons in both mode-line and the C-x b buffer list, the second shows how many buffers are currently still loading, the third shows how many buffers are present and which buffer is the current buffer, the last simply disables the clock. The order of the first three is actually important; this order puts the buffer count on the far left, then the "loading buffer" count, then the favicon, (and then the url of the current buffer).

Tabs & Mouse:
// Tabs
require("new-tabs.js");

//Open Middle-Clicked Links in New Buffers
require("clicks-in-new-buffer.js");
clicks_in_new_buffer_target = OPEN_NEW_BUFFER_BACKGROUND; // Now buffers open in background.

//Bind Number Keys to Switch to Buffers 1-10
function define_switch_buffer_key (key, buf_num) {
    define_key(default_global_keymap, key,
               function (I) {
                   switch_to_buffer(I.window,
                                    I.window.buffers.get_buffer(buf_num));
               });
}
for (let i = 0; i < 10; ++i) {
    define_switch_buffer_key(String((i+1)%10), i);
}
I like having tabs, and the first bit above turns on the tabs, with numbering (but no favicons). I still find it useful to use the mouse from time to time. What's annoying is having to switch from keyboard to mouse or vice-versa. So if I'm using the mouse, I want it to be effective. The second bit above opens new buffers/tabs on middle-clicked links (in background buffers). The last bit allow for quick switching between tabs (the first 10 anyway) using 1-10 (where 0=10). Other useful things to know (though not dependent on this particular configuration): tabs can be closed directly with right click; you can scroll through your tabs by positioning the mouse pointer on the tab bar and using the mouse wheel.

External editor
//set emacs as external editor
editor_shell_command = "emacsclient -c";

// view source in your editor.
view_source_use_external_editor = true;

Set emacs as an external editor (assuming you're running emacs as a daemon), etc.

Managing/Navigating Tab-Buffers
// redefine C-f as "forwards" and C-b as "backwards"
// using F and B (that is Shift+F, Shift+B is actually rather inconvenient since 
// many other command use Control and so requires shifting fingers)
define_key(content_buffer_normal_keymap, "C-f", "forward");
define_key(content_buffer_normal_keymap, "C-b", "back");

// make M-f and M-b switch to next and previous buffers
define_key(content_buffer_normal_keymap, "M-f", "buffer-next");
define_key(content_buffer_normal_keymap, "M-b", "buffer-previous");

// redefine l as "follow link" (like f)
// (too many of the keys are for the left hand, I like "l" for "link")
define_key(content_buffer_normal_keymap, "l", "follow");

// Use M-l to follow link in new background buffer
define_key(default_global_keymap, "M-l", "follow-new-buffer-background");

// open url in new background buffer  (I can't think of a good keybinding for this)
interactive("find-url-new-background-buffer",
    "Open a URL in a new background buffer",
    alternates(follow_new_buffer_background, follow_new_window),
    $browser_object = browser_object_url,
    $prompt = "Find url");

The default keys for navigate forwards and navigate backwards are F and B, respectively. I imagine there are historic reasons behind this (it's how W3 does it), but I find it annoying to have to switch from Ctrl to Shift (since most of the other keybindings involve Ctrl), so the initial bit above binds C-f and C-b to forwards and backwards (F and B still work too). And I bound M-f and M-b to "move to next tab-buffer", "move to previous tab-buffer", respectively. These seem to me to make sense as extensions of the normal behaviour of C-f, C-b, M-f, M-b, in Emacs. [Note: These bindings don't affect the how these keys behave in text boxes. All of these keys retain their expected Emacs-ish behaviour in text boxes.]

I prefer using l for following links in hint mode, so that's another binding added above (f still works too). And I added M-l as "follow link in new background buffer" (works like middle-click).

The last bit is a new command for opening URLs in new background buffers, but I haven't thought of a good keybinding for it so far.

Yanking
// use M-y to google current selection in new buffer
// use M-Y to google current selection in new buffer "double-quoted"

// [ref: http://www.mozdev.org/pipermail/conkeror/2009-February/001334.html ]
// (See also "**c" for selecting text)
interactive("search-clipboard-contents", "Search in Google the content of the X clipboard (the selected text)",
              "find-url",
              $browser_object=
              function(I) {
                  return "g "+ read_from_x_primary_selection();
              }
);
interactive("search-clipboard-contents-doublequoted", "Search in Google the content of the X clipboard (the selected text) as a fixed string",
              "find-url",
              $browser_object=
              function(I) {
                  return "g \""+ read_from_x_primary_selection()+"\"";
              }

);
define_key(content_buffer_normal_keymap, "M-y", "search-clipboard-contents");
define_key(content_buffer_normal_keymap, "M-Y", "search-clipboard-contents-doublequoted");

By default, (outside of text boxes, where, again, Conkeror has the expected Emacs-ish behaviour), M-w copies the selected area. And C-y opens whatever's in the clipboard (I would say kill-ring, but it's not) in the current buffer---assuming that what's in the clipboard is a URL. The above code adds two new commands, performing a Google search on the current clipboard contents, and performing a Google search on the current clipboard contents double-quoted. I bound these to M-y, and M-Y, respectively.

Oh, in general, adding a C-u prefix results in the command being executed in a new buffer. So C-u M-y performs a Google search on the clipboard contents in a new (focussed) buffer.

More buffer management/misc. bindings
// make C-c C-c "submit form"
define_key(content_buffer_normal_keymap, "C-c C-c", "submit-form");

// make C-x 0 "kill current buffer"
define_key(default_global_keymap, "C-x 0", "kill-current-buffer");

// make C-x 1 "kill other buffers"
define_key(content_buffer_normal_keymap, "C-x 1", "kill-other-buffers");

// make C-x 2 "duplicate buffer"
interactive("duplicate-buffer", "Duplicate buffer",
            function (I) {
              browser_object_follow(I.buffer, OPEN_NEW_BUFFER, I.buffer.current_uri.spec);
            });
define_key(content_buffer_normal_keymap, "C-x 2", "duplicate-buffer");

Use C-c C-c to submit forms.

Now Conkeror doesn't have "windows" in the Emacs-sense. It has buffers/tabs. So the above commands depart somewhat from being truly parallel with Emacs behaviour, since in Emacs these commands all involve window management. But I still find it useful to have some keybinding for these commands, and these make a sort of sense to me. C-x 0 closes the current tab-buffer (C-x k is the command for "close buffer" in general; it defaults to the current buffer, but requires another keystroke (Enter)); C-x 1 closes all tab-buffers except for the current one; C-x 2 duplicates the current tab-buffer in a new tab.

Set default download directory
// cwd
cwd=get_home_directory(); 
cwd.append("Downloads"); 

This sets the default download directory to "~/Downloads". "cwd" is useful, and can be used cross-platform. It gets the "home directory". The last line obviously appends "Downloads" to the home directory location.


Misc.
// xkcd add mouse-over text
xkcd_add_title = true;

// No new window for downloads
download_buffer_automatic_open_target=OPEN_NEW_BUFFER_BACKGROUND;

// Make sure I don't close by accident
 add_hook("before_quit_hook",
           function () {
               var w = get_recent_conkeror_window();
               var result = (w == null) ||
                   "y" == (yield w.minibuffer.read_single_character_option(
                       $prompt = "Quit Conkeror? (y/n)",
                       $options = ["y", "n"]));
               yield co_return(result);
           });

can_kill_last_buffer = false;

Show xkcd mouse-over text on the page below the cartoon (it would be nice to have another way of handling mouse-over text though).

The second command handles how the download info buffer is handled; here it opens in a new tab rather than opening a new window ("frame" in the Emacs sense).

The last bits makes sure that if by accident I hit C-x C-c rather than C-x C-v it doesn't close everything without asking me, and then final line disallows killing of the last buffer.

Webjumps
Webjumps are a useful feature of Conkeror. In the mini-buffer, instead of typing a url, one can type a webjump. A number of such webjumps are built-in to Conkeror already. For example, "google emacs" will open a google search for "emacs". Actually, "g emacs" will do the same thing, and that's preferable since it's relatively quick to type. Also, "wikipedia emacs" will open the wikipedia page on "emacs", etc. Conkeror also already has webjumps designed for use with del.icio.us (press C-h i for more info).

And you can define your own webjumps. Here's a good page of some other useful webjumps you can add. Webjumps are not limited simply to site names, but can include javascript code as well. Here are a couple relatively simple ones I wrote for adding favourites to Stumbleupon and adding stories to Digg:


// Define add favourite to Stumbleupon
 define_webjump("astumble","javascript:location.href='http://www.stumbleupon.com/submit?url='+encodeURIComponent(location.href)+'&title='+"+"encodeURIComponent(document.title);");

// Define Digg story
define_webjump("adigg","javascript:location.href='http://www.digg.com/submit?url='+encodeURIComponent(location.href);");


And here's a screenshot:


12 comments :

  1. Hi!!:.sorry but I can't config my conkeror..I don't know where is my config file..if it's the same as .emacs must be inside "C:\Documents and Settings\me\Datos de programa" but I create this file inside..paste the config code and open the conkeror and nothing happen...

    is possible open the config file using conkeror and edit using it..similar to .emacs and emacs?

    thanks

    ReplyDelete
  2. Hi coco,

    I really haven't used Conkeror under Windows before, so I'm not sure (from your path, I'm assuming you're using Windows).

    Yes, there is a config file. It's just a text file, so it's easiest to edit it in some text editor (I use Emacs for this purpose). Under Linux the config file is called by default ".conkerorrc" and is in the user home directory.

    For Windows, I think you need to explicitly set the location of the file. Check here: http://conkeror.org/ConkerorRC#ChangingtheRCLocation

    ReplyDelete
  3. Hi again...I can fix the error..I open about:config and restart to default the conkeror.rcfile.

    is really great, I can save to readitlater or use google translate o instapaper only with keys combination..really great

    only a question...I don't like use C-y for paste..is possible a "cua-mode" with conkror?...I don't know how copy text with it...

    thank so much

    ReplyDelete
  4. As a note, I found that readitlater has stopped working with the conkeror bindings, and have switched to using instapaper (which I find I like better anyway).

    On "CUA-mode": I don't know. It's unfortunately not as simple as just rebinding, since the CUA-mode bindings conflict with lots of standard bindings. In Emacs, the way it works is that (most of?) the CUA-mode bindings (C-x for cut etc) only take affect when there is an active region (something highlighted/selected).

    I'll see what I can find out.

    ReplyDelete
  5. Hi again...well I've tried use C-c c and C-c v but it seems doesn't work

    define_key(default_global_keymap, "C-x c", "cmd_copy");
    define_key(default_global_keymap, "C-x v", "paste-x-primary-selection");

    It's hard found information and configs for conqueror, only you and the conkeror page have help me in config this browser :D

    ReplyDelete
  6. You might try "content_buffer_normal_keymap" instead of "default_global_keymap" (w/o quotes). I don't know if it will work or not. (Just as a note, you mention C-c c & C-c v, but define in your code C-x c and C-x v.)

    Still, using a prefix seems non-ideal; really one would want to imitate Emacs's CUA-mode.

    There's also a mailing list/forum for Conkeror here: http://old.nabble.com/MozDev---conkeror-f11304.html

    ReplyDelete
  7. How to download using Conkeror when the download link is hidden behind a Rt Click menu?
    As an example consider emacswiki and the need to download .el files. This page http://www.emacswiki.org/emacs/help-fns%2b.el has a green download button which the user Rt Clicks on in Firefox to get a "Save Link As" function. How to do something similar in Conkeror? It would be nice to have a both keyboard and mouse solutions.

    ReplyDelete
  8. In Conkeror, type "s" and then then type the number of the link you want to download, and then ENTER, and then input the save path and then ENTER. (In the example page you mention, the link appears to be no. 14).

    (In Conkeror, I don't think there is a mouse-y solution to this problem. But, as I understand it, the Conkeror team is focussed on keyboard support first, as that is one of the main focusses of the project. Full-blown mouse support is a 2.0 target, as I recall.)

    ReplyDelete
    Replies
    1. This saved the file 'help-fns+.el' with the name 'help-fns+.html'. Is this a bug or do we need a bit of helper code to get the file name or ???

      Thanks
      Mike

      Delete
    2. I think it's trying to do some sort of file-type detection. I don't know if there's a way to override this. But even for this example, in the minibuffer area, you see can that it says it is saving file ....help-fns+.el, and then you can edit the "save as" name as you desire.

      Delete
  9. In Conkeror, how do we get CUA style bindings for copy (C-c), paste (C-v) and possibly cut (C-x ), during Gmail rather than the EMACS bindings? Understand cut as C-x is harder because it is a prefix.

    ReplyDelete
  10. I love ya man. I'm new to Conkeror and loving it, but you just made my experience of it skyrocket. I arrived here looking for a Pocket/RIL solution and found so much more. Thanks.

    ReplyDelete