move button-release callback into widgets/common.c
[luakit.git] / rc.lua
blob67fb1ed52a294ac84954ad86ec4310d003091727
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 all = {
76 bind.key({}, "Escape", function (w) w:set_mode() end),
77 bind.key({"Control"}, "[", function (w) w:set_mode() end),
79 normal = {
80 bind.key({}, "i", function (w) w:set_mode("insert") end),
81 bind.key({}, ":", function (w) w:set_mode("command") end),
83 -- Scrolling
84 bind.key({}, "h", function (w) w:scroll_horiz("-"..SCROLL_STEP.."px") end),
85 bind.key({}, "j", function (w) w:scroll_vert ("+"..SCROLL_STEP.."px") end),
86 bind.key({}, "k", function (w) w:scroll_vert ("-"..SCROLL_STEP.."px") end),
87 bind.key({}, "l", function (w) w:scroll_horiz("+"..SCROLL_STEP.."px") end),
88 bind.key({}, "Left", function (w) w:scroll_horiz("-"..SCROLL_STEP.."px") end),
89 bind.key({}, "Down", function (w) w:scroll_vert ("+"..SCROLL_STEP.."px") end),
90 bind.key({}, "Up", function (w) w:scroll_vert ("-"..SCROLL_STEP.."px") end),
91 bind.key({}, "Right", function (w) w:scroll_horiz("+"..SCROLL_STEP.."px") end),
92 bind.buf("^gg$", function (w) w:scroll_vert("0%") end),
93 bind.buf("^G$", function (w) w:scroll_vert("100%") end),
94 bind.buf("^[\-\+]?[0-9]+[%%G]$", function (w, b) w:scroll_vert(string.match(b, "^([\-\+]?%d+)[%%G]$") .. "%") end),
96 -- Clipboard
97 bind.key({}, "p", function (w) w:navigate(luakit.selection()) end),
98 bind.key({}, "P", function (w) w:new_tab(luakit.selection()) end),
100 -- Commands
101 bind.buf("^o$", function (w, c) w:enter_cmd(":open ") end),
102 bind.buf("^t$", function (w, c) w:enter_cmd(":tabopen ") end),
103 bind.buf("^,g$", function (w, c) w:enter_cmd(":websearch google ") end),
105 -- Searching
106 bind.key({}, "/", function (w) w:start_search(true) end),
107 bind.key({}, "?", function (w) w:start_search(false) end),
108 bind.key({}, "n", function (w) w:search(nil, true) end),
109 bind.key({}, "N", function (w) w:search(nil, false) end),
111 -- History
112 bind.buf("^[0-9]*H$", function (w, b) w:back (tonumber(string.match(b, "^(%d*)H$") or 1)) end),
113 bind.buf("^[0-9]*L$", function (w, b) w:forward(tonumber(string.match(b, "^(%d*)L$") or 1)) end),
115 -- Tab
116 bind.buf("^[0-9]*gT$", function (w, b) w:prev_tab(tonumber(string.match(b, "^(%d*)gT$") or 1)) end),
117 bind.buf("^[0-9]*gt$", function (w, b) w:next_tab(tonumber(string.match(b, "^(%d*)gt$") or 1)) end),
118 bind.buf("^gH$", function (w) w:new_tab(HOMEPAGE) end),
119 bind.buf("^d$", function (w) w:close_tab() end),
121 bind.key({}, "r", function (w) w:reload() 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),
154 bind.cmd({"reload", }, function (w) w:reload() end),
157 function set_http_options(w)
158 local proxy = HTTPPROXY or os.getenv("http_proxy")
159 if proxy then w:set('proxy-uri', proxy) end
160 w:set('user-agent', 'luakit')
161 -- Uncomment the following options if you want to enable SSL certs validation.
162 -- w:set('ssl-ca-file', '/etc/certs/ca-certificates.crt')
163 -- w:set('ssl-strict', true)
166 -- Build and pack window widgets
167 function build_window()
168 -- Create a table for widgets and state variables for a window
169 local w = {
170 win = window(),
171 ebox = eventbox(),
172 layout = vbox(),
173 tabs = notebook(),
174 -- Tab bar widgets
175 tbar = {
176 layout = hbox(),
177 ebox = eventbox(),
178 titles = { },
180 -- Status bar widgets
181 sbar = {
182 layout = hbox(),
183 ebox = eventbox(),
184 -- Left aligned widgets
185 l = {
186 layout = hbox(),
187 ebox = eventbox(),
188 uri = label(),
189 loaded = label(),
191 -- Fills space between the left and right aligned widgets
192 filler = label(),
193 -- Right aligned widgets
194 r = {
195 layout = hbox(),
196 ebox = eventbox(),
197 buf = label(),
198 tabi = label(),
199 scroll = label(),
202 -- Input bar widgets
203 ibar = {
204 layout = hbox(),
205 ebox = eventbox(),
206 prompt = label(),
207 input = entry(),
211 -- Assemble window
212 w.ebox:set_child(w.layout)
213 w.win:set_child(w.ebox)
215 -- Pack tab bar
216 local t = w.tbar
217 t.ebox:set_child(t.layout, false, false, 0)
218 w.layout:pack_start(t.ebox, false, false, 0)
220 -- Pack notebook
221 w.layout:pack_start(w.tabs, true, true, 0)
223 -- Pack left-aligned statusbar elements
224 local l = w.sbar.l
225 l.layout:pack_start(l.uri, false, false, 0)
226 l.layout:pack_start(l.loaded, false, false, 0)
227 l.ebox:set_child(l.layout)
229 -- Pack right-aligned statusbar elements
230 local r = w.sbar.r
231 r.layout:pack_start(r.buf, false, false, 0)
232 r.layout:pack_start(r.tabi, false, false, 0)
233 r.layout:pack_start(r.scroll, false, false, 0)
234 r.ebox:set_child(r.layout)
236 -- Pack status bar elements
237 local s = w.sbar
238 s.layout:pack_start(l.ebox, false, false, 0)
239 s.layout:pack_start(s.filler, true, true, 0)
240 s.layout:pack_start(r.ebox, false, false, 0)
241 s.ebox:set_child(s.layout)
242 w.layout:pack_start(s.ebox, false, false, 0)
244 -- Pack input bar
245 local i = w.ibar
246 i.layout:pack_start(i.prompt, false, false, 0)
247 i.layout:pack_start(i.input, true, true, 0)
248 i.ebox:set_child(i.layout)
249 w.layout:pack_start(i.ebox, false, false, 0)
251 -- Other settings
252 i.input.show_frame = false
253 w.tabs.show_tabs = false
254 l.loaded:hide()
255 l.uri.selectable = true
257 return w
260 function attach_window_signals(w)
261 -- Attach notebook widget signals
262 w.tabs:add_signal("page-added", function (nbook, view, idx)
263 w:update_tab_count(idx)
264 w:update_tab_labels()
265 end)
267 w.tabs:add_signal("switch-page", function (nbook, view, idx)
268 w:update_tab_count(idx)
269 w:update_win_title(view)
270 w:update_uri(view)
271 w:update_progress(view)
272 w:update_tab_labels(idx)
273 end)
275 -- Attach window widget signals
276 w.win:add_signal("key-press", function (win, mods, key)
277 -- Reset command line completion
278 if w:get_mode() == "command" and key ~= "Tab" and w.compl_start then
279 w:update_uri()
280 w.compl_index = 0
283 if w:hit(mods, key) then
284 return true
286 end)
288 w.win:add_signal("mode-changed", function (win, mode)
289 local i, p = w.ibar.input, w.ibar.prompt
291 w:update_binds(mode)
292 w.cmd_hist_cursor = nil
294 -- Clear following hints if the user exits follow mode
295 if w.showing_hints then
296 w:eval_js("clear();");
297 w.showing_hints = false
300 -- If a user aborts a search return to the original position
301 if w.search_start_marker then
302 w:get_current():set_scroll_vert(w.search_start_marker)
303 w.search_start_marker = nil
306 if mode == "normal" then
307 p:hide()
308 i:hide()
309 elseif mode == "insert" then
310 i:hide()
311 i.text = ""
312 p.text = "-- INSERT --"
313 p:show()
314 elseif mode == "command" then
315 p:hide()
316 i.text = ":"
317 i:show()
318 i:focus()
319 i:set_position(-1)
320 elseif mode == "search" then
321 p:hide()
322 i:show()
323 elseif mode == "follow" then
324 w:eval_js_from_file(util.find_data("scripts/follow.js"))
325 w:eval_js("clear(); show_hints();")
326 w.showing_hints = true
327 p.text = "Follow:"
328 p:show()
329 i.text = ""
330 i:show()
331 i:focus()
332 i:set_position(-1)
333 else
334 w.ibar.prompt.text = ""
335 w.ibar.input.text = ""
337 end)
339 -- Attach inputbar widget signals
340 w.ibar.input:add_signal("changed", function()
341 local text = w.ibar.input.text
342 -- Auto-exit "command" mode if you backspace or delete the ":"
343 -- character at the start of the input box when in "command" mode.
344 if w:is_mode("command") and not string.match(text, "^:") then
345 w:set_mode()
346 elseif w:is_mode("search") then
347 if string.match(text, "^[\?\/]") then
348 w:search(string.sub(text, 2), (string.sub(text, 1, 1) == "/"))
349 else
350 w:clear_search()
351 w:set_mode()
353 elseif w:is_mode("follow") then
354 w:eval_js(string.format("update(%q)", w.ibar.input.text))
356 end)
358 w.ibar.input:add_signal("activate", function()
359 local text = w.ibar.input.text
360 if w:is_mode("command") then
361 w:cmd_hist_add(text)
362 w:match_cmd(string.sub(text, 2))
363 w:set_mode()
364 elseif w:is_mode("search") then
365 w:srch_hist_add(text)
366 w:search(string.sub(text, 2), string.sub(text, 1, 1) == "/")
367 -- User doesn't want to return to start position
368 w.search_start_marker = nil
369 w:set_mode()
370 w.ibar.prompt.text = util.escape(text)
371 w.ibar.prompt:show()
373 end)
376 -- Attach signal handlers to a new tab's webview
377 function attach_webview_signals(w, view)
378 view:add_signal("title-changed", function (v)
379 w:update_tab_labels()
380 if w:is_current(v) then
381 w:update_win_title(v)
383 end)
385 view:add_signal("property::uri", function (v)
386 w:update_tab_labels()
387 if w:is_current(v) then
388 w:update_uri(v)
390 end)
392 view:add_signal("link-hover", function (v, link)
393 if w:is_current(v) and link then
394 w.sbar.l.uri.text = "Link: " .. util.escape(link)
396 end)
398 view:add_signal("link-unhover", function (v)
399 if w:is_current(v) then
400 w:update_uri(v)
402 end)
404 view:add_signal("form-active", function ()
405 w:set_mode("insert")
406 end)
408 view:add_signal("root-active", function ()
409 w:set_mode()
410 end)
412 view:add_signal("key-press", function ()
413 -- Only allow key press events to hit the webview if the user is in
414 -- "insert" mode.
415 if not w:is_mode("insert") then
416 return true
418 end)
420 view:add_signal("load-status", function (v, status)
421 if w:is_current(v) then
422 w:update_progress(v)
423 if status == "provisional" then
424 w:set_mode()
427 end)
429 -- 'link' contains the download link
430 -- 'mime' contains the mime type that is requested
431 -- return TRUE to accept or FALSE to reject
432 view:add_signal("mime-type-decision", function (v, link, mime)
433 if w:is_current(v) then
434 if luakit.verbose then print(string.format("Requested link: %s (%s)", link, mime)) end
436 -- i.e. block binary files like *.exe
437 if string.match(mime, "application/octet-stream") then
438 return false
441 end)
443 -- 'link' contains the download link
444 -- 'filename' contains the suggested filename (from server or webkit)
445 view:add_signal("download-request", function (v, link, filename)
446 if w:is_current(v) and filename then
447 -- Make download dir
448 os.execute(string.format("mkdir -p %q", DOWNLOAD_DIR))
450 local dl = DOWNLOAD_DIR .. "/" .. filename
451 local wget = string.format("wget -q %q -O %q &", link, dl)
452 if luakit.verbose then print("Launching: " .. wget) end
453 os.execute(wget)
455 end)
457 view:add_signal("property::progress", function (v)
458 if w:is_current(v) then
459 w:update_progress(v)
461 end)
463 view:add_signal("expose", function (v)
464 if w:is_current(v) then
465 w:update_scroll(v)
467 end)
469 -- return TRUE in order to prevent the signal to be handled any further
470 view:add_signal("button-release", function (v, button, state)
471 if w:is_current(v) then
472 print (string.format("Button release - button %d state %d", button, state))
473 if button == 8 then
474 w:back(1)
475 return true
476 elseif button == 9 then
477 w:forward(1)
478 return true
481 return false
482 end)
485 -- Parses scroll amounts of the form:
486 -- Relative: "+20%", "-20%", "+20px", "-20px"
487 -- Absolute: 20, "20%", "20px"
488 -- And returns an absolute value.
489 function parse_scroll(current, max, value)
490 if string.match(value, "^%d+px$") then
491 return tonumber(string.match(value, "^(%d+)px$"))
492 elseif string.match(value, "^%d+%%$") then
493 return math.ceil(max * (tonumber(string.match(value, "^(%d+)%%$")) / 100))
494 elseif string.match(value, "^[\-\+]%d+px") then
495 return current + tonumber(string.match(value, "^([\-\+]%d+)px"))
496 elseif string.match(value, "^[\-\+]%d+%%$") then
497 return math.ceil(current + (max * (tonumber(string.match(value, "^([\-\+]%d+)%%$")) / 100)))
498 else
499 print("E: unable to parse scroll amount:", value)
503 -- Helper functions which operate on a windows widget structure
504 window_helpers = {
505 -- Return the widget in the currently active tab
506 get_current = function (w) return w.tabs:atindex(w.tabs:current()) end,
507 -- Check if given widget is the widget in the currently active tab
508 is_current = function (w, wi) return w.tabs:indexof(wi) == w.tabs:current() end,
510 -- Wrappers around the mode plugin
511 set_mode = function (w, name) mode.set(w.win, name) end,
512 get_mode = function (w) return mode.get(w.win) end,
513 is_mode = function (w, name) return name == w:get_mode() end,
514 is_any_mode = function (w, t, name) return util.table.hasitem(t, name or w:get_mode()) end,
516 -- Wrappers around the view:get_prop & view:set_prop methods
517 get = function (w, prop, view)
518 if not view then view = w:get_current() end
519 return view:get_prop(prop)
520 end,
522 set = function (w, prop, val, view)
523 if not view then view = w:get_current() end
524 view:set_prop(prop, val)
525 end,
527 get_tab_title = function (w, view)
528 if not view then view = w:get_current() end
529 return view:get_prop("title") or view.uri or "(Untitled)"
530 end,
532 navigate = function (w, uri, view)
533 local v = view or w:get_current()
534 if v then
535 v.uri = uri
536 else
537 return w:new_tab(uri)
539 end,
541 reload = function (w, view)
542 if not view then view = w:get_current() end
543 view:reload()
544 end,
546 new_tab = function (w, uri)
547 local view = webview()
548 w.tabs:append(view)
549 set_http_options(w)
550 attach_webview_signals(w, view)
551 if uri then view.uri = uri end
552 view.show_scrollbars = false
553 w:update_tab_count()
554 end,
556 -- close the current tab
557 close_tab = function (w)
558 view = w:get_current()
559 if not view then return end
560 w.tabs:remove(view)
561 view:destroy()
562 w:update_tab_count()
563 end,
565 -- evaluate javascript code and return string result
566 eval_js = function (w, script, file, view)
567 if not view then view = w:get_current() end
568 return view:eval_js(script, file or "(buffer)")
569 end,
571 -- evaluate javascript code from file and return string result
572 eval_js_from_file = function (w, file, view)
573 local fh, err = io.open(file)
574 if not fh then return error(err) end
575 local script = fh:read("*a")
576 fh:close()
577 return w:eval_js(script, file, view)
578 end,
580 -- Wrapper around the bind plugin's hit method
581 hit = function (w, mods, key)
582 local caught, newbuf = bind.hit(w.binds or {}, mods, key, w.buffer, w:is_mode("normal"), w)
583 w.buffer = newbuf
584 w:update_buf()
585 return caught
586 end,
588 -- Wrapper around the bind plugin's match_cmd method
589 match_cmd = function (w, buffer)
590 return bind.match_cmd(commands, buffer, w)
591 end,
593 -- enter command or characters into command line
594 enter_cmd = function (w, cmd)
595 local i = w.ibar.input
596 w:set_mode("command")
597 i.text = cmd
598 i:set_position(-1)
599 end,
601 -- insert a string into the command line at the current cursor position
602 insert_cmd = function (w, str)
603 if not str then return nil end
604 local i = w.ibar.input
605 local text = i.text
606 local pos = i:get_position()
607 local left, right = string.sub(text, 1, pos), string.sub(text, pos+1)
608 i.text = left .. str .. right
609 i:set_position(pos + #str + 1)
610 end,
612 -- search engine wrapper
613 websearch = function (w, args)
614 local sep = string.find(args, " ")
615 local engine = string.sub(args, 1, sep-1)
616 local search = string.sub(args, sep+1)
617 if not search_engines[engine] then
618 print("E: No matching search engine found:", engine)
619 return 0
621 local uri = string.gsub(search_engines[engine], "{%d}", search)
622 return w:navigate(uri)
623 end,
625 -- Command line completion of available commands
626 cmd_completion = function (w)
627 local i = w.ibar.input
628 local s = w.sbar.l.uri
629 local cmpl = {}
631 -- Get last completion (is reset on key press other than <Tab>)
632 if not w.compl_start or w.compl_index == 0 then
633 w.compl_start = "^" .. string.sub(i.text, 2)
634 w.compl_index = 1
637 -- Get suitable commands
638 for _, b in ipairs(commands) do
639 for _, c in pairs(b.commands) do
640 if c and string.match(c, w.compl_start) then
641 table.insert(cmpl, c)
646 table.sort(cmpl)
648 if #cmpl > 0 then
649 local text = ""
650 for index, comp in pairs(cmpl) do
651 if index == w.compl_index then
652 i.text = ":" .. comp .. " "
653 i:set_position(-1)
655 if text ~= "" then
656 text = text .. " | "
658 text = text .. comp
661 -- cycle through all possible completions
662 if w.compl_index == #cmpl then
663 w.compl_index = 1
664 else
665 w.compl_index = w.compl_index + 1
667 s.text = util.escape(text)
669 end,
671 del_word = function (w)
672 local i = w.ibar.input
673 local text = i.text
674 local pos = i:get_position()
675 if text and #text > 1 and pos > 1 then
676 local left, right = string.sub(text, 2, pos), string.sub(text, pos+1)
677 if not string.find(left, "%s") then
678 left = ""
679 elseif string.find(left, "%w+%s*$") then
680 left = string.sub(left, 0, string.find(left, "%w+%s*$") - 1)
681 elseif string.find(left, "%W+%s*$") then
682 left = string.sub(left, 0, string.find(left, "%W+%s*$") - 1)
684 i.text = string.sub(text, 1, 1) .. left .. right
685 i:set_position(#left + 2)
687 end,
689 del_line = function (w)
690 local i = w.ibar.input
691 if i.text ~= ":" then
692 i.text = ":"
693 i:set_position(-1)
695 end,
697 -- Search history adding
698 srch_hist_add = function (w, srch)
699 if not w.srch_hist then w.srch_hist = {} end
700 -- Check overflow
701 if #w.srch_hist > ((MAX_SRCH_HISTORY or 100) + 5) then
702 while #w.srch_hist > (MAX_SRCH_HISTORY or 100) do
703 table.remove(w.srch_hist, 1)
706 table.insert(w.srch_hist, srch)
707 end,
709 -- Search history traversing
710 srch_hist_prev = function (w)
711 if not w.srch_hist then w.srch_hist = {} end
712 if not w.srch_hist_cursor then
713 w.srch_hist_cursor = #w.srch_hist + 1
714 w.srch_hist_current = w.ibar.input.text
716 local c = w.srch_hist_cursor - 1
717 if w.srch_hist[c] then
718 w.srch_hist_cursor = c
719 w.ibar.input.text = w.srch_hist[c]
720 w.ibar.input:set_position(-1)
722 end,
724 srch_hist_next = function (w)
725 if not w.srch_hist then w.srch_hist = {} end
726 local c = (w.srch_hist_cursor or #w.srch_hist) + 1
727 if w.srch_hist[c] then
728 w.srch_hist_cursor = c
729 w.ibar.input.text = w.srch_hist[c]
730 w.ibar.input:set_position(-1)
731 elseif w.srch_hist_current then
732 w.srch_hist_cursor = nil
733 w.ibar.input.text = w.srch_hist_current
734 w.ibar.input:set_position(-1)
736 end,
738 -- Command history adding
739 cmd_hist_add = function (w, cmd)
740 if not w.cmd_hist then w.cmd_hist = {} end
741 -- Make sure history doesn't overflow
742 if #w.cmd_hist > ((MAX_CMD_HISTORY or 100) + 5) then
743 while #w.cmd_hist > (MAX_CMD_HISTORY or 100) do
744 table.remove(w.cmd_hist, 1)
747 table.insert(w.cmd_hist, cmd)
748 end,
750 -- Command history traversing
751 cmd_hist_prev = function (w)
752 if not w.cmd_hist then w.cmd_hist = {} end
753 if not w.cmd_hist_cursor then
754 w.cmd_hist_cursor = #w.cmd_hist + 1
755 w.cmd_hist_current = w.ibar.input.text
757 local c = w.cmd_hist_cursor - 1
758 if w.cmd_hist[c] then
759 w.cmd_hist_cursor = c
760 w.ibar.input.text = w.cmd_hist[c]
761 w.ibar.input:set_position(-1)
763 end,
765 cmd_hist_next = function (w)
766 if not w.cmd_hist then w.cmd_hist = {} end
767 local c = (w.cmd_hist_cursor or #w.cmd_hist) + 1
768 if w.cmd_hist[c] then
769 w.cmd_hist_cursor = c
770 w.ibar.input.text = w.cmd_hist[c]
771 w.ibar.input:set_position(-1)
772 elseif w.cmd_hist_current then
773 w.cmd_hist_cursor = nil
774 w.ibar.input.text = w.cmd_hist_current
775 w.ibar.input:set_position(-1)
777 end,
779 -- Searching functions
780 start_search = function (w, forward)
781 -- Clear previous search results
782 w:clear_search()
783 w:set_mode("search")
784 local i = w.ibar.input
785 if forward then
786 i.text = "/"
787 else
788 i.text = "?"
790 i:focus()
791 i:set_position(-1)
792 end,
794 search = function (w, text, forward)
795 local view = w:get_current()
796 local text = text or w.last_search
797 if forward == nil then forward = true end
798 local case_sensitive = false
799 local wrap = true
801 if not text or #text == 0 then
802 w:clear_search()
803 return nil
806 w.last_search = text
807 if w.searching_forward == nil then
808 w.searching_forward = forward
809 w.search_start_marker = view:get_scroll_vert()
810 else
811 -- Invert the direction if originally searching in reverse
812 forward = (w.searching_forward == forward)
815 view:search(text, case_sensitive, forward, wrap);
816 end,
818 clear_search = function (w)
819 w:get_current():clear_search()
820 -- Clear search state
821 w.last_search = nil
822 w.searching_forward = nil
823 w.search_start_marker = nil
824 end,
826 -- Webview scroll functions
827 scroll_vert = function(w, value, view)
828 if not view then view = w:get_current() end
829 local cur, max = view:get_scroll_vert()
830 if type(value) == "string" then
831 value = parse_scroll(cur, max, value)
833 view:set_scroll_vert(value)
834 end,
836 scroll_horiz = function(w, value)
837 if not view then view = w:get_current() end
838 local cur, max = view:get_scroll_horiz()
839 if type(value) == "string" then
840 value = parse_scroll(cur, max, value)
842 view:set_scroll_horiz(value)
843 end,
845 -- Tab traversing functions
846 next_tab = function (w, n)
847 w.tabs:switch((((n or 1) + w.tabs:current() -1) % w.tabs:count()) + 1)
848 end,
849 prev_tab = function (w, n)
850 w.tabs:switch(((w.tabs:current() - (n or 1) -1) % w.tabs:count()) + 1)
851 end,
852 goto_tab = function (w, n)
853 w.tabs:switch(n)
854 end,
856 -- History traversing functions
857 back = function (w, n, view)
858 (view or w:get_current()):go_back(n or 1)
859 end,
860 forward = function (w, n, view)
861 (view or w:get_current()):go_forward(n or 1)
862 end,
864 -- GUI content update functions
865 update_tab_count = function (w, i, t)
866 w.sbar.r.tabi.text = string.format("[%d/%d]", i or w.tabs:current(), t or w.tabs:count())
867 end,
869 update_win_title = function (w, view)
870 if not view then view = w:get_current() end
871 local title = view:get_prop("title")
872 local uri = view.uri
873 if not title and not uri then
874 w.win.title = "luakit"
875 else
876 w.win.title = (title or "luakit") .. " - " .. (uri or "about:blank")
878 end,
880 update_uri = function (w, view, uri)
881 if not view then view = w:get_current() end
882 w.sbar.l.uri.text = util.escape((uri or (view and view.uri) or "about:blank"))
883 end,
885 update_progress = function (w, view, p)
886 if not view then view = w:get_current() end
887 if not p then p = view:get_prop("progress") end
888 if not view:loading() or p == 1 then
889 w.sbar.l.loaded:hide()
890 else
891 w.sbar.l.loaded:show()
892 w.sbar.l.loaded.text = string.format("(%d%%)", p * 100)
894 end,
896 update_scroll = function (w, view)
897 if not view then view = w:get_current() end
898 local val, max = view:get_scroll_vert()
899 if max == 0 then val = "All"
900 elseif val == 0 then val = "Top"
901 elseif val == max then val = "Bot"
902 else val = string.format("%2d%%", (val/max) * 100)
904 w.sbar.r.scroll.text = val
905 end,
907 update_buf = function (w)
908 if w.buffer then
909 w.sbar.r.buf.text = util.escape(string.format(" %-3s", w.buffer))
910 w.sbar.r.buf:show()
911 else
912 w.sbar.r.buf:hide()
914 end,
916 update_binds = function (w, mode)
917 -- Generate the list of active key & buffer binds for this mode
918 w.binds = util.table.join(mode_binds[mode], mode_binds.all)
919 -- Clear & hide buffer
920 w.buffer = nil
921 w:update_buf()
922 end,
924 -- Tab label functions
925 make_tab_label = function (w, pos)
926 local t = {
927 label = label(),
928 sep = label(),
929 ebox = eventbox(),
930 layout = hbox(),
932 t.label.font = theme.tablabel_font or theme.font
933 t.layout:pack_start(t.label, true, true, 0)
934 t.layout:pack_start(t.sep, false, false, 0)
935 t.ebox:set_child(t.layout)
936 t.ebox:add_signal("clicked", function (e) w.tabs:switch(pos) end)
937 return t
938 end,
940 destroy_tab_label = function (w, t)
941 if not t then t = table.remove(w.tbar.titles) end
942 for _, wi in pairs(t) do
943 wi:destroy()
945 end,
947 update_tab_labels = function (w, current)
948 local tb = w.tbar
949 local count, current = w.tabs:count(), current or w.tabs:current()
950 tb.ebox:hide()
952 -- Leave the tablist hidden if there is only one tab open
953 if count <= 1 then
954 return nil
957 if count ~= #tb.titles then
958 -- Grow the number of labels
959 while count > #tb.titles do
960 local t = w:make_tab_label(#tb.titles + 1)
961 tb.layout:pack_start(t.ebox, true, true, 0)
962 table.insert(tb.titles, t)
964 -- Prune number of labels
965 while count < #tb.titles do
966 w:destroy_tab_label()
970 if count ~= 0 then
971 for i = 1, count do
972 local t = tb.titles[i]
973 local title = " " ..i.. " "..w:get_tab_title(w.tabs:atindex(i))
974 t.label.text = util.escape(string.format(theme.tablabel_format or "%s", title))
975 w:apply_tablabel_theme(t, i == current)
978 tb.ebox:show()
979 end,
981 -- Theme functions
982 apply_tablabel_theme = function (w, t, selected, atheme)
983 local theme = atheme or theme
984 if selected then
985 t.label.fg = theme.selected_tablabel_fg or theme.tablabel_fg or theme.fg
986 t.ebox.bg = theme.selected_tablabel_bg or theme.tablabel_bg or theme.bg
987 else
988 t.label.fg = theme.tablabel_fg or theme.fg
989 t.ebox.bg = theme.tablabel_bg or theme.bg
991 end,
993 apply_window_theme = function (w, atheme)
994 local theme = atheme or theme
995 local s, i, t = w.sbar, w.ibar, w.tbar
996 local fg, bg, font = theme.fg, theme.bg, theme.font
998 -- Set foregrounds
999 for wi, v in pairs({
1000 [s.l.uri] = theme.uri_fg or theme.statusbar_fg or fg,
1001 [s.l.loaded] = theme.loaded_fg or theme.statusbar_fg or fg,
1002 [s.r.buf] = theme.buf_fg or theme.statusbar_fg or fg,
1003 [s.r.tabi] = theme.tabi_fg or theme.statusbar_fg or fg,
1004 [s.r.scroll] = theme.scroll_fg or theme.statusbar_fg or fg,
1005 [i.prompt] = theme.prompt_fg or theme.inputbar_fg or fg,
1006 [i.input] = theme.input_fg or theme.inputbar_fg or fg,
1007 }) do wi.fg = v end
1009 -- Set backgrounds
1010 for wi, v in pairs({
1011 [s.l.ebox] = theme.statusbar_bg or bg,
1012 [s.r.ebox] = theme.statusbar_bg or bg,
1013 [s.ebox] = theme.statusbar_bg or bg,
1014 [i.ebox] = theme.inputbar_bg or bg,
1015 [i.input] = theme.input_bg or theme.inputbar_bg or bg,
1016 }) do wi.bg = v end
1018 -- Set fonts
1019 for wi, v in pairs({
1020 [s.l.uri] = theme.uri_font or theme.statusbar_font or font,
1021 [s.l.loaded] = theme.loaded_font or theme.statusbar_font or font,
1022 [s.r.buf] = theme.buf_font or theme.statusbar_font or font,
1023 [s.r.tabi] = theme.tabi_font or theme.statusbar_font or font,
1024 [s.r.scroll] = theme.scroll_font or theme.statusbar_font or font,
1025 [i.prompt] = theme.prompt_font or theme.inputbar_font or font,
1026 [i.input] = theme.input_font or theme.inputbar_font or font,
1027 }) do wi.font = v end
1028 end,
1031 -- Create new window
1032 function new_window(uris)
1033 local w = build_window()
1035 -- Pack the window table full of the common helper functions
1036 for k, v in pairs(window_helpers) do w[k] = v end
1038 attach_window_signals(w)
1040 -- Apply window theme
1041 w:apply_window_theme()
1043 -- Populate notebook with tabs
1044 for _, uri in ipairs(uris or {}) do
1045 w:new_tab(uri)
1048 -- Make sure something is loaded
1049 if w.tabs:count() == 0 then
1050 w:new_tab(HOMEPAGE)
1053 -- Set initial mode
1054 w:set_mode()
1056 return w
1059 new_window(uris)
1061 -- vim: ft=lua:et:sw=4:ts=8:sts=4:tw=80