Rename method `view_source` to `{set,get}_view_source` and update rc.lua
[luakit.git] / rc.lua
blobf6e51fbf620319524b0c209d640731b98cca2b60
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 -- Small util functions
29 function debug(...) if luakit.verbose then print(string.format(...)) end end
31 -- Luakit theme
32 theme = theme or {
33 -- Default settings
34 font = "monospace normal 9",
35 fg = "#fff",
36 bg = "#000",
38 -- General settings
39 statusbar_fg = "#fff",
40 statusbar_bg = "#000",
41 inputbar_fg = "#000",
42 inputbar_bg = "#fff",
44 -- Specific settings
45 loaded_fg = "#33AADD",
46 tablabel_fg = "#999",
47 tablabel_bg = "#111",
48 selected_tablabel_fg = "#fff",
49 selected_tablabel_bg = "#000",
51 -- Enforce a minimum tab width of 30 characters to prevent longer tab
52 -- titles overshadowing small tab titles when things get crowded.
53 tablabel_format = "%-30s",
56 widget.add_signal("new", function (wi)
57 wi:add_signal("init", function (wi)
58 if wi.type == "window" then
59 wi:add_signal("destroy", function ()
60 -- Call the quit function if this was the last window left
61 if #luakit.windows == 0 then luakit.quit() end
62 end)
63 end
64 end)
65 end)
67 -- Search engines
68 search_engines = {
69 google = "http://google.com/search?q={0}",
70 imdb = "http://imdb.com/find?s=all&q={0}",
71 sourceforge = "http://sf.net/search/?words={0}",
74 -- Add key bindings to be used across all windows
75 mode_binds = {
76 -- bind.buf(Pattern, function (w, buffer, opts) .. end, opts),
77 -- bind.key({Modifiers}, Key name, function (w, opts) .. end, opts),
78 -- bind.but({Modifiers}, Button num, function (w, opts) .. end, opts),
79 all = {
80 bind.key({}, "Escape", function (w) w:set_mode() end),
81 bind.key({"Control"}, "[", function (w) w:set_mode() end),
83 -- Mouse bindings
84 bind.but({}, 2, function (w)
85 -- Open hovered uri in new tab
86 local uri = w:get_current().hovered_uri
87 if uri then w:new_tab(uri)
88 else -- Open selection in current tab
89 uri = luakit.get_selection()
90 if uri then w:get_current().uri = uri end
91 end
92 end),
93 bind.but({}, 8, function (w) w:back() end),
94 bind.but({}, 9, function (w) w:forward() end),
96 normal = {
97 bind.key({}, "i", function (w) w:set_mode("insert") end),
98 bind.key({}, ":", function (w) w:set_mode("command") end),
100 -- Scrolling
101 bind.key({}, "h", function (w) w:scroll_horiz("-"..SCROLL_STEP.."px") end),
102 bind.key({}, "j", function (w) w:scroll_vert ("+"..SCROLL_STEP.."px") end),
103 bind.key({}, "k", function (w) w:scroll_vert ("-"..SCROLL_STEP.."px") end),
104 bind.key({}, "l", function (w) w:scroll_horiz("+"..SCROLL_STEP.."px") end),
105 bind.key({}, "Left", function (w) w:scroll_horiz("-"..SCROLL_STEP.."px") end),
106 bind.key({}, "Down", function (w) w:scroll_vert ("+"..SCROLL_STEP.."px") end),
107 bind.key({}, "Up", function (w) w:scroll_vert ("-"..SCROLL_STEP.."px") end),
108 bind.key({}, "Right", function (w) w:scroll_horiz("+"..SCROLL_STEP.."px") end),
109 bind.key({"Control"}, "d", function (w) w:scroll_page(0.5) end),
110 bind.key({"Control"}, "u", function (w) w:scroll_page(-0.5) end),
111 bind.key({"Control"}, "f", function (w) w:scroll_page(1.0) end),
112 bind.key({"Control"}, "b", function (w) w:scroll_page(-1.0) end),
113 bind.buf("^gg$", function (w) w:scroll_vert("0%") end),
114 bind.buf("^G$", function (w) w:scroll_vert("100%") end),
115 bind.buf("^[\-\+]?[0-9]+[%%G]$", function (w, b) w:scroll_vert(string.match(b, "^([\-\+]?%d+)[%%G]$") .. "%") end),
117 -- Clipboard
118 bind.key({}, "p", function (w) w:navigate(luakit.get_selection()) end),
119 bind.key({}, "P", function (w) w:new_tab(luakit.get_selection()) end),
120 bind.buf("^yy$", function (w) luakit.set_selection(w:get_current().uri) end),
121 bind.buf("^yt$", function (w) luakit.set_selection(w.win.title) end),
123 -- Commands
124 bind.buf("^o$", function (w, c) w:enter_cmd(":open ") end),
125 bind.buf("^t$", function (w, c) w:enter_cmd(":tabopen ") end),
126 bind.buf("^,g$", function (w, c) w:enter_cmd(":websearch google ") end),
128 -- Searching
129 bind.key({}, "/", function (w) w:start_search(true) end),
130 bind.key({}, "?", function (w) w:start_search(false) end),
131 bind.key({}, "n", function (w) w:search(nil, true) end),
132 bind.key({}, "N", function (w) w:search(nil, false) end),
134 -- History
135 bind.buf("^[0-9]*H$", function (w, b) w:back (tonumber(string.match(b, "^(%d*)H$") or 1)) end),
136 bind.buf("^[0-9]*L$", function (w, b) w:forward(tonumber(string.match(b, "^(%d*)L$") or 1)) end),
138 -- Tab
139 bind.buf("^[0-9]*gT$", function (w, b) w:prev_tab(tonumber(string.match(b, "^(%d*)gT$") or 1)) end),
140 bind.buf("^[0-9]*gt$", function (w, b) w:next_tab(tonumber(string.match(b, "^(%d*)gt$") or 1)) end),
141 bind.buf("^gH$", function (w) w:new_tab(HOMEPAGE) end),
142 bind.buf("^d$", function (w) w:close_tab() end),
144 bind.key({}, "r", function (w) w:reload() end),
145 bind.buf("^gh$", function (w) w:navigate(HOMEPAGE) end),
146 bind.buf("^ZZ$", function (w) luakit.quit() end),
148 -- Link following
149 bind.key({}, "f", function (w) w:set_mode("follow") end),
152 command = {
153 bind.key({"Shift"}, "Insert", function (w) w:insert_cmd(luakit.get_selection()) end),
154 bind.key({}, "Up", function (w) w:cmd_hist_prev() end),
155 bind.key({}, "Down", function (w) w:cmd_hist_next() end),
156 bind.key({}, "Tab", function (w) w:cmd_completion() end),
157 bind.key({"Control"}, "w", function (w) w:del_word() end),
158 bind.key({"Control"}, "u", function (w) w:del_line() end),
160 search = {
161 bind.key({}, "Up", function (w) w:srch_hist_prev() end),
162 bind.key({}, "Down", function (w) w:srch_hist_next() end),
164 insert = { },
167 -- Commands
168 commands = {
169 -- bind.cmd({Command, Alias1, ...}, function (w, arg, opts) .. end, opts),
170 bind.cmd({"open", "o" }, function (w, a) w:navigate(a) end),
171 bind.cmd({"tabopen", "t" }, function (w, a) w:new_tab(a) end),
172 bind.cmd({"back" }, function (w, a) w:back(tonumber(a) or 1) end),
173 bind.cmd({"forward", "f" }, function (w, a) w:forward(tonumber(a) or 1) end),
174 bind.cmd({"scroll" }, function (w, a) w:scroll_vert(a) end),
175 bind.cmd({"quit", "q" }, function (w) luakit.quit() end),
176 bind.cmd({"close", "c" }, function (w) w:close_tab() end),
177 bind.cmd({"websearch", "ws" }, function (w, e, s) w:websearch(e, s) end),
178 bind.cmd({"reload", }, function (w) w:reload() end),
179 bind.cmd({"viewsource", "vs" }, function (w) w:get_current():set_view_source(true) end),
180 bind.cmd({"viewsource!", "vs!"}, function (w) w:toggle_source() end),
183 function set_http_options(w)
184 local proxy = HTTPPROXY or os.getenv("http_proxy")
185 if proxy then w:set('proxy-uri', proxy) end
186 w:set('user-agent', 'luakit')
187 -- Uncomment the following options if you want to enable SSL certs validation.
188 -- w:set('ssl-ca-file', '/etc/certs/ca-certificates.crt')
189 -- w:set('ssl-strict', true)
192 -- Build and pack window widgets
193 function build_window()
194 -- Create a table for widgets and state variables for a window
195 local w = {
196 win = window(),
197 ebox = eventbox(),
198 layout = vbox(),
199 tabs = notebook(),
200 -- Tab bar widgets
201 tbar = {
202 layout = hbox(),
203 ebox = eventbox(),
204 titles = { },
206 -- Status bar widgets
207 sbar = {
208 layout = hbox(),
209 ebox = eventbox(),
210 -- Left aligned widgets
211 l = {
212 layout = hbox(),
213 ebox = eventbox(),
214 uri = label(),
215 loaded = label(),
217 -- Fills space between the left and right aligned widgets
218 filler = label(),
219 -- Right aligned widgets
220 r = {
221 layout = hbox(),
222 ebox = eventbox(),
223 buf = label(),
224 tabi = label(),
225 scroll = label(),
228 -- Input bar widgets
229 ibar = {
230 layout = hbox(),
231 ebox = eventbox(),
232 prompt = label(),
233 input = entry(),
237 -- Assemble window
238 w.ebox:set_child(w.layout)
239 w.win:set_child(w.ebox)
241 -- Pack tab bar
242 local t = w.tbar
243 t.ebox:set_child(t.layout, false, false, 0)
244 w.layout:pack_start(t.ebox, false, false, 0)
246 -- Pack notebook
247 w.layout:pack_start(w.tabs, true, true, 0)
249 -- Pack left-aligned statusbar elements
250 local l = w.sbar.l
251 l.layout:pack_start(l.uri, false, false, 0)
252 l.layout:pack_start(l.loaded, false, false, 0)
253 l.ebox:set_child(l.layout)
255 -- Pack right-aligned statusbar elements
256 local r = w.sbar.r
257 r.layout:pack_start(r.buf, false, false, 0)
258 r.layout:pack_start(r.tabi, false, false, 0)
259 r.layout:pack_start(r.scroll, false, false, 0)
260 r.ebox:set_child(r.layout)
262 -- Pack status bar elements
263 local s = w.sbar
264 s.layout:pack_start(l.ebox, false, false, 0)
265 s.layout:pack_start(s.filler, true, true, 0)
266 s.layout:pack_start(r.ebox, false, false, 0)
267 s.ebox:set_child(s.layout)
268 w.layout:pack_start(s.ebox, false, false, 0)
270 -- Pack input bar
271 local i = w.ibar
272 i.layout:pack_start(i.prompt, false, false, 0)
273 i.layout:pack_start(i.input, true, true, 0)
274 i.ebox:set_child(i.layout)
275 w.layout:pack_start(i.ebox, false, false, 0)
277 -- Other settings
278 i.input.show_frame = false
279 w.tabs.show_tabs = false
280 l.loaded:hide()
281 l.uri.selectable = true
283 return w
286 function attach_window_signals(w)
287 -- Attach notebook widget signals
288 w.tabs:add_signal("page-added", function (nbook, view, idx)
289 w:update_tab_count(idx)
290 w:update_tab_labels()
291 end)
293 w.tabs:add_signal("switch-page", function (nbook, view, idx)
294 w:update_tab_count(idx)
295 w:update_win_title(view)
296 w:update_uri(view)
297 w:update_progress(view)
298 w:update_tab_labels(idx)
299 end)
301 -- Attach window widget signals
302 w.win:add_signal("key-press", function (win, mods, key)
303 -- Reset command line completion
304 if w:get_mode() == "command" and key ~= "Tab" and w.compl_start then
305 w:update_uri()
306 w.compl_index = 0
309 if w:hit(mods, key) then
310 return true
312 end)
314 w.win:add_signal("mode-changed", function (win, mode)
315 local i, p = w.ibar.input, w.ibar.prompt
317 w:update_binds(mode)
318 w.cmd_hist_cursor = nil
320 -- Clear following hints if the user exits follow mode
321 if w.showing_hints then
322 w:eval_js("clear();");
323 w.showing_hints = false
326 -- If a user aborts a search return to the original position
327 if w.search_start_marker then
328 w:get_current():set_scroll_vert(w.search_start_marker)
329 w.search_start_marker = nil
332 if mode == "normal" then
333 p:hide()
334 i:hide()
335 elseif mode == "insert" then
336 i:hide()
337 i.text = ""
338 p.text = "-- INSERT --"
339 p:show()
340 elseif mode == "command" then
341 p:hide()
342 i.text = ":"
343 i:show()
344 i:focus()
345 i:set_position(-1)
346 elseif mode == "search" then
347 p:hide()
348 i:show()
349 elseif mode == "follow" then
350 w:eval_js_from_file(util.find_data("scripts/follow.js"))
351 w:eval_js("clear(); show_hints();")
352 w.showing_hints = true
353 p.text = "Follow:"
354 p:show()
355 i.text = ""
356 i:show()
357 i:focus()
358 i:set_position(-1)
359 else
360 w.ibar.prompt.text = ""
361 w.ibar.input.text = ""
363 end)
365 -- Attach inputbar widget signals
366 w.ibar.input:add_signal("changed", function()
367 local text = w.ibar.input.text
368 -- Auto-exit "command" mode if you backspace or delete the ":"
369 -- character at the start of the input box when in "command" mode.
370 if w:is_mode("command") and not string.match(text, "^:") then
371 w:set_mode()
372 elseif w:is_mode("search") then
373 if string.match(text, "^[\?\/]") then
374 w:search(string.sub(text, 2), (string.sub(text, 1, 1) == "/"))
375 else
376 w:clear_search()
377 w:set_mode()
379 elseif w:is_mode("follow") then
380 w:eval_js(string.format("update(%q)", w.ibar.input.text))
382 end)
384 w.ibar.input:add_signal("activate", function()
385 local text = w.ibar.input.text
386 if w:is_mode("command") then
387 w:cmd_hist_add(text)
388 w:match_cmd(string.sub(text, 2))
389 w:set_mode()
390 elseif w:is_mode("search") then
391 w:srch_hist_add(text)
392 w:search(string.sub(text, 2), string.sub(text, 1, 1) == "/")
393 -- User doesn't want to return to start position
394 w.search_start_marker = nil
395 w:set_mode()
396 w.ibar.prompt.text = util.escape(text)
397 w.ibar.prompt:show()
399 end)
402 -- Attach signal handlers to a new tab's webview
403 function attach_webview_signals(w, view)
404 view:add_signal("property::title", function (v)
405 w:update_tab_labels()
406 if w:is_current(v) then
407 w:update_win_title(v)
409 end)
411 view:add_signal("property::uri", function (v)
412 w:update_tab_labels()
413 if w:is_current(v) then
414 w:update_uri(v)
416 end)
418 view:add_signal("link-hover", function (v, link)
419 if w:is_current(v) and link then
420 w.sbar.l.uri.text = "Link: " .. util.escape(link)
422 end)
424 view:add_signal("link-unhover", function (v)
425 if w:is_current(v) then
426 w:update_uri(v)
428 end)
430 view:add_signal("form-active", function ()
431 w:set_mode("insert")
432 end)
434 view:add_signal("root-active", function ()
435 w:set_mode()
436 end)
438 view:add_signal("key-press", function ()
439 -- Only allow key press events to hit the webview if the user is in
440 -- "insert" mode.
441 if not w:is_mode("insert") then
442 return true
444 end)
446 view:add_signal("button-release", function (v, mods, button)
447 if w:hit(mods, button) then
448 return true
450 end)
452 view:add_signal("load-status", function (v, status)
453 if w:is_current(v) then
454 w:update_progress(v)
455 if status == "provisional" then
456 w:set_mode()
459 end)
461 -- 'link' contains the download link
462 -- 'mime' contains the mime type that is requested
463 -- return TRUE to accept or FALSE to reject
464 view:add_signal("mime-type-decision", function (v, link, mime)
465 debug("Requested link: %s (%s)", link, mime)
466 -- i.e. block binary files like *.exe
467 --if mime == "application/octet-stream" then
468 -- return false
469 --end
470 end)
472 -- 'link' contains the download link
473 -- 'filename' contains the suggested filename (from server or webkit)
474 view:add_signal("download-request", function (v, link, filename)
475 if not filename then return end
476 -- Make download dir
477 os.execute(string.format("mkdir -p %q", DOWNLOAD_DIR))
478 local dl = DOWNLOAD_DIR .. "/" .. filename
479 local wget = string.format("wget -q %q -O %q", link, dl)
480 debug("Launching: %s", wget)
481 luakit.spawn(wget)
482 end)
484 -- 'link' contains the download link
485 -- 'reason' contains the reason of the request (i.e. "link-clicked")
486 -- return TRUE to handle the request by yourself or FALSE to proceed
487 -- with default behaviour
488 view:add_signal("new-window-decision", function (v, link, reason)
489 debug("New window decision: %s (%s)", link, reason)
490 if reason == "link-clicked" then
491 new_window({ link })
492 return true
494 w:new_tab(link)
495 end)
497 view:add_signal("property::progress", function (v)
498 if w:is_current(v) then
499 w:update_progress(v)
501 end)
503 view:add_signal("expose", function (v)
504 if w:is_current(v) then
505 w:update_scroll(v)
507 end)
510 -- Parses scroll amounts of the form:
511 -- Relative: "+20%", "-20%", "+20px", "-20px"
512 -- Absolute: 20, "20%", "20px"
513 -- And returns an absolute value.
514 function parse_scroll(current, max, value)
515 if string.match(value, "^%d+px$") then
516 return tonumber(string.match(value, "^(%d+)px$"))
517 elseif string.match(value, "^%d+%%$") then
518 return math.ceil(max * (tonumber(string.match(value, "^(%d+)%%$")) / 100))
519 elseif string.match(value, "^[\-\+]%d+px") then
520 return current + tonumber(string.match(value, "^([\-\+]%d+)px"))
521 elseif string.match(value, "^[\-\+]%d+%%$") then
522 return math.ceil(current + (max * (tonumber(string.match(value, "^([\-\+]%d+)%%$")) / 100)))
523 else
524 print("E: unable to parse scroll amount:", value)
528 -- Helper functions which operate on a windows widget structure
529 window_helpers = {
530 -- Return the widget in the currently active tab
531 get_current = function (w) return w.tabs:atindex(w.tabs:current()) end,
532 -- Check if given widget is the widget in the currently active tab
533 is_current = function (w, wi) return w.tabs:indexof(wi) == w.tabs:current() end,
535 -- Wrappers around the mode plugin
536 set_mode = function (w, name) mode.set(w.win, name) end,
537 get_mode = function (w) return mode.get(w.win) end,
538 is_mode = function (w, name) return name == w:get_mode() end,
539 is_any_mode = function (w, t, name) return util.table.hasitem(t, name or w:get_mode()) end,
541 -- Wrappers around the view:get_prop & view:set_prop methods
542 get = function (w, prop, view)
543 if not view then view = w:get_current() end
544 return view:get_prop(prop)
545 end,
547 set = function (w, prop, val, view)
548 if not view then view = w:get_current() end
549 view:set_prop(prop, val)
550 end,
552 get_tab_title = function (w, view)
553 if not view then view = w:get_current() end
554 return view:get_prop("title") or view.uri or "(Untitled)"
555 end,
557 navigate = function (w, uri, view)
558 local v = view or w:get_current()
559 if v then
560 v.uri = uri
561 else
562 return w:new_tab(uri)
564 end,
566 reload = function (w, view)
567 if not view then view = w:get_current() end
568 view:reload()
569 end,
571 new_tab = function (w, uri)
572 local view = webview()
573 w.tabs:append(view)
574 set_http_options(w)
575 attach_webview_signals(w, view)
576 if uri then view.uri = uri end
577 view.show_scrollbars = false
578 w:update_tab_count()
579 end,
581 -- close the current tab
582 close_tab = function (w, view)
583 if not view then view = w:get_current() end
584 if not view then return end
585 w.tabs:remove(view)
586 view:destroy()
587 w:update_tab_count()
588 end,
590 -- evaluate javascript code and return string result
591 eval_js = function (w, script, file, view)
592 if not view then view = w:get_current() end
593 return view:eval_js(script, file or "(buffer)")
594 end,
596 -- evaluate javascript code from file and return string result
597 eval_js_from_file = function (w, file, view)
598 local fh, err = io.open(file)
599 if not fh then return error(err) end
600 local script = fh:read("*a")
601 fh:close()
602 return w:eval_js(script, file, view)
603 end,
605 -- Wrapper around the bind plugin's hit method
606 hit = function (w, mods, key)
607 local caught, newbuf = bind.hit(w.binds or {}, mods, key, w.buffer, w:is_mode("normal"), w)
608 w.buffer = newbuf
609 w:update_buf()
610 return caught
611 end,
613 -- Wrapper around the bind plugin's match_cmd method
614 match_cmd = function (w, buffer)
615 return bind.match_cmd(commands, buffer, w)
616 end,
618 -- Toggle source view
619 toggle_source = function (w, view)
620 if not view then view = w:get_current() end
621 view:set_view_source(not view:get_view_source())
622 end,
624 -- enter command or characters into command line
625 enter_cmd = function (w, cmd)
626 local i = w.ibar.input
627 w:set_mode("command")
628 i.text = cmd
629 i:set_position(-1)
630 end,
632 -- insert a string into the command line at the current cursor position
633 insert_cmd = function (w, str)
634 if not str then return nil end
635 local i = w.ibar.input
636 local text = i.text
637 local pos = i:get_position()
638 local left, right = string.sub(text, 1, pos), string.sub(text, pos+1)
639 i.text = left .. str .. right
640 i:set_position(pos + #str + 1)
641 end,
643 -- search engine wrapper
644 websearch = function (w, args)
645 local sep = string.find(args, " ")
646 local engine = string.sub(args, 1, sep-1)
647 local search = string.sub(args, sep+1)
648 if not search_engines[engine] then
649 print("E: No matching search engine found:", engine)
650 return 0
652 local uri = string.gsub(search_engines[engine], "{%d}", search)
653 return w:navigate(uri)
654 end,
656 -- Command line completion of available commands
657 cmd_completion = function (w)
658 local i = w.ibar.input
659 local s = w.sbar.l.uri
660 local cmpl = {}
662 -- Get last completion (is reset on key press other than <Tab>)
663 if not w.compl_start or w.compl_index == 0 then
664 w.compl_start = "^" .. string.sub(i.text, 2)
665 w.compl_index = 1
668 -- Get suitable commands
669 for _, b in ipairs(commands) do
670 for _, c in pairs(b.commands) do
671 if c and string.match(c, w.compl_start) then
672 table.insert(cmpl, c)
677 table.sort(cmpl)
679 if #cmpl > 0 then
680 local text = ""
681 for index, comp in pairs(cmpl) do
682 if index == w.compl_index then
683 i.text = ":" .. comp .. " "
684 i:set_position(-1)
686 if text ~= "" then
687 text = text .. " | "
689 text = text .. comp
692 -- cycle through all possible completions
693 if w.compl_index == #cmpl then
694 w.compl_index = 1
695 else
696 w.compl_index = w.compl_index + 1
698 s.text = util.escape(text)
700 end,
702 del_word = function (w)
703 local i = w.ibar.input
704 local text = i.text
705 local pos = i:get_position()
706 if text and #text > 1 and pos > 1 then
707 local left, right = string.sub(text, 2, pos), string.sub(text, pos+1)
708 if not string.find(left, "%s") then
709 left = ""
710 elseif string.find(left, "%w+%s*$") then
711 left = string.sub(left, 0, string.find(left, "%w+%s*$") - 1)
712 elseif string.find(left, "%W+%s*$") then
713 left = string.sub(left, 0, string.find(left, "%W+%s*$") - 1)
715 i.text = string.sub(text, 1, 1) .. left .. right
716 i:set_position(#left + 2)
718 end,
720 del_line = function (w)
721 local i = w.ibar.input
722 if i.text ~= ":" then
723 i.text = ":"
724 i:set_position(-1)
726 end,
728 -- Search history adding
729 srch_hist_add = function (w, srch)
730 if not w.srch_hist then w.srch_hist = {} end
731 -- Check overflow
732 if #w.srch_hist > ((MAX_SRCH_HISTORY or 100) + 5) then
733 while #w.srch_hist > (MAX_SRCH_HISTORY or 100) do
734 table.remove(w.srch_hist, 1)
737 table.insert(w.srch_hist, srch)
738 end,
740 -- Search history traversing
741 srch_hist_prev = function (w)
742 if not w.srch_hist then w.srch_hist = {} end
743 if not w.srch_hist_cursor then
744 w.srch_hist_cursor = #w.srch_hist + 1
745 w.srch_hist_current = w.ibar.input.text
747 local c = w.srch_hist_cursor - 1
748 if w.srch_hist[c] then
749 w.srch_hist_cursor = c
750 w.ibar.input.text = w.srch_hist[c]
751 w.ibar.input:set_position(-1)
753 end,
755 srch_hist_next = function (w)
756 if not w.srch_hist then w.srch_hist = {} end
757 local c = (w.srch_hist_cursor or #w.srch_hist) + 1
758 if w.srch_hist[c] then
759 w.srch_hist_cursor = c
760 w.ibar.input.text = w.srch_hist[c]
761 w.ibar.input:set_position(-1)
762 elseif w.srch_hist_current then
763 w.srch_hist_cursor = nil
764 w.ibar.input.text = w.srch_hist_current
765 w.ibar.input:set_position(-1)
767 end,
769 -- Command history adding
770 cmd_hist_add = function (w, cmd)
771 if not w.cmd_hist then w.cmd_hist = {} end
772 -- Make sure history doesn't overflow
773 if #w.cmd_hist > ((MAX_CMD_HISTORY or 100) + 5) then
774 while #w.cmd_hist > (MAX_CMD_HISTORY or 100) do
775 table.remove(w.cmd_hist, 1)
778 table.insert(w.cmd_hist, cmd)
779 end,
781 -- Command history traversing
782 cmd_hist_prev = function (w)
783 if not w.cmd_hist then w.cmd_hist = {} end
784 if not w.cmd_hist_cursor then
785 w.cmd_hist_cursor = #w.cmd_hist + 1
786 w.cmd_hist_current = w.ibar.input.text
788 local c = w.cmd_hist_cursor - 1
789 if w.cmd_hist[c] then
790 w.cmd_hist_cursor = c
791 w.ibar.input.text = w.cmd_hist[c]
792 w.ibar.input:set_position(-1)
794 end,
796 cmd_hist_next = function (w)
797 if not w.cmd_hist then w.cmd_hist = {} end
798 local c = (w.cmd_hist_cursor or #w.cmd_hist) + 1
799 if w.cmd_hist[c] then
800 w.cmd_hist_cursor = c
801 w.ibar.input.text = w.cmd_hist[c]
802 w.ibar.input:set_position(-1)
803 elseif w.cmd_hist_current then
804 w.cmd_hist_cursor = nil
805 w.ibar.input.text = w.cmd_hist_current
806 w.ibar.input:set_position(-1)
808 end,
810 -- Searching functions
811 start_search = function (w, forward)
812 -- Clear previous search results
813 w:clear_search()
814 w:set_mode("search")
815 local i = w.ibar.input
816 if forward then
817 i.text = "/"
818 else
819 i.text = "?"
821 i:focus()
822 i:set_position(-1)
823 end,
825 search = function (w, text, forward)
826 local view = w:get_current()
827 local text = text or w.last_search
828 if forward == nil then forward = true end
829 local case_sensitive = false
830 local wrap = true
832 if not text or #text == 0 then
833 w:clear_search()
834 return nil
837 w.last_search = text
838 if w.searching_forward == nil then
839 w.searching_forward = forward
840 w.search_start_marker = view:get_scroll_vert()
841 else
842 -- Invert the direction if originally searching in reverse
843 forward = (w.searching_forward == forward)
846 view:search(text, case_sensitive, forward, wrap);
847 end,
849 clear_search = function (w)
850 w:get_current():clear_search()
851 -- Clear search state
852 w.last_search = nil
853 w.searching_forward = nil
854 w.search_start_marker = nil
855 end,
857 -- Webview scroll functions
858 scroll_vert = function (w, value, view)
859 if not view then view = w:get_current() end
860 local cur, max = view:get_scroll_vert()
861 if type(value) == "string" then
862 value = parse_scroll(cur, max, value)
864 view:set_scroll_vert(value)
865 end,
867 scroll_horiz = function (w, value, view)
868 if not view then view = w:get_current() end
869 local cur, max = view:get_scroll_horiz()
870 if type(value) == "string" then
871 value = parse_scroll(cur, max, value)
873 view:set_scroll_horiz(value)
874 end,
876 -- vertical scroll of a multiple of the view_size
877 scroll_page = function (w, value, view)
878 if not view then view = w:get_current() end
879 local cur, max, size = view:get_scroll_vert()
880 view:set_scroll_vert(cur + size * value)
881 end,
883 -- Tab traversing functions
884 next_tab = function (w, n)
885 w.tabs:switch((((n or 1) + w.tabs:current() -1) % w.tabs:count()) + 1)
886 end,
887 prev_tab = function (w, n)
888 w.tabs:switch(((w.tabs:current() - (n or 1) -1) % w.tabs:count()) + 1)
889 end,
890 goto_tab = function (w, n)
891 w.tabs:switch(n)
892 end,
894 -- History traversing functions
895 back = function (w, n, view)
896 (view or w:get_current()):go_back(n or 1)
897 end,
898 forward = function (w, n, view)
899 (view or w:get_current()):go_forward(n or 1)
900 end,
902 -- GUI content update functions
903 update_tab_count = function (w, i, t)
904 w.sbar.r.tabi.text = string.format("[%d/%d]", i or w.tabs:current(), t or w.tabs:count())
905 end,
907 update_win_title = function (w, view)
908 if not view then view = w:get_current() end
909 local title = view:get_prop("title")
910 local uri = view.uri
911 if not title and not uri then
912 w.win.title = "luakit"
913 else
914 w.win.title = (title or "luakit") .. " - " .. (uri or "about:blank")
916 end,
918 update_uri = function (w, view, uri)
919 if not view then view = w:get_current() end
920 w.sbar.l.uri.text = util.escape((uri or (view and view.uri) or "about:blank"))
921 end,
923 update_progress = function (w, view, p)
924 if not view then view = w:get_current() end
925 if not p then p = view:get_prop("progress") end
926 if not view:loading() or p == 1 then
927 w.sbar.l.loaded:hide()
928 else
929 w.sbar.l.loaded:show()
930 w.sbar.l.loaded.text = string.format("(%d%%)", p * 100)
932 end,
934 update_scroll = function (w, view)
935 if not view then view = w:get_current() end
936 local val, max = view:get_scroll_vert()
937 if max == 0 then val = "All"
938 elseif val == 0 then val = "Top"
939 elseif val == max then val = "Bot"
940 else val = string.format("%2d%%", (val/max) * 100)
942 w.sbar.r.scroll.text = val
943 end,
945 update_buf = function (w)
946 if w.buffer then
947 w.sbar.r.buf.text = util.escape(string.format(" %-3s", w.buffer))
948 w.sbar.r.buf:show()
949 else
950 w.sbar.r.buf:hide()
952 end,
954 update_binds = function (w, mode)
955 -- Generate the list of active key & buffer binds for this mode
956 w.binds = util.table.join(mode_binds[mode], mode_binds.all)
957 -- Clear & hide buffer
958 w.buffer = nil
959 w:update_buf()
960 end,
962 -- Tab label functions
963 make_tab_label = function (w, pos)
964 local t = {
965 label = label(),
966 sep = label(),
967 ebox = eventbox(),
968 layout = hbox(),
970 t.label.font = theme.tablabel_font or theme.font
971 t.layout:pack_start(t.label, true, true, 0)
972 t.layout:pack_start(t.sep, false, false, 0)
973 t.ebox:set_child(t.layout)
974 t.ebox:add_signal("button-release", function (e, m, b)
975 if b == 1 then
976 w.tabs:switch(pos)
977 return true
978 elseif b == 2 then
979 w:close_tab(w.tabs:atindex(pos))
980 return true
982 end)
983 return t
984 end,
986 destroy_tab_label = function (w, t)
987 if not t then t = table.remove(w.tbar.titles) end
988 for _, wi in pairs(t) do
989 wi:destroy()
991 end,
993 update_tab_labels = function (w, current)
994 local tb = w.tbar
995 local count, current = w.tabs:count(), current or w.tabs:current()
996 tb.ebox:hide()
998 -- Leave the tablist hidden if there is only one tab open
999 if count <= 1 then
1000 return nil
1003 if count ~= #tb.titles then
1004 -- Grow the number of labels
1005 while count > #tb.titles do
1006 local t = w:make_tab_label(#tb.titles + 1)
1007 tb.layout:pack_start(t.ebox, true, true, 0)
1008 table.insert(tb.titles, t)
1010 -- Prune number of labels
1011 while count < #tb.titles do
1012 w:destroy_tab_label()
1016 if count ~= 0 then
1017 for i = 1, count do
1018 local t = tb.titles[i]
1019 local title = " " ..i.. " "..w:get_tab_title(w.tabs:atindex(i))
1020 t.label.text = util.escape(string.format(theme.tablabel_format or "%s", title))
1021 w:apply_tablabel_theme(t, i == current)
1024 tb.ebox:show()
1025 end,
1027 -- Theme functions
1028 apply_tablabel_theme = function (w, t, selected, atheme)
1029 local theme = atheme or theme
1030 if selected then
1031 t.label.fg = theme.selected_tablabel_fg or theme.tablabel_fg or theme.fg
1032 t.ebox.bg = theme.selected_tablabel_bg or theme.tablabel_bg or theme.bg
1033 else
1034 t.label.fg = theme.tablabel_fg or theme.fg
1035 t.ebox.bg = theme.tablabel_bg or theme.bg
1037 end,
1039 apply_window_theme = function (w, atheme)
1040 local theme = atheme or theme
1041 local s, i, t = w.sbar, w.ibar, w.tbar
1042 local fg, bg, font = theme.fg, theme.bg, theme.font
1044 -- Set foregrounds
1045 for wi, v in pairs({
1046 [s.l.uri] = theme.uri_fg or theme.statusbar_fg or fg,
1047 [s.l.loaded] = theme.loaded_fg or theme.statusbar_fg or fg,
1048 [s.r.buf] = theme.buf_fg or theme.statusbar_fg or fg,
1049 [s.r.tabi] = theme.tabi_fg or theme.statusbar_fg or fg,
1050 [s.r.scroll] = theme.scroll_fg or theme.statusbar_fg or fg,
1051 [i.prompt] = theme.prompt_fg or theme.inputbar_fg or fg,
1052 [i.input] = theme.input_fg or theme.inputbar_fg or fg,
1053 }) do wi.fg = v end
1055 -- Set backgrounds
1056 for wi, v in pairs({
1057 [s.l.ebox] = theme.statusbar_bg or bg,
1058 [s.r.ebox] = theme.statusbar_bg or bg,
1059 [s.ebox] = theme.statusbar_bg or bg,
1060 [i.ebox] = theme.inputbar_bg or bg,
1061 [i.input] = theme.input_bg or theme.inputbar_bg or bg,
1062 }) do wi.bg = v end
1064 -- Set fonts
1065 for wi, v in pairs({
1066 [s.l.uri] = theme.uri_font or theme.statusbar_font or font,
1067 [s.l.loaded] = theme.loaded_font or theme.statusbar_font or font,
1068 [s.r.buf] = theme.buf_font or theme.statusbar_font or font,
1069 [s.r.tabi] = theme.tabi_font or theme.statusbar_font or font,
1070 [s.r.scroll] = theme.scroll_font or theme.statusbar_font or font,
1071 [i.prompt] = theme.prompt_font or theme.inputbar_font or font,
1072 [i.input] = theme.input_font or theme.inputbar_font or font,
1073 }) do wi.font = v end
1074 end,
1077 -- Create new window
1078 function new_window(uris)
1079 local w = build_window()
1081 -- Pack the window table full of the common helper functions
1082 for k, v in pairs(window_helpers) do w[k] = v end
1084 attach_window_signals(w)
1086 -- Apply window theme
1087 w:apply_window_theme()
1089 -- Populate notebook with tabs
1090 for _, uri in ipairs(uris or {}) do
1091 w:new_tab(uri)
1094 -- Make sure something is loaded
1095 if w.tabs:count() == 0 then
1096 w:new_tab(HOMEPAGE)
1099 -- Set initial mode
1100 w:set_mode()
1102 return w
1105 new_window(uris)
1107 -- vim: ft=lua:et:sw=4:ts=8:sts=4:tw=80