Add -DDEVELOPMENT_PATHS to toggle searching of ./lib & ./rc.lua
[luakit.git] / rc.lua
blob8b491ad994c2e74b3073e8f208aefe0cd8a49566
1 -- Luakit configuration file, more information at http://luakit.org/
3 require("math")
4 require("mode")
5 require("bind")
7 -- Widget construction aliases
8 function eventbox() return widget{type="eventbox"} end
9 function hbox() return widget{type="hbox"} end
10 function label() return widget{type="label"} end
11 function notebook() return widget{type="notebook"} end
12 function vbox() return widget{type="vbox"} end
13 function webview() return widget{type="webview"} end
14 function window() return widget{type="window"} end
15 function entry() return widget{type="entry"} end
17 -- Variable definitions
18 HOMEPAGE = "http://luakit.org/"
19 --HOMEPAGE = "http://github.com/mason-larobina/luakit"
20 SCROLL_STEP = 20
21 MAX_CMD_HISTORY = 100
22 MAX_SRCH_HISTORY = 100
23 --HTTPPROXY = "http://example.com:3128"
25 -- Setup download directory
26 DOWNLOAD_DIR = (os.getenv("HOME") or ".") .. "/downloads"
27 os.execute(string.format("mkdir -p %q", DOWNLOAD_DIR))
29 -- Luakit theme
30 theme = theme or {
31 -- Default settings
32 font = "monospace normal 9",
33 fg = "#fff",
34 bg = "#000",
36 -- General settings
37 statusbar_fg = "#fff",
38 statusbar_bg = "#000",
39 inputbar_fg = "#000",
40 inputbar_bg = "#fff",
42 -- Specific settings
43 loaded_fg = "#33AADD",
44 tablabel_fg = "#999",
45 tablabel_bg = "#111",
46 selected_tablabel_fg = "#fff",
47 selected_tablabel_bg = "#000",
49 -- Enforce a minimum tab width of 30 characters to prevent longer tab
50 -- titles overshadowing small tab titles when things get crowded.
51 tablabel_format = "%-30s",
54 widget.add_signal("new", function(wi)
55 wi:add_signal("init", function(wi)
56 if wi.type == "window" then
57 wi:add_signal("destroy", function ()
58 -- Call the quit function if this was the last window left
59 if #luakit.windows == 0 then luakit.quit() end
60 end)
61 end
62 end)
63 end)
65 -- Search engines
66 search_engines = {
67 google = "http://google.com/search?q={0}",
68 imdb = "http://imdb.com/find?s=all&q={0}",
69 sourceforge = "http://sf.net/search/?words={0}"
72 -- Add key bindings to be used across all windows
73 mode_binds = {
74 -- bind.buf(Pattern, function (w, buffer, opts) .. end, opts),
75 -- bind.key({Modifiers}, Key name, function (w, opts) .. end, opts),
76 all = {
77 bind.key({}, "Escape", function (w) w:set_mode() end),
78 bind.key({"Control"}, "[", function (w) w:set_mode() end),
80 normal = {
81 bind.key({}, "i", function (w) w:set_mode("insert") end),
82 bind.key({}, ":", function (w) w:set_mode("command") end),
84 -- Scrolling
85 bind.key({}, "h", function (w) w:scroll_horiz("-"..SCROLL_STEP.."px") end),
86 bind.key({}, "j", function (w) w:scroll_vert ("+"..SCROLL_STEP.."px") end),
87 bind.key({}, "k", function (w) w:scroll_vert ("-"..SCROLL_STEP.."px") end),
88 bind.key({}, "l", function (w) w:scroll_horiz("+"..SCROLL_STEP.."px") end),
89 bind.key({}, "Left", function (w) w:scroll_horiz("-"..SCROLL_STEP.."px") end),
90 bind.key({}, "Down", function (w) w:scroll_vert ("+"..SCROLL_STEP.."px") end),
91 bind.key({}, "Up", function (w) w:scroll_vert ("-"..SCROLL_STEP.."px") end),
92 bind.key({}, "Right", function (w) w:scroll_horiz("+"..SCROLL_STEP.."px") end),
93 bind.buf("^gg$", function (w) w:scroll_vert("0%") end),
94 bind.buf("^G$", function (w) w:scroll_vert("100%") end),
95 bind.buf("^[\-\+]?[0-9]+[%%G]$", function (w, b) w:scroll_vert(string.match(b, "^([\-\+]?%d+)[%%G]$") .. "%") end),
97 -- Clipboard
98 bind.key({}, "p", function (w) w:navigate(luakit.selection()) end),
99 bind.key({}, "P", function (w) w:new_tab(luakit.selection()) end),
101 -- Commands
102 bind.buf("^o$", function (w, c) w:enter_cmd(":open ") end),
103 bind.buf("^t$", function (w, c) w:enter_cmd(":tabopen ") end),
104 bind.buf("^,g$", function (w, c) w:enter_cmd(":websearch google ") end),
106 -- Searching
107 bind.key({}, "/", function (w) w:start_search(true) end),
108 bind.key({}, "?", function (w) w:start_search(false) end),
109 bind.key({}, "n", function (w) w:search(nil, true) end),
110 bind.key({}, "N", function (w) w:search(nil, false) end),
112 -- History
113 bind.buf("^[0-9]*H$", function (w, b) w:back (tonumber(string.match(b, "^(%d*)H$") or 1)) end),
114 bind.buf("^[0-9]*L$", function (w, b) w:forward(tonumber(string.match(b, "^(%d*)L$") or 1)) end),
116 -- Tab
117 bind.buf("^[0-9]*gT$", function (w, b) w:prev_tab(tonumber(string.match(b, "^(%d*)gT$") or 1)) end),
118 bind.buf("^[0-9]*gt$", function (w, b) w:next_tab(tonumber(string.match(b, "^(%d*)gt$") or 1)) end),
119 bind.buf("^gH$", function (w) w:new_tab(HOMEPAGE) end),
120 bind.buf("^d$", function (w) w:close_tab() end),
122 bind.buf("^gh$", function (w) w:navigate(HOMEPAGE) end),
123 bind.buf("^ZZ$", function (w) luakit.quit() end),
125 -- Link following
126 bind.key({}, "f", function (w) w:set_mode("follow") end),
128 command = {
129 bind.key({"Shift"}, "Insert", function (w) w:insert_cmd(luakit.selection()) end),
130 bind.key({}, "Up", function (w) w:cmd_hist_prev() end),
131 bind.key({}, "Down", function (w) w:cmd_hist_next() end),
132 bind.key({}, "Tab", function (w) w:cmd_completion() end),
133 bind.key({"Control"}, "w", function (w) w:del_word() end),
134 bind.key({"Control"}, "u", function (w) w:del_line() end),
136 search = {
137 bind.key({}, "Up", function (w) w:srch_hist_prev() end),
138 bind.key({}, "Down", function (w) w:srch_hist_next() end),
140 insert = { },
143 -- Commands
144 commands = {
145 -- bind.cmd({Command, Alias1, ...}, function (w, arg, opts) .. end, opts),
146 bind.cmd({"open", "o"}, function (w, a) w:navigate(a) end),
147 bind.cmd({"tabopen", "t"}, function (w, a) w:new_tab(a) end),
148 bind.cmd({"back" }, function (w, a) w:back(tonumber(a) or 1) end),
149 bind.cmd({"forward", "f"}, function (w, a) w:forward(tonumber(a) or 1) end),
150 bind.cmd({"scroll" }, function (w, a) w:scroll_vert(a) end),
151 bind.cmd({"quit", "q"}, function (w) luakit.quit() end),
152 bind.cmd({"close", "c"}, function (w) w:close_tab() end),
153 bind.cmd({"websearch", "ws"}, function (w, e, s) w:websearch(e, s) end),
156 function set_http_options(w)
157 local proxy = HTTPPROXY or os.getenv("http_proxy")
158 if proxy then w:set('proxy-uri', proxy) end
159 w:set('user-agent', 'luakit')
160 -- Uncomment the following options if you want to enable SSL certs validation.
161 -- w:set('ssl-ca-file', '/etc/certs/ca-certificates.crt')
162 -- w:set('ssl-strict', true)
165 -- Build and pack window widgets
166 function build_window()
167 -- Create a table for widgets and state variables for a window
168 local w = {
169 win = window(),
170 ebox = eventbox(),
171 layout = vbox(),
172 tabs = notebook(),
173 -- Tab bar widgets
174 tbar = {
175 layout = hbox(),
176 ebox = eventbox(),
177 titles = { },
179 -- Status bar widgets
180 sbar = {
181 layout = hbox(),
182 ebox = eventbox(),
183 -- Left aligned widgets
184 l = {
185 layout = hbox(),
186 ebox = eventbox(),
187 uri = label(),
188 loaded = label(),
190 -- Fills space between the left and right aligned widgets
191 filler = label(),
192 -- Right aligned widgets
193 r = {
194 layout = hbox(),
195 ebox = eventbox(),
196 buf = label(),
197 tabi = label(),
198 scroll = label(),
201 -- Input bar widgets
202 ibar = {
203 layout = hbox(),
204 ebox = eventbox(),
205 prompt = label(),
206 input = entry(),
210 -- Assemble window
211 w.ebox:set_child(w.layout)
212 w.win:set_child(w.ebox)
214 -- Pack tab bar
215 local t = w.tbar
216 t.ebox:set_child(t.layout, false, false, 0)
217 w.layout:pack_start(t.ebox, false, false, 0)
219 -- Pack notebook
220 w.layout:pack_start(w.tabs, true, true, 0)
222 -- Pack left-aligned statusbar elements
223 local l = w.sbar.l
224 l.layout:pack_start(l.uri, false, false, 0)
225 l.layout:pack_start(l.loaded, false, false, 0)
226 l.ebox:set_child(l.layout)
228 -- Pack right-aligned statusbar elements
229 local r = w.sbar.r
230 r.layout:pack_start(r.buf, false, false, 0)
231 r.layout:pack_start(r.tabi, false, false, 0)
232 r.layout:pack_start(r.scroll, false, false, 0)
233 r.ebox:set_child(r.layout)
235 -- Pack status bar elements
236 local s = w.sbar
237 s.layout:pack_start(l.ebox, false, false, 0)
238 s.layout:pack_start(s.filler, true, true, 0)
239 s.layout:pack_start(r.ebox, false, false, 0)
240 s.ebox:set_child(s.layout)
241 w.layout:pack_start(s.ebox, false, false, 0)
243 -- Pack input bar
244 local i = w.ibar
245 i.layout:pack_start(i.prompt, false, false, 0)
246 i.layout:pack_start(i.input, true, true, 0)
247 i.ebox:set_child(i.layout)
248 w.layout:pack_start(i.ebox, false, false, 0)
250 -- Other settings
251 i.input.show_frame = false
252 w.tabs.show_tabs = false
253 l.loaded:hide()
254 l.uri.selectable = true
256 return w
259 function attach_window_signals(w)
260 -- Attach notebook widget signals
261 w.tabs:add_signal("page-added", function(nbook, view, idx)
262 w:update_tab_count(idx)
263 w:update_tab_labels()
264 end)
266 w.tabs:add_signal("switch-page", function(nbook, view, idx)
267 w:update_tab_count(idx)
268 w:update_win_title(view)
269 w:update_uri(view)
270 w:update_progress(view)
271 w:update_tab_labels(idx)
272 end)
274 -- Attach window widget signals
275 w.win:add_signal("key-press", function(win, mods, key)
276 -- Reset command line completion
277 if w:get_mode() == "command" and key ~= "Tab" and w.compl_start then
278 w:update_uri()
279 w.compl_index = 0
282 if w:hit(mods, key) then
283 return true
285 end)
287 w.win:add_signal("mode-changed", function(win, mode)
288 local i, p = w.ibar.input, w.ibar.prompt
290 w:update_binds(mode)
291 w.cmd_hist_cursor = nil
293 -- Clear following hints if the user exits follow mode
294 if w.showing_hints then
295 w:eval_js("clear();");
296 w.showing_hints = false
299 -- If a user aborts a search return to the original position
300 if w.search_start_marker then
301 w:get_current():set_scroll_vert(w.search_start_marker)
302 w.search_start_marker = nil
305 if mode == "normal" then
306 p:hide()
307 i:hide()
308 elseif mode == "insert" then
309 i:hide()
310 i.text = ""
311 p.text = "-- INSERT --"
312 p:show()
313 elseif mode == "command" then
314 p:hide()
315 i.text = ":"
316 i:show()
317 i:focus()
318 i:set_position(-1)
319 elseif mode == "search" then
320 p:hide()
321 i:show()
322 elseif mode == "follow" then
323 w:eval_js_from_file(util.find_data("scripts/follow.js"))
324 w:eval_js("clear(); show_hints();")
325 w.showing_hints = true
326 p.text = "Follow:"
327 p:show()
328 i.text = ""
329 i:show()
330 i:focus()
331 i:set_position(-1)
332 else
333 w.ibar.prompt.text = ""
334 w.ibar.input.text = ""
336 end)
338 -- Attach inputbar widget signals
339 w.ibar.input:add_signal("changed", function()
340 local text = w.ibar.input.text
341 -- Auto-exit "command" mode if you backspace or delete the ":"
342 -- character at the start of the input box when in "command" mode.
343 if w:is_mode("command") and not string.match(text, "^:") then
344 w:set_mode()
345 elseif w:is_mode("search") then
346 if string.match(text, "^[\?\/]") then
347 w:search(string.sub(text, 2), (string.sub(text, 1, 1) == "/"))
348 else
349 w:clear_search()
350 w:set_mode()
352 elseif w:is_mode("follow") then
353 w:eval_js(string.format("update(%q)", w.ibar.input.text))
355 end)
357 w.ibar.input:add_signal("activate", function()
358 local text = w.ibar.input.text
359 if w:is_mode("command") then
360 w:cmd_hist_add(text)
361 w:match_cmd(string.sub(text, 2))
362 w:set_mode()
363 elseif w:is_mode("search") then
364 w:srch_hist_add(text)
365 w:search(string.sub(text, 2), string.sub(text, 1, 1) == "/")
366 -- User doesn't want to return to start position
367 w.search_start_marker = nil
368 w:set_mode()
369 w.ibar.prompt.text = util.escape(text)
370 w.ibar.prompt:show()
372 end)
375 -- Attach signal handlers to a new tab's webview
376 function attach_webview_signals(w, view)
377 view:add_signal("title-changed", function (v)
378 w:update_tab_labels()
379 if w:is_current(v) then
380 w:update_win_title(v)
382 end)
384 view:add_signal("property::uri", function(v)
385 w:update_tab_labels()
386 if w:is_current(v) then
387 w:update_uri(v)
389 end)
391 view:add_signal("link-hover", function (v, link)
392 if w:is_current(v) and link then
393 w.sbar.l.uri.text = "Link: " .. util.escape(link)
395 end)
397 view:add_signal("link-unhover", function (v)
398 if w:is_current(v) then
399 w:update_uri(v)
401 end)
403 view:add_signal("key-press", function ()
404 -- Only allow key press events to hit the webview if the user is in
405 -- "insert" mode.
406 if not w:is_mode("insert") then
407 return true
409 end)
411 view:add_signal("load-start", function (v)
412 if w:is_current(v) then
413 w:update_progress(v, 0)
414 w:set_mode()
416 end)
418 -- 'link' contains the download link
419 -- 'mime' contains the mime type that is requested
420 -- return TRUE to accept or FALSE to reject
421 view:add_signal("mime-type-decision", function (v, link, mime)
422 if w:is_current(v) then
423 print(string.format("Requested link: %s (%s)", link, mime))
425 -- i.e. block binary files like *.exe
426 if string.match(mime, "application/octet-stream") then
427 return false
430 end)
432 -- 'link' contains the download link
433 -- 'filename' contains the suggested filename (from server or webkit)
434 view:add_signal("download-request", function (v, link, filename)
435 if w:is_current(v) and filename then
436 local dl = DOWNLOAD_DIR .. "/" .. filename
438 print ("Download request:", link)
439 print ("Suggested filename:", filename)
441 local wget = string.format("wget -q %q -O %q &", link, dl)
442 print("Launching: " .. wget)
443 os.execute(wget)
445 end)
447 view:add_signal("progress-update", function (v)
448 if w:is_current(v) then
449 w:update_progress(v)
451 end)
453 view:add_signal("expose", function(v)
454 if w:is_current(v) then
455 w:update_scroll(v)
457 end)
460 -- Parses scroll amounts of the form:
461 -- Relative: "+20%", "-20%", "+20px", "-20px"
462 -- Absolute: 20, "20%", "20px"
463 -- And returns an absolute value.
464 function parse_scroll(current, max, value)
465 if string.match(value, "^%d+px$") then
466 return tonumber(string.match(value, "^(%d+)px$"))
467 elseif string.match(value, "^%d+%%$") then
468 return math.ceil(max * (tonumber(string.match(value, "^(%d+)%%$")) / 100))
469 elseif string.match(value, "^[\-\+]%d+px") then
470 return current + tonumber(string.match(value, "^([\-\+]%d+)px"))
471 elseif string.match(value, "^[\-\+]%d+%%$") then
472 return math.ceil(current + (max * (tonumber(string.match(value, "^([\-\+]%d+)%%$")) / 100)))
473 else
474 print("E: unable to parse scroll amount:", value)
478 -- Helper functions which operate on a windows widget structure
479 window_helpers = {
480 -- Return the widget in the currently active tab
481 get_current = function(w) return w.tabs:atindex(w.tabs:current()) end,
482 -- Check if given widget is the widget in the currently active tab
483 is_current = function(w, wi) return w.tabs:indexof(wi) == w.tabs:current() end,
485 -- Wrappers around the mode plugin
486 set_mode = function(w, name) mode.set(w.win, name) end,
487 get_mode = function(w) return mode.get(w.win) end,
488 is_mode = function(w, name) return name == w:get_mode() end,
489 is_any_mode = function(w, t, name) return util.table.hasitem(t, name or w:get_mode()) end,
491 -- Wrappers around the view:get_prop & view:set_prop methods
492 get = function (w, prop, view)
493 if not view then view = w:get_current() end
494 return view:get_prop(prop)
495 end,
497 set = function (w, prop, val, view)
498 if not view then view = w:get_current() end
499 view:set_prop(prop, val)
500 end,
502 get_tab_title = function (w, view)
503 if not view then view = w:get_current() end
504 return view:get_prop("title") or view.uri or "(Untitled)"
505 end,
507 navigate = function(w, uri, view)
508 local v = view or w:get_current()
509 if v then
510 v.uri = uri
511 else
512 return w:new_tab(uri)
514 end,
516 new_tab = function(w, uri)
517 local view = webview()
518 w.tabs:append(view)
519 set_http_options(w)
520 attach_webview_signals(w, view)
521 if uri then view.uri = uri end
522 view.show_scrollbars = false
523 w:update_tab_count()
524 end,
526 -- close the current tab
527 close_tab = function(w)
528 view = w:get_current()
529 if not view then return end
530 w.tabs:remove(view)
531 view:destroy()
532 w:update_tab_count()
533 end,
535 -- evaluate javascript code and return string result
536 eval_js = function(w, script, file, view)
537 if not view then view = w:get_current() end
538 return view:eval_js(script, file or "(buffer)")
539 end,
541 -- evaluate javascript code from file and return string result
542 eval_js_from_file = function(w, file, view)
543 local fh, err = io.open(file)
544 if not fh then return error(err) end
545 local script = fh:read("*a")
546 fh:close()
547 return w:eval_js(script, file, view)
548 end,
550 -- Wrapper around the bind plugin's hit method
551 hit = function (w, mods, key)
552 local caught, newbuf = bind.hit(w.binds or {}, mods, key, w.buffer, w:is_mode("normal"), w)
553 w.buffer = newbuf
554 w:update_buf()
555 return caught
556 end,
558 -- Wrapper around the bind plugin's match_cmd method
559 match_cmd = function (w, buffer)
560 return bind.match_cmd(commands, buffer, w)
561 end,
563 -- enter command or characters into command line
564 enter_cmd = function(w, cmd)
565 local i = w.ibar.input
566 w:set_mode("command")
567 i.text = cmd
568 i:set_position(-1)
569 end,
571 -- insert a string into the command line at the current cursor position
572 insert_cmd = function(w, str)
573 if not str then return nil end
574 local i = w.ibar.input
575 local text = i.text
576 local pos = i:get_position()
577 local left, right = string.sub(text, 1, pos), string.sub(text, pos+1)
578 i.text = left .. str .. right
579 i:set_position(pos + #str + 1)
580 end,
582 -- search engine wrapper
583 websearch = function(w, args)
584 local sep = string.find(args, " ")
585 local engine = string.sub(args, 1, sep-1)
586 local search = string.sub(args, sep+1)
587 if not search_engines[engine] then
588 print("E: No matching search engine found:", engine)
589 return 0
591 local uri = string.gsub(search_engines[engine], "{%d}", search)
592 return w:navigate(uri)
593 end,
595 -- Command line completion of available commands
596 cmd_completion = function(w)
597 local i = w.ibar.input
598 local s = w.sbar.l.uri
599 local cmpl = {}
601 -- Get last completion (is reset on key press other than <Tab>)
602 if not w.compl_start or w.compl_index == 0 then
603 w.compl_start = "^" .. string.sub(i.text, 2)
604 w.compl_index = 1
607 -- Get suitable commands
608 for _, b in ipairs(commands) do
609 for _, c in pairs(b.commands) do
610 if c and string.match(c, w.compl_start) then
611 table.insert(cmpl, c)
616 table.sort(cmpl)
618 if #cmpl > 0 then
619 local text = ""
620 for index, comp in pairs(cmpl) do
621 if index == w.compl_index then
622 i.text = ":" .. comp .. " "
623 i:set_position(-1)
625 if text ~= "" then
626 text = text .. " | "
628 text = text .. comp
631 -- cycle through all possible completions
632 if w.compl_index == #cmpl then
633 w.compl_index = 1
634 else
635 w.compl_index = w.compl_index + 1
637 s.text = util.escape(text)
639 end,
641 del_word = function(w)
642 local i = w.ibar.input
643 local text = i.text
644 local pos = i:get_position()
645 if text and #text > 1 and pos > 1 then
646 local left, right = string.sub(text, 2, pos), string.sub(text, pos+1)
647 if not string.find(left, "%s") then
648 left = ""
649 elseif string.find(left, "%w+%s*$") then
650 left = string.sub(left, 0, string.find(left, "%w+%s*$") - 1)
651 elseif string.find(left, "%W+%s*$") then
652 left = string.sub(left, 0, string.find(left, "%W+%s*$") - 1)
654 i.text = string.sub(text, 1, 1) .. left .. right
655 i:set_position(#left + 2)
657 end,
659 del_line = function(w)
660 local i = w.ibar.input
661 if i.text ~= ":" then
662 i.text = ":"
663 i:set_position(-1)
665 end,
667 -- Search history adding
668 srch_hist_add = function(w, srch)
669 if not w.srch_hist then w.srch_hist = {} end
670 -- Check overflow
671 if #w.srch_hist > ((MAX_SRCH_HISTORY or 100) + 5) then
672 while #w.srch_hist > (MAX_SRCH_HISTORY or 100) do
673 table.remove(w.srch_hist, 1)
676 table.insert(w.srch_hist, srch)
677 end,
679 -- Search history traversing
680 srch_hist_prev = function(w)
681 if not w.srch_hist then w.srch_hist = {} end
682 if not w.srch_hist_cursor then
683 w.srch_hist_cursor = #w.srch_hist + 1
684 w.srch_hist_current = w.ibar.input.text
686 local c = w.srch_hist_cursor - 1
687 if w.srch_hist[c] then
688 w.srch_hist_cursor = c
689 w.ibar.input.text = w.srch_hist[c]
690 w.ibar.input:set_position(-1)
692 end,
694 srch_hist_next = function(w)
695 if not w.srch_hist then w.srch_hist = {} end
696 local c = (w.srch_hist_cursor or #w.srch_hist) + 1
697 if w.srch_hist[c] then
698 w.srch_hist_cursor = c
699 w.ibar.input.text = w.srch_hist[c]
700 w.ibar.input:set_position(-1)
701 elseif w.srch_hist_current then
702 w.srch_hist_cursor = nil
703 w.ibar.input.text = w.srch_hist_current
704 w.ibar.input:set_position(-1)
706 end,
708 -- Command history adding
709 cmd_hist_add = function(w, cmd)
710 if not w.cmd_hist then w.cmd_hist = {} end
711 -- Make sure history doesn't overflow
712 if #w.cmd_hist > ((MAX_CMD_HISTORY or 100) + 5) then
713 while #w.cmd_hist > (MAX_CMD_HISTORY or 100) do
714 table.remove(w.cmd_hist, 1)
717 table.insert(w.cmd_hist, cmd)
718 end,
720 -- Command history traversing
721 cmd_hist_prev = function(w)
722 if not w.cmd_hist then w.cmd_hist = {} end
723 if not w.cmd_hist_cursor then
724 w.cmd_hist_cursor = #w.cmd_hist + 1
725 w.cmd_hist_current = w.ibar.input.text
727 local c = w.cmd_hist_cursor - 1
728 if w.cmd_hist[c] then
729 w.cmd_hist_cursor = c
730 w.ibar.input.text = w.cmd_hist[c]
731 w.ibar.input:set_position(-1)
733 end,
735 cmd_hist_next = function(w)
736 if not w.cmd_hist then w.cmd_hist = {} end
737 local c = (w.cmd_hist_cursor or #w.cmd_hist) + 1
738 if w.cmd_hist[c] then
739 w.cmd_hist_cursor = c
740 w.ibar.input.text = w.cmd_hist[c]
741 w.ibar.input:set_position(-1)
742 elseif w.cmd_hist_current then
743 w.cmd_hist_cursor = nil
744 w.ibar.input.text = w.cmd_hist_current
745 w.ibar.input:set_position(-1)
747 end,
749 -- Searching functions
750 start_search = function(w, forward)
751 -- Clear previous search results
752 w:clear_search()
753 w:set_mode("search")
754 local i = w.ibar.input
755 if forward then
756 i.text = "/"
757 else
758 i.text = "?"
760 i:focus()
761 i:set_position(-1)
762 end,
764 search = function(w, text, forward)
765 local view = w:get_current()
766 local text = text or w.last_search
767 if forward == nil then forward = true end
768 local case_sensitive = false
769 local wrap = true
771 if not text or #text == 0 then
772 w:clear_search()
773 return nil
776 w.last_search = text
777 if w.searching_forward == nil then
778 w.searching_forward = forward
779 w.search_start_marker = view:get_scroll_vert()
780 else
781 -- Invert the direction if originally searching in reverse
782 forward = (w.searching_forward == forward)
785 view:search(text, case_sensitive, forward, wrap);
786 end,
788 clear_search = function (w)
789 w:get_current():clear_search()
790 -- Clear search state
791 w.last_search = nil
792 w.searching_forward = nil
793 w.search_start_marker = nil
794 end,
796 -- Webview scroll functions
797 scroll_vert = function(w, value, view)
798 if not view then view = w:get_current() end
799 local cur, max = view:get_scroll_vert()
800 if type(value) == "string" then
801 value = parse_scroll(cur, max, value)
803 view:set_scroll_vert(value)
804 end,
806 scroll_horiz = function(w, value)
807 if not view then view = w:get_current() end
808 local cur, max = view:get_scroll_horiz()
809 if type(value) == "string" then
810 value = parse_scroll(cur, max, value)
812 view:set_scroll_horiz(value)
813 end,
815 -- Tab traversing functions
816 next_tab = function(w, n)
817 w.tabs:switch((((n or 1) + w.tabs:current() -1) % w.tabs:count()) + 1)
818 end,
819 prev_tab = function(w, n)
820 w.tabs:switch(((w.tabs:current() - (n or 1) -1) % w.tabs:count()) + 1)
821 end,
822 goto_tab = function(w, n)
823 w.tabs:switch(n)
824 end,
826 -- History traversing functions
827 back = function(w, n, view)
828 (view or w:get_current()):go_back(n or 1)
829 end,
830 forward = function(w, n, view)
831 (view or w:get_current()):go_forward(n or 1)
832 end,
834 -- GUI content update functions
835 update_tab_count = function (w, i, t)
836 w.sbar.r.tabi.text = string.format("[%d/%d]", i or w.tabs:current(), t or w.tabs:count())
837 end,
839 update_win_title = function (w, view)
840 if not view then view = w:get_current() end
841 local title = view:get_prop("title")
842 local uri = view.uri
843 if not title and not uri then
844 w.win.title = "luakit"
845 else
846 w.win.title = (title or "luakit") .. " - " .. (uri or "about:blank")
848 end,
850 update_uri = function (w, view, uri)
851 local v = view or w:get_current()
852 if not v then return end
853 w.sbar.l.uri.text = util.escape((uri or v.uri or "about:blank"))
854 end,
856 update_progress = function (w, view, p)
857 if not view then view = w:get_current() end
858 if not p then p = view:get_prop("progress") end
859 if not view:loading() or p == 1 then
860 w.sbar.l.loaded:hide()
861 else
862 w.sbar.l.loaded:show()
863 w.sbar.l.loaded.text = string.format("(%d%%)", p * 100)
865 end,
867 update_scroll = function (w, view)
868 if not view then view = w:get_current() end
869 local val, max = view:get_scroll_vert()
870 if max == 0 then val = "All"
871 elseif val == 0 then val = "Top"
872 elseif val == max then val = "Bot"
873 else val = string.format("%2d%%", (val/max) * 100)
875 w.sbar.r.scroll.text = val
876 end,
878 update_buf = function (w)
879 if w.buffer then
880 w.sbar.r.buf.text = util.escape(string.format(" %-3s", w.buffer))
881 w.sbar.r.buf:show()
882 else
883 w.sbar.r.buf:hide()
885 end,
887 update_binds = function (w, mode)
888 -- Generate the list of active key & buffer binds for this mode
889 w.binds = util.table.join(mode_binds[mode], mode_binds.all)
890 -- Clear & hide buffer
891 w.buffer = nil
892 w:update_buf()
893 end,
895 -- Tab label functions
896 make_tab_label = function (w, pos)
897 local t = {
898 label = label(),
899 sep = label(),
900 ebox = eventbox(),
901 layout = hbox(),
903 t.label.font = theme.tablabel_font or theme.font
904 t.layout:pack_start(t.label, true, true, 0)
905 t.layout:pack_start(t.sep, false, false, 0)
906 t.ebox:set_child(t.layout)
907 t.ebox:add_signal("clicked", function(e) w.tabs:switch(pos) end)
908 return t
909 end,
911 destroy_tab_label = function(w, t)
912 if not t then t = table.remove(w.tbar.titles) end
913 for _, wi in pairs(t) do
914 wi:destroy()
916 end,
918 update_tab_labels = function (w, current)
919 local tb = w.tbar
920 local count, current = w.tabs:count(), current or w.tabs:current()
921 tb.ebox:hide()
923 -- Leave the tablist hidden if there is only one tab open
924 if count <= 1 then
925 return nil
928 if count ~= #tb.titles then
929 -- Grow the number of labels
930 while count > #tb.titles do
931 local t = w:make_tab_label(#tb.titles + 1)
932 tb.layout:pack_start(t.ebox, true, true, 0)
933 table.insert(tb.titles, t)
935 -- Prune number of labels
936 while count < #tb.titles do
937 w:destroy_tab_label()
941 if count ~= 0 then
942 for i = 1, count do
943 local t = tb.titles[i]
944 local title = " " ..i.. " "..w:get_tab_title(w.tabs:atindex(i))
945 t.label.text = util.escape(string.format(theme.tablabel_format or "%s", title))
946 w:apply_tablabel_theme(t, i == current)
949 tb.ebox:show()
950 end,
952 -- Theme functions
953 apply_tablabel_theme = function (w, t, selected, atheme)
954 local theme = atheme or theme
955 if selected then
956 t.label.fg = theme.selected_tablabel_fg or theme.tablabel_fg or theme.fg
957 t.ebox.bg = theme.selected_tablabel_bg or theme.tablabel_bg or theme.bg
958 else
959 t.label.fg = theme.tablabel_fg or theme.fg
960 t.ebox.bg = theme.tablabel_bg or theme.bg
962 end,
964 apply_window_theme = function (w, atheme)
965 local theme = atheme or theme
966 local s, i, t = w.sbar, w.ibar, w.tbar
967 local fg, bg, font = theme.fg, theme.bg, theme.font
969 -- Set foregrounds
970 for wi, v in pairs({
971 [s.l.uri] = theme.uri_fg or theme.statusbar_fg or fg,
972 [s.l.loaded] = theme.loaded_fg or theme.statusbar_fg or fg,
973 [s.r.buf] = theme.buf_fg or theme.statusbar_fg or fg,
974 [s.r.tabi] = theme.tabi_fg or theme.statusbar_fg or fg,
975 [s.r.scroll] = theme.scroll_fg or theme.statusbar_fg or fg,
976 [i.prompt] = theme.prompt_fg or theme.inputbar_fg or fg,
977 [i.input] = theme.input_fg or theme.inputbar_fg or fg,
978 }) do wi.fg = v end
980 -- Set backgrounds
981 for wi, v in pairs({
982 [s.l.ebox] = theme.statusbar_bg or bg,
983 [s.r.ebox] = theme.statusbar_bg or bg,
984 [s.ebox] = theme.statusbar_bg or bg,
985 [i.ebox] = theme.inputbar_bg or bg,
986 [i.input] = theme.input_bg or theme.inputbar_bg or bg,
987 }) do wi.bg = v end
989 -- Set fonts
990 for wi, v in pairs({
991 [s.l.uri] = theme.uri_font or theme.statusbar_font or font,
992 [s.l.loaded] = theme.loaded_font or theme.statusbar_font or font,
993 [s.r.buf] = theme.buf_font or theme.statusbar_font or font,
994 [s.r.tabi] = theme.tabi_font or theme.statusbar_font or font,
995 [s.r.scroll] = theme.scroll_font or theme.statusbar_font or font,
996 [i.prompt] = theme.prompt_font or theme.inputbar_font or font,
997 [i.input] = theme.input_font or theme.inputbar_font or font,
998 }) do wi.font = v end
999 end,
1002 -- Create new window
1003 function new_window(uris)
1004 local w = build_window()
1006 -- Pack the window table full of the common helper functions
1007 for k, v in pairs(window_helpers) do w[k] = v end
1009 attach_window_signals(w)
1011 -- Apply window theme
1012 w:apply_window_theme()
1014 -- Populate notebook with tabs
1015 for _, uri in ipairs(uris or {}) do
1016 w:new_tab(uri)
1019 -- Make sure something is loaded
1020 if w.tabs:count() == 0 then
1021 w:new_tab(HOMEPAGE)
1024 -- Set initial mode
1025 w:set_mode()
1027 return w
1030 new_window(uris)
1032 -- vim: ft=lua:et:sw=4:ts=8:sts=4:tw=80