External editor

gwurr3 edited this page Dec 22, 2015 · 5 revisions

External editor

There are two different ways to integrate an external editor: the blocking way, in which luakit is disabled for the duration of the external edit, and the non-blocking way, in which only the relevant text input is disabled.

The non-blocking way is a bit long and uses a bogus class to mark the edited element, but all in all it's pretty neat. Inspiration and advice from http://luakit.org/issues/97#change-193

Non-blocking method

Bind a key in config/binds.lua, I guess adding an insert bind makes the most sense:

key({"Control"},  "e",       function (w)
  local editor = "urxvt -e vi -c 'set spell'" 
  local dir = "/existing/dir/with/trailing/slash/" 
  local time = os.time()
  local file = dir .. time
  local marker = "luakit_extedit_" .. time
  local function editor_callback(exit_reason, exit_status)
    f = io.open(file, "r")
    s = f:read("*all")
    f:close()
    -- Strip the string
    s = s:gsub("^%s*(.-)%s*$", "%1")
    -- Escape it but remove the quotes
    s = string.format("%q", s):sub(2, -2)
    -- lua escaped newlines (slash+newline) into js newlines (slash+n)
    s = s:gsub("\\\n", "\\n")
    w.view:eval_js(string.format([=[
    var e = document.getElementsByClassName('%s');
    if(1 == e.length && e[0].disabled){
      e[0].focus();
      e[0].value = "%s";
      e[0].disabled = false;
      e[0].className = e[0].className.replace(/\b %s\b/,'');
    }
    ]=], marker, s, marker))
  end

  local s = w.view:eval_js(string.format([=[
  var e = document.activeElement;
  if(e && (e.tagName && 'TEXTAREA' == e.tagName || e.type && 'text' == e.type)){
    var s = e.value;
    e.className += " %s";
    e.disabled = true;
    e.value = '%s';
    s;
  }else 'false';
  ]=], marker, file))
  if "false" ~= s then
    local f = io.open(file, "w")
    f:write(s)
    f:flush()
    f:close()
    luakit.spawn(string.format("%s %q", editor, file), editor_callback)
  end
end),

Blocking method

This is a simple bind I added to edit text fields in an external editor. For me, an added feature is saving all that text in separate fields. Also, not being very familiar with lua, I bet there are much better ways to go about it. This is how I did it.

Added this to config/binds.lua:

key({"Control"},  "e",       function (w)
  local s = w.view:eval_js("document.activeElement.value")

  local n = "some/dir/" .. os.time()
  local f = io.open(n, "w")
  f:write(s)
  f:flush()
  f:close()

  luakit.spawn_sync("urxvt -e vi -c \"set spell\" \"" .. n .. "\"")

  f = io.open(n, "r")
  s = f:read("*all")
  f:close()
  os.remove(n)
  -- Strip the string
  s = s:gsub("^%s*(.-)%s*$", "%1")
  -- Escape it but remove the quotes
  s = string.format("%q", s):sub(2, -2)
  -- lua escaped newlines (slash+newline) into js newlines (slash+n)
  s = s:gsub("\\\n", "\\n")
  w.view:eval_js("document.activeElement.value = '" .. s .. "'")
end),

So now, when a text field is focused I can hit ctrl-e to do the following:

  1. Get the text of the active element
  2. Write it into a file in some/dir (with current timestamp for a name)
  3. Open the file in an external editor (vim with spellcheck enables in urxvt in my case) and wait for it to exit
  4. Read the new text from the file
  5. Put the new text in the active element, sans the ending newline, automatically escaping everything to fit in quotes using string.format (as per Mason's advice - http://luakit.org/issues/97#change-193)

Still somewhat convoluted, I agree, but works. Note that this won't work if you have a terminal emulator that returns immediately when called. This will cause your text field to stay empty, as you will not have had a chance to write anything.