Merge commit '2f8fb053' into develop
[luakit.git] / rc.lua
blobe5a5a0e589431f9654633631528227c4f40776dd
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 = luakit.get_special_dir("DOWNLOAD") or (os.getenv("HOME") .. "/downloads")
28 -- Luakit theme
29 theme = theme or {
30 -- Default settings
31 font = "monospace normal 9",
32 fg = "#fff",
33 bg = "#000",
35 -- General settings
36 statusbar_fg = "#fff",
37 statusbar_bg = "#000",
38 inputbar_fg = "#000",
39 inputbar_bg = "#fff",
41 -- Specific settings
42 loaded_fg = "#33AADD",
43 tablabel_fg = "#999",
44 tablabel_bg = "#111",
45 selected_tablabel_fg = "#fff",
46 selected_tablabel_bg = "#000",
48 -- Enforce a minimum tab width of 30 characters to prevent longer tab
49 -- titles overshadowing small tab titles when things get crowded.
50 tablabel_format = "%-30s",
53 widget.add_signal("new", function (wi)
54 wi:add_signal("init", function (wi)
55 if wi.type == "window" then
56 wi:add_signal("destroy", function ()
57 -- Call the quit function if this was the last window left
58 if #luakit.windows == 0 then luakit.quit() end
59 end)
60 end
61 end)
62 end)
64 -- Search engines
65 search_engines = {
66 google = "http://google.com/search?q={0}",
67 imdb = "http://imdb.com/find?s=all&q={0}",
68 sourceforge = "http://sf.net/search/?words={0}",
71 -- Add key bindings to be used across all windows
72 mode_binds = {
73 -- bind.buf(Pattern, function (w, buffer, opts) .. end, opts),
74 -- bind.key({Modifiers}, Key name, function (w, opts) .. end, opts),
75 -- bind.but({Modifiers}, Button num, 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 -- Mouse bindings
81 bind.but({}, 2, function (w)
82 -- Open hovered uri in new tab
83 local uri = w:get_current().hovered_uri
84 if uri then w:new_tab(uri)
85 else -- Open selection in current tab
86 uri = luakit.get_selection()
87 if uri then w:get_current().uri = uri end
88 end
89 end),
90 bind.but({}, 8, function (w) w:back() end),
91 bind.but({}, 9, function (w) w:forward() end),
93 normal = {
94 bind.key({}, "i", function (w) w:set_mode("insert") end),
95 bind.key({}, ":", function (w) w:set_mode("command") end),
97 -- Scrolling
98 bind.key({}, "h", function (w) w:scroll_horiz("-"..SCROLL_STEP.."px") end),
99 bind.key({}, "j", function (w) w:scroll_vert ("+"..SCROLL_STEP.."px") end),
100 bind.key({}, "k", function (w) w:scroll_vert ("-"..SCROLL_STEP.."px") end),
101 bind.key({}, "l", function (w) w:scroll_horiz("+"..SCROLL_STEP.."px") end),
102 bind.key({}, "Left", function (w) w:scroll_horiz("-"..SCROLL_STEP.."px") end),
103 bind.key({}, "Down", function (w) w:scroll_vert ("+"..SCROLL_STEP.."px") end),
104 bind.key({}, "Up", function (w) w:scroll_vert ("-"..SCROLL_STEP.."px") end),
105 bind.key({}, "Right", function (w) w:scroll_horiz("+"..SCROLL_STEP.."px") end),
106 bind.buf("^gg$", function (w) w:scroll_vert("0%") end),
107 bind.buf("^G$", function (w) w:scroll_vert("100%") end),
108 bind.buf("^[\-\+]?[0-9]+[%%G]$", function (w, b) w:scroll_vert(string.match(b, "^([\-\+]?%d+)[%%G]$") .. "%") end),
110 -- Clipboard
111 bind.key({}, "p", function (w) w:navigate(luakit.get_selection()) end),
112 bind.key({}, "P", function (w) w:new_tab(luakit.get_selection()) end),
113 bind.buf("^yy$", function (w) luakit.set_selection(w:get_current().uri) end),
114 bind.buf("^yt$", function (w) luakit.set_selection(w.win.title) end),
116 -- Commands
117 bind.buf("^o$", function (w, c) w:enter_cmd(":open ") end),
118 bind.buf("^t$", function (w, c) w:enter_cmd(":tabopen ") end),
119 bind.buf("^,g$", function (w, c) w:enter_cmd(":websearch google ") end),
121 -- Searching
122 bind.key({}, "/", function (w) w:start_search(true) end),
123 bind.key({}, "?", function (w) w:start_search(false) end),
124 bind.key({}, "n", function (w) w:search(nil, true) end),
125 bind.key({}, "N", function (w) w:search(nil, false) end),
127 -- History
128 bind.buf("^[0-9]*H$", function (w, b) w:back (tonumber(string.match(b, "^(%d*)H$") or 1)) end),
129 bind.buf("^[0-9]*L$", function (w, b) w:forward(tonumber(string.match(b, "^(%d*)L$") or 1)) end),
131 -- Tab
132 bind.buf("^[0-9]*gT$", function (w, b) w:prev_tab(tonumber(string.match(b, "^(%d*)gT$") or 1)) end),
133 bind.buf("^[0-9]*gt$", function (w, b) w:next_tab(tonumber(string.match(b, "^(%d*)gt$") or 1)) end),
134 bind.buf("^gH$", function (w) w:new_tab(HOMEPAGE) end),
135 bind.buf("^d$", function (w) w:close_tab() end),
137 bind.key({}, "r", function (w) w:reload() end),
138 bind.buf("^gh$", function (w) w:navigate(HOMEPAGE) end),
139 bind.buf("^ZZ$", function (w) luakit.quit() end),
141 -- Link following
142 bind.key({}, "f", function (w) w:set_mode("follow") end),
145 command = {
146 bind.key({"Shift"}, "Insert", function (w) w:insert_cmd(luakit.get_selection()) end),
147 bind.key({}, "Up", function (w) w:cmd_hist_prev() end),
148 bind.key({}, "Down", function (w) w:cmd_hist_next() end),
149 bind.key({}, "Tab", function (w) w:cmd_completion() end),
150 bind.key({"Control"}, "w", function (w) w:del_word() end),
151 bind.key({"Control"}, "u", function (w) w:del_line() end),
153 search = {
154 bind.key({}, "Up", function (w) w:srch_hist_prev() end),
155 bind.key({}, "Down", function (w) w:srch_hist_next() end),
157 insert = { },
160 -- Commands
161 commands = {
162 -- bind.cmd({Command, Alias1, ...}, function (w, arg, opts) .. end, opts),
163 bind.cmd({"open", "o" }, function (w, a) w:navigate(a) end),
164 bind.cmd({"tabopen", "t" }, function (w, a) w:new_tab(a) end),
165 bind.cmd({"back" }, function (w, a) w:back(tonumber(a) or 1) end),
166 bind.cmd({"forward", "f" }, function (w, a) w:forward(tonumber(a) or 1) end),
167 bind.cmd({"scroll" }, function (w, a) w:scroll_vert(a) end),
168 bind.cmd({"quit", "q" }, function (w) luakit.quit() end),
169 bind.cmd({"close", "c" }, function (w) w:close_tab() end),
170 bind.cmd({"websearch", "ws"}, function (w, e, s) w:websearch(e, s) end),
171 bind.cmd({"reload", }, function (w) w:reload() end),
174 function set_http_options(w)
175 local proxy = HTTPPROXY or os.getenv("http_proxy")
176 if proxy then w:set('proxy-uri', proxy) end
177 w:set('user-agent', 'luakit')
178 -- Uncomment the following options if you want to enable SSL certs validation.
179 -- w:set('ssl-ca-file', '/etc/certs/ca-certificates.crt')
180 -- w:set('ssl-strict', true)
183 -- Build and pack window widgets
184 function build_window()
185 -- Create a table for widgets and state variables for a window
186 local w = {
187 win = window(),
188 ebox = eventbox(),
189 layout = vbox(),
190 tabs = notebook(),
191 -- Tab bar widgets
192 tbar = {
193 layout = hbox(),
194 ebox = eventbox(),
195 titles = { },
197 -- Status bar widgets
198 sbar = {
199 layout = hbox(),
200 ebox = eventbox(),
201 -- Left aligned widgets
202 l = {
203 layout = hbox(),
204 ebox = eventbox(),
205 uri = label(),
206 loaded = label(),
208 -- Fills space between the left and right aligned widgets
209 filler = label(),
210 -- Right aligned widgets
211 r = {
212 layout = hbox(),
213 ebox = eventbox(),
214 buf = label(),
215 tabi = label(),
216 scroll = label(),
219 -- Input bar widgets
220 ibar = {
221 layout = hbox(),
222 ebox = eventbox(),
223 prompt = label(),
224 input = entry(),
228 -- Assemble window
229 w.ebox:set_child(w.layout)
230 w.win:set_child(w.ebox)
232 -- Pack tab bar
233 local t = w.tbar
234 t.ebox:set_child(t.layout, false, false, 0)
235 w.layout:pack_start(t.ebox, false, false, 0)
237 -- Pack notebook
238 w.layout:pack_start(w.tabs, true, true, 0)
240 -- Pack left-aligned statusbar elements
241 local l = w.sbar.l
242 l.layout:pack_start(l.uri, false, false, 0)
243 l.layout:pack_start(l.loaded, false, false, 0)
244 l.ebox:set_child(l.layout)
246 -- Pack right-aligned statusbar elements
247 local r = w.sbar.r
248 r.layout:pack_start(r.buf, false, false, 0)
249 r.layout:pack_start(r.tabi, false, false, 0)
250 r.layout:pack_start(r.scroll, false, false, 0)
251 r.ebox:set_child(r.layout)
253 -- Pack status bar elements
254 local s = w.sbar
255 s.layout:pack_start(l.ebox, false, false, 0)
256 s.layout:pack_start(s.filler, true, true, 0)
257 s.layout:pack_start(r.ebox, false, false, 0)
258 s.ebox:set_child(s.layout)
259 w.layout:pack_start(s.ebox, false, false, 0)
261 -- Pack input bar
262 local i = w.ibar
263 i.layout:pack_start(i.prompt, false, false, 0)
264 i.layout:pack_start(i.input, true, true, 0)
265 i.ebox:set_child(i.layout)
266 w.layout:pack_start(i.ebox, false, false, 0)
268 -- Other settings
269 i.input.show_frame = false
270 w.tabs.show_tabs = false
271 l.loaded:hide()
272 l.uri.selectable = true
274 return w
277 function attach_window_signals(w)
278 -- Attach notebook widget signals
279 w.tabs:add_signal("page-added", function (nbook, view, idx)
280 w:update_tab_count(idx)
281 w:update_tab_labels()
282 end)
284 w.tabs:add_signal("switch-page", function (nbook, view, idx)
285 w:update_tab_count(idx)
286 w:update_win_title(view)
287 w:update_uri(view)
288 w:update_progress(view)
289 w:update_tab_labels(idx)
290 end)
292 -- Attach window widget signals
293 w.win:add_signal("key-press", function (win, mods, key)
294 -- Reset command line completion
295 if w:get_mode() == "command" and key ~= "Tab" and w.compl_start then
296 w:update_uri()
297 w.compl_index = 0
300 if w:hit(mods, key) then
301 return true
303 end)
305 w.win:add_signal("mode-changed", function (win, mode)
306 local i, p = w.ibar.input, w.ibar.prompt
308 w:update_binds(mode)
309 w.cmd_hist_cursor = nil
311 -- Clear following hints if the user exits follow mode
312 if w.showing_hints then
313 w:eval_js("clear();");
314 w.showing_hints = false
317 -- If a user aborts a search return to the original position
318 if w.search_start_marker then
319 w:get_current():set_scroll_vert(w.search_start_marker)
320 w.search_start_marker = nil
323 if mode == "normal" then
324 p:hide()
325 i:hide()
326 elseif mode == "insert" then
327 i:hide()
328 i.text = ""
329 p.text = "-- INSERT --"
330 p:show()
331 elseif mode == "command" then
332 p:hide()
333 i.text = ":"
334 i:show()
335 i:focus()
336 i:set_position(-1)
337 elseif mode == "search" then
338 p:hide()
339 i:show()
340 elseif mode == "follow" then
341 w:eval_js_from_file(util.find_data("scripts/follow.js"))
342 w:eval_js("clear(); show_hints();")
343 w.showing_hints = true
344 p.text = "Follow:"
345 p:show()
346 i.text = ""
347 i:show()
348 i:focus()
349 i:set_position(-1)
350 else
351 w.ibar.prompt.text = ""
352 w.ibar.input.text = ""
354 end)
356 -- Attach inputbar widget signals
357 w.ibar.input:add_signal("changed", function()
358 local text = w.ibar.input.text
359 -- Auto-exit "command" mode if you backspace or delete the ":"
360 -- character at the start of the input box when in "command" mode.
361 if w:is_mode("command") and not string.match(text, "^:") then
362 w:set_mode()
363 elseif w:is_mode("search") then
364 if string.match(text, "^[\?\/]") then
365 w:search(string.sub(text, 2), (string.sub(text, 1, 1) == "/"))
366 else
367 w:clear_search()
368 w:set_mode()
370 elseif w:is_mode("follow") then
371 w:eval_js(string.format("update(%q)", w.ibar.input.text))
373 end)
375 w.ibar.input:add_signal("activate", function()
376 local text = w.ibar.input.text
377 if w:is_mode("command") then
378 w:cmd_hist_add(text)
379 w:match_cmd(string.sub(text, 2))
380 w:set_mode()
381 elseif w:is_mode("search") then
382 w:srch_hist_add(text)
383 w:search(string.sub(text, 2), string.sub(text, 1, 1) == "/")
384 -- User doesn't want to return to start position
385 w.search_start_marker = nil
386 w:set_mode()
387 w.ibar.prompt.text = util.escape(text)
388 w.ibar.prompt:show()
390 end)
393 -- Attach signal handlers to a new tab's webview
394 function attach_webview_signals(w, view)
395 view:add_signal("property::title", function (v)
396 w:update_tab_labels()
397 if w:is_current(v) then
398 w:update_win_title(v)
400 end)
402 view:add_signal("property::uri", function (v)
403 w:update_tab_labels()
404 if w:is_current(v) then
405 w:update_uri(v)
407 end)
409 view:add_signal("link-hover", function (v, link)
410 if w:is_current(v) and link then
411 w.sbar.l.uri.text = "Link: " .. util.escape(link)
413 end)
415 view:add_signal("link-unhover", function (v)
416 if w:is_current(v) then
417 w:update_uri(v)
419 end)
421 view:add_signal("form-active", function ()
422 w:set_mode("insert")
423 end)
425 view:add_signal("root-active", function ()
426 w:set_mode()
427 end)
429 view:add_signal("key-press", function ()
430 -- Only allow key press events to hit the webview if the user is in
431 -- "insert" mode.
432 if not w:is_mode("insert") then
433 return true
435 end)
437 view:add_signal("button-release", function (v, mods, button)
438 if w:hit(mods, button) then
439 return true
441 end)
443 view:add_signal("load-status", function (v, status)
444 if w:is_current(v) then
445 w:update_progress(v)
446 if status == "provisional" then
447 w:set_mode()
450 end)
452 -- 'link' contains the download link
453 -- 'mime' contains the mime type that is requested
454 -- return TRUE to accept or FALSE to reject
455 view:add_signal("mime-type-decision", function (v, link, mime)
456 if w:is_current(v) then
457 if luakit.verbose then print(string.format("Requested link: %s (%s)", link, mime)) end
459 -- i.e. block binary files like *.exe
460 if string.match(mime, "application/octet-stream") then
461 return false
464 end)
466 -- 'link' contains the download link
467 -- 'filename' contains the suggested filename (from server or webkit)
468 view:add_signal("download-request", function (v, link, filename)
469 if w:is_current(v) and filename then
470 -- Make download dir
471 os.execute(string.format("mkdir -p %q", DOWNLOAD_DIR))
473 local dl = DOWNLOAD_DIR .. "/" .. filename
474 local wget = string.format("wget -q %q -O %q &", link, dl)
475 if luakit.verbose then print("Launching: " .. wget) end
476 os.execute(wget)
478 end)
480 view:add_signal("property::progress", function (v)
481 if w:is_current(v) then
482 w:update_progress(v)
484 end)
486 view:add_signal("expose", function (v)
487 if w:is_current(v) then
488 w:update_scroll(v)
490 end)
493 -- Parses scroll amounts of the form:
494 -- Relative: "+20%", "-20%", "+20px", "-20px"
495 -- Absolute: 20, "20%", "20px"
496 -- And returns an absolute value.
497 function parse_scroll(current, max, value)
498 if string.match(value, "^%d+px$") then
499 return tonumber(string.match(value, "^(%d+)px$"))
500 elseif string.match(value, "^%d+%%$") then
501 return math.ceil(max * (tonumber(string.match(value, "^(%d+)%%$")) / 100))
502 elseif string.match(value, "^[\-\+]%d+px") then
503 return current + tonumber(string.match(value, "^([\-\+]%d+)px"))
504 elseif string.match(value, "^[\-\+]%d+%%$") then
505 return math.ceil(current + (max * (tonumber(string.match(value, "^([\-\+]%d+)%%$")) / 100)))
506 else
507 print("E: unable to parse scroll amount:", value)
511 -- Helper functions which operate on a windows widget structure
512 window_helpers = {
513 -- Return the widget in the currently active tab
514 get_current = function (w) return w.tabs:atindex(w.tabs:current()) end,
515 -- Check if given widget is the widget in the currently active tab
516 is_current = function (w, wi) return w.tabs:indexof(wi) == w.tabs:current() end,
518 -- Wrappers around the mode plugin
519 set_mode = function (w, name) mode.set(w.win, name) end,
520 get_mode = function (w) return mode.get(w.win) end,
521 is_mode = function (w, name) return name == w:get_mode() end,
522 is_any_mode = function (w, t, name) return util.table.hasitem(t, name or w:get_mode()) end,
524 -- Wrappers around the view:get_prop & view:set_prop methods
525 get = function (w, prop, view)
526 if not view then view = w:get_current() end
527 return view:get_prop(prop)
528 end,
530 set = function (w, prop, val, view)
531 if not view then view = w:get_current() end
532 view:set_prop(prop, val)
533 end,
535 get_tab_title = function (w, view)
536 if not view then view = w:get_current() end
537 return view:get_prop("title") or view.uri or "(Untitled)"
538 end,
540 navigate = function (w, uri, view)
541 local v = view or w:get_current()
542 if v then
543 v.uri = uri
544 else
545 return w:new_tab(uri)
547 end,
549 reload = function (w, view)
550 if not view then view = w:get_current() end
551 view:reload()
552 end,
554 new_tab = function (w, uri)
555 local view = webview()
556 w.tabs:append(view)
557 set_http_options(w)
558 attach_webview_signals(w, view)
559 if uri then view.uri = uri end
560 view.show_scrollbars = false
561 w:update_tab_count()
562 end,
564 -- close the current tab
565 close_tab = function (w, view)
566 if not view then view = w:get_current() end
567 if not view then return end
568 w.tabs:remove(view)
569 view:destroy()
570 w:update_tab_count()
571 end,
573 -- evaluate javascript code and return string result
574 eval_js = function (w, script, file, view)
575 if not view then view = w:get_current() end
576 return view:eval_js(script, file or "(buffer)")
577 end,
579 -- evaluate javascript code from file and return string result
580 eval_js_from_file = function (w, file, view)
581 local fh, err = io.open(file)
582 if not fh then return error(err) end
583 local script = fh:read("*a")
584 fh:close()
585 return w:eval_js(script, file, view)
586 end,
588 -- Wrapper around the bind plugin's hit method
589 hit = function (w, mods, key)
590 local caught, newbuf = bind.hit(w.binds or {}, mods, key, w.buffer, w:is_mode("normal"), w)
591 w.buffer = newbuf
592 w:update_buf()
593 return caught
594 end,
596 -- Wrapper around the bind plugin's match_cmd method
597 match_cmd = function (w, buffer)
598 return bind.match_cmd(commands, buffer, w)
599 end,
601 -- enter command or characters into command line
602 enter_cmd = function (w, cmd)
603 local i = w.ibar.input
604 w:set_mode("command")
605 i.text = cmd
606 i:set_position(-1)
607 end,
609 -- insert a string into the command line at the current cursor position
610 insert_cmd = function (w, str)
611 if not str then return nil end
612 local i = w.ibar.input
613 local text = i.text
614 local pos = i:get_position()
615 local left, right = string.sub(text, 1, pos), string.sub(text, pos+1)
616 i.text = left .. str .. right
617 i:set_position(pos + #str + 1)
618 end,
620 -- search engine wrapper
621 websearch = function (w, args)
622 local sep = string.find(args, " ")
623 local engine = string.sub(args, 1, sep-1)
624 local search = string.sub(args, sep+1)
625 if not search_engines[engine] then
626 print("E: No matching search engine found:", engine)
627 return 0
629 local uri = string.gsub(search_engines[engine], "{%d}", search)
630 return w:navigate(uri)
631 end,
633 -- Command line completion of available commands
634 cmd_completion = function (w)
635 local i = w.ibar.input
636 local s = w.sbar.l.uri
637 local cmpl = {}
639 -- Get last completion (is reset on key press other than <Tab>)
640 if not w.compl_start or w.compl_index == 0 then
641 w.compl_start = "^" .. string.sub(i.text, 2)
642 w.compl_index = 1
645 -- Get suitable commands
646 for _, b in ipairs(commands) do
647 for _, c in pairs(b.commands) do
648 if c and string.match(c, w.compl_start) then
649 table.insert(cmpl, c)
654 table.sort(cmpl)
656 if #cmpl > 0 then
657 local text = ""
658 for index, comp in pairs(cmpl) do
659 if index == w.compl_index then
660 i.text = ":" .. comp .. " "
661 i:set_position(-1)
663 if text ~= "" then
664 text = text .. " | "
666 text = text .. comp
669 -- cycle through all possible completions
670 if w.compl_index == #cmpl then
671 w.compl_index = 1
672 else
673 w.compl_index = w.compl_index + 1
675 s.text = util.escape(text)
677 end,
679 del_word = function (w)
680 local i = w.ibar.input
681 local text = i.text
682 local pos = i:get_position()
683 if text and #text > 1 and pos > 1 then
684 local left, right = string.sub(text, 2, pos), string.sub(text, pos+1)
685 if not string.find(left, "%s") then
686 left = ""
687 elseif string.find(left, "%w+%s*$") then
688 left = string.sub(left, 0, string.find(left, "%w+%s*$") - 1)
689 elseif string.find(left, "%W+%s*$") then
690 left = string.sub(left, 0, string.find(left, "%W+%s*$") - 1)
692 i.text = string.sub(text, 1, 1) .. left .. right
693 i:set_position(#left + 2)
695 end,
697 del_line = function (w)
698 local i = w.ibar.input
699 if i.text ~= ":" then
700 i.text = ":"
701 i:set_position(-1)
703 end,
705 -- Search history adding
706 srch_hist_add = function (w, srch)
707 if not w.srch_hist then w.srch_hist = {} end
708 -- Check overflow
709 if #w.srch_hist > ((MAX_SRCH_HISTORY or 100) + 5) then
710 while #w.srch_hist > (MAX_SRCH_HISTORY or 100) do
711 table.remove(w.srch_hist, 1)
714 table.insert(w.srch_hist, srch)
715 end,
717 -- Search history traversing
718 srch_hist_prev = function (w)
719 if not w.srch_hist then w.srch_hist = {} end
720 if not w.srch_hist_cursor then
721 w.srch_hist_cursor = #w.srch_hist + 1
722 w.srch_hist_current = w.ibar.input.text
724 local c = w.srch_hist_cursor - 1
725 if w.srch_hist[c] then
726 w.srch_hist_cursor = c
727 w.ibar.input.text = w.srch_hist[c]
728 w.ibar.input:set_position(-1)
730 end,
732 srch_hist_next = function (w)
733 if not w.srch_hist then w.srch_hist = {} end
734 local c = (w.srch_hist_cursor or #w.srch_hist) + 1
735 if w.srch_hist[c] then
736 w.srch_hist_cursor = c
737 w.ibar.input.text = w.srch_hist[c]
738 w.ibar.input:set_position(-1)
739 elseif w.srch_hist_current then
740 w.srch_hist_cursor = nil
741 w.ibar.input.text = w.srch_hist_current
742 w.ibar.input:set_position(-1)
744 end,
746 -- Command history adding
747 cmd_hist_add = function (w, cmd)
748 if not w.cmd_hist then w.cmd_hist = {} end
749 -- Make sure history doesn't overflow
750 if #w.cmd_hist > ((MAX_CMD_HISTORY or 100) + 5) then
751 while #w.cmd_hist > (MAX_CMD_HISTORY or 100) do
752 table.remove(w.cmd_hist, 1)
755 table.insert(w.cmd_hist, cmd)
756 end,
758 -- Command history traversing
759 cmd_hist_prev = function (w)
760 if not w.cmd_hist then w.cmd_hist = {} end
761 if not w.cmd_hist_cursor then
762 w.cmd_hist_cursor = #w.cmd_hist + 1
763 w.cmd_hist_current = w.ibar.input.text
765 local c = w.cmd_hist_cursor - 1
766 if w.cmd_hist[c] then
767 w.cmd_hist_cursor = c
768 w.ibar.input.text = w.cmd_hist[c]
769 w.ibar.input:set_position(-1)
771 end,
773 cmd_hist_next = function (w)
774 if not w.cmd_hist then w.cmd_hist = {} end
775 local c = (w.cmd_hist_cursor or #w.cmd_hist) + 1
776 if w.cmd_hist[c] then
777 w.cmd_hist_cursor = c
778 w.ibar.input.text = w.cmd_hist[c]
779 w.ibar.input:set_position(-1)
780 elseif w.cmd_hist_current then
781 w.cmd_hist_cursor = nil
782 w.ibar.input.text = w.cmd_hist_current
783 w.ibar.input:set_position(-1)
785 end,
787 -- Searching functions
788 start_search = function (w, forward)
789 -- Clear previous search results
790 w:clear_search()
791 w:set_mode("search")
792 local i = w.ibar.input
793 if forward then
794 i.text = "/"
795 else
796 i.text = "?"
798 i:focus()
799 i:set_position(-1)
800 end,
802 search = function (w, text, forward)
803 local view = w:get_current()
804 local text = text or w.last_search
805 if forward == nil then forward = true end
806 local case_sensitive = false
807 local wrap = true
809 if not text or #text == 0 then
810 w:clear_search()
811 return nil
814 w.last_search = text
815 if w.searching_forward == nil then
816 w.searching_forward = forward
817 w.search_start_marker = view:get_scroll_vert()
818 else
819 -- Invert the direction if originally searching in reverse
820 forward = (w.searching_forward == forward)
823 view:search(text, case_sensitive, forward, wrap);
824 end,
826 clear_search = function (w)
827 w:get_current():clear_search()
828 -- Clear search state
829 w.last_search = nil
830 w.searching_forward = nil
831 w.search_start_marker = nil
832 end,
834 -- Webview scroll functions
835 scroll_vert = function(w, value, view)
836 if not view then view = w:get_current() end
837 local cur, max = view:get_scroll_vert()
838 if type(value) == "string" then
839 value = parse_scroll(cur, max, value)
841 view:set_scroll_vert(value)
842 end,
844 scroll_horiz = function(w, value)
845 if not view then view = w:get_current() end
846 local cur, max = view:get_scroll_horiz()
847 if type(value) == "string" then
848 value = parse_scroll(cur, max, value)
850 view:set_scroll_horiz(value)
851 end,
853 -- Tab traversing functions
854 next_tab = function (w, n)
855 w.tabs:switch((((n or 1) + w.tabs:current() -1) % w.tabs:count()) + 1)
856 end,
857 prev_tab = function (w, n)
858 w.tabs:switch(((w.tabs:current() - (n or 1) -1) % w.tabs:count()) + 1)
859 end,
860 goto_tab = function (w, n)
861 w.tabs:switch(n)
862 end,
864 -- History traversing functions
865 back = function (w, n, view)
866 (view or w:get_current()):go_back(n or 1)
867 end,
868 forward = function (w, n, view)
869 (view or w:get_current()):go_forward(n or 1)
870 end,
872 -- GUI content update functions
873 update_tab_count = function (w, i, t)
874 w.sbar.r.tabi.text = string.format("[%d/%d]", i or w.tabs:current(), t or w.tabs:count())
875 end,
877 update_win_title = function (w, view)
878 if not view then view = w:get_current() end
879 local title = view:get_prop("title")
880 local uri = view.uri
881 if not title and not uri then
882 w.win.title = "luakit"
883 else
884 w.win.title = (title or "luakit") .. " - " .. (uri or "about:blank")
886 end,
888 update_uri = function (w, view, uri)
889 if not view then view = w:get_current() end
890 w.sbar.l.uri.text = util.escape((uri or (view and view.uri) or "about:blank"))
891 end,
893 update_progress = function (w, view, p)
894 if not view then view = w:get_current() end
895 if not p then p = view:get_prop("progress") end
896 if not view:loading() or p == 1 then
897 w.sbar.l.loaded:hide()
898 else
899 w.sbar.l.loaded:show()
900 w.sbar.l.loaded.text = string.format("(%d%%)", p * 100)
902 end,
904 update_scroll = function (w, view)
905 if not view then view = w:get_current() end
906 local val, max = view:get_scroll_vert()
907 if max == 0 then val = "All"
908 elseif val == 0 then val = "Top"
909 elseif val == max then val = "Bot"
910 else val = string.format("%2d%%", (val/max) * 100)
912 w.sbar.r.scroll.text = val
913 end,
915 update_buf = function (w)
916 if w.buffer then
917 w.sbar.r.buf.text = util.escape(string.format(" %-3s", w.buffer))
918 w.sbar.r.buf:show()
919 else
920 w.sbar.r.buf:hide()
922 end,
924 update_binds = function (w, mode)
925 -- Generate the list of active key & buffer binds for this mode
926 w.binds = util.table.join(mode_binds[mode], mode_binds.all)
927 -- Clear & hide buffer
928 w.buffer = nil
929 w:update_buf()
930 end,
932 -- Tab label functions
933 make_tab_label = function (w, pos)
934 local t = {
935 label = label(),
936 sep = label(),
937 ebox = eventbox(),
938 layout = hbox(),
940 t.label.font = theme.tablabel_font or theme.font
941 t.layout:pack_start(t.label, true, true, 0)
942 t.layout:pack_start(t.sep, false, false, 0)
943 t.ebox:set_child(t.layout)
944 t.ebox:add_signal("button-release", function (e, m, b)
945 if b == 1 then
946 w.tabs:switch(pos)
947 return true
948 elseif b == 2 then
949 w:close_tab(w.tabs:atindex(pos))
950 return true
952 end)
953 return t
954 end,
956 destroy_tab_label = function (w, t)
957 if not t then t = table.remove(w.tbar.titles) end
958 for _, wi in pairs(t) do
959 wi:destroy()
961 end,
963 update_tab_labels = function (w, current)
964 local tb = w.tbar
965 local count, current = w.tabs:count(), current or w.tabs:current()
966 tb.ebox:hide()
968 -- Leave the tablist hidden if there is only one tab open
969 if count <= 1 then
970 return nil
973 if count ~= #tb.titles then
974 -- Grow the number of labels
975 while count > #tb.titles do
976 local t = w:make_tab_label(#tb.titles + 1)
977 tb.layout:pack_start(t.ebox, true, true, 0)
978 table.insert(tb.titles, t)
980 -- Prune number of labels
981 while count < #tb.titles do
982 w:destroy_tab_label()
986 if count ~= 0 then
987 for i = 1, count do
988 local t = tb.titles[i]
989 local title = " " ..i.. " "..w:get_tab_title(w.tabs:atindex(i))
990 t.label.text = util.escape(string.format(theme.tablabel_format or "%s", title))
991 w:apply_tablabel_theme(t, i == current)
994 tb.ebox:show()
995 end,
997 -- Theme functions
998 apply_tablabel_theme = function (w, t, selected, atheme)
999 local theme = atheme or theme
1000 if selected then
1001 t.label.fg = theme.selected_tablabel_fg or theme.tablabel_fg or theme.fg
1002 t.ebox.bg = theme.selected_tablabel_bg or theme.tablabel_bg or theme.bg
1003 else
1004 t.label.fg = theme.tablabel_fg or theme.fg
1005 t.ebox.bg = theme.tablabel_bg or theme.bg
1007 end,
1009 apply_window_theme = function (w, atheme)
1010 local theme = atheme or theme
1011 local s, i, t = w.sbar, w.ibar, w.tbar
1012 local fg, bg, font = theme.fg, theme.bg, theme.font
1014 -- Set foregrounds
1015 for wi, v in pairs({
1016 [s.l.uri] = theme.uri_fg or theme.statusbar_fg or fg,
1017 [s.l.loaded] = theme.loaded_fg or theme.statusbar_fg or fg,
1018 [s.r.buf] = theme.buf_fg or theme.statusbar_fg or fg,
1019 [s.r.tabi] = theme.tabi_fg or theme.statusbar_fg or fg,
1020 [s.r.scroll] = theme.scroll_fg or theme.statusbar_fg or fg,
1021 [i.prompt] = theme.prompt_fg or theme.inputbar_fg or fg,
1022 [i.input] = theme.input_fg or theme.inputbar_fg or fg,
1023 }) do wi.fg = v end
1025 -- Set backgrounds
1026 for wi, v in pairs({
1027 [s.l.ebox] = theme.statusbar_bg or bg,
1028 [s.r.ebox] = theme.statusbar_bg or bg,
1029 [s.ebox] = theme.statusbar_bg or bg,
1030 [i.ebox] = theme.inputbar_bg or bg,
1031 [i.input] = theme.input_bg or theme.inputbar_bg or bg,
1032 }) do wi.bg = v end
1034 -- Set fonts
1035 for wi, v in pairs({
1036 [s.l.uri] = theme.uri_font or theme.statusbar_font or font,
1037 [s.l.loaded] = theme.loaded_font or theme.statusbar_font or font,
1038 [s.r.buf] = theme.buf_font or theme.statusbar_font or font,
1039 [s.r.tabi] = theme.tabi_font or theme.statusbar_font or font,
1040 [s.r.scroll] = theme.scroll_font or theme.statusbar_font or font,
1041 [i.prompt] = theme.prompt_font or theme.inputbar_font or font,
1042 [i.input] = theme.input_font or theme.inputbar_font or font,
1043 }) do wi.font = v end
1044 end,
1047 -- Create new window
1048 function new_window(uris)
1049 local w = build_window()
1051 -- Pack the window table full of the common helper functions
1052 for k, v in pairs(window_helpers) do w[k] = v end
1054 attach_window_signals(w)
1056 -- Apply window theme
1057 w:apply_window_theme()
1059 -- Populate notebook with tabs
1060 for _, uri in ipairs(uris or {}) do
1061 w:new_tab(uri)
1064 -- Make sure something is loaded
1065 if w.tabs:count() == 0 then
1066 w:new_tab(HOMEPAGE)
1069 -- Set initial mode
1070 w:set_mode()
1072 return w
1075 new_window(uris)
1077 -- vim: ft=lua:et:sw=4:ts=8:sts=4:tw=80