Merge branch 'master' into lineguide-config
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
local b05 = 'rgba(0,0,0,0.5)' local red = '#994D4D'
|
||||
local b80 = '#333333' local orange = '#B3661A'
|
||||
local b60 = '#808080' local green = '#52994D'
|
||||
local b40 = '#ADADAD' local teal = '#4D9999'
|
||||
local b20 = '#CECECE' local blue = '#1A66B3'
|
||||
local b00 = '#E6E6E6' local magenta = '#994D99'
|
||||
--------------------------=--------------------------
|
||||
local style = require 'core.style'
|
||||
local common = require 'core.common'
|
||||
--------------------------=--------------------------
|
||||
style.line_highlight = { common.color(b20) }
|
||||
style.background = { common.color(b00) }
|
||||
style.background2 = { common.color(b20) }
|
||||
style.background3 = { common.color(b20) }
|
||||
style.text = { common.color(b60) }
|
||||
style.caret = { common.color(b80) }
|
||||
style.accent = { common.color(b80) }
|
||||
style.dim = { common.color(b60) }
|
||||
style.divider = { common.color(b40) }
|
||||
style.selection = { common.color(b40) }
|
||||
style.line_number = { common.color(b60) }
|
||||
style.line_number2 = { common.color(b80) }
|
||||
style.scrollbar = { common.color(b40) }
|
||||
style.scrollbar2 = { common.color(b60) }
|
||||
style.nagbar = { common.color(red) }
|
||||
style.nagbar_text = { common.color(b00) }
|
||||
style.nagbar_dim = { common.color(b05) }
|
||||
--------------------------=--------------------------
|
||||
style.syntax = {}
|
||||
style.syntax['normal'] = { common.color(b80) }
|
||||
style.syntax['symbol'] = { common.color(b80) }
|
||||
style.syntax['comment'] = { common.color(b60) }
|
||||
style.syntax['keyword'] = { common.color(blue) }
|
||||
style.syntax['keyword2'] = { common.color(red) }
|
||||
style.syntax['number'] = { common.color(teal) }
|
||||
style.syntax['literal'] = { common.color(blue) }
|
||||
style.syntax['string'] = { common.color(green) }
|
||||
style.syntax['operator'] = { common.color(magenta) }
|
||||
style.syntax['function'] = { common.color(blue) }
|
||||
--------------------------=--------------------------
|
||||
style.syntax.paren1 = { common.color(magenta) }
|
||||
style.syntax.paren2 = { common.color(orange) }
|
||||
style.syntax.paren3 = { common.color(teal) }
|
||||
style.syntax.paren4 = { common.color(blue) }
|
||||
style.syntax.paren5 = { common.color(red) }
|
||||
--------------------------=--------------------------
|
||||
style.lint = {}
|
||||
style.lint.info = { common.color(blue) }
|
||||
style.lint.hint = { common.color(green) }
|
||||
style.lint.warning = { common.color(red) }
|
||||
style.lint.error = { common.color(orange) }
|
||||
@@ -42,10 +42,10 @@ function command.get_all_valid()
|
||||
end
|
||||
|
||||
|
||||
local function perform(name)
|
||||
local function perform(name, ...)
|
||||
local cmd = command.map[name]
|
||||
if cmd and cmd.predicate() then
|
||||
cmd.perform()
|
||||
if cmd and cmd.predicate(...) then
|
||||
cmd.perform(...)
|
||||
return true
|
||||
end
|
||||
return false
|
||||
|
||||
@@ -104,11 +104,20 @@ command.add(nil, {
|
||||
end, function (text)
|
||||
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
||||
end, nil, function(text)
|
||||
local path_stat, err = system.get_file_info(common.home_expand(text))
|
||||
local filename = common.home_expand(text)
|
||||
local path_stat, err = system.get_file_info(filename)
|
||||
if err then
|
||||
core.error("Cannot open file %q: %q", text, err)
|
||||
if err:find("No such file", 1, true) then
|
||||
-- check if the containing directory exists
|
||||
local dirname = common.dirname(filename)
|
||||
local dir_stat = dirname and system.get_file_info(dirname)
|
||||
if not dirname or (dir_stat and dir_stat.type == 'dir') then
|
||||
return true
|
||||
end
|
||||
end
|
||||
core.error("Cannot open file %s: %s", text, err)
|
||||
elseif path_stat.type == 'dir' then
|
||||
core.error("Cannot open %q, is a folder", text)
|
||||
core.error("Cannot open %s, is a folder", text)
|
||||
else
|
||||
return true
|
||||
end
|
||||
@@ -138,6 +147,10 @@ command.add(nil, {
|
||||
end,
|
||||
|
||||
["core:change-project-folder"] = function()
|
||||
local dirname = common.dirname(core.project_dir)
|
||||
if dirname then
|
||||
core.command_view:set_text(common.home_encode(dirname) .. PATHSEP)
|
||||
end
|
||||
core.command_view:enter("Change Project Folder", function(text, item)
|
||||
text = system.absolute_path(common.home_expand(item and item.text or text))
|
||||
if text == core.project_dir then return end
|
||||
@@ -146,11 +159,15 @@ command.add(nil, {
|
||||
core.error("Cannot open folder %q", text)
|
||||
return
|
||||
end
|
||||
core.confirm_close_all(core.open_folder_project, text)
|
||||
core.confirm_close_docs(core.docs, core.open_folder_project, text)
|
||||
end, suggest_directory)
|
||||
end,
|
||||
|
||||
["core:open-project-folder"] = function()
|
||||
local dirname = common.dirname(core.project_dir)
|
||||
if dirname then
|
||||
core.command_view:set_text(common.home_encode(dirname) .. PATHSEP)
|
||||
end
|
||||
core.command_view:enter("Open Project", function(text, item)
|
||||
text = common.home_expand(item and item.text or text)
|
||||
local path_stat = system.get_file_info(text)
|
||||
|
||||
+53
-15
@@ -43,7 +43,12 @@ local function append_line_if_last_line(line)
|
||||
end
|
||||
|
||||
local function save(filename)
|
||||
doc():save(filename and core.normalize_to_project_dir(filename))
|
||||
local abs_filename
|
||||
if filename then
|
||||
filename = core.normalize_to_project_dir(filename)
|
||||
abs_filename = core.project_absolute_path(filename)
|
||||
end
|
||||
doc():save(filename, abs_filename)
|
||||
local saved_filename = doc().filename
|
||||
core.log("Saved \"%s\"", saved_filename)
|
||||
end
|
||||
@@ -62,13 +67,14 @@ local function cut_or_copy(delete)
|
||||
doc().cursor_clipboard[idx] = ""
|
||||
end
|
||||
end
|
||||
doc().cursor_clipboard["full"] = full_text
|
||||
system.set_clipboard(full_text)
|
||||
end
|
||||
|
||||
local function split_cursor(direction)
|
||||
local new_cursors = {}
|
||||
for _, line1, col1 in doc():get_selections() do
|
||||
if line1 > 1 and line1 < #doc().lines then
|
||||
if line1 + direction >= 1 and line1 + direction <= #doc().lines then
|
||||
table.insert(new_cursors, { line1 + direction, col1 })
|
||||
end
|
||||
end
|
||||
@@ -76,6 +82,16 @@ local function split_cursor(direction)
|
||||
core.blink_reset()
|
||||
end
|
||||
|
||||
local function set_cursor(x, y, type)
|
||||
local line, col = dv():resolve_screen_position(x, y)
|
||||
doc():set_selection(line, col, line, col)
|
||||
if type == "word" or type == "lines" then
|
||||
command.perform("doc:select-" .. type)
|
||||
end
|
||||
dv().mouse_selecting = { line, col }
|
||||
core.blink_reset()
|
||||
end
|
||||
|
||||
local commands = {
|
||||
["doc:undo"] = function()
|
||||
doc():undo()
|
||||
@@ -94,8 +110,13 @@ local commands = {
|
||||
end,
|
||||
|
||||
["doc:paste"] = function()
|
||||
local clipboard = system.get_clipboard()
|
||||
-- If the clipboard has changed since our last look, use that instead
|
||||
if doc().cursor_clipboard["full"] ~= clipboard then
|
||||
doc().cursor_clipboard = {}
|
||||
end
|
||||
for idx, line1, col1, line2, col2 in doc():get_selections() do
|
||||
local value = doc().cursor_clipboard[idx] or system.get_clipboard()
|
||||
local value = doc().cursor_clipboard[idx] or clipboard
|
||||
doc():text_input(value:gsub("\r", ""), idx)
|
||||
end
|
||||
end,
|
||||
@@ -157,16 +178,6 @@ local commands = {
|
||||
doc():set_selection(line, col)
|
||||
end,
|
||||
|
||||
|
||||
["doc:indent"] = function()
|
||||
for idx, line1, col1, line2, col2 in doc_multiline_selections(true) do
|
||||
local l1, c1, l2, c2 = doc():indent_text(false, line1, col1, line2, col2)
|
||||
if l1 then
|
||||
doc():set_selections(idx, l1, c1, l2, c2)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
["doc:select-lines"] = function()
|
||||
for idx, line1, _, line2 in doc():get_selections(true) do
|
||||
append_line_if_last_line(line2)
|
||||
@@ -363,12 +374,14 @@ local commands = {
|
||||
end
|
||||
core.command_view:set_text(old_filename)
|
||||
core.command_view:enter("Rename", function(filename)
|
||||
doc():save(filename)
|
||||
save(common.home_expand(filename))
|
||||
core.log("Renamed \"%s\" to \"%s\"", old_filename, filename)
|
||||
if filename ~= old_filename then
|
||||
os.remove(old_filename)
|
||||
end
|
||||
end, common.path_suggest)
|
||||
end, function (text)
|
||||
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
|
||||
end)
|
||||
end,
|
||||
|
||||
|
||||
@@ -385,6 +398,30 @@ local commands = {
|
||||
os.remove(filename)
|
||||
core.log("Removed \"%s\"", filename)
|
||||
end,
|
||||
|
||||
["doc:select-to-cursor"] = function(x, y, clicks)
|
||||
local line1, col1 = select(3, doc():get_selection())
|
||||
local line2, col2 = dv():resolve_screen_position(x, y)
|
||||
dv().mouse_selecting = { line1, col1 }
|
||||
doc():set_selection(line2, col2, line1, col1)
|
||||
end,
|
||||
|
||||
["doc:set-cursor"] = function(x, y)
|
||||
set_cursor(x, y, "set")
|
||||
end,
|
||||
|
||||
["doc:set-cursor-word"] = function(x, y)
|
||||
set_cursor(x, y, "word")
|
||||
end,
|
||||
|
||||
["doc:set-cursor-line"] = function(x, y, clicks)
|
||||
set_cursor(x, y, "lines")
|
||||
end,
|
||||
|
||||
["doc:split-cursor"] = function(x, y, clicks)
|
||||
local line, col = dv():resolve_screen_position(x, y)
|
||||
doc():add_selection(line, col, line, col)
|
||||
end,
|
||||
|
||||
["doc:create-cursor-previous-line"] = function()
|
||||
split_cursor(-1)
|
||||
@@ -411,6 +448,7 @@ local translations = {
|
||||
["start-of-line"] = translate.start_of_line,
|
||||
["end-of-line"] = translate.end_of_line,
|
||||
["start-of-word"] = translate.start_of_word,
|
||||
["start-of-indentation"] = translate.start_of_indentation,
|
||||
["end-of-word"] = translate.end_of_word,
|
||||
["previous-line"] = DocView.translate.previous_line,
|
||||
["next-line"] = DocView.translate.next_line,
|
||||
|
||||
@@ -12,6 +12,7 @@ local last_finds, last_view, last_fn, last_text, last_sel
|
||||
|
||||
local case_sensitive = config.find_case_sensitive or false
|
||||
local find_regex = config.find_regex or false
|
||||
local found_expression
|
||||
|
||||
local function doc()
|
||||
return core.active_view:is(DocView) and core.active_view.doc or last_view.doc
|
||||
@@ -29,43 +30,57 @@ local function get_find_tooltip()
|
||||
end
|
||||
|
||||
local function update_preview(sel, search_fn, text)
|
||||
local ok, line1, col1, line2, col2 =
|
||||
pcall(search_fn, last_view.doc, sel[1], sel[2], text, case_sensitive, find_regex)
|
||||
local ok, line1, col1, line2, col2 = pcall(search_fn, last_view.doc,
|
||||
sel[1], sel[2], text, case_sensitive, find_regex)
|
||||
if ok and line1 and text ~= "" then
|
||||
last_view.doc:set_selection(line2, col2, line1, col1)
|
||||
last_view:scroll_to_line(line2, true)
|
||||
return true
|
||||
found_expression = true
|
||||
else
|
||||
last_view.doc:set_selection(unpack(sel))
|
||||
return false
|
||||
last_view.doc:set_selection(table.unpack(sel))
|
||||
found_expression = false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function insert_unique(t, v)
|
||||
local n = #t
|
||||
for i = 1, n do
|
||||
if t[i] == v then return end
|
||||
end
|
||||
t[n + 1] = v
|
||||
end
|
||||
|
||||
|
||||
local function find(label, search_fn)
|
||||
last_view, last_sel, last_finds = core.active_view,
|
||||
{ core.active_view.doc:get_selection() }, {}
|
||||
local text, found = last_view.doc:get_text(unpack(last_sel)), false
|
||||
local text = last_view.doc:get_text(table.unpack(last_sel))
|
||||
found_expression = false
|
||||
|
||||
core.command_view:set_text(text, true)
|
||||
core.status_view:show_tooltip(get_find_tooltip())
|
||||
|
||||
core.command_view:enter(label, function(text)
|
||||
core.command_view:set_hidden_suggestions()
|
||||
core.command_view:enter(label, function(text, item)
|
||||
insert_unique(core.previous_find, text)
|
||||
core.status_view:remove_tooltip()
|
||||
if found then
|
||||
if found_expression then
|
||||
last_fn, last_text = search_fn, text
|
||||
else
|
||||
core.error("Couldn't find %q", text)
|
||||
last_view.doc:set_selection(unpack(last_sel))
|
||||
last_view:scroll_to_make_visible(unpack(last_sel))
|
||||
last_view.doc:set_selection(table.unpack(last_sel))
|
||||
last_view:scroll_to_make_visible(table.unpack(last_sel))
|
||||
end
|
||||
end, function(text)
|
||||
found = update_preview(last_sel, search_fn, text)
|
||||
update_preview(last_sel, search_fn, text)
|
||||
last_fn, last_text = search_fn, text
|
||||
return core.previous_find
|
||||
end, function(explicit)
|
||||
core.status_view:remove_tooltip()
|
||||
if explicit then
|
||||
last_view.doc:set_selection(unpack(last_sel))
|
||||
last_view:scroll_to_make_visible(unpack(last_sel))
|
||||
last_view.doc:set_selection(table.unpack(last_sel))
|
||||
last_view:scroll_to_make_visible(table.unpack(last_sel))
|
||||
end
|
||||
end)
|
||||
end
|
||||
@@ -75,18 +90,25 @@ local function replace(kind, default, fn)
|
||||
core.command_view:set_text(default, true)
|
||||
|
||||
core.status_view:show_tooltip(get_find_tooltip())
|
||||
core.command_view:set_hidden_suggestions()
|
||||
core.command_view:enter("Find To Replace " .. kind, function(old)
|
||||
insert_unique(core.previous_find, old)
|
||||
core.command_view:set_text(old, true)
|
||||
|
||||
local s = string.format("Replace %s %q With", kind, old)
|
||||
core.command_view:set_hidden_suggestions()
|
||||
core.command_view:enter(s, function(new)
|
||||
core.status_view:remove_tooltip()
|
||||
insert_unique(core.previous_replace, new)
|
||||
local n = doc():replace(function(text)
|
||||
return fn(text, old, new)
|
||||
end)
|
||||
core.log("Replaced %d instance(s) of %s %q with %q", n, kind, old, new)
|
||||
end, function() end, function()
|
||||
end, function() return core.previous_replace end, function()
|
||||
core.status_view:remove_tooltip()
|
||||
end)
|
||||
end, function() return core.previous_find end, function()
|
||||
core.status_view:remove_tooltip()
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -94,13 +116,60 @@ local function has_selection()
|
||||
return core.active_view:is(DocView) and core.active_view.doc:has_selection()
|
||||
end
|
||||
|
||||
command.add(has_selection, {
|
||||
local function has_unique_selection()
|
||||
if not core.active_view:is(DocView) then return false end
|
||||
local text = nil
|
||||
for idx, line1, col1, line2, col2 in doc():get_selections(true, true) do
|
||||
if line1 == line2 and col1 == col2 then return false end
|
||||
local selection = doc():get_text(line1, col1, line2, col2)
|
||||
if text ~= nil and text ~= selection then return false end
|
||||
text = selection
|
||||
end
|
||||
return text ~= nil
|
||||
end
|
||||
|
||||
local function is_in_selection(line, col, l1, c1, l2, c2)
|
||||
if line < l1 or line > l2 then return false end
|
||||
if line == l1 and col <= c1 then return false end
|
||||
if line == l2 and col > c2 then return false end
|
||||
return true
|
||||
end
|
||||
|
||||
local function is_in_any_selection(line, col)
|
||||
for idx, l1, c1, l2, c2 in doc():get_selections(true, false) do
|
||||
if is_in_selection(line, col, l1, c1, l2, c2) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function select_next(all)
|
||||
local il1, ic1 = doc():get_selection(true)
|
||||
for idx, l1, c1, l2, c2 in doc():get_selections(true, true) do
|
||||
local text = doc():get_text(l1, c1, l2, c2)
|
||||
repeat
|
||||
l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
|
||||
if l1 == il1 and c1 == ic1 then break end
|
||||
if l2 and (all or not is_in_any_selection(l2, c2)) then
|
||||
doc():add_selection(l2, c2, l1, c1)
|
||||
if not all then
|
||||
core.active_view:scroll_to_make_visible(l2, c2)
|
||||
return
|
||||
end
|
||||
end
|
||||
until not all or not l2
|
||||
if all then break end
|
||||
end
|
||||
end
|
||||
|
||||
command.add(has_unique_selection, {
|
||||
["find-replace:select-next"] = function()
|
||||
local l1, c1, l2, c2 = doc():get_selection(true)
|
||||
local text = doc():get_text(l1, c1, l2, c2)
|
||||
l1, c1, l2, c2 = search.find(doc(), l2, c2, text, { wrap = true })
|
||||
if l2 then doc():set_selection(l2, c2, l1, c1) end
|
||||
end
|
||||
end,
|
||||
["find-replace:select-add-next"] = function() select_next(false) end,
|
||||
["find-replace:select-add-all"] = function() select_next(true) end
|
||||
})
|
||||
|
||||
command.add("core.docview", {
|
||||
@@ -112,11 +181,13 @@ command.add("core.docview", {
|
||||
end,
|
||||
|
||||
["find-replace:replace"] = function()
|
||||
replace("Text", doc():get_text(doc():get_selection(true)), function(text, old, new)
|
||||
local l1, c1, l2, c2 = doc():get_selection()
|
||||
local selected_text = doc():get_text(l1, c1, l2, c2)
|
||||
replace("Text", l1 == l2 and selected_text or "", function(text, old, new)
|
||||
if not find_regex then
|
||||
return text:gsub(old:gsub("%W", "%%%1"), new:gsub("%%", "%%%%"), nil)
|
||||
end
|
||||
local result, matches = regex.gsub(regex.compile(old), text, new)
|
||||
local result, matches = regex.gsub(regex.compile(old, "m"), text, new)
|
||||
return result, #matches
|
||||
end)
|
||||
end,
|
||||
@@ -180,12 +251,12 @@ command.add("core.commandview", {
|
||||
["find-replace:toggle-sensitivity"] = function()
|
||||
case_sensitive = not case_sensitive
|
||||
core.status_view:show_tooltip(get_find_tooltip())
|
||||
update_preview(last_sel, last_fn, last_text)
|
||||
if last_sel then update_preview(last_sel, last_fn, last_text) end
|
||||
end,
|
||||
|
||||
["find-replace:toggle-regex"] = function()
|
||||
find_regex = not find_regex
|
||||
core.status_view:show_tooltip(get_find_tooltip())
|
||||
update_preview(last_sel, last_fn, last_text)
|
||||
if last_sel then update_preview(last_sel, last_fn, last_text) end
|
||||
end
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@ local style = require "core.style"
|
||||
local DocView = require "core.docview"
|
||||
local command = require "core.command"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
|
||||
|
||||
local t = {
|
||||
@@ -21,9 +22,15 @@ local t = {
|
||||
end,
|
||||
|
||||
["root:close-all"] = function()
|
||||
core.confirm_close_all(core.root_view.close_all_docviews, core.root_view)
|
||||
core.confirm_close_docs(core.docs, core.root_view.close_all_docviews, core.root_view)
|
||||
end,
|
||||
|
||||
["root:close-all-others"] = function()
|
||||
local active_doc, docs = core.active_view and core.active_view.doc, {}
|
||||
for i, v in ipairs(core.docs) do if v ~= active_doc then table.insert(docs, v) end end
|
||||
core.confirm_close_docs(docs, core.root_view.close_all_docviews, core.root_view, true)
|
||||
end,
|
||||
|
||||
["root:switch-to-previous-tab"] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
local idx = node:get_view_idx(core.active_view)
|
||||
@@ -57,7 +64,7 @@ local t = {
|
||||
table.insert(node.views, idx + 1, core.active_view)
|
||||
end
|
||||
end,
|
||||
|
||||
|
||||
["root:shrink"] = function()
|
||||
local node = core.root_view:get_active_node()
|
||||
local parent = node:get_parent_node(core.root_view.root_node)
|
||||
@@ -70,7 +77,7 @@ local t = {
|
||||
local parent = node:get_parent_node(core.root_view.root_node)
|
||||
local n = (parent.a == node) and 0.1 or -0.1
|
||||
parent.divider = common.clamp(parent.divider + n, 0.1, 0.9)
|
||||
end,
|
||||
end
|
||||
}
|
||||
|
||||
|
||||
@@ -116,3 +123,14 @@ command.add(function()
|
||||
local node = core.root_view:get_active_node()
|
||||
return not node:get_locked_size()
|
||||
end, t)
|
||||
|
||||
command.add(nil, {
|
||||
["root:scroll"] = function(delta)
|
||||
local view = (core.root_view.overlapping_node and core.root_view.overlapping_node.active_view) or core.active_view
|
||||
if view and view.scrollable then
|
||||
view.scroll.to.y = view.scroll.to.y + delta * -config.mouse_wheel_scroll
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
})
|
||||
|
||||
@@ -15,6 +15,8 @@ end
|
||||
|
||||
local CommandView = DocView:extend()
|
||||
|
||||
CommandView.context = "application"
|
||||
|
||||
local max_suggestions = 10
|
||||
|
||||
local noop = function() end
|
||||
@@ -32,6 +34,7 @@ function CommandView:new()
|
||||
self.suggestion_idx = 1
|
||||
self.suggestions = {}
|
||||
self.suggestions_height = 0
|
||||
self.show_suggestions = true
|
||||
self.last_change_id = 0
|
||||
self.gutter_width = 0
|
||||
self.gutter_text_brightness = 0
|
||||
@@ -43,6 +46,11 @@ function CommandView:new()
|
||||
end
|
||||
|
||||
|
||||
function CommandView:set_hidden_suggestions()
|
||||
self.show_suggestions = false
|
||||
end
|
||||
|
||||
|
||||
function CommandView:get_name()
|
||||
return View.get_name(self)
|
||||
end
|
||||
@@ -81,10 +89,29 @@ end
|
||||
|
||||
|
||||
function CommandView:move_suggestion_idx(dir)
|
||||
local n = self.suggestion_idx + dir
|
||||
self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
|
||||
self:complete()
|
||||
self.last_change_id = self.doc:get_change_id()
|
||||
if self.show_suggestions then
|
||||
local n = self.suggestion_idx + dir
|
||||
self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
|
||||
self:complete()
|
||||
self.last_change_id = self.doc:get_change_id()
|
||||
else
|
||||
local current_suggestion = #self.suggestions > 0 and self.suggestions[self.suggestion_idx].text
|
||||
local text = self:get_text()
|
||||
if text == current_suggestion then
|
||||
local n = self.suggestion_idx + dir
|
||||
if n == 0 and self.save_suggestion then
|
||||
self:set_text(self.save_suggestion)
|
||||
else
|
||||
self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
|
||||
self:complete()
|
||||
end
|
||||
else
|
||||
self.save_suggestion = text
|
||||
self:complete()
|
||||
end
|
||||
self.last_change_id = self.doc:get_change_id()
|
||||
self.state.suggest(self:get_text())
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -132,6 +159,8 @@ function CommandView:exit(submitted, inexplicit)
|
||||
self.doc:reset()
|
||||
self.suggestions = {}
|
||||
if not submitted then cancel(not inexplicit) end
|
||||
self.show_suggestions = true
|
||||
self.save_suggestion = nil
|
||||
end
|
||||
|
||||
|
||||
@@ -185,7 +214,7 @@ function CommandView:update()
|
||||
|
||||
-- update suggestions box height
|
||||
local lh = self:get_suggestion_line_height()
|
||||
local dest = math.min(#self.suggestions, max_suggestions) * lh
|
||||
local dest = self.show_suggestions and math.min(#self.suggestions, max_suggestions) * lh or 0
|
||||
self:move_towards("suggestions_height", dest)
|
||||
|
||||
-- update suggestion cursor offset
|
||||
@@ -254,7 +283,9 @@ end
|
||||
|
||||
function CommandView:draw()
|
||||
CommandView.super.draw(self)
|
||||
core.root_view:defer_draw(draw_suggestions_box, self)
|
||||
if self.show_suggestions then
|
||||
core.root_view:defer_draw(draw_suggestions_box, self)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
+33
-11
@@ -41,6 +41,11 @@ function common.lerp(a, b, t)
|
||||
end
|
||||
|
||||
|
||||
function common.distance(x1, y1, x2, y2)
|
||||
return math.sqrt(math.pow(x2-x1, 2)+math.pow(y2-y1, 2))
|
||||
end
|
||||
|
||||
|
||||
function common.color(str)
|
||||
local r, g, b, a = str:match("#(%x%x)(%x%x)(%x%x)")
|
||||
if r then
|
||||
@@ -230,6 +235,12 @@ function common.basename(path)
|
||||
end
|
||||
|
||||
|
||||
-- can return nil if there is no directory part in the path
|
||||
function common.dirname(path)
|
||||
return path:match("(.+)[\\/][^\\/]+$")
|
||||
end
|
||||
|
||||
|
||||
function common.home_encode(text)
|
||||
if HOME and string.find(text, HOME, 1, true) == 1 then
|
||||
local dir_pos = #HOME + 1
|
||||
@@ -257,16 +268,6 @@ function common.home_expand(text)
|
||||
end
|
||||
|
||||
|
||||
function common.normalize_path(filename)
|
||||
if PATHSEP == '\\' then
|
||||
filename = filename:gsub('[/\\]', '\\')
|
||||
local drive, rem = filename:match('^([a-zA-Z])(:.*)')
|
||||
return drive and drive:upper() .. rem or filename
|
||||
end
|
||||
return filename
|
||||
end
|
||||
|
||||
|
||||
local function split_on_slash(s, sep_pattern)
|
||||
local t = {}
|
||||
if s:match("^[/\\]") then
|
||||
@@ -279,8 +280,29 @@ local function split_on_slash(s, sep_pattern)
|
||||
end
|
||||
|
||||
|
||||
function common.normalize_path(filename)
|
||||
if not filename then return end
|
||||
if PATHSEP == '\\' then
|
||||
filename = filename:gsub('[/\\]', '\\')
|
||||
local drive, rem = filename:match('^([a-zA-Z])(:.*)')
|
||||
filename = drive and drive:upper() .. rem or filename
|
||||
end
|
||||
local parts = split_on_slash(filename, PATHSEP)
|
||||
local accu = {}
|
||||
for _, part in ipairs(parts) do
|
||||
if part == '..' and #accu > 0 and accu[#accu] ~= ".." then
|
||||
table.remove(accu)
|
||||
elseif part ~= '.' then
|
||||
table.insert(accu, part)
|
||||
end
|
||||
end
|
||||
local npath = table.concat(accu, PATHSEP)
|
||||
return npath == "" and PATHSEP or npath
|
||||
end
|
||||
|
||||
|
||||
function common.path_belongs_to(filename, path)
|
||||
return filename and string.find(filename, path .. PATHSEP, 1, true) == 1
|
||||
return string.find(filename, path .. PATHSEP, 1, true) == 1
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ config.fps = 60
|
||||
config.max_log_items = 80
|
||||
config.message_timeout = 5
|
||||
config.mouse_wheel_scroll = 50 * SCALE
|
||||
config.scroll_past_end = true
|
||||
config.file_size_limit = 10
|
||||
config.ignore_files = "^%."
|
||||
config.symbol_pattern = "[%a_][%w_]*"
|
||||
@@ -12,6 +13,7 @@ config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
|
||||
config.undo_merge_timeout = 0.3
|
||||
config.max_undos = 10000
|
||||
config.max_tabs = 10
|
||||
config.always_show_tabs = false
|
||||
config.highlight_current_line = true
|
||||
config.line_height = 1.2
|
||||
config.indent_size = 2
|
||||
@@ -22,9 +24,11 @@ config.max_project_files = 2000
|
||||
config.transitions = true
|
||||
config.animation_rate = 1.0
|
||||
config.blink_period = 0.8
|
||||
config.disable_blink = false
|
||||
config.draw_whitespace = false
|
||||
config.borderless = false
|
||||
config.tab_close_button = true
|
||||
config.max_clicks = 3
|
||||
|
||||
-- Disable plugin loading setting to false the config entry
|
||||
-- of the same name.
|
||||
@@ -32,5 +36,6 @@ config.plugins = {}
|
||||
|
||||
config.plugins.trimwhitespace = false
|
||||
config.plugins.lineguide = false
|
||||
config.plugins.drawwhitespace = false
|
||||
|
||||
return config
|
||||
|
||||
@@ -49,7 +49,7 @@ function ContextMenu:register(predicate, items)
|
||||
local width, height = 0, 0 --precalculate the size of context menu
|
||||
for i, item in ipairs(items) do
|
||||
if item ~= DIVIDER then
|
||||
item.info = keymap.reverse_map[item.command]
|
||||
item.info = keymap.get_binding(item.command)
|
||||
end
|
||||
local lw, lh = get_item_size(item)
|
||||
width = math.max(width, lw)
|
||||
|
||||
@@ -24,7 +24,7 @@ function Highlighter:new(doc)
|
||||
for i = self.first_invalid_line, max do
|
||||
local state = (i > 1) and self.lines[i - 1].state
|
||||
local line = self.lines[i]
|
||||
if not (line and line.init_state == state) then
|
||||
if not (line and line.init_state == state and line.text == self.doc.lines[i]) then
|
||||
self.lines[i] = self:tokenize_line(i, state)
|
||||
end
|
||||
end
|
||||
@@ -44,12 +44,25 @@ function Highlighter:reset()
|
||||
self.max_wanted_line = 0
|
||||
end
|
||||
|
||||
|
||||
function Highlighter:invalidate(idx)
|
||||
self.first_invalid_line = math.min(self.first_invalid_line, idx)
|
||||
self.max_wanted_line = math.min(self.max_wanted_line, #self.doc.lines)
|
||||
end
|
||||
|
||||
function Highlighter:insert_notify(line, n)
|
||||
self:invalidate(line)
|
||||
for i = 1, n do
|
||||
table.insert(self.lines, line, nil)
|
||||
end
|
||||
end
|
||||
|
||||
function Highlighter:remove_notify(line, n)
|
||||
self:invalidate(line)
|
||||
for i = 1, n do
|
||||
table.remove(self.lines, line)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Highlighter:tokenize_line(idx, state)
|
||||
local res = {}
|
||||
|
||||
+79
-26
@@ -17,10 +17,15 @@ local function split_lines(text)
|
||||
return res
|
||||
end
|
||||
|
||||
function Doc:new(filename)
|
||||
|
||||
function Doc:new(filename, abs_filename, new_file)
|
||||
self.new_file = new_file
|
||||
self:reset()
|
||||
if filename then
|
||||
self:load(filename)
|
||||
self:set_filename(filename, abs_filename)
|
||||
if not new_file then
|
||||
self:load(filename)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -47,16 +52,15 @@ function Doc:reset_syntax()
|
||||
end
|
||||
|
||||
|
||||
function Doc:set_filename(filename)
|
||||
function Doc:set_filename(filename, abs_filename)
|
||||
self.filename = filename
|
||||
self.abs_filename = system.absolute_path(filename)
|
||||
self.abs_filename = abs_filename
|
||||
end
|
||||
|
||||
|
||||
function Doc:load(filename)
|
||||
local fp = assert( io.open(filename, "rb") )
|
||||
self:reset()
|
||||
self:set_filename(filename)
|
||||
self.lines = {}
|
||||
for line in fp:lines() do
|
||||
if line:byte(-1) == 13 then
|
||||
@@ -73,17 +77,20 @@ function Doc:load(filename)
|
||||
end
|
||||
|
||||
|
||||
function Doc:save(filename)
|
||||
filename = filename or assert(self.filename, "no filename set to default to")
|
||||
function Doc:save(filename, abs_filename)
|
||||
if not filename then
|
||||
assert(self.filename, "no filename set to default to")
|
||||
filename = self.filename
|
||||
abs_filename = self.abs_filename
|
||||
end
|
||||
local fp = assert( io.open(filename, "wb") )
|
||||
for _, line in ipairs(self.lines) do
|
||||
if self.crlf then line = line:gsub("\n", "\r\n") end
|
||||
fp:write(line)
|
||||
end
|
||||
fp:close()
|
||||
if filename then
|
||||
self:set_filename(filename)
|
||||
end
|
||||
self:set_filename(filename, abs_filename)
|
||||
self.new_file = false
|
||||
self:reset_syntax()
|
||||
self:clean()
|
||||
end
|
||||
@@ -95,7 +102,11 @@ end
|
||||
|
||||
|
||||
function Doc:is_dirty()
|
||||
return self.clean_change_id ~= self:get_change_id()
|
||||
if self.new_file then
|
||||
return #self.lines > 1 or #self.lines[1] > 1
|
||||
else
|
||||
return self.clean_change_id ~= self:get_change_id()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -117,6 +128,19 @@ function Doc:get_selection(sort)
|
||||
return line1, col1, line2, col2, sort
|
||||
end
|
||||
|
||||
function Doc:get_selection_text(limit)
|
||||
limit = limit or math.huge
|
||||
local result = {}
|
||||
for idx, line1, col1, line2, col2 in self:get_selections() do
|
||||
if idx > limit then break end
|
||||
if line1 ~= line2 or col1 ~= col2 then
|
||||
local text = self:get_text(line1, col1, line2, col2)
|
||||
if text ~= "" then result[#result + 1] = text end
|
||||
end
|
||||
end
|
||||
return table.concat(result, "\n")
|
||||
end
|
||||
|
||||
function Doc:has_selection()
|
||||
local line1, col1, line2, col2 = self:get_selection(false)
|
||||
return line1 ~= line2 or col1 ~= col2
|
||||
@@ -166,19 +190,21 @@ function Doc:merge_cursors(idx)
|
||||
if self.selections[i] == self.selections[j] and
|
||||
self.selections[i+1] == self.selections[j+1] then
|
||||
common.splice(self.selections, i, 4)
|
||||
common.splice(self.cursor_clipboard, i, 1)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if #self.selections <= 4 then self.cursor_clipboard = {} end
|
||||
end
|
||||
|
||||
local function selection_iterator(invariant, idx)
|
||||
local target = invariant[3] and (idx*4 - 7) or (idx*4 + 1)
|
||||
if target > #invariant[1] or target <= 0 or (type(invariant[3]) == "number" and invariant[3] ~= idx - 1) then return end
|
||||
if invariant[2] then
|
||||
return idx+(invariant[3] and -1 or 1), sort_positions(unpack(invariant[1], target, target+4))
|
||||
return idx+(invariant[3] and -1 or 1), sort_positions(table.unpack(invariant[1], target, target+4))
|
||||
else
|
||||
return idx+(invariant[3] and -1 or 1), unpack(invariant[1], target, target+4)
|
||||
return idx+(invariant[3] and -1 or 1), table.unpack(invariant[1], target, target+4)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -279,7 +305,7 @@ local function pop_undo(self, undo_stack, redo_stack, modified)
|
||||
local line1, col1, line2, col2 = table.unpack(cmd)
|
||||
self:raw_remove(line1, col1, line2, col2, redo_stack, cmd.time)
|
||||
elseif cmd.type == "selection" then
|
||||
self.selections = { unpack(cmd) }
|
||||
self.selections = { table.unpack(cmd) }
|
||||
end
|
||||
|
||||
modified = modified or (cmd.type ~= "selection")
|
||||
@@ -300,6 +326,7 @@ end
|
||||
function Doc:raw_insert(line, col, text, undo_stack, time)
|
||||
-- split text into lines and merge with line at insertion point
|
||||
local lines = split_lines(text)
|
||||
local len = #lines[#lines]
|
||||
local before = self.lines[line]:sub(1, col - 1)
|
||||
local after = self.lines[line]:sub(col)
|
||||
for i = 1, #lines - 1 do
|
||||
@@ -310,14 +337,22 @@ function Doc:raw_insert(line, col, text, undo_stack, time)
|
||||
|
||||
-- splice lines into line array
|
||||
common.splice(self.lines, line, 1, lines)
|
||||
|
||||
-- keep cursors where they should be
|
||||
for idx, cline1, ccol1, cline2, ccol2 in self:get_selections(true, true) do
|
||||
if cline1 < line then break end
|
||||
local line_addition = (line < cline1 or col < ccol1) and #lines - 1 or 0
|
||||
local column_addition = line == cline1 and ccol1 > col and len or 0
|
||||
self:set_selections(idx, cline1 + line_addition, ccol1 + column_addition, cline2 + line_addition, ccol2 + column_addition)
|
||||
end
|
||||
|
||||
-- push undo
|
||||
local line2, col2 = self:position_offset(line, col, #text)
|
||||
push_undo(undo_stack, time, "selection", unpack(self.selections))
|
||||
push_undo(undo_stack, time, "selection", table.unpack(self.selections))
|
||||
push_undo(undo_stack, time, "remove", line, col, line2, col2)
|
||||
|
||||
-- update highlighter and assure selection is in bounds
|
||||
self.highlighter:invalidate(line)
|
||||
self.highlighter:insert_notify(line, #lines - 1)
|
||||
self:sanitize_selection()
|
||||
end
|
||||
|
||||
@@ -325,7 +360,7 @@ end
|
||||
function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
|
||||
-- push undo
|
||||
local text = self:get_text(line1, col1, line2, col2)
|
||||
push_undo(undo_stack, time, "selection", unpack(self.selections))
|
||||
push_undo(undo_stack, time, "selection", table.unpack(self.selections))
|
||||
push_undo(undo_stack, time, "insert", line1, col1, text)
|
||||
|
||||
-- get line content before/after removed text
|
||||
@@ -334,9 +369,17 @@ function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
|
||||
|
||||
-- splice line into line array
|
||||
common.splice(self.lines, line1, line2 - line1 + 1, { before .. after })
|
||||
|
||||
-- move all cursors back if they share a line with the removed text
|
||||
for idx, cline1, ccol1, cline2, ccol2 in self:get_selections(true, true) do
|
||||
if cline1 < line2 then break end
|
||||
local line_removal = line2 - line1
|
||||
local column_removal = line2 == cline2 and col2 < ccol1 and (line2 == line1 and col2 - col1 or col2) or 0
|
||||
self:set_selections(idx, cline1 - line_removal, ccol1 - column_removal, cline2 - line_removal, ccol2 - column_removal)
|
||||
end
|
||||
|
||||
-- update highlighter and assure selection is in bounds
|
||||
self.highlighter:invalidate(line1)
|
||||
self.highlighter:remove_notify(line1, line2 - line1)
|
||||
self:sanitize_selection()
|
||||
end
|
||||
|
||||
@@ -370,7 +413,7 @@ end
|
||||
|
||||
|
||||
function Doc:text_input(text, idx)
|
||||
for sidx, line1, col1, line2, col2 in self:get_selections(true, idx) do
|
||||
for sidx, line1, col1, line2, col2 in self:get_selections(true, idx or true) do
|
||||
if line1 ~= line2 or col1 ~= col2 then
|
||||
self:delete_to_cursor(sidx)
|
||||
end
|
||||
@@ -379,12 +422,7 @@ function Doc:text_input(text, idx)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Doc:replace(fn)
|
||||
local line1, col1, line2, col2 = self:get_selection(true)
|
||||
if line1 == line2 and col1 == col2 then
|
||||
line1, col1, line2, col2 = 1, 1, #self.lines, #self.lines[#self.lines]
|
||||
end
|
||||
function Doc:replace_cursor(idx, line1, col1, line2, col2, fn)
|
||||
local old_text = self:get_text(line1, col1, line2, col2)
|
||||
local new_text, n = fn(old_text)
|
||||
if old_text ~= new_text then
|
||||
@@ -392,12 +430,27 @@ function Doc:replace(fn)
|
||||
self:remove(line1, col1, line2, col2)
|
||||
if line1 == line2 and col1 == col2 then
|
||||
line2, col2 = self:position_offset(line1, col1, #new_text)
|
||||
self:set_selection(line1, col1, line2, col2)
|
||||
self:set_selections(idx, line1, col1, line2, col2)
|
||||
end
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
function Doc:replace(fn)
|
||||
local has_selection, n = false, 0
|
||||
for idx, line1, col1, line2, col2 in self:get_selections(true) do
|
||||
if line1 ~= line2 or col1 ~= col2 then
|
||||
n = n + self:replace_cursor(idx, line1, col1, line2, col2, fn)
|
||||
has_selection = true
|
||||
end
|
||||
end
|
||||
if not has_selection then
|
||||
self:set_selection(table.unpack(self.selections))
|
||||
n = n + self:replace_cursor(1, 1, 1, #self.lines, #self.lines[#self.lines], fn)
|
||||
end
|
||||
return n
|
||||
end
|
||||
|
||||
|
||||
function Doc:delete_to_cursor(idx, ...)
|
||||
for sidx, line1, col1, line2, col2 in self:get_selections(true, idx) do
|
||||
|
||||
@@ -117,6 +117,10 @@ function translate.start_of_line(doc, line, col)
|
||||
return line, 1
|
||||
end
|
||||
|
||||
function translate.start_of_indentation(doc, line, col)
|
||||
local s, e = doc.lines[line]:find("^%s*")
|
||||
return line, col > e + 1 and e + 1 or 1
|
||||
end
|
||||
|
||||
function translate.end_of_line(doc, line, col)
|
||||
return line, math.huge
|
||||
|
||||
+33
-74
@@ -9,6 +9,7 @@ local View = require "core.view"
|
||||
|
||||
local DocView = View:extend()
|
||||
|
||||
DocView.context = "session"
|
||||
|
||||
local function move_to_line_offset(dv, line, col, offset)
|
||||
local xo = dv.last_x_offset
|
||||
@@ -97,6 +98,9 @@ end
|
||||
|
||||
|
||||
function DocView:get_scrollable_size()
|
||||
if not config.scroll_past_end then
|
||||
return self:get_line_height() * (#self.doc.lines) + style.padding.y * 2
|
||||
end
|
||||
return self:get_line_height() * (#self.doc.lines - 1) + self.size.y
|
||||
end
|
||||
|
||||
@@ -149,14 +153,14 @@ function DocView:get_col_x_offset(line, col)
|
||||
local font = style.syntax_fonts[type] or default_font
|
||||
for char in common.utf8_chars(text) do
|
||||
if column == col then
|
||||
return xoffset / font:subpixel_scale()
|
||||
return xoffset
|
||||
end
|
||||
xoffset = xoffset + font:get_width_subpixel(char)
|
||||
xoffset = xoffset + font:get_width(char)
|
||||
column = column + #char
|
||||
end
|
||||
end
|
||||
|
||||
return xoffset / default_font:subpixel_scale()
|
||||
return xoffset
|
||||
end
|
||||
|
||||
|
||||
@@ -165,14 +169,12 @@ function DocView:get_x_offset_col(line, x)
|
||||
|
||||
local xoffset, last_i, i = 0, 1, 1
|
||||
local default_font = self:get_font()
|
||||
local subpixel_scale = default_font:subpixel_scale()
|
||||
local x_subpixel = subpixel_scale * x + subpixel_scale / 2
|
||||
for _, type, text in self.doc.highlighter:each_token(line) do
|
||||
local font = style.syntax_fonts[type] or default_font
|
||||
for char in common.utf8_chars(text) do
|
||||
local w = font:get_width_subpixel(char)
|
||||
if xoffset >= subpixel_scale * x then
|
||||
return (xoffset - x_subpixel > w / 2) and last_i or i
|
||||
local w = font:get_width(char)
|
||||
if xoffset >= x then
|
||||
return (xoffset - x > w / 2) and last_i or i
|
||||
end
|
||||
xoffset = xoffset + w
|
||||
last_i = i
|
||||
@@ -222,52 +224,6 @@ function DocView:scroll_to_make_visible(line, col)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function mouse_selection(doc, clicks, line1, col1, line2, col2)
|
||||
local swap = line2 < line1 or line2 == line1 and col2 <= col1
|
||||
if swap then
|
||||
line1, col1, line2, col2 = line2, col2, line1, col1
|
||||
end
|
||||
if clicks % 4 == 2 then
|
||||
line1, col1 = translate.start_of_word(doc, line1, col1)
|
||||
line2, col2 = translate.end_of_word(doc, line2, col2)
|
||||
elseif clicks % 4 == 3 then
|
||||
if line2 == #doc.lines and doc.lines[#doc.lines] ~= "\n" then
|
||||
doc:insert(math.huge, math.huge, "\n")
|
||||
end
|
||||
line1, col1, line2, col2 = line1, 1, line2 + 1, 1
|
||||
end
|
||||
if swap then
|
||||
return line2, col2, line1, col1
|
||||
end
|
||||
return line1, col1, line2, col2
|
||||
end
|
||||
|
||||
|
||||
function DocView:on_mouse_pressed(button, x, y, clicks)
|
||||
local caught = DocView.super.on_mouse_pressed(self, button, x, y, clicks)
|
||||
if caught then
|
||||
return
|
||||
end
|
||||
if keymap.modkeys["shift"] then
|
||||
if clicks % 2 == 1 then
|
||||
local line1, col1 = select(3, self.doc:get_selection())
|
||||
local line2, col2 = self:resolve_screen_position(x, y)
|
||||
self.doc:set_selection(line2, col2, line1, col1)
|
||||
end
|
||||
else
|
||||
local line, col = self:resolve_screen_position(x, y)
|
||||
if keymap.modkeys["ctrl"] then
|
||||
self.doc:add_selection(mouse_selection(self.doc, clicks, line, col, line, col))
|
||||
else
|
||||
self.doc:set_selection(mouse_selection(self.doc, clicks, line, col, line, col))
|
||||
end
|
||||
self.mouse_selecting = { line, col, clicks = clicks }
|
||||
end
|
||||
core.blink_reset()
|
||||
end
|
||||
|
||||
|
||||
function DocView:on_mouse_moved(x, y, ...)
|
||||
DocView.super.on_mouse_moved(self, x, y, ...)
|
||||
|
||||
@@ -280,7 +236,6 @@ function DocView:on_mouse_moved(x, y, ...)
|
||||
if self.mouse_selecting then
|
||||
local l1, c1 = self:resolve_screen_position(x, y)
|
||||
local l2, c2 = table.unpack(self.mouse_selecting)
|
||||
local clicks = self.mouse_selecting.clicks
|
||||
if keymap.modkeys["ctrl"] then
|
||||
if l1 > l2 then l1, l2 = l2, l1 end
|
||||
self.doc.selections = { }
|
||||
@@ -288,7 +243,7 @@ function DocView:on_mouse_moved(x, y, ...)
|
||||
self.doc:set_selections(i - l1 + 1, i, math.min(c1, #self.doc.lines[i]), i, math.min(c2, #self.doc.lines[i]))
|
||||
end
|
||||
else
|
||||
self.doc:set_selection(mouse_selection(self.doc, clicks, l1, c1, l2, c2))
|
||||
self.doc:set_selection(l1, c1, l2, c2)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -338,16 +293,11 @@ end
|
||||
|
||||
function DocView:draw_line_text(idx, x, y)
|
||||
local default_font = self:get_font()
|
||||
local subpixel_scale = default_font:subpixel_scale()
|
||||
local tx, ty = subpixel_scale * x, y + self:get_line_text_y_offset()
|
||||
local tx, ty = x, y + self:get_line_text_y_offset()
|
||||
for _, type, text in self.doc.highlighter:each_token(idx) do
|
||||
local color = style.syntax[type]
|
||||
local font = style.syntax_fonts[type] or default_font
|
||||
if config.draw_whitespace then
|
||||
tx = renderer.draw_text_subpixel(font, text, tx, ty, color, core.replacements, style.syntax.comment)
|
||||
else
|
||||
tx = renderer.draw_text_subpixel(font, text, tx, ty, color)
|
||||
end
|
||||
tx = renderer.draw_text(font, text, tx, ty, color)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -357,6 +307,18 @@ function DocView:draw_caret(x, y)
|
||||
end
|
||||
|
||||
function DocView:draw_line_body(idx, x, y)
|
||||
-- draw highlight if any selection ends on this line
|
||||
local draw_highlight = false
|
||||
for lidx, line1, col1, line2, col2 in self.doc:get_selections(false) do
|
||||
if line1 == idx then
|
||||
draw_highlight = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if draw_highlight and config.highlight_current_line and core.active_view == self then
|
||||
self:draw_line_highlight(x + self.scroll.x, y)
|
||||
end
|
||||
|
||||
-- draw selection if it overlaps this line
|
||||
for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do
|
||||
if idx >= line1 and idx <= line2 then
|
||||
@@ -366,14 +328,9 @@ function DocView:draw_line_body(idx, x, y)
|
||||
local x1 = x + self:get_col_x_offset(idx, col1)
|
||||
local x2 = x + self:get_col_x_offset(idx, col2)
|
||||
local lh = self:get_line_height()
|
||||
renderer.draw_rect(x1, y, x2 - x1, lh, style.selection)
|
||||
end
|
||||
end
|
||||
for lidx, line1, col1, line2, col2 in self.doc:get_selections(true) do
|
||||
-- draw line highlight if caret is on this line
|
||||
if config.highlight_current_line and (line1 == line2 and col1 == col2)
|
||||
and line1 == idx and core.active_view == self then
|
||||
self:draw_line_highlight(x + self.scroll.x, y)
|
||||
if x1 ~= x2 then
|
||||
renderer.draw_rect(x1, y, x2 - x1, lh, style.selection)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -403,10 +360,12 @@ function DocView:draw_overlay()
|
||||
local T = config.blink_period
|
||||
for _, line, col in self.doc:get_selections() do
|
||||
if line >= minline and line <= maxline
|
||||
and (core.blink_timer - core.blink_start) % T < T / 2
|
||||
and system.window_has_focus() then
|
||||
local x, y = self:get_line_screen_position(line)
|
||||
self:draw_caret(x + self:get_col_x_offset(line, col), y)
|
||||
if config.disable_blink
|
||||
or (core.blink_timer - core.blink_start) % T < T / 2 then
|
||||
local x, y = self:get_line_screen_position(line)
|
||||
self:draw_caret(x + self:get_col_x_offset(line, col), y)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+103
-73
@@ -17,10 +17,7 @@ local core = {}
|
||||
|
||||
local function load_session()
|
||||
local ok, t = pcall(dofile, USERDIR .. "/session.lua")
|
||||
if ok then
|
||||
return t.recents, t.window, t.window_mode
|
||||
end
|
||||
return {}
|
||||
return ok and t or {}
|
||||
end
|
||||
|
||||
|
||||
@@ -30,6 +27,8 @@ local function save_session()
|
||||
fp:write("return {recents=", common.serialize(core.recent_projects),
|
||||
", window=", common.serialize(table.pack(system.get_window_size())),
|
||||
", window_mode=", common.serialize(system.get_window_mode()),
|
||||
", previous_find=", common.serialize(core.previous_find),
|
||||
", previous_replace=", common.serialize(core.previous_replace),
|
||||
"}\n")
|
||||
fp:close()
|
||||
end
|
||||
@@ -80,6 +79,9 @@ function core.open_folder_project(dir_path_abs)
|
||||
if core.set_project_dir(dir_path_abs, core.on_quit_project) then
|
||||
core.root_view:close_all_docviews()
|
||||
update_recents_project("add", dir_path_abs)
|
||||
if not core.load_project_module() then
|
||||
command.perform("core:open-log")
|
||||
end
|
||||
core.on_enter_project(dir_path_abs)
|
||||
end
|
||||
end
|
||||
@@ -320,8 +322,8 @@ local style = require "core.style"
|
||||
------------------------------- Fonts ----------------------------------------
|
||||
|
||||
-- customize fonts:
|
||||
-- style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 13 * SCALE)
|
||||
-- style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 13 * SCALE)
|
||||
-- style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 14 * SCALE)
|
||||
-- style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 14 * SCALE)
|
||||
--
|
||||
-- font names used by lite:
|
||||
-- style.font : user interface
|
||||
@@ -342,11 +344,11 @@ local style = require "core.style"
|
||||
|
||||
-- enable or disable plugin loading setting config entries:
|
||||
|
||||
-- enable trimwhitespace, otherwise it is disable by default:
|
||||
-- config.trimwhitespace = true
|
||||
-- enable plugins.trimwhitespace, otherwise it is disable by default:
|
||||
-- config.plugins.trimwhitespace = true
|
||||
--
|
||||
-- disable detectindent, otherwise it is enabled by default
|
||||
-- config.detectindent = false
|
||||
-- config.plugins.detectindent = false
|
||||
]])
|
||||
init_file:close()
|
||||
end
|
||||
@@ -394,15 +396,6 @@ function core.remove_project_directory(path)
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
local function whitespace_replacements()
|
||||
local r = renderer.replacements.new()
|
||||
r:add(" ", "·")
|
||||
r:add("\t", "»")
|
||||
return r
|
||||
end
|
||||
|
||||
|
||||
local function reload_on_user_module_save()
|
||||
-- auto-realod style when user's module is saved by overriding Doc:Save()
|
||||
local doc_save = Doc.save
|
||||
@@ -435,13 +428,15 @@ function core.init()
|
||||
end
|
||||
|
||||
do
|
||||
local recent_projects, window_position, window_mode = load_session()
|
||||
if window_mode == "normal" then
|
||||
system.set_window_size(table.unpack(window_position))
|
||||
elseif window_mode == "maximized" then
|
||||
local session = load_session()
|
||||
if session.window_mode == "normal" then
|
||||
system.set_window_size(table.unpack(session.window))
|
||||
elseif session.window_mode == "maximized" then
|
||||
system.set_window_mode("maximized")
|
||||
end
|
||||
core.recent_projects = recent_projects
|
||||
core.recent_projects = session.recents or {}
|
||||
core.previous_find = session.previous_find or {}
|
||||
core.previous_replace = session.previous_replace or {}
|
||||
end
|
||||
|
||||
local project_dir = core.recent_projects[1] or "."
|
||||
@@ -461,7 +456,10 @@ function core.init()
|
||||
project_dir = arg_filename
|
||||
project_dir_explicit = true
|
||||
else
|
||||
delayed_error = string.format("error: invalid file or directory %q", ARGS[i])
|
||||
-- on macOS we can get an argument like "-psn_0_52353" that we just ignore.
|
||||
if not ARGS[i]:match("^-psn") then
|
||||
delayed_error = string.format("error: invalid file or directory %q", ARGS[i])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -494,7 +492,7 @@ function core.init()
|
||||
core.redraw = true
|
||||
core.visited_files = {}
|
||||
core.restart_request = false
|
||||
core.replacements = whitespace_replacements()
|
||||
core.quit_request = false
|
||||
|
||||
core.root_view = RootView()
|
||||
core.command_view = CommandView()
|
||||
@@ -564,10 +562,10 @@ function core.init()
|
||||
end
|
||||
|
||||
|
||||
function core.confirm_close_all(close_fn, ...)
|
||||
function core.confirm_close_docs(docs, close_fn, ...)
|
||||
local dirty_count = 0
|
||||
local dirty_name
|
||||
for _, doc in ipairs(core.docs) do
|
||||
for _, doc in ipairs(docs or core.docs) do
|
||||
if doc:is_dirty() then
|
||||
dirty_count = dirty_count + 1
|
||||
dirty_name = doc:get_name()
|
||||
@@ -620,24 +618,6 @@ do
|
||||
end
|
||||
|
||||
|
||||
-- DEPRECATED function
|
||||
core.doc_save_hooks = {}
|
||||
function core.add_save_hook(fn)
|
||||
core.error("The function core.add_save_hook is deprecated." ..
|
||||
" Modules should now directly override the Doc:save function.")
|
||||
core.doc_save_hooks[#core.doc_save_hooks + 1] = fn
|
||||
end
|
||||
|
||||
|
||||
-- DEPRECATED function
|
||||
function core.on_doc_save(filename)
|
||||
-- for backward compatibility in modules. Hooks are deprecated, the function Doc:save
|
||||
-- should be directly overidded.
|
||||
for _, hook in ipairs(core.doc_save_hooks) do
|
||||
hook(filename)
|
||||
end
|
||||
end
|
||||
|
||||
local function quit_with_function(quit_fn, force)
|
||||
if force then
|
||||
delete_temp_files()
|
||||
@@ -645,12 +625,12 @@ local function quit_with_function(quit_fn, force)
|
||||
save_session()
|
||||
quit_fn()
|
||||
else
|
||||
core.confirm_close_all(quit_with_function, quit_fn, true)
|
||||
core.confirm_close_docs(core.docs, quit_with_function, quit_fn, true)
|
||||
end
|
||||
end
|
||||
|
||||
function core.quit(force)
|
||||
quit_with_function(os.exit, force)
|
||||
quit_with_function(function() core.quit_request = true end, force)
|
||||
end
|
||||
|
||||
|
||||
@@ -679,8 +659,8 @@ local function check_plugin_version(filename)
|
||||
-- Future versions will look only at the mod-version tag.
|
||||
local version = line:match('%-%-%s*lite%-xl%s*(%d+%.%d+)$')
|
||||
if version then
|
||||
-- we consider the version tag 1.16 equivalent to mod-version:1
|
||||
version_match = (version == '1.16' and MOD_VERSION == "1")
|
||||
-- we consider the version tag 2.0 equivalent to mod-version:2
|
||||
version_match = (version == '2.0' and MOD_VERSION == "2")
|
||||
break
|
||||
end
|
||||
end
|
||||
@@ -695,24 +675,30 @@ function core.load_plugins()
|
||||
userdir = {dir = USERDIR, plugins = {}},
|
||||
datadir = {dir = DATADIR, plugins = {}},
|
||||
}
|
||||
for _, root_dir in ipairs {USERDIR, DATADIR} do
|
||||
local files, ordered = {}, {}
|
||||
for _, root_dir in ipairs {DATADIR, USERDIR} do
|
||||
local plugin_dir = root_dir .. "/plugins"
|
||||
local files = system.list_dir(plugin_dir)
|
||||
for _, filename in ipairs(files or {}) do
|
||||
local basename = filename:match("(.-)%.lua$") or filename
|
||||
local is_lua_file, version_match = check_plugin_version(plugin_dir .. '/' .. filename)
|
||||
if is_lua_file then
|
||||
if not version_match then
|
||||
core.log_quiet("Version mismatch for plugin %q from %s", basename, plugin_dir)
|
||||
local ls = refused_list[root_dir == USERDIR and 'userdir' or 'datadir'].plugins
|
||||
ls[#ls + 1] = filename
|
||||
elseif config.plugins[basename] ~= false then
|
||||
local modname = "plugins." .. basename
|
||||
local ok = core.try(require, modname)
|
||||
if ok then core.log_quiet("Loaded plugin %q from %s", basename, plugin_dir) end
|
||||
if not ok then
|
||||
no_errors = false
|
||||
end
|
||||
for _, filename in ipairs(system.list_dir(plugin_dir) or {}) do
|
||||
if not files[filename] then table.insert(ordered, filename) end
|
||||
files[filename] = plugin_dir -- user plugins will always replace system plugins
|
||||
end
|
||||
end
|
||||
table.sort(ordered)
|
||||
|
||||
for _, filename in ipairs(ordered) do
|
||||
local plugin_dir, basename = files[filename], filename:match("(.-)%.lua$") or filename
|
||||
local is_lua_file, version_match = check_plugin_version(plugin_dir .. '/' .. filename)
|
||||
if is_lua_file then
|
||||
if not version_match then
|
||||
core.log_quiet("Version mismatch for plugin %q from %s", basename, plugin_dir)
|
||||
local list = refused_list[plugin_dir:find(USERDIR, 1, true) == 1 and 'userdir' or 'datadir'].plugins
|
||||
table.insert(list, filename)
|
||||
end
|
||||
if version_match and config.plugins[basename] ~= false then
|
||||
local ok = core.try(require, "plugins." .. basename)
|
||||
if ok then core.log_quiet("Loaded plugin %q from %s", basename, plugin_dir) end
|
||||
if not ok then
|
||||
no_errors = false
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -759,8 +745,12 @@ end
|
||||
|
||||
function core.set_active_view(view)
|
||||
assert(view, "Tried to set active view to nil")
|
||||
if core.active_view and core.active_view.force_focus then return end
|
||||
if view ~= core.active_view then
|
||||
if core.active_view and core.active_view.force_focus then
|
||||
core.next_active_view = view
|
||||
return
|
||||
end
|
||||
core.next_active_view = nil
|
||||
if view.doc and view.doc.filename then
|
||||
core.set_visited(view.doc.filename)
|
||||
end
|
||||
@@ -810,10 +800,30 @@ function core.normalize_to_project_dir(filename)
|
||||
end
|
||||
|
||||
|
||||
-- The function below works like system.absolute_path except it
|
||||
-- doesn't fail if the file does not exist. We consider that the
|
||||
-- current dir is core.project_dir so relative filename are considered
|
||||
-- to be in core.project_dir.
|
||||
-- Please note that .. or . in the filename are not taken into account.
|
||||
-- This function should get only filenames normalized using
|
||||
-- common.normalize_path function.
|
||||
function core.project_absolute_path(filename)
|
||||
if filename:match('^%a:\\') or filename:find('/', 1, true) == 1 then
|
||||
return filename
|
||||
else
|
||||
return core.project_dir .. PATHSEP .. filename
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function core.open_doc(filename)
|
||||
local new_file = not filename or not system.get_file_info(filename)
|
||||
local abs_filename
|
||||
if filename then
|
||||
-- normalize filename and set absolute filename then
|
||||
-- try to find existing doc for filename
|
||||
local abs_filename = system.absolute_path(filename)
|
||||
filename = core.normalize_to_project_dir(filename)
|
||||
abs_filename = core.project_absolute_path(filename)
|
||||
for _, doc in ipairs(core.docs) do
|
||||
if doc.abs_filename and abs_filename == doc.abs_filename then
|
||||
return doc
|
||||
@@ -821,8 +831,7 @@ function core.open_doc(filename)
|
||||
end
|
||||
end
|
||||
-- no existing doc for filename; create new
|
||||
filename = filename and core.normalize_to_project_dir(filename)
|
||||
local doc = Doc(filename)
|
||||
local doc = Doc(filename, abs_filename, new_file)
|
||||
table.insert(core.docs, doc)
|
||||
core.log_quiet(filename and "Opened doc \"%s\"" or "Opened new doc", filename)
|
||||
return doc
|
||||
@@ -871,6 +880,23 @@ function core.error(...)
|
||||
end
|
||||
|
||||
|
||||
function core.get_log(i)
|
||||
if i == nil then
|
||||
local r = {}
|
||||
for _, item in ipairs(core.log_items) do
|
||||
table.insert(r, core.get_log(item))
|
||||
end
|
||||
return table.concat(r, "\n")
|
||||
end
|
||||
local item = type(i) == "number" and core.log_items[i] or i
|
||||
local text = string.format("[%s] %s at %s", os.date(nil, item.time), item.text, item.at)
|
||||
if item.info then
|
||||
text = string.format("%s\n%s\n", text, item.info)
|
||||
end
|
||||
return text
|
||||
end
|
||||
|
||||
|
||||
function core.try(fn, ...)
|
||||
local err
|
||||
local ok, res = xpcall(fn, function(msg)
|
||||
@@ -896,11 +922,15 @@ function core.on_event(type, ...)
|
||||
elseif type == "mousemoved" then
|
||||
core.root_view:on_mouse_moved(...)
|
||||
elseif type == "mousepressed" then
|
||||
core.root_view:on_mouse_pressed(...)
|
||||
if not core.root_view:on_mouse_pressed(...) then
|
||||
did_keymap = keymap.on_mouse_pressed(...)
|
||||
end
|
||||
elseif type == "mousereleased" then
|
||||
core.root_view:on_mouse_released(...)
|
||||
elseif type == "mousewheel" then
|
||||
core.root_view:on_mouse_wheel(...)
|
||||
if not core.root_view:on_mouse_wheel(...) then
|
||||
did_keymap = keymap.on_mouse_wheel(...)
|
||||
end
|
||||
elseif type == "resized" then
|
||||
core.window_mode = system.get_window_mode()
|
||||
elseif type == "minimized" or type == "maximized" or type == "restored" then
|
||||
@@ -1027,7 +1057,7 @@ function core.run()
|
||||
core.frame_start = system.get_time()
|
||||
local did_redraw = core.step()
|
||||
local need_more_work = run_threads()
|
||||
if core.restart_request then break end
|
||||
if core.restart_request or core.quit_request then break end
|
||||
if not did_redraw and not need_more_work then
|
||||
idle_iterations = idle_iterations + 1
|
||||
-- do not wait of events at idle_iterations = 1 to give a chance at core.step to run
|
||||
|
||||
+32
-17
@@ -32,6 +32,8 @@ local function keymap_macos(keymap)
|
||||
["cmd+7"] = "root:switch-to-tab-7",
|
||||
["cmd+8"] = "root:switch-to-tab-8",
|
||||
["cmd+9"] = "root:switch-to-tab-9",
|
||||
["wheel"] = "root:scroll",
|
||||
|
||||
["cmd+f"] = "find-replace:find",
|
||||
["cmd+r"] = "find-replace:replace",
|
||||
["f3"] = "find-replace:repeat-find",
|
||||
@@ -52,23 +54,27 @@ local function keymap_macos(keymap)
|
||||
["shift+tab"] = "doc:unindent",
|
||||
["backspace"] = "doc:backspace",
|
||||
["shift+backspace"] = "doc:backspace",
|
||||
["cmd+backspace"] = "doc:delete-to-previous-word-start",
|
||||
["option+backspace"] = "doc:delete-to-previous-word-start",
|
||||
["cmd+shift+backspace"] = "doc:delete-to-previous-word-start",
|
||||
["cmd+backspace"] = "doc:delete-to-start-of-indentation",
|
||||
["delete"] = "doc:delete",
|
||||
["shift+delete"] = "doc:delete",
|
||||
["cmd+delete"] = "doc:delete-to-next-word-end",
|
||||
["option+delete"] = "doc:delete-to-next-word-end",
|
||||
["cmd+shift+delete"] = "doc:delete-to-next-word-end",
|
||||
["cmd+delete"] = "doc:delete-to-end-of-line",
|
||||
["return"] = { "command:submit", "doc:newline", "dialog:select" },
|
||||
["keypad enter"] = { "command:submit", "doc:newline", "dialog:select" },
|
||||
["cmd+return"] = "doc:newline-below",
|
||||
["cmd+shift+return"] = "doc:newline-above",
|
||||
["cmd+j"] = "doc:join-lines",
|
||||
["cmd+a"] = "doc:select-all",
|
||||
["cmd+d"] = { "find-replace:select-next", "doc:select-word" },
|
||||
["cmd+d"] = { "find-replace:select-add-next", "doc:select-word" },
|
||||
["cmd+f3"] = "find-replace:select-next",
|
||||
["cmd+l"] = "doc:select-lines",
|
||||
["cmd+shift+l"] = { "find-replace:select-add-all", "doc:select-word" },
|
||||
["cmd+/"] = "doc:toggle-line-comments",
|
||||
["cmd+up"] = "doc:move-lines-up",
|
||||
["cmd+down"] = "doc:move-lines-down",
|
||||
["option+up"] = "doc:move-lines-up",
|
||||
["option+down"] = "doc:move-lines-down",
|
||||
["cmd+shift+d"] = "doc:duplicate-lines",
|
||||
["cmd+shift+k"] = "doc:delete-lines",
|
||||
|
||||
@@ -76,33 +82,42 @@ local function keymap_macos(keymap)
|
||||
["right"] = { "doc:move-to-next-char", "dialog:next-entry"},
|
||||
["up"] = { "command:select-previous", "doc:move-to-previous-line" },
|
||||
["down"] = { "command:select-next", "doc:move-to-next-line" },
|
||||
["cmd+left"] = "doc:move-to-previous-word-start",
|
||||
["cmd+right"] = "doc:move-to-next-word-end",
|
||||
["option+left"] = "doc:move-to-previous-word-start",
|
||||
["option+right"] = "doc:move-to-next-word-end",
|
||||
["cmd+left"] = "doc:move-to-start-of-indentation",
|
||||
["cmd+right"] = "doc:move-to-end-of-line",
|
||||
["cmd+["] = "doc:move-to-previous-block-start",
|
||||
["cmd+]"] = "doc:move-to-next-block-end",
|
||||
["home"] = "doc:move-to-start-of-line",
|
||||
["home"] = "doc:move-to-start-of-indentation",
|
||||
["end"] = "doc:move-to-end-of-line",
|
||||
["cmd+home"] = "doc:move-to-start-of-doc",
|
||||
["cmd+end"] = "doc:move-to-end-of-doc",
|
||||
["cmd+up"] = "doc:move-to-start-of-doc",
|
||||
["cmd+down"] = "doc:move-to-end-of-doc",
|
||||
["pageup"] = "doc:move-to-previous-page",
|
||||
["pagedown"] = "doc:move-to-next-page",
|
||||
|
||||
["shift+1lclick"] = "doc:select-to-cursor",
|
||||
["ctrl+1lclick"] = "doc:split-cursor",
|
||||
["1lclick"] = "doc:set-cursor",
|
||||
["2lclick"] = "doc:set-cursor-word",
|
||||
["3lclick"] = "doc:set-cursor-line",
|
||||
["shift+left"] = "doc:select-to-previous-char",
|
||||
["shift+right"] = "doc:select-to-next-char",
|
||||
["shift+up"] = "doc:select-to-previous-line",
|
||||
["shift+down"] = "doc:select-to-next-line",
|
||||
["cmd+shift+left"] = "doc:select-to-previous-word-start",
|
||||
["cmd+shift+right"] = "doc:select-to-next-word-end",
|
||||
["option+shift+left"] = "doc:select-to-previous-word-start",
|
||||
["option+shift+right"] = "doc:select-to-next-word-end",
|
||||
["cmd+shift+left"] = "doc:select-to-start-of-indentation",
|
||||
["cmd+shift+right"] = "doc:select-to-end-of-line",
|
||||
["cmd+shift+["] = "doc:select-to-previous-block-start",
|
||||
["cmd+shift+]"] = "doc:select-to-next-block-end",
|
||||
["shift+home"] = "doc:select-to-start-of-line",
|
||||
["shift+home"] = "doc:select-to-start-of-indentation",
|
||||
["shift+end"] = "doc:select-to-end-of-line",
|
||||
["cmd+shift+home"] = "doc:select-to-start-of-doc",
|
||||
["cmd+shift+end"] = "doc:select-to-end-of-doc",
|
||||
["cmd+shift+up"] = "doc:select-to-start-of-doc",
|
||||
["cmd+shift+down"] = "doc:select-to-end-of-doc",
|
||||
["shift+pageup"] = "doc:select-to-previous-page",
|
||||
["shift+pagedown"] = "doc:select-to-next-page",
|
||||
["cmd+shift+up"] = "doc:create-cursor-previous-line",
|
||||
["cmd+shift+down"] = "doc:create-cursor-next-line"
|
||||
["cmd+option+up"] = "doc:create-cursor-previous-line",
|
||||
["cmd+option+down"] = "doc:create-cursor-next-line"
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
+60
-12
@@ -1,11 +1,12 @@
|
||||
local command = require "core.command"
|
||||
local config = require "core.config"
|
||||
local keymap = {}
|
||||
|
||||
keymap.modkeys = {}
|
||||
keymap.map = {}
|
||||
keymap.reverse_map = {}
|
||||
|
||||
local macos = rawget(_G, "MACOS_RESOURCES")
|
||||
local macos = PLATFORM == "Mac OS X"
|
||||
|
||||
-- Thanks to mathewmariani, taken from his lite-macos github repository.
|
||||
local modkeys_os = require("core.modkeys-" .. (macos and "macos" or "generic"))
|
||||
@@ -30,7 +31,8 @@ function keymap.add_direct(map)
|
||||
end
|
||||
keymap.map[stroke] = commands
|
||||
for _, cmd in ipairs(commands) do
|
||||
keymap.reverse_map[cmd] = stroke
|
||||
keymap.reverse_map[cmd] = keymap.reverse_map[cmd] or {}
|
||||
table.insert(keymap.reverse_map[cmd], stroke)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -52,18 +54,43 @@ function keymap.add(map, overwrite)
|
||||
end
|
||||
end
|
||||
for _, cmd in ipairs(commands) do
|
||||
keymap.reverse_map[cmd] = stroke
|
||||
keymap.reverse_map[cmd] = keymap.reverse_map[cmd] or {}
|
||||
table.insert(keymap.reverse_map[cmd], stroke)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function keymap.get_binding(cmd)
|
||||
return keymap.reverse_map[cmd]
|
||||
local function remove_only(tbl, k, v)
|
||||
for key, values in pairs(tbl) do
|
||||
if key == k then
|
||||
if v then
|
||||
for i, value in ipairs(values) do
|
||||
if value == v then
|
||||
table.remove(values, i)
|
||||
end
|
||||
end
|
||||
else
|
||||
tbl[key] = nil
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function keymap.on_key_pressed(k)
|
||||
function keymap.unbind(key, cmd)
|
||||
remove_only(keymap.map, key, cmd)
|
||||
remove_only(keymap.reverse_map, cmd, key)
|
||||
end
|
||||
|
||||
|
||||
function keymap.get_binding(cmd)
|
||||
return table.unpack(keymap.reverse_map[cmd] or {})
|
||||
end
|
||||
|
||||
|
||||
function keymap.on_key_pressed(k, ...)
|
||||
local mk = modkey_map[k]
|
||||
if mk then
|
||||
keymap.modkeys[mk] = true
|
||||
@@ -73,18 +100,30 @@ function keymap.on_key_pressed(k)
|
||||
end
|
||||
else
|
||||
local stroke = key_to_stroke(k)
|
||||
local commands = keymap.map[stroke]
|
||||
local commands, performed = keymap.map[stroke]
|
||||
if commands then
|
||||
for _, cmd in ipairs(commands) do
|
||||
local performed = command.perform(cmd)
|
||||
performed = command.perform(cmd, ...)
|
||||
if performed then break end
|
||||
end
|
||||
return true
|
||||
return performed
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function keymap.on_mouse_wheel(delta, ...)
|
||||
return not (keymap.on_key_pressed("wheel" .. (delta > 0 and "up" or "down"), delta, ...)
|
||||
or keymap.on_key_pressed("wheel", delta, ...))
|
||||
end
|
||||
|
||||
function keymap.on_mouse_pressed(button, x, y, clicks)
|
||||
local click_number = (((clicks - 1) % config.max_clicks) + 1)
|
||||
return not (keymap.on_key_pressed(click_number .. button:sub(1,1) .. "click", x, y, clicks) or
|
||||
keymap.on_key_pressed(button:sub(1,1) .. "click", x, y, clicks) or
|
||||
keymap.on_key_pressed(click_number .. "click", x, y, clicks) or
|
||||
keymap.on_key_pressed("click", x, y, clicks))
|
||||
end
|
||||
|
||||
function keymap.on_key_released(k)
|
||||
local mk = modkey_map[k]
|
||||
@@ -108,6 +147,7 @@ keymap.add_direct {
|
||||
["ctrl+shift+c"] = "core:change-project-folder",
|
||||
["ctrl+shift+o"] = "core:open-project-folder",
|
||||
["alt+return"] = "core:toggle-fullscreen",
|
||||
["f11"] = "core:toggle-fullscreen",
|
||||
|
||||
["alt+shift+j"] = "root:split-left",
|
||||
["alt+shift+l"] = "root:split-right",
|
||||
@@ -132,6 +172,7 @@ keymap.add_direct {
|
||||
["alt+7"] = "root:switch-to-tab-7",
|
||||
["alt+8"] = "root:switch-to-tab-8",
|
||||
["alt+9"] = "root:switch-to-tab-9",
|
||||
["wheel"] = "root:scroll",
|
||||
|
||||
["ctrl+f"] = "find-replace:find",
|
||||
["ctrl+r"] = "find-replace:replace",
|
||||
@@ -167,8 +208,10 @@ keymap.add_direct {
|
||||
["ctrl+shift+return"] = "doc:newline-above",
|
||||
["ctrl+j"] = "doc:join-lines",
|
||||
["ctrl+a"] = "doc:select-all",
|
||||
["ctrl+d"] = { "find-replace:select-next", "doc:select-word" },
|
||||
["ctrl+d"] = { "find-replace:select-add-next", "doc:select-word" },
|
||||
["ctrl+f3"] = "find-replace:select-next",
|
||||
["ctrl+l"] = "doc:select-lines",
|
||||
["ctrl+shift+l"] = { "find-replace:select-add-all", "doc:select-word" },
|
||||
["ctrl+/"] = "doc:toggle-line-comments",
|
||||
["ctrl+up"] = "doc:move-lines-up",
|
||||
["ctrl+down"] = "doc:move-lines-down",
|
||||
@@ -183,13 +226,18 @@ keymap.add_direct {
|
||||
["ctrl+right"] = "doc:move-to-next-word-end",
|
||||
["ctrl+["] = "doc:move-to-previous-block-start",
|
||||
["ctrl+]"] = "doc:move-to-next-block-end",
|
||||
["home"] = "doc:move-to-start-of-line",
|
||||
["home"] = "doc:move-to-start-of-indentation",
|
||||
["end"] = "doc:move-to-end-of-line",
|
||||
["ctrl+home"] = "doc:move-to-start-of-doc",
|
||||
["ctrl+end"] = "doc:move-to-end-of-doc",
|
||||
["pageup"] = "doc:move-to-previous-page",
|
||||
["pagedown"] = "doc:move-to-next-page",
|
||||
|
||||
["shift+1lclick"] = "doc:select-to-cursor",
|
||||
["ctrl+1lclick"] = "doc:split-cursor",
|
||||
["1lclick"] = "doc:set-cursor",
|
||||
["2lclick"] = "doc:set-cursor-word",
|
||||
["3lclick"] = "doc:set-cursor-line",
|
||||
["shift+left"] = "doc:select-to-previous-char",
|
||||
["shift+right"] = "doc:select-to-next-char",
|
||||
["shift+up"] = "doc:select-to-previous-line",
|
||||
@@ -198,7 +246,7 @@ keymap.add_direct {
|
||||
["ctrl+shift+right"] = "doc:select-to-next-word-end",
|
||||
["ctrl+shift+["] = "doc:select-to-previous-block-start",
|
||||
["ctrl+shift+]"] = "doc:select-to-next-block-end",
|
||||
["shift+home"] = "doc:select-to-start-of-line",
|
||||
["shift+home"] = "doc:select-to-start-of-indentation",
|
||||
["shift+end"] = "doc:select-to-end-of-line",
|
||||
["ctrl+shift+home"] = "doc:select-to-start-of-doc",
|
||||
["ctrl+shift+end"] = "doc:select-to-end-of-doc",
|
||||
|
||||
+117
-19
@@ -1,14 +1,45 @@
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local style = require "core.style"
|
||||
local View = require "core.view"
|
||||
|
||||
|
||||
local function lines(text)
|
||||
if text == "" then return 0 end
|
||||
local l = 1
|
||||
for _ in string.gmatch(text, "\n") do
|
||||
l = l + 1
|
||||
end
|
||||
return l
|
||||
end
|
||||
|
||||
|
||||
local item_height_result = {}
|
||||
|
||||
|
||||
local function get_item_height(item)
|
||||
local h = item_height_result[item]
|
||||
if not h then
|
||||
h = {}
|
||||
local l = 1 + lines(item.text) + lines(item.info or "")
|
||||
h.normal = style.font:get_height() + style.padding.y
|
||||
h.expanded = l * style.font:get_height() + style.padding.y
|
||||
h.current = h.normal
|
||||
h.target = h.current
|
||||
item_height_result[item] = h
|
||||
end
|
||||
return h
|
||||
end
|
||||
|
||||
|
||||
local LogView = View:extend()
|
||||
|
||||
LogView.context = "session"
|
||||
|
||||
function LogView:new()
|
||||
LogView.super.new(self)
|
||||
self.last_item = core.log_items[#core.log_items]
|
||||
self.expanding = {}
|
||||
self.scrollable = true
|
||||
self.yoffset = 0
|
||||
end
|
||||
@@ -19,6 +50,55 @@ function LogView:get_name()
|
||||
end
|
||||
|
||||
|
||||
local function is_expanded(item)
|
||||
local item_height = get_item_height(item)
|
||||
return item_height.target == item_height.expanded
|
||||
end
|
||||
|
||||
|
||||
function LogView:expand_item(item)
|
||||
item = get_item_height(item)
|
||||
item.target = item.target == item.expanded and item.normal or item.expanded
|
||||
table.insert(self.expanding, item)
|
||||
end
|
||||
|
||||
|
||||
function LogView:each_item()
|
||||
local x, y = self:get_content_offset()
|
||||
y = y + style.padding.y + self.yoffset
|
||||
return coroutine.wrap(function()
|
||||
for i = #core.log_items, 1, -1 do
|
||||
local item = core.log_items[i]
|
||||
local h = get_item_height(item).current
|
||||
coroutine.yield(i, item, x, y, self.size.x, h)
|
||||
y = y + h
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
function LogView:on_mouse_moved(px, py, ...)
|
||||
LogView.super.on_mouse_moved(self, px, py, ...)
|
||||
local hovered = false
|
||||
for _, item, x, y, w, h in self:each_item() do
|
||||
if px >= x and py >= y and px < x + w and py < y + h then
|
||||
hovered = true
|
||||
self.hovered_item = item
|
||||
break
|
||||
end
|
||||
end
|
||||
if not hovered then self.hovered_item = nil end
|
||||
end
|
||||
|
||||
|
||||
function LogView:on_mouse_pressed(button, mx, my, clicks)
|
||||
if LogView.super.on_mouse_pressed(self, button, mx, my, clicks) then return end
|
||||
if self.hovered_item then
|
||||
self:expand_item(self.hovered_item)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function LogView:update()
|
||||
local item = core.log_items[#core.log_items]
|
||||
if self.last_item ~= item then
|
||||
@@ -27,6 +107,14 @@ function LogView:update()
|
||||
self.yoffset = -(style.font:get_height() + style.padding.y)
|
||||
end
|
||||
|
||||
local expanding = self.expanding[1]
|
||||
if expanding then
|
||||
self:move_towards(expanding, "current", expanding.target)
|
||||
if expanding.current == expanding.target then
|
||||
table.remove(self.expanding, 1)
|
||||
end
|
||||
end
|
||||
|
||||
self:move_towards("yoffset", 0)
|
||||
|
||||
LogView.super.update(self)
|
||||
@@ -35,38 +123,48 @@ end
|
||||
|
||||
local function draw_text_multiline(font, text, x, y, color)
|
||||
local th = font:get_height()
|
||||
local resx, resy = x, y
|
||||
local resx = x
|
||||
for line in text:gmatch("[^\n]+") do
|
||||
resy = y
|
||||
resx = renderer.draw_text(style.font, line, x, y, color)
|
||||
y = y + th
|
||||
end
|
||||
return resx, resy
|
||||
return resx, y
|
||||
end
|
||||
|
||||
|
||||
function LogView:draw()
|
||||
self:draw_background(style.background)
|
||||
|
||||
local ox, oy = self:get_content_offset()
|
||||
local th = style.font:get_height()
|
||||
local y = oy + style.padding.y + self.yoffset
|
||||
|
||||
for i = #core.log_items, 1, -1 do
|
||||
local x = ox + style.padding.x
|
||||
local item = core.log_items[i]
|
||||
local time = os.date(nil, item.time)
|
||||
x = renderer.draw_text(style.font, time, x, y, style.dim)
|
||||
local lh = th + style.padding.y -- for one line
|
||||
for _, item, x, y, w in self:each_item() do
|
||||
x = x + style.padding.x
|
||||
local subx = x
|
||||
x, y = draw_text_multiline(style.font, item.text, x, y, style.text)
|
||||
renderer.draw_text(style.font, " at " .. item.at, x, y, style.dim)
|
||||
y = y + th
|
||||
if item.info then
|
||||
subx, y = draw_text_multiline(style.font, item.info, subx, y, style.dim)
|
||||
y = y + th
|
||||
|
||||
local time = os.date(nil, item.time)
|
||||
x = common.draw_text(style.font, style.dim, time, "left", x, y, w, lh)
|
||||
x = x + style.padding.x
|
||||
|
||||
x = common.draw_text(style.code_font, style.dim, is_expanded(item) and "-" or "+", "left", x, y, w, lh)
|
||||
x = x + style.padding.x
|
||||
w = w - (x - self:get_content_offset())
|
||||
|
||||
if is_expanded(item) then
|
||||
y = y + common.round(style.padding.y / 2)
|
||||
_, y = draw_text_multiline(style.font, item.text, x, y, style.text)
|
||||
|
||||
local at = "at " .. common.home_encode(item.at)
|
||||
_, y = common.draw_text(style.font, style.dim, at, "left", x, y, w, lh)
|
||||
|
||||
if item.info then
|
||||
_, y = draw_text_multiline(style.font, item.info, x, y, style.dim)
|
||||
end
|
||||
else
|
||||
local line, has_newline = string.match(item.text, "([^\n]+)(\n?)")
|
||||
if has_newline ~= "" then
|
||||
line = line .. " ..."
|
||||
end
|
||||
_, y = common.draw_text(style.font, style.text, line, "left", x, y, w, lh)
|
||||
end
|
||||
y = y + style.padding.y
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -193,7 +193,8 @@ function NagView:next()
|
||||
self:change_hovered(common.find_index(self.options, "default_yes"))
|
||||
end
|
||||
self.force_focus = self.message ~= nil
|
||||
core.set_active_view(self.message ~= nil and self or core.last_active_view)
|
||||
core.set_active_view(self.message ~= nil and self or
|
||||
core.next_active_view or core.last_active_view)
|
||||
end
|
||||
|
||||
function NagView:show(title, message, options, on_select)
|
||||
|
||||
@@ -20,17 +20,6 @@ function Object:extend()
|
||||
end
|
||||
|
||||
|
||||
function Object:implement(...)
|
||||
for _, cls in pairs({...}) do
|
||||
for k, v in pairs(cls) do
|
||||
if self[k] == nil and type(v) == "function" then
|
||||
self[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Object:is(T)
|
||||
local mt = getmetatable(self)
|
||||
while mt do
|
||||
|
||||
+8
-8
@@ -1,5 +1,5 @@
|
||||
|
||||
-- So that in addition to regex.gsub(pattern, string), we can also do
|
||||
-- So that in addition to regex.gsub(pattern, string), we can also do
|
||||
-- pattern:gsub(string).
|
||||
regex.__index = function(table, key) return regex[key]; end
|
||||
|
||||
@@ -9,7 +9,7 @@ regex.match = function(pattern_string, string, offset, options)
|
||||
return regex.cmatch(pattern, string, offset or 1, options or 0)
|
||||
end
|
||||
|
||||
-- Will iterate back through any UTF-8 bytes so that we don't replace bits
|
||||
-- Will iterate back through any UTF-8 bytes so that we don't replace bits
|
||||
-- mid character.
|
||||
local function previous_character(str, index)
|
||||
local byte
|
||||
@@ -23,7 +23,7 @@ end
|
||||
-- Moves to the end of the identified character.
|
||||
local function end_character(str, index)
|
||||
local byte = string.byte(str, index + 1)
|
||||
while byte >= 128 and byte < 192 do
|
||||
while byte and byte >= 128 and byte < 192 do
|
||||
index = index + 1
|
||||
byte = string.byte(str, index + 1)
|
||||
end
|
||||
@@ -32,7 +32,7 @@ end
|
||||
|
||||
-- Build off matching. For now, only support basic replacements, but capture
|
||||
-- groupings should be doable. We can even have custom group replacements and
|
||||
-- transformations and stuff in lua. Currently, this takes group replacements
|
||||
-- transformations and stuff in lua. Currently, this takes group replacements
|
||||
-- as \1 - \9.
|
||||
-- Should work on UTF-8 text.
|
||||
regex.gsub = function(pattern_string, str, replacement)
|
||||
@@ -48,8 +48,8 @@ regex.gsub = function(pattern_string, str, replacement)
|
||||
if #indices > 2 then
|
||||
for i = 1, (#indices/2 - 1) do
|
||||
currentReplacement = string.gsub(
|
||||
currentReplacement,
|
||||
"\\" .. i,
|
||||
currentReplacement,
|
||||
"\\" .. i,
|
||||
str:sub(indices[i*2+1], end_character(str,indices[i*2+2]-1))
|
||||
)
|
||||
end
|
||||
@@ -57,10 +57,10 @@ regex.gsub = function(pattern_string, str, replacement)
|
||||
currentReplacement = string.gsub(currentReplacement, "\\%d", "")
|
||||
table.insert(replacements, { indices[1], #currentReplacement+indices[1] })
|
||||
if indices[1] > 1 then
|
||||
result = result ..
|
||||
result = result ..
|
||||
str:sub(1, previous_character(str, indices[1])) .. currentReplacement
|
||||
else
|
||||
result = result .. currentReplacement
|
||||
result = result .. currentReplacement
|
||||
end
|
||||
str = str:sub(indices[2])
|
||||
end
|
||||
|
||||
+365
-94
@@ -5,7 +5,6 @@ local style = require "core.style"
|
||||
local keymap = require "core.keymap"
|
||||
local Object = require "core.object"
|
||||
local View = require "core.view"
|
||||
local CommandView = require "core.commandview"
|
||||
local NagView = require "core.nagview"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
@@ -143,7 +142,14 @@ function Node:remove_view(root, view)
|
||||
local parent = self:get_parent_node(root)
|
||||
local is_a = (parent.a == self)
|
||||
local other = parent[is_a and "b" or "a"]
|
||||
if other:get_locked_size() then
|
||||
local locked_size_x, locked_size_y = other:get_locked_size()
|
||||
local locked_size
|
||||
if parent.type == "hsplit" then
|
||||
locked_size = locked_size_x
|
||||
else
|
||||
locked_size = locked_size_y
|
||||
end
|
||||
if self.is_primary_node or locked_size then
|
||||
self.views = {}
|
||||
self:add_view(EmptyView())
|
||||
else
|
||||
@@ -240,12 +246,15 @@ end
|
||||
|
||||
function Node:get_divider_overlapping_point(px, py)
|
||||
if self.type ~= "leaf" then
|
||||
local p = 6
|
||||
local x, y, w, h = self:get_divider_rect()
|
||||
x, y = x - p, y - p
|
||||
w, h = w + p * 2, h + p * 2
|
||||
if px > x and py > y and px < x + w and py < y + h then
|
||||
return self
|
||||
local axis = self.type == "hsplit" and "x" or "y"
|
||||
if self.a:is_resizable(axis) and self.b:is_resizable(axis) then
|
||||
local p = 6
|
||||
local x, y, w, h = self:get_divider_rect()
|
||||
x, y = x - p, y - p
|
||||
w, h = w + p * 2, h + p * 2
|
||||
if px > x and py > y and px < x + w and py < y + h then
|
||||
return self
|
||||
end
|
||||
end
|
||||
return self.a:get_divider_overlapping_point(px, py)
|
||||
or self.b:get_divider_overlapping_point(px, py)
|
||||
@@ -259,7 +268,7 @@ end
|
||||
|
||||
|
||||
function Node:get_tab_overlapping_point(px, py)
|
||||
if #self.views == 1 then return nil end
|
||||
if not self:should_show_tabs() then return nil end
|
||||
local tabs_number = self:get_visible_tabs_number()
|
||||
local x1, y1, w, h = self:get_tab_rect(self.tab_offset)
|
||||
local x2, y2 = self:get_tab_rect(self.tab_offset + tabs_number)
|
||||
@@ -269,6 +278,19 @@ function Node:get_tab_overlapping_point(px, py)
|
||||
end
|
||||
|
||||
|
||||
function Node:should_show_tabs()
|
||||
if self.locked then return false end
|
||||
local dn = core.root_view.dragged_node
|
||||
if #self.views > 1
|
||||
or (dn and dn.dragging) then -- show tabs while dragging
|
||||
return true
|
||||
elseif config.always_show_tabs then
|
||||
return not self.views[1]:is(EmptyView)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
local function close_button_location(x, w)
|
||||
local cw = style.icon_font:get_width("C")
|
||||
local pad = style.padding.y
|
||||
@@ -412,7 +434,7 @@ end
|
||||
function Node:update_layout()
|
||||
if self.type == "leaf" then
|
||||
local av = self.active_view
|
||||
if #self.views > 1 then
|
||||
if self:should_show_tabs() then
|
||||
local _, _, _, th = self:get_tab_rect(1)
|
||||
av.position.x, av.position.y = self.position.x, self.position.y + th
|
||||
av.size.x, av.size.y = self.size.x, self.size.y - th
|
||||
@@ -494,6 +516,53 @@ function Node:update()
|
||||
end
|
||||
end
|
||||
|
||||
function Node:draw_tab(text, is_active, is_hovered, is_close_hovered, x, y, w, h, standalone)
|
||||
local ds = style.divider_size
|
||||
local dots_width = style.font:get_width("…")
|
||||
local color = style.dim
|
||||
local padding_y = style.padding.y
|
||||
renderer.draw_rect(x + w, y + padding_y, ds, h - padding_y * 2, style.dim)
|
||||
if standalone then
|
||||
renderer.draw_rect(x-1, y-1, w+2, h+2, style.background2)
|
||||
end
|
||||
if is_active then
|
||||
color = style.text
|
||||
renderer.draw_rect(x, y, w, h, style.background)
|
||||
renderer.draw_rect(x + w, y, ds, h, style.divider)
|
||||
renderer.draw_rect(x - ds, y, ds, h, style.divider)
|
||||
end
|
||||
local cx, cw, cspace = close_button_location(x, w)
|
||||
local show_close_button = ((is_active or is_hovered) and not standalone and config.tab_close_button)
|
||||
if show_close_button then
|
||||
local close_style = is_close_hovered and style.text or style.dim
|
||||
common.draw_text(style.icon_font, close_style, "C", nil, cx, y, 0, h)
|
||||
end
|
||||
if is_hovered then
|
||||
color = style.text
|
||||
end
|
||||
local padx = style.padding.x
|
||||
-- Normally we should substract "cspace" from text_avail_width and from the
|
||||
-- clipping width. It is the padding space we give to the left and right of the
|
||||
-- close button. However, since we are using dots to terminate filenames, we
|
||||
-- choose to ignore "cspace" accepting that the text can possibly "touch" the
|
||||
-- close button.
|
||||
local text_avail_width = cx - x - padx
|
||||
core.push_clip_rect(x, y, cx - x, h)
|
||||
x, w = x + padx, w - padx * 2
|
||||
local align = "center"
|
||||
if style.font:get_width(text) > text_avail_width then
|
||||
align = "left"
|
||||
for i = 1, #text do
|
||||
local reduced_text = text:sub(1, #text - i)
|
||||
if style.font:get_width(reduced_text) + dots_width <= text_avail_width then
|
||||
text = reduced_text .. "…"
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
common.draw_text(style.font, color, text, align, x, y, w, h)
|
||||
core.pop_clip_rect()
|
||||
end
|
||||
|
||||
function Node:draw_tabs()
|
||||
local x, y, w, h, scroll_padding = self:get_scroll_button_rect(1)
|
||||
@@ -518,47 +587,9 @@ function Node:draw_tabs()
|
||||
for i = self.tab_offset, self.tab_offset + tabs_number - 1 do
|
||||
local view = self.views[i]
|
||||
local x, y, w, h = self:get_tab_rect(i)
|
||||
local text = view:get_name()
|
||||
local color = style.dim
|
||||
local padding_y = style.padding.y
|
||||
renderer.draw_rect(x + w, y + padding_y, ds, h - padding_y * 2, style.dim)
|
||||
if view == self.active_view then
|
||||
color = style.text
|
||||
renderer.draw_rect(x, y, w, h, style.background)
|
||||
renderer.draw_rect(x + w, y, ds, h, style.divider)
|
||||
renderer.draw_rect(x - ds, y, ds, h, style.divider)
|
||||
end
|
||||
local cx, cw, cspace = close_button_location(x, w)
|
||||
local show_close_button = ((view == self.active_view or i == self.hovered_tab) and config.tab_close_button)
|
||||
if show_close_button then
|
||||
local close_style = self.hovered_close == i and style.text or style.dim
|
||||
common.draw_text(style.icon_font, close_style, "C", nil, cx, y, 0, h)
|
||||
end
|
||||
if i == self.hovered_tab then
|
||||
color = style.text
|
||||
end
|
||||
local padx = style.padding.x
|
||||
-- Normally we should substract "cspace" from text_avail_width and from the
|
||||
-- clipping width. It is the padding space we give to the left and right of the
|
||||
-- close button. However, since we are using dots to terminate filenames, we
|
||||
-- choose to ignore "cspace" accepting that the text can possibly "touch" the
|
||||
-- close button.
|
||||
local text_avail_width = cx - x - padx
|
||||
core.push_clip_rect(x, y, cx - x, h)
|
||||
x, w = x + padx, w - padx * 2
|
||||
local align = "center"
|
||||
if style.font:get_width(text) > text_avail_width then
|
||||
align = "left"
|
||||
for i = 1, #text do
|
||||
local reduced_text = text:sub(1, #text - i)
|
||||
if style.font:get_width(reduced_text) + dots_width <= text_avail_width then
|
||||
text = reduced_text .. "…"
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
common.draw_text(style.font, color, text, align, x, y, w, h)
|
||||
core.pop_clip_rect()
|
||||
self:draw_tab(view:get_name(), view == self.active_view,
|
||||
i == self.hovered_tab, i == self.hovered_close,
|
||||
x, y, w, h)
|
||||
end
|
||||
|
||||
core.pop_clip_rect()
|
||||
@@ -567,7 +598,7 @@ end
|
||||
|
||||
function Node:draw()
|
||||
if self.type == "leaf" then
|
||||
if #self.views > 1 then
|
||||
if self:should_show_tabs() then
|
||||
self:draw_tabs()
|
||||
end
|
||||
local pos, size = self.active_view.position, self.active_view.size
|
||||
@@ -591,23 +622,37 @@ function Node:is_empty()
|
||||
end
|
||||
|
||||
|
||||
function Node:close_all_docviews()
|
||||
function Node:close_all_docviews(keep_active)
|
||||
local node_active_view = self.active_view
|
||||
local lost_active_view = false
|
||||
if self.type == "leaf" then
|
||||
local i = 1
|
||||
while i <= #self.views do
|
||||
local view = self.views[i]
|
||||
if view:is(DocView) and not view:is(CommandView) then
|
||||
if view.context == "session" and (not keep_active or view ~= self.active_view) then
|
||||
table.remove(self.views, i)
|
||||
if view == node_active_view then
|
||||
lost_active_view = true
|
||||
end
|
||||
else
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
self.tab_offset = 1
|
||||
if #self.views == 0 and self.is_primary_node then
|
||||
-- if we are not the primary view and we had the active view it doesn't
|
||||
-- matter to reattribute the active view because, within the close_all_docviews
|
||||
-- top call, the primary node will take the active view anyway.
|
||||
-- Set the empty view and takes the active view.
|
||||
self:add_view(EmptyView())
|
||||
elseif #self.views > 0 and lost_active_view then
|
||||
-- In practice we never get there but if a view remain we need
|
||||
-- to reset the Node's active view.
|
||||
self:set_active_view(self.views[1])
|
||||
end
|
||||
else
|
||||
self.a:close_all_docviews()
|
||||
self.b:close_all_docviews()
|
||||
self.a:close_all_docviews(keep_active)
|
||||
self.b:close_all_docviews(keep_active)
|
||||
if self.a:is_empty() and not self.a.is_primary_node then
|
||||
self:consume(self.b)
|
||||
elseif self.b:is_empty() and not self.b.is_primary_node then
|
||||
@@ -670,6 +715,62 @@ function Node:resize(axis, value)
|
||||
end
|
||||
|
||||
|
||||
function Node:get_split_type(mouse_x, mouse_y)
|
||||
local x, y = self.position.x, self.position.y
|
||||
local w, h = self.size.x, self.size.y
|
||||
local _, _, _, tab_h = self:get_scroll_button_rect(1)
|
||||
y = y + tab_h
|
||||
h = h - tab_h
|
||||
|
||||
local local_mouse_x = mouse_x - x
|
||||
local local_mouse_y = mouse_y - y
|
||||
|
||||
if local_mouse_y < 0 then
|
||||
return "tab"
|
||||
else
|
||||
local left_pct = local_mouse_x * 100 / w
|
||||
local top_pct = local_mouse_y * 100 / h
|
||||
if left_pct <= 30 then
|
||||
return "left"
|
||||
elseif left_pct >= 70 then
|
||||
return "right"
|
||||
elseif top_pct <= 30 then
|
||||
return "up"
|
||||
elseif top_pct >= 70 then
|
||||
return "down"
|
||||
end
|
||||
return "middle"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Node:get_drag_overlay_tab_position(x, y, dragged_node, dragged_index)
|
||||
local tab_index = self:get_tab_overlapping_point(x, y)
|
||||
if not tab_index then
|
||||
local first_tab_x = self:get_tab_rect(1)
|
||||
if x < first_tab_x then
|
||||
-- mouse before first visible tab
|
||||
tab_index = self.tab_offset or 1
|
||||
else
|
||||
-- mouse after last visible tab
|
||||
tab_index = self:get_visible_tabs_number() + (self.tab_offset - 1 or 0)
|
||||
end
|
||||
end
|
||||
local tab_x, tab_y, tab_w, tab_h = self:get_tab_rect(tab_index)
|
||||
if x > tab_x + tab_w / 2 and tab_index <= #self.views then
|
||||
-- use next tab
|
||||
tab_x = tab_x + tab_w
|
||||
tab_index = tab_index + 1
|
||||
end
|
||||
if self == dragged_node and dragged_index and tab_index > dragged_index then
|
||||
-- the tab we are moving is counted in tab_index
|
||||
tab_index = tab_index - 1
|
||||
tab_x = tab_x - tab_w
|
||||
end
|
||||
return tab_index, tab_x, tab_y, tab_w, tab_h
|
||||
end
|
||||
|
||||
|
||||
local RootView = View:extend()
|
||||
|
||||
function RootView:new()
|
||||
@@ -677,6 +778,14 @@ function RootView:new()
|
||||
self.root_node = Node()
|
||||
self.deferred_draws = {}
|
||||
self.mouse = { x = 0, y = 0 }
|
||||
self.drag_overlay = { x = 0, y = 0, w = 0, h = 0, visible = false, opacity = 0,
|
||||
base_color = style.drag_overlay,
|
||||
color = { table.unpack(style.drag_overlay) } }
|
||||
self.drag_overlay.to = { x = 0, y = 0, w = 0, h = 0 }
|
||||
self.drag_overlay_tab = { x = 0, y = 0, w = 0, h = 0, visible = false, opacity = 0,
|
||||
base_color = style.drag_overlay_tab,
|
||||
color = { table.unpack(style.drag_overlay_tab) } }
|
||||
self.drag_overlay_tab.to = { x = 0, y = 0, w = 0, h = 0 }
|
||||
end
|
||||
|
||||
|
||||
@@ -733,8 +842,8 @@ function RootView:open_doc(doc)
|
||||
end
|
||||
|
||||
|
||||
function RootView:close_all_docviews()
|
||||
self.root_node:close_all_docviews()
|
||||
function RootView:close_all_docviews(keep_active)
|
||||
self.root_node:close_all_docviews(keep_active)
|
||||
end
|
||||
|
||||
|
||||
@@ -748,38 +857,101 @@ function RootView:on_mouse_pressed(button, x, y, clicks)
|
||||
local div = self.root_node:get_divider_overlapping_point(x, y)
|
||||
if div then
|
||||
self.dragged_divider = div
|
||||
return
|
||||
return true
|
||||
end
|
||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||
if node.hovered_scroll_button > 0 then
|
||||
node:scroll_tabs(node.hovered_scroll_button)
|
||||
return
|
||||
return true
|
||||
end
|
||||
local idx = node:get_tab_overlapping_point(x, y)
|
||||
if idx then
|
||||
if button == "middle" or node.hovered_close == idx then
|
||||
node:close_view(self.root_node, node.views[idx])
|
||||
return true
|
||||
else
|
||||
self.dragged_node = { node, idx }
|
||||
if button == "left" then
|
||||
self.dragged_node = { node = node, idx = idx, dragging = false, drag_start_x = x, drag_start_y = y}
|
||||
end
|
||||
node:set_active_view(node.views[idx])
|
||||
return true
|
||||
end
|
||||
else
|
||||
elseif not self.dragged_node then -- avoid sending on_mouse_pressed events when dragging tabs
|
||||
core.set_active_view(node.active_view)
|
||||
if not self.on_view_mouse_pressed(button, x, y, clicks) then
|
||||
node.active_view:on_mouse_pressed(button, x, y, clicks)
|
||||
return node.active_view:on_mouse_pressed(button, x, y, clicks)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function RootView:on_mouse_released(...)
|
||||
function RootView:get_overlay_base_color(overlay)
|
||||
if overlay == self.drag_overlay then
|
||||
return style.drag_overlay
|
||||
else
|
||||
return style.drag_overlay_tab
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function RootView:set_show_overlay(overlay, status)
|
||||
overlay.visible = status
|
||||
if status then -- reset colors
|
||||
-- reload base_color
|
||||
overlay.base_color = self:get_overlay_base_color(overlay)
|
||||
overlay.color[1] = overlay.base_color[1]
|
||||
overlay.color[2] = overlay.base_color[2]
|
||||
overlay.color[3] = overlay.base_color[3]
|
||||
overlay.color[4] = overlay.base_color[4]
|
||||
overlay.opacity = 0
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function RootView:on_mouse_released(button, x, y, ...)
|
||||
if self.dragged_divider then
|
||||
self.dragged_divider = nil
|
||||
end
|
||||
if self.dragged_node then
|
||||
self.dragged_node = nil
|
||||
if button == "left" then
|
||||
if self.dragged_node.dragging then
|
||||
local node = self.root_node:get_child_overlapping_point(self.mouse.x, self.mouse.y)
|
||||
local dragged_node = self.dragged_node.node
|
||||
|
||||
if node and not node.locked
|
||||
-- don't do anything if dragging onto own node, with only one view
|
||||
and (node ~= dragged_node or #node.views > 1) then
|
||||
local split_type = node:get_split_type(self.mouse.x, self.mouse.y)
|
||||
local view = dragged_node.views[self.dragged_node.idx]
|
||||
|
||||
if split_type ~= "middle" and split_type ~= "tab" then -- needs splitting
|
||||
local new_node = node:split(split_type)
|
||||
self.root_node:get_node_for_view(view):remove_view(self.root_node, view)
|
||||
new_node:add_view(view)
|
||||
elseif split_type == "middle" and node ~= dragged_node then -- move to other node
|
||||
dragged_node:remove_view(self.root_node, view)
|
||||
node:add_view(view)
|
||||
self.root_node:get_node_for_view(view):set_active_view(view)
|
||||
elseif split_type == "tab" then -- move besides other tabs
|
||||
local tab_index = node:get_drag_overlay_tab_position(self.mouse.x, self.mouse.y, dragged_node, self.dragged_node.idx)
|
||||
dragged_node:remove_view(self.root_node, view)
|
||||
node:add_view(view, tab_index)
|
||||
self.root_node:get_node_for_view(view):set_active_view(view)
|
||||
end
|
||||
self.root_node:update_layout()
|
||||
core.redraw = true
|
||||
end
|
||||
end
|
||||
self:set_show_overlay(self.drag_overlay, false)
|
||||
self:set_show_overlay(self.drag_overlay_tab, false)
|
||||
if self.dragged_node and self.dragged_node.dragging then
|
||||
core.request_cursor("arrow")
|
||||
end
|
||||
self.dragged_node = nil
|
||||
end
|
||||
else -- avoid sending on_mouse_released events when dragging tabs
|
||||
self.root_node:on_mouse_released(button, x, y, ...)
|
||||
end
|
||||
self.root_node:on_mouse_released(...)
|
||||
end
|
||||
|
||||
|
||||
@@ -815,40 +987,33 @@ function RootView:on_mouse_moved(x, y, dx, dy)
|
||||
end
|
||||
|
||||
self.mouse.x, self.mouse.y = x, y
|
||||
|
||||
local dn = self.dragged_node
|
||||
if dn and not dn.dragging then
|
||||
-- start dragging only after enough movement
|
||||
dn.dragging = common.distance(x, y, dn.drag_start_x, dn.drag_start_y) > style.tab_width * .05
|
||||
if dn.dragging then
|
||||
core.request_cursor("hand")
|
||||
end
|
||||
end
|
||||
|
||||
-- avoid sending on_mouse_moved events when dragging tabs
|
||||
if dn then return end
|
||||
|
||||
self.root_node:on_mouse_moved(x, y, dx, dy)
|
||||
|
||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||
self.overlapping_node = self.root_node:get_child_overlapping_point(x, y)
|
||||
|
||||
local div = self.root_node:get_divider_overlapping_point(x, y)
|
||||
local tab_index = node and node:get_tab_overlapping_point(x, y)
|
||||
if node and node:get_scroll_button_index(x, y) then
|
||||
local tab_index = self.overlapping_node and self.overlapping_node:get_tab_overlapping_point(x, y)
|
||||
if self.overlapping_node and self.overlapping_node:get_scroll_button_index(x, y) then
|
||||
core.request_cursor("arrow")
|
||||
elseif div then
|
||||
local axis = (div.type == "hsplit" and "x" or "y")
|
||||
if div.a:is_resizable(axis) and div.b:is_resizable(axis) then
|
||||
core.request_cursor(div.type == "hsplit" and "sizeh" or "sizev")
|
||||
end
|
||||
core.request_cursor(div.type == "hsplit" and "sizeh" or "sizev")
|
||||
elseif tab_index then
|
||||
core.request_cursor("arrow")
|
||||
elseif node then
|
||||
core.request_cursor(node.active_view.cursor)
|
||||
end
|
||||
if node and self.dragged_node and (self.dragged_node[1] ~= node or (tab_index and self.dragged_node[2] ~= tab_index))
|
||||
and node.type == "leaf" and #node.views > 0 and node.views[1]:is(DocView) then
|
||||
local tab = self.dragged_node[1].views[self.dragged_node[2]]
|
||||
if self.dragged_node[1] ~= node then
|
||||
for i, v in ipairs(node.views) do if v.doc == tab.doc then tab = nil break end end
|
||||
if tab then
|
||||
self.dragged_node[1]:remove_view(self.root_node, tab)
|
||||
node:add_view(tab, tab_index)
|
||||
self.root_node:update_layout()
|
||||
self.dragged_node = { node, tab_index or #node.views }
|
||||
core.redraw = true
|
||||
end
|
||||
else
|
||||
table.remove(self.dragged_node[1].views, self.dragged_node[2])
|
||||
table.insert(node.views, tab_index, tab)
|
||||
self.dragged_node = { node, tab_index }
|
||||
end
|
||||
elseif self.overlapping_node then
|
||||
core.request_cursor(self.overlapping_node.active_view.cursor)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -856,7 +1021,7 @@ end
|
||||
function RootView:on_mouse_wheel(...)
|
||||
local x, y = self.mouse.x, self.mouse.y
|
||||
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||
node.active_view:on_mouse_wheel(...)
|
||||
return node.active_view:on_mouse_wheel(...)
|
||||
end
|
||||
|
||||
|
||||
@@ -870,10 +1035,110 @@ function RootView:on_focus_lost(...)
|
||||
core.redraw = true
|
||||
end
|
||||
|
||||
|
||||
function RootView:interpolate_drag_overlay(overlay)
|
||||
self:move_towards(overlay, "x", overlay.to.x)
|
||||
self:move_towards(overlay, "y", overlay.to.y)
|
||||
self:move_towards(overlay, "w", overlay.to.w)
|
||||
self:move_towards(overlay, "h", overlay.to.h)
|
||||
|
||||
self:move_towards(overlay, "opacity", overlay.visible and 100 or 0)
|
||||
overlay.color[4] = overlay.base_color[4] * overlay.opacity / 100
|
||||
end
|
||||
|
||||
|
||||
function RootView:update()
|
||||
copy_position_and_size(self.root_node, self)
|
||||
self.root_node:update()
|
||||
self.root_node:update_layout()
|
||||
|
||||
self:update_drag_overlay()
|
||||
self:interpolate_drag_overlay(self.drag_overlay)
|
||||
self:interpolate_drag_overlay(self.drag_overlay_tab)
|
||||
end
|
||||
|
||||
|
||||
function RootView:set_drag_overlay(overlay, x, y, w, h, immediate)
|
||||
overlay.to.x = x
|
||||
overlay.to.y = y
|
||||
overlay.to.w = w
|
||||
overlay.to.h = h
|
||||
if immediate then
|
||||
overlay.x = x
|
||||
overlay.y = y
|
||||
overlay.w = w
|
||||
overlay.h = h
|
||||
end
|
||||
if not overlay.visible then
|
||||
self:set_show_overlay(overlay, true)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function get_split_sizes(split_type, x, y, w, h)
|
||||
if split_type == "left" then
|
||||
w = w * .5
|
||||
elseif split_type == "right" then
|
||||
x = x + w * .5
|
||||
w = w * .5
|
||||
elseif split_type == "up" then
|
||||
h = h * .5
|
||||
elseif split_type == "down" then
|
||||
y = y + h * .5
|
||||
h = h * .5
|
||||
end
|
||||
return x, y, w, h
|
||||
end
|
||||
|
||||
|
||||
function RootView:update_drag_overlay()
|
||||
if not (self.dragged_node and self.dragged_node.dragging) then return end
|
||||
local over = self.root_node:get_child_overlapping_point(self.mouse.x, self.mouse.y)
|
||||
if over and not over.locked then
|
||||
local _, _, _, tab_h = over:get_scroll_button_rect(1)
|
||||
local x, y = over.position.x, over.position.y
|
||||
local w, h = over.size.x, over.size.y
|
||||
local split_type = over:get_split_type(self.mouse.x, self.mouse.y)
|
||||
|
||||
if split_type == "tab" and (over ~= self.dragged_node.node or #over.views > 1) then
|
||||
local tab_index, tab_x, tab_y, tab_w, tab_h = over:get_drag_overlay_tab_position(self.mouse.x, self.mouse.y)
|
||||
self:set_drag_overlay(self.drag_overlay_tab,
|
||||
tab_x + (tab_index and 0 or tab_w), tab_y,
|
||||
style.caret_width, tab_h,
|
||||
-- avoid showing tab overlay moving between nodes
|
||||
over ~= self.drag_overlay_tab.last_over)
|
||||
self:set_show_overlay(self.drag_overlay, false)
|
||||
self.drag_overlay_tab.last_over = over
|
||||
else
|
||||
if (over ~= self.dragged_node.node or #over.views > 1) then
|
||||
y = y + tab_h
|
||||
h = h - tab_h
|
||||
x, y, w, h = get_split_sizes(split_type, x, y, w, h)
|
||||
end
|
||||
self:set_drag_overlay(self.drag_overlay, x, y, w, h)
|
||||
self:set_show_overlay(self.drag_overlay_tab, false)
|
||||
end
|
||||
else
|
||||
self:set_show_overlay(self.drag_overlay, false)
|
||||
self:set_show_overlay(self.drag_overlay_tab, false)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function RootView:draw_grabbed_tab()
|
||||
local dn = self.dragged_node
|
||||
local _,_, w, h = dn.node:get_tab_rect(dn.idx)
|
||||
local x = self.mouse.x - w / 2
|
||||
local y = self.mouse.y - h / 2
|
||||
local text = dn.node.views[dn.idx] and dn.node.views[dn.idx]:get_name() or ""
|
||||
self.root_node:draw_tab(text, true, true, false, x, y, w, h, true)
|
||||
end
|
||||
|
||||
|
||||
function RootView:draw_drag_overlay(ov)
|
||||
if ov.opacity > 0 then
|
||||
renderer.draw_rect(ov.x, ov.y, ov.w, ov.h, ov.color)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -883,6 +1148,12 @@ function RootView:draw()
|
||||
local t = table.remove(self.deferred_draws)
|
||||
t.fn(table.unpack(t))
|
||||
end
|
||||
|
||||
self:draw_drag_overlay(self.drag_overlay)
|
||||
self:draw_drag_overlay(self.drag_overlay_tab)
|
||||
if self.dragged_node and self.dragged_node.dragging then
|
||||
self:draw_grabbed_tab()
|
||||
end
|
||||
if core.cursor_change_req then
|
||||
system.set_cursor(core.cursor_change_req)
|
||||
core.cursor_change_req = nil
|
||||
|
||||
+13
-2
@@ -1,6 +1,6 @@
|
||||
-- this file is used by lite-xl to setup the Lua environment when starting
|
||||
VERSION = "2.0-beta1"
|
||||
MOD_VERSION = "1"
|
||||
VERSION = "@PROJECT_VERSION@"
|
||||
MOD_VERSION = "2"
|
||||
|
||||
SCALE = tonumber(os.getenv("LITE_SCALE")) or SCALE
|
||||
PATHSEP = package.config:sub(1, 1)
|
||||
@@ -20,3 +20,14 @@ package.path = DATADIR .. '/?/init.lua;' .. package.path
|
||||
package.path = USERDIR .. '/?.lua;' .. package.path
|
||||
package.path = USERDIR .. '/?/init.lua;' .. package.path
|
||||
|
||||
local dynamic_suffix = PLATFORM == "Mac OS X" and 'lib' or (PLATFORM == "Windows" and 'dll' or 'so')
|
||||
package.cpath = DATADIR .. '/?.' .. dynamic_suffix .. ";" .. USERDIR .. '/?.' .. dynamic_suffix
|
||||
package.native_plugins = {}
|
||||
package.searchers = { package.searchers[1], package.searchers[2], function(modname)
|
||||
local path = package.searchpath(modname, package.cpath)
|
||||
if not path then return nil end
|
||||
return system.load_native_plugin, path
|
||||
end }
|
||||
|
||||
table.pack = table.pack or pack or function(...) return {...} end
|
||||
table.unpack = table.unpack or unpack
|
||||
|
||||
@@ -6,6 +6,7 @@ local style = require "core.style"
|
||||
local DocView = require "core.docview"
|
||||
local LogView = require "core.logview"
|
||||
local View = require "core.view"
|
||||
local Object = require "core.object"
|
||||
|
||||
|
||||
local StatusView = View:extend()
|
||||
@@ -70,7 +71,7 @@ local function draw_items(self, items, x, y, draw_fn)
|
||||
local color = style.text
|
||||
|
||||
for _, item in ipairs(items) do
|
||||
if type(item) == "userdata" then
|
||||
if Object.is(item, renderer.font) then
|
||||
font = item
|
||||
elseif type(item) == "table" then
|
||||
color = item
|
||||
|
||||
+7
-5
@@ -21,11 +21,11 @@ style.tab_width = common.round(170 * SCALE)
|
||||
--
|
||||
-- On High DPI monitor or non RGB monitor you may consider using antialiasing grayscale instead.
|
||||
-- The antialiasing grayscale with full hinting is interesting for crisp font rendering.
|
||||
style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 13 * SCALE)
|
||||
style.big_font = style.font:copy(40 * SCALE)
|
||||
style.icon_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 14 * SCALE, {antialiasing="grayscale", hinting="full"})
|
||||
style.icon_big_font = style.icon_font:copy(20 * SCALE)
|
||||
style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 13 * SCALE)
|
||||
style.font = renderer.font.load(DATADIR .. "/fonts/FiraSans-Regular.ttf", 15 * SCALE)
|
||||
style.big_font = style.font:copy(46 * SCALE)
|
||||
style.icon_font = renderer.font.load(DATADIR .. "/fonts/icons.ttf", 16 * SCALE, {antialiasing="grayscale", hinting="full"})
|
||||
style.icon_big_font = style.icon_font:copy(23 * SCALE)
|
||||
style.code_font = renderer.font.load(DATADIR .. "/fonts/JetBrainsMono-Regular.ttf", 15 * SCALE)
|
||||
|
||||
style.background = { common.color "#2e2e32" }
|
||||
style.background2 = { common.color "#252529" }
|
||||
@@ -44,6 +44,8 @@ style.scrollbar2 = { common.color "#4b4b52" }
|
||||
style.nagbar = { common.color "#FF0000" }
|
||||
style.nagbar_text = { common.color "#FFFFFF" }
|
||||
style.nagbar_dim = { common.color "rgba(0, 0, 0, 0.45)" }
|
||||
style.drag_overlay = { common.color "rgba(255,255,255,0.1)" }
|
||||
style.drag_overlay_tab = { common.color "#93DDFA" }
|
||||
|
||||
style.syntax = {}
|
||||
style.syntax["normal"] = { common.color "#e1e1e6" }
|
||||
|
||||
@@ -155,7 +155,7 @@ function tokenizer.tokenize(incoming_syntax, text, state)
|
||||
if count % 2 == 0 then break end
|
||||
end
|
||||
until not res[1] or not close or not target[3]
|
||||
return unpack(res)
|
||||
return table.unpack(res)
|
||||
end
|
||||
|
||||
while i <= #text do
|
||||
|
||||
+5
-5
@@ -7,6 +7,10 @@ local Object = require "core.object"
|
||||
|
||||
local View = Object:extend()
|
||||
|
||||
-- context can be "application" or "session". The instance of objects
|
||||
-- with context "session" will be closed when a project session is
|
||||
-- terminated. The context "application" is for functional UI elements.
|
||||
View.context = "application"
|
||||
|
||||
function View:new()
|
||||
self.position = { x = 0, y = 0 }
|
||||
@@ -98,13 +102,9 @@ function View:on_text_input(text)
|
||||
-- no-op
|
||||
end
|
||||
|
||||
|
||||
function View:on_mouse_wheel(y)
|
||||
if self.scrollable then
|
||||
self.scroll.to.y = self.scroll.to.y + y * -config.mouse_wheel_scroll
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function View:get_content_bounds()
|
||||
local x = self.scroll.x
|
||||
|
||||
+257
-31
@@ -1,4 +1,4 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local config = require "core.config"
|
||||
@@ -8,25 +8,65 @@ local keymap = require "core.keymap"
|
||||
local translate = require "core.doc.translate"
|
||||
local RootView = require "core.rootview"
|
||||
local DocView = require "core.docview"
|
||||
local Doc = require "core.doc"
|
||||
|
||||
config.plugins.autocomplete = { max_suggestions = 6 }
|
||||
config.plugins.autocomplete = {
|
||||
-- Amount of characters that need to be written for autocomplete
|
||||
min_len = 3,
|
||||
-- The max amount of visible items
|
||||
max_height = 6,
|
||||
-- The max amount of scrollable items
|
||||
max_suggestions = 100,
|
||||
}
|
||||
|
||||
local autocomplete = {}
|
||||
autocomplete.map = {}
|
||||
|
||||
autocomplete.map = {}
|
||||
autocomplete.map_manually = {}
|
||||
autocomplete.on_close = nil
|
||||
|
||||
-- Flag that indicates if the autocomplete box was manually triggered
|
||||
-- with the autocomplete.complete() function to prevent the suggestions
|
||||
-- from getting cluttered with arbitrary document symbols by using the
|
||||
-- autocomplete.map_manually table.
|
||||
local triggered_manually = false
|
||||
|
||||
local mt = { __tostring = function(t) return t.text end }
|
||||
|
||||
function autocomplete.add(t)
|
||||
function autocomplete.add(t, triggered_manually)
|
||||
local items = {}
|
||||
for text, info in pairs(t.items) do
|
||||
info = (type(info) == "string") and info
|
||||
table.insert(items, setmetatable({ text = text, info = info }, mt))
|
||||
if type(info) == "table" then
|
||||
table.insert(
|
||||
items,
|
||||
setmetatable(
|
||||
{
|
||||
text = text,
|
||||
info = info.info,
|
||||
desc = info.desc, -- Description shown on item selected
|
||||
cb = info.cb, -- A callback called once when item is selected
|
||||
data = info.data -- Optional data that can be used on cb
|
||||
},
|
||||
mt
|
||||
)
|
||||
)
|
||||
else
|
||||
info = (type(info) == "string") and info
|
||||
table.insert(items, setmetatable({ text = text, info = info }, mt))
|
||||
end
|
||||
end
|
||||
|
||||
if not triggered_manually then
|
||||
autocomplete.map[t.name] = { files = t.files or ".*", items = items }
|
||||
else
|
||||
autocomplete.map_manually[t.name] = { files = t.files or ".*", items = items }
|
||||
end
|
||||
autocomplete.map[t.name] = { files = t.files or ".*", items = items }
|
||||
end
|
||||
|
||||
local max_symbols = config.max_symbols or 2000
|
||||
--
|
||||
-- Thread that scans open document symbols and cache them
|
||||
--
|
||||
local max_symbols = config.max_symbols
|
||||
|
||||
core.add_thread(function()
|
||||
local cache = setmetatable({}, { __mode = "k" })
|
||||
@@ -109,16 +149,39 @@ local last_line, last_col
|
||||
local function reset_suggestions()
|
||||
suggestions_idx = 1
|
||||
suggestions = {}
|
||||
|
||||
triggered_manually = false
|
||||
|
||||
local doc = core.active_view.doc
|
||||
if autocomplete.on_close then
|
||||
autocomplete.on_close(doc, suggestions[suggestions_idx])
|
||||
autocomplete.on_close = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function in_table(value, table_array)
|
||||
for i, element in pairs(table_array) do
|
||||
if element == value then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function update_suggestions()
|
||||
local doc = core.active_view.doc
|
||||
local filename = doc and doc.filename or ""
|
||||
|
||||
local map = autocomplete.map
|
||||
|
||||
if triggered_manually then
|
||||
map = autocomplete.map_manually
|
||||
end
|
||||
|
||||
-- get all relevant suggestions for given filename
|
||||
local items = {}
|
||||
for _, v in pairs(autocomplete.map) do
|
||||
for _, v in pairs(map) do
|
||||
if common.match_pattern(filename, v.files) then
|
||||
for _, item in pairs(v.items) do
|
||||
table.insert(items, item)
|
||||
@@ -138,7 +201,6 @@ local function update_suggestions()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function get_partial_symbol()
|
||||
local doc = core.active_view.doc
|
||||
local line2, col2 = doc:get_selection()
|
||||
@@ -146,14 +208,12 @@ local function get_partial_symbol()
|
||||
return doc:get_text(line1, col1, line2, col2)
|
||||
end
|
||||
|
||||
|
||||
local function get_active_view()
|
||||
if getmetatable(core.active_view) == DocView then
|
||||
return core.active_view
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function get_suggestions_rect(av)
|
||||
if #suggestions == 0 then
|
||||
return 0, 0, 0, 0
|
||||
@@ -175,15 +235,67 @@ local function get_suggestions_rect(av)
|
||||
max_width = math.max(max_width, w)
|
||||
end
|
||||
|
||||
local ah = config.plugins.autocomplete.max_height
|
||||
|
||||
local max_items = #suggestions
|
||||
if max_items > ah then
|
||||
max_items = ah
|
||||
end
|
||||
|
||||
-- additional line to display total items
|
||||
max_items = max_items + 1
|
||||
|
||||
if max_width < 150 then
|
||||
max_width = 150
|
||||
end
|
||||
|
||||
return
|
||||
x - style.padding.x,
|
||||
y - style.padding.y,
|
||||
max_width + style.padding.x * 2,
|
||||
#suggestions * (th + style.padding.y) + style.padding.y
|
||||
max_items * (th + style.padding.y) + style.padding.y
|
||||
end
|
||||
|
||||
local function draw_description_box(text, av, sx, sy, sw, sh)
|
||||
local width = 0
|
||||
|
||||
local lines = {}
|
||||
for line in string.gmatch(text.."\n", "(.-)\n") do
|
||||
width = math.max(width, style.font:get_width(line))
|
||||
table.insert(lines, line)
|
||||
end
|
||||
|
||||
local height = #lines * style.font:get_height()
|
||||
|
||||
-- draw background rect
|
||||
renderer.draw_rect(
|
||||
sx + sw + style.padding.x / 4,
|
||||
sy,
|
||||
width + style.padding.x * 2,
|
||||
height + style.padding.y * 2,
|
||||
style.background3
|
||||
)
|
||||
|
||||
-- draw text
|
||||
local lh = style.font:get_height()
|
||||
local y = sy + style.padding.y
|
||||
local x = sx + sw + style.padding.x / 4
|
||||
|
||||
for _, line in pairs(lines) do
|
||||
common.draw_text(
|
||||
style.font, style.text, line, "left", x + style.padding.x, y, width, lh
|
||||
)
|
||||
y = y + lh
|
||||
end
|
||||
end
|
||||
|
||||
local function draw_suggestions_box(av)
|
||||
if #suggestions <= 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local ah = config.plugins.autocomplete.max_height
|
||||
|
||||
-- draw background rect
|
||||
local rx, ry, rw, rh = get_suggestions_rect(av)
|
||||
renderer.draw_rect(rx, ry, rw, rh, style.background3)
|
||||
@@ -192,7 +304,14 @@ local function draw_suggestions_box(av)
|
||||
local font = av:get_font()
|
||||
local lh = font:get_height() + style.padding.y
|
||||
local y = ry + style.padding.y / 2
|
||||
for i, s in ipairs(suggestions) do
|
||||
local show_count = #suggestions <= ah and #suggestions or ah
|
||||
local start_index = suggestions_idx > ah and (suggestions_idx-(ah-1)) or 1
|
||||
|
||||
for i=start_index, start_index+show_count-1, 1 do
|
||||
if not suggestions[i] then
|
||||
break
|
||||
end
|
||||
local s = suggestions[i]
|
||||
local color = (i == suggestions_idx) and style.accent or style.text
|
||||
common.draw_text(font, color, s.text, "left", rx + style.padding.x, y, rw, lh)
|
||||
if s.info then
|
||||
@@ -200,26 +319,55 @@ local function draw_suggestions_box(av)
|
||||
common.draw_text(style.font, color, s.info, "right", rx, y, rw - style.padding.x, lh)
|
||||
end
|
||||
y = y + lh
|
||||
if suggestions_idx == i then
|
||||
if s.cb then
|
||||
s.cb(suggestions_idx, s)
|
||||
s.cb = nil
|
||||
s.data = nil
|
||||
end
|
||||
if s.desc and #s.desc > 0 then
|
||||
draw_description_box(s.desc, av, rx, ry, rw, rh)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
renderer.draw_rect(rx, y, rw, 2, style.caret)
|
||||
renderer.draw_rect(rx, y+2, rw, lh, style.background)
|
||||
common.draw_text(
|
||||
style.font,
|
||||
style.accent,
|
||||
"Items",
|
||||
"left",
|
||||
rx + style.padding.x, y, rw, lh
|
||||
)
|
||||
common.draw_text(
|
||||
style.font,
|
||||
style.accent,
|
||||
tostring(suggestions_idx) .. "/" .. tostring(#suggestions),
|
||||
"right",
|
||||
rx, y, rw - style.padding.x, lh
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
-- patch event logic into RootView
|
||||
local on_text_input = RootView.on_text_input
|
||||
local update = RootView.update
|
||||
local draw = RootView.draw
|
||||
|
||||
|
||||
RootView.on_text_input = function(...)
|
||||
on_text_input(...)
|
||||
|
||||
local function show_autocomplete()
|
||||
local av = get_active_view()
|
||||
if av then
|
||||
-- update partial symbol and suggestions
|
||||
partial = get_partial_symbol()
|
||||
if #partial >= 3 then
|
||||
|
||||
if #partial >= config.plugins.autocomplete.min_len or triggered_manually then
|
||||
update_suggestions()
|
||||
last_line, last_col = av.doc:get_selection()
|
||||
|
||||
if not triggered_manually then
|
||||
last_line, last_col = av.doc:get_selection()
|
||||
else
|
||||
local line, col = av.doc:get_selection()
|
||||
local char = av.doc:get_char(line, col-1, line, col-1)
|
||||
|
||||
if char:match("%s") or (char:match("%p") and col ~= last_col) then
|
||||
reset_suggestions()
|
||||
end
|
||||
end
|
||||
else
|
||||
reset_suggestions()
|
||||
end
|
||||
@@ -233,6 +381,30 @@ RootView.on_text_input = function(...)
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- Patch event logic into RootView and Doc
|
||||
--
|
||||
local on_text_input = RootView.on_text_input
|
||||
local on_text_remove = Doc.remove
|
||||
local update = RootView.update
|
||||
local draw = RootView.draw
|
||||
|
||||
RootView.on_text_input = function(...)
|
||||
on_text_input(...)
|
||||
show_autocomplete()
|
||||
end
|
||||
|
||||
Doc.remove = function(self, line1, col1, line2, col2)
|
||||
on_text_remove(self, line1, col1, line2, col2)
|
||||
|
||||
if triggered_manually and line1 == line2 then
|
||||
if last_col >= col1 then
|
||||
reset_suggestions()
|
||||
else
|
||||
show_autocomplete()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RootView.update = function(...)
|
||||
update(...)
|
||||
@@ -241,13 +413,19 @@ RootView.update = function(...)
|
||||
if av then
|
||||
-- reset suggestions if caret was moved
|
||||
local line, col = av.doc:get_selection()
|
||||
if line ~= last_line or col ~= last_col then
|
||||
reset_suggestions()
|
||||
|
||||
if not triggered_manually then
|
||||
if line ~= last_line or col ~= last_col then
|
||||
reset_suggestions()
|
||||
end
|
||||
else
|
||||
if line ~= last_line or col < last_col then
|
||||
reset_suggestions()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
RootView.draw = function(...)
|
||||
draw(...)
|
||||
|
||||
@@ -258,12 +436,53 @@ RootView.draw = function(...)
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- Public functions
|
||||
--
|
||||
function autocomplete.open(on_close)
|
||||
triggered_manually = true
|
||||
|
||||
if on_close then
|
||||
autocomplete.on_close = on_close
|
||||
end
|
||||
|
||||
local av = get_active_view()
|
||||
last_line, last_col = av.doc:get_selection()
|
||||
update_suggestions()
|
||||
end
|
||||
|
||||
function autocomplete.close()
|
||||
reset_suggestions()
|
||||
end
|
||||
|
||||
function autocomplete.is_open()
|
||||
return #suggestions > 0
|
||||
end
|
||||
|
||||
function autocomplete.complete(completions, on_close)
|
||||
reset_suggestions()
|
||||
|
||||
autocomplete.map_manually = {}
|
||||
autocomplete.add(completions, true)
|
||||
|
||||
autocomplete.open(on_close)
|
||||
end
|
||||
|
||||
function autocomplete.can_complete()
|
||||
if #partial >= config.plugins.autocomplete.min_len then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- Commands
|
||||
--
|
||||
local function predicate()
|
||||
return get_active_view() and #suggestions > 0
|
||||
end
|
||||
|
||||
|
||||
command.add(predicate, {
|
||||
["autocomplete:complete"] = function()
|
||||
local doc = core.active_view.doc
|
||||
@@ -283,12 +502,19 @@ command.add(predicate, {
|
||||
suggestions_idx = math.min(suggestions_idx + 1, #suggestions)
|
||||
end,
|
||||
|
||||
["autocomplete:cycle"] = function()
|
||||
local newidx = suggestions_idx + 1
|
||||
suggestions_idx = newidx > #suggestions and 1 or newidx
|
||||
end,
|
||||
|
||||
["autocomplete:cancel"] = function()
|
||||
reset_suggestions()
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
--
|
||||
-- Keymaps
|
||||
--
|
||||
keymap.add {
|
||||
["tab"] = "autocomplete:complete",
|
||||
["up"] = "autocomplete:previous",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local config = require "core.config"
|
||||
local Doc = require "core.doc"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
@@ -42,6 +42,24 @@ keymap.add {
|
||||
["menu"] = "context:show"
|
||||
}
|
||||
|
||||
local function copy_log()
|
||||
local item = core.active_view.hovered_item
|
||||
if item then
|
||||
system.set_clipboard(core.get_log(item))
|
||||
end
|
||||
end
|
||||
|
||||
local function open_as_doc()
|
||||
local doc = core.open_doc("logs.txt")
|
||||
core.root_view:open_doc(doc)
|
||||
doc:insert(1, 1, core.get_log())
|
||||
end
|
||||
|
||||
menu:register("core.logview", {
|
||||
{ text = "Copy entry", command = copy_log },
|
||||
{ text = "Open as file", command = open_as_doc }
|
||||
})
|
||||
|
||||
if require("plugins.scale") then
|
||||
menu:register("core.docview", {
|
||||
{ text = "Font +", command = "scale:increase" },
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local common = require "core.common"
|
||||
@@ -102,6 +102,11 @@ end
|
||||
local function update_cache(doc)
|
||||
local type, size, score = detect_indent_stat(doc)
|
||||
local score_threshold = 4
|
||||
if score < score_threshold then
|
||||
-- use default values
|
||||
type = config.tab_type
|
||||
size = config.indent_size
|
||||
end
|
||||
cache[doc] = { type = type, size = size, confirmed = (score >= score_threshold) }
|
||||
doc.indent_info = cache[doc]
|
||||
end
|
||||
@@ -111,20 +116,14 @@ local new = Doc.new
|
||||
function Doc:new(...)
|
||||
new(self, ...)
|
||||
update_cache(self)
|
||||
if not cache[self].confirmed then
|
||||
core.add_thread(function ()
|
||||
while not cache[self].confirmed do
|
||||
update_cache(self)
|
||||
coroutine.yield(1)
|
||||
end
|
||||
end, self)
|
||||
end
|
||||
end
|
||||
|
||||
local clean = Doc.clean
|
||||
function Doc:clean(...)
|
||||
clean(self, ...)
|
||||
update_cache(self)
|
||||
if not cache[self].confirmed then
|
||||
update_cache(self)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -152,3 +151,78 @@ function DocView:draw(...)
|
||||
return with_indent_override(self.doc, draw, self, ...)
|
||||
end
|
||||
|
||||
|
||||
local function set_indent_type(doc, type)
|
||||
cache[doc] = {type = type,
|
||||
size = cache[doc].value or config.indent_size,
|
||||
confirmed = true}
|
||||
doc.indent_info = cache[doc]
|
||||
end
|
||||
|
||||
local function set_indent_type_command()
|
||||
core.command_view:enter(
|
||||
"Specify indent style for this file",
|
||||
function(value) -- submit
|
||||
local doc = core.active_view.doc
|
||||
value = value:lower()
|
||||
set_indent_type(doc, value == "tabs" and "hard" or "soft")
|
||||
end,
|
||||
function(text) -- suggest
|
||||
return common.fuzzy_match({"tabs", "spaces"}, text)
|
||||
end,
|
||||
nil, -- cancel
|
||||
function(text) -- validate
|
||||
local t = text:lower()
|
||||
return t == "tabs" or t == "spaces"
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
local function set_indent_size(doc, size)
|
||||
cache[doc] = {type = cache[doc].type or config.tab_type,
|
||||
size = size,
|
||||
confirmed = true}
|
||||
doc.indent_info = cache[doc]
|
||||
end
|
||||
|
||||
local function set_indent_size_command()
|
||||
core.command_view:enter(
|
||||
"Specify indent size for current file",
|
||||
function(value) -- submit
|
||||
local value = math.floor(tonumber(value))
|
||||
local doc = core.active_view.doc
|
||||
set_indent_size(doc, value)
|
||||
end,
|
||||
nil, -- suggest
|
||||
nil, -- cancel
|
||||
function(value) -- validate
|
||||
local value = tonumber(value)
|
||||
return value ~= nil and value >= 1
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
command.add("core.docview", {
|
||||
["indent:set-file-indent-type"] = set_indent_type_command,
|
||||
["indent:set-file-indent-size"] = set_indent_size_command
|
||||
})
|
||||
|
||||
|
||||
command.add(function()
|
||||
return core.active_view:is(DocView)
|
||||
and cache[core.active_view.doc]
|
||||
and cache[core.active_view.doc].type == "soft"
|
||||
end, {
|
||||
["indent:switch-file-to-tabs-indentation"] = function() set_indent_type(core.active_view.doc, "hard") end
|
||||
})
|
||||
|
||||
|
||||
command.add(function()
|
||||
return core.active_view:is(DocView)
|
||||
and cache[core.active_view.doc]
|
||||
and cache[core.active_view.doc].type == "hard"
|
||||
end, {
|
||||
["indent:switch-file-to-spaces-indentation"] = function() set_indent_type(core.active_view.doc, "soft") end
|
||||
})
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
|
||||
local style = require "core.style"
|
||||
local DocView = require "core.docview"
|
||||
local common = require "core.common"
|
||||
|
||||
local draw_line_text = DocView.draw_line_text
|
||||
|
||||
function DocView:draw_line_text(idx, x, y)
|
||||
local font = (self:get_font() or style.syntax_fonts["comment"])
|
||||
local color = style.syntax.comment
|
||||
local ty = y + self:get_line_text_y_offset()
|
||||
local tx
|
||||
local text, offset, s, e = self.doc.lines[idx], 1
|
||||
local x1, _, x2, _ = self:get_content_bounds()
|
||||
local _offset = self:get_x_offset_col(idx, x1)
|
||||
offset = _offset
|
||||
while true do
|
||||
s, e = text:find(" +", offset)
|
||||
if not s then break end
|
||||
tx = self:get_col_x_offset(idx, s) + x
|
||||
renderer.draw_text(font, string.rep("·", e - s + 1), tx, ty, color)
|
||||
if tx > x + x2 then break end
|
||||
offset = e + 1
|
||||
end
|
||||
offset = _offset
|
||||
while true do
|
||||
s, e = text:find("\t", offset)
|
||||
if not s then break end
|
||||
tx = self:get_col_x_offset(idx, s) + x
|
||||
renderer.draw_text(font, "»", tx, ty, color)
|
||||
if tx > x + x2 then break end
|
||||
offset = e + 1
|
||||
end
|
||||
draw_line_text(self, idx, x, y)
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
@@ -55,6 +55,17 @@ syntax.add {
|
||||
["true"] = "literal",
|
||||
["false"] = "literal",
|
||||
["NULL"] = "literal",
|
||||
["#include"] = "keyword",
|
||||
["#if"] = "keyword",
|
||||
["#ifdef"] = "keyword",
|
||||
["#ifndef"] = "keyword",
|
||||
["#else"] = "keyword",
|
||||
["#elseif"] = "keyword",
|
||||
["#endif"] = "keyword",
|
||||
["#define"] = "keyword",
|
||||
["#warning"] = "keyword",
|
||||
["#error"] = "keyword",
|
||||
["#pragma"] = "keyword",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
pcall(require, "plugins.language_c")
|
||||
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
|
||||
@@ -1,22 +1,41 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
|
||||
|
||||
syntax.add {
|
||||
files = { "%.md$", "%.markdown$" },
|
||||
patterns = {
|
||||
{ pattern = "\\.", type = "normal" },
|
||||
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
||||
{ pattern = { "```", "```" }, type = "string" },
|
||||
{ pattern = { "``", "``", "\\" }, type = "string" },
|
||||
{ pattern = { "`", "`", "\\" }, type = "string" },
|
||||
{ pattern = { "~~", "~~", "\\" }, type = "keyword2" },
|
||||
{ pattern = "%-%-%-+", type = "comment" },
|
||||
{ pattern = "%*%s+", type = "operator" },
|
||||
{ pattern = { "%*", "[%*\n]", "\\" }, type = "operator" },
|
||||
{ pattern = { "%_", "[%_\n]", "\\" }, type = "keyword2" },
|
||||
{ pattern = "#.-\n", type = "keyword" },
|
||||
{ pattern = "!?%[.-%]%(.-%)", type = "function" },
|
||||
{ pattern = "https?://%S+", type = "function" },
|
||||
{ pattern = "\\.", type = "normal" },
|
||||
{ pattern = { "<!%-%-", "%-%->" }, type = "comment" },
|
||||
{ pattern = { "```c", "```" }, type = "string", syntax = ".c" },
|
||||
{ pattern = { "```c++", "```" }, type = "string", syntax = ".cpp" },
|
||||
{ pattern = { "```python", "```" }, type = "string", syntax = ".py" },
|
||||
{ pattern = { "```ruby", "```" }, type = "string", syntax = ".rb" },
|
||||
{ pattern = { "```perl", "```" }, type = "string", syntax = ".pl" },
|
||||
{ pattern = { "```php", "```" }, type = "string", syntax = ".php" },
|
||||
{ pattern = { "```javascript", "```" }, type = "string", syntax = ".js" },
|
||||
{ pattern = { "```html", "```" }, type = "string", syntax = ".html" },
|
||||
{ pattern = { "```xml", "```" }, type = "string", syntax = ".xml" },
|
||||
{ pattern = { "```css", "```" }, type = "string", syntax = ".css" },
|
||||
{ pattern = { "```lua", "```" }, type = "string", syntax = ".lua" },
|
||||
{ pattern = { "```bash", "```" }, type = "string", syntax = ".sh" },
|
||||
{ pattern = { "```java", "```" }, type = "string", syntax = ".java" },
|
||||
{ pattern = { "```c#", "```" }, type = "string", syntax = ".cs" },
|
||||
{ pattern = { "```cmake", "```" }, type = "string", syntax = ".cmake" },
|
||||
{ pattern = { "```d", "```" }, type = "string", syntax = ".d" },
|
||||
{ pattern = { "```glsl", "```" }, type = "string", syntax = ".glsl" },
|
||||
{ pattern = { "```", "```" }, type = "string" },
|
||||
{ pattern = { "``", "``", "\\" }, type = "string" },
|
||||
{ pattern = { "`", "`", "\\" }, type = "string" },
|
||||
{ pattern = { "~~", "~~", "\\" }, type = "keyword2" },
|
||||
{ pattern = "%-%-%-+", type = "comment" },
|
||||
{ pattern = "%*%s+", type = "operator" },
|
||||
{ pattern = { "%*", "[%*\n]", "\\" }, type = "operator" },
|
||||
{ pattern = { "%_", "[%_\n]", "\\" }, type = "keyword2" },
|
||||
{ pattern = "#.-\n", type = "keyword" },
|
||||
{ pattern = "!?%[.-%]%(.-%)", type = "function" },
|
||||
{ pattern = "https?://%S+", type = "function" },
|
||||
},
|
||||
symbols = { },
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
files = { "%.py$", "%.pyw$" },
|
||||
files = { "%.py$", "%.pyw$", "%.rpy$" },
|
||||
headers = "^#!.*[ /]python",
|
||||
comment = "#",
|
||||
patterns = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local config = require "core.config"
|
||||
local style = require "core.style"
|
||||
local DocView = require "core.docview"
|
||||
@@ -6,9 +6,7 @@ local DocView = require "core.docview"
|
||||
local draw_overlay = DocView.draw_overlay
|
||||
|
||||
function DocView:draw_overlay(...)
|
||||
local ns = self:get_font():get_width_subpixel("n") * config.line_limit
|
||||
local ss = self:get_font():subpixel_scale()
|
||||
local offset = ns / ss
|
||||
local offset = self:get_font():get_width("n") * config.line_limit
|
||||
local x = self:get_line_screen_position(1) + offset
|
||||
local y = self.position.y
|
||||
local w = math.ceil(SCALE * 1)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local keymap = require "core.keymap"
|
||||
@@ -9,6 +9,7 @@ local View = require "core.view"
|
||||
|
||||
local ResultsView = View:extend()
|
||||
|
||||
ResultsView.context = "session"
|
||||
|
||||
function ResultsView:new(text, fn)
|
||||
ResultsView.super.new(self)
|
||||
@@ -91,7 +92,7 @@ end
|
||||
function ResultsView:on_mouse_pressed(...)
|
||||
local caught = ResultsView.super.on_mouse_pressed(self, ...)
|
||||
if not caught then
|
||||
self:open_selected_result()
|
||||
return self:open_selected_result()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -107,6 +108,7 @@ function ResultsView:open_selected_result()
|
||||
dv.doc:set_selection(res.line, res.col)
|
||||
dv:scroll_to_line(res.line, false, true)
|
||||
end)
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
@@ -170,7 +172,7 @@ function ResultsView:draw()
|
||||
local ox, oy = self:get_content_offset()
|
||||
local x, y = ox + style.padding.x, oy + style.padding.y
|
||||
local files_number = core.project_files_number()
|
||||
local per = files_number and self.last_file_idx / files_number or 1
|
||||
local per = common.clamp(files_number and self.last_file_idx / files_number or 1, 0, 1)
|
||||
local text
|
||||
if self.searching then
|
||||
if files_number then
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local config = require "core.config"
|
||||
local command = require "core.command"
|
||||
|
||||
+12
-24
@@ -1,4 +1,4 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local command = require "core.command"
|
||||
@@ -13,7 +13,6 @@ config.plugins.scale = {
|
||||
use_mousewheel = true
|
||||
}
|
||||
|
||||
local scale_level = 0
|
||||
local scale_steps = 0.05
|
||||
|
||||
local current_scale = SCALE
|
||||
@@ -34,9 +33,6 @@ local function set_scale(scale)
|
||||
local s = scale / current_scale
|
||||
current_scale = scale
|
||||
|
||||
-- we set scale_level in case this was called by user
|
||||
scale_level = (scale - default_scale) / scale_steps
|
||||
|
||||
if config.plugins.scale.mode == "ui" then
|
||||
SCALE = scale
|
||||
|
||||
@@ -48,10 +44,14 @@ local function set_scale(scale)
|
||||
style.tab_width = style.tab_width * s
|
||||
|
||||
for _, name in ipairs {"font", "big_font", "icon_font", "icon_big_font", "code_font"} do
|
||||
renderer.font.set_size(style[name], s * style[name]:get_size())
|
||||
style[name] = renderer.font.copy(style[name], s * style[name]:get_size())
|
||||
end
|
||||
else
|
||||
renderer.font.set_size(style.code_font, s * style.code_font:get_size())
|
||||
style.code_font = renderer.font.copy(style.code_font, s * style.code_font:get_size())
|
||||
end
|
||||
|
||||
for _, font in pairs(style.syntax_fonts) do
|
||||
renderer.font.set_size(font, s * font:get_size())
|
||||
end
|
||||
|
||||
-- restore scroll positions
|
||||
@@ -67,30 +67,16 @@ local function get_scale()
|
||||
return current_scale
|
||||
end
|
||||
|
||||
local on_mouse_wheel = RootView.on_mouse_wheel
|
||||
|
||||
function RootView:on_mouse_wheel(d, ...)
|
||||
if keymap.modkeys["ctrl"] and config.plugins.scale.use_mousewheel then
|
||||
if d < 0 then command.perform "scale:decrease" end
|
||||
if d > 0 then command.perform "scale:increase" end
|
||||
else
|
||||
return on_mouse_wheel(self, d, ...)
|
||||
end
|
||||
end
|
||||
|
||||
local function res_scale()
|
||||
scale_level = 0
|
||||
set_scale(default_scale)
|
||||
set_scale(default_scale)
|
||||
end
|
||||
|
||||
local function inc_scale()
|
||||
scale_level = scale_level + 1
|
||||
set_scale(default_scale + scale_level * scale_steps)
|
||||
set_scale(current_scale + scale_steps)
|
||||
end
|
||||
|
||||
local function dec_scale()
|
||||
scale_level = scale_level - 1
|
||||
set_scale(default_scale + scale_level * scale_steps)
|
||||
set_scale(current_scale - scale_steps)
|
||||
end
|
||||
|
||||
|
||||
@@ -104,6 +90,8 @@ keymap.add {
|
||||
["ctrl+0"] = "scale:reset",
|
||||
["ctrl+-"] = "scale:decrease",
|
||||
["ctrl+="] = "scale:increase",
|
||||
["ctrl+wheelup"] = "scale:increase",
|
||||
["ctrl+wheeldown"] = "scale:decrease"
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local translate = require "core.doc.translate"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local command = require "core.command"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local command = require "core.command"
|
||||
@@ -243,7 +243,7 @@ function TreeView:on_mouse_pressed(button, x, y, clicks)
|
||||
end
|
||||
else
|
||||
core.try(function()
|
||||
local doc_filename = common.relative_path(core.project_dir, hovered_item.abs_filename)
|
||||
local doc_filename = core.normalize_to_project_dir(hovered_item.abs_filename)
|
||||
core.root_view:open_doc(core.open_doc(doc_filename))
|
||||
end)
|
||||
end
|
||||
@@ -437,15 +437,31 @@ menu:register(
|
||||
command.add(nil, {
|
||||
["treeview:toggle"] = function()
|
||||
view.visible = not view.visible
|
||||
end,
|
||||
end})
|
||||
|
||||
|
||||
command.add(function() return view.hovered_item ~= nil end, {
|
||||
["treeview:rename"] = function()
|
||||
local old_filename = view.hovered_item.filename
|
||||
local old_abs_filename = view.hovered_item.abs_filename
|
||||
core.command_view:set_text(old_filename)
|
||||
core.command_view:enter("Rename", function(filename)
|
||||
os.rename(old_filename, filename)
|
||||
filename = core.normalize_to_project_dir(filename)
|
||||
local abs_filename = core.project_absolute_path(filename)
|
||||
local res, err = os.rename(old_abs_filename, abs_filename)
|
||||
if res then -- successfully renamed
|
||||
for _, doc in ipairs(core.docs) do
|
||||
if doc.abs_filename and old_abs_filename == doc.abs_filename then
|
||||
doc:set_filename(filename, abs_filename) -- make doc point to the new filename
|
||||
doc:reset_syntax()
|
||||
break -- only first needed
|
||||
end
|
||||
end
|
||||
core.log("Renamed \"%s\" to \"%s\"", old_filename, filename)
|
||||
else
|
||||
core.error("Error while renaming \"%s\" to \"%s\": %s", old_abs_filename, abs_filename, err)
|
||||
end
|
||||
core.reschedule_project_scan()
|
||||
core.log("Renamed \"%s\" to \"%s\"", old_filename, filename)
|
||||
end, common.path_suggest)
|
||||
end,
|
||||
|
||||
@@ -521,7 +537,7 @@ command.add(nil, {
|
||||
local hovered_item = view.hovered_item
|
||||
|
||||
if PLATFORM == "Windows" then
|
||||
system.exec("start " .. hovered_item.abs_filename)
|
||||
system.exec(string.format("start \"\" %q", hovered_item.abs_filename))
|
||||
elseif string.find(PLATFORM, "Mac") then
|
||||
system.exec(string.format("open %q", hovered_item.abs_filename))
|
||||
elseif PLATFORM == "Linux" then
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local Doc = require "core.doc"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-- mod-version:1 -- lite-xl 1.16
|
||||
-- mod-version:2 -- lite-xl 2.0
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
Reference in New Issue
Block a user