Merge branch 'master' into replace-unpack
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -148,7 +148,9 @@ command.add(nil, {
|
||||
|
||||
["core:change-project-folder"] = function()
|
||||
local dirname = common.dirname(core.project_dir)
|
||||
core.command_view:set_text(common.home_encode(dirname) .. PATHSEP)
|
||||
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
|
||||
@@ -162,6 +164,10 @@ command.add(nil, {
|
||||
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)
|
||||
|
||||
+34
-10
@@ -82,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()
|
||||
@@ -168,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)
|
||||
@@ -398,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)
|
||||
|
||||
@@ -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
|
||||
@@ -34,24 +35,37 @@ local function update_preview(sel, search_fn, text)
|
||||
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(table.unpack(sel))
|
||||
return false
|
||||
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(table.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)
|
||||
@@ -59,8 +73,9 @@ local function find(label, search_fn)
|
||||
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
|
||||
@@ -75,19 +90,24 @@ 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() end, function()
|
||||
end, function() return core.previous_find end, function()
|
||||
core.status_view:remove_tooltip()
|
||||
end)
|
||||
end
|
||||
@@ -96,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", {
|
||||
@@ -114,7 +181,9 @@ 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
|
||||
|
||||
@@ -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 = {
|
||||
@@ -63,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)
|
||||
@@ -76,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
|
||||
}
|
||||
|
||||
|
||||
@@ -122,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
|
||||
})
|
||||
|
||||
@@ -34,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
|
||||
@@ -45,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
|
||||
@@ -83,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
|
||||
|
||||
|
||||
@@ -134,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
|
||||
|
||||
|
||||
@@ -187,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
|
||||
@@ -256,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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -276,6 +281,7 @@ 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])(:.*)')
|
||||
@@ -290,7 +296,8 @@ function common.normalize_path(filename)
|
||||
table.insert(accu, part)
|
||||
end
|
||||
end
|
||||
return table.concat(accu, PATHSEP)
|
||||
local npath = table.concat(accu, PATHSEP)
|
||||
return npath == "" and PATHSEP or npath
|
||||
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_]*"
|
||||
@@ -23,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.
|
||||
@@ -33,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 = {}
|
||||
|
||||
+42
-11
@@ -102,7 +102,11 @@ end
|
||||
|
||||
|
||||
function Doc:is_dirty()
|
||||
return self.clean_change_id ~= self:get_change_id() or self.new_file
|
||||
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
|
||||
|
||||
|
||||
@@ -322,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
|
||||
@@ -332,6 +337,14 @@ 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)
|
||||
@@ -339,7 +352,7 @@ function Doc:raw_insert(line, col, text, undo_stack, time)
|
||||
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
|
||||
|
||||
@@ -356,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
|
||||
|
||||
@@ -392,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
|
||||
@@ -401,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
|
||||
@@ -414,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
|
||||
|
||||
+32
-74
@@ -98,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
|
||||
|
||||
@@ -150,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
|
||||
|
||||
|
||||
@@ -166,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
|
||||
@@ -223,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, ...)
|
||||
|
||||
@@ -281,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 = { }
|
||||
@@ -289,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
|
||||
@@ -339,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
|
||||
|
||||
@@ -358,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
|
||||
@@ -367,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
|
||||
|
||||
@@ -404,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
|
||||
|
||||
+32
-29
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -495,7 +493,6 @@ function core.init()
|
||||
core.visited_files = {}
|
||||
core.restart_request = false
|
||||
core.quit_request = false
|
||||
core.replacements = whitespace_replacements()
|
||||
|
||||
core.root_view = RootView()
|
||||
core.command_view = CommandView()
|
||||
@@ -678,21 +675,23 @@ function core.load_plugins()
|
||||
userdir = {dir = USERDIR, plugins = {}},
|
||||
datadir = {dir = DATADIR, plugins = {}},
|
||||
}
|
||||
local files = {}
|
||||
local files, ordered = {}, {}
|
||||
for _, root_dir in ipairs {DATADIR, USERDIR} do
|
||||
local plugin_dir = root_dir .. "/plugins"
|
||||
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, plugin_dir in pairs(files) do
|
||||
local basename = filename:match("(.-)%.lua$") or filename
|
||||
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 and 'userdir' or 'datadir'].plugins
|
||||
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
|
||||
@@ -809,7 +808,7 @@ end
|
||||
-- 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) then
|
||||
if filename:match('^%a:\\') or filename:find('/', 1, true) == 1 then
|
||||
return filename
|
||||
else
|
||||
return core.project_dir .. PATHSEP .. filename
|
||||
@@ -923,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
|
||||
|
||||
+30
-15
@@ -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-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-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
|
||||
|
||||
|
||||
+57
-10
@@ -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")
|
||||
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]
|
||||
@@ -133,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",
|
||||
@@ -168,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",
|
||||
@@ -191,6 +233,11 @@ keymap.add_direct {
|
||||
["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",
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+323
-75
@@ -142,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
|
||||
@@ -273,7 +280,10 @@ end
|
||||
|
||||
function Node:should_show_tabs()
|
||||
if self.locked then return false end
|
||||
if #self.views > 1 then return true
|
||||
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
|
||||
@@ -506,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)
|
||||
@@ -530,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()
|
||||
@@ -696,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()
|
||||
@@ -703,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
|
||||
|
||||
|
||||
@@ -774,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
|
||||
|
||||
|
||||
@@ -841,37 +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
|
||||
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
|
||||
|
||||
@@ -879,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
|
||||
|
||||
|
||||
@@ -893,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
|
||||
|
||||
|
||||
@@ -906,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
|
||||
|
||||
@@ -20,5 +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" }
|
||||
|
||||
+1
-5
@@ -102,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
|
||||
|
||||
@@ -10,9 +10,9 @@ local RootView = require "core.rootview"
|
||||
local DocView = require "core.docview"
|
||||
local Doc = require "core.doc"
|
||||
|
||||
config.plugins.autocomplete = {
|
||||
config.plugins.autocomplete = {
|
||||
-- Amount of characters that need to be written for autocomplete
|
||||
min_len = 1,
|
||||
min_len = 3,
|
||||
-- The max amount of visible items
|
||||
max_height = 6,
|
||||
-- The max amount of scrollable items
|
||||
@@ -502,6 +502,11 @@ 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,
|
||||
|
||||
@@ -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,32 @@
|
||||
-- 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, tx = y + self:get_line_text_y_offset()
|
||||
local text, offset, s, e = self.doc.lines[idx], 1
|
||||
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)
|
||||
offset = e + 1
|
||||
end
|
||||
offset = 1
|
||||
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)
|
||||
offset = e + 1
|
||||
end
|
||||
draw_line_text(self, idx, x, y)
|
||||
end
|
||||
|
||||
|
||||
@@ -1,22 +1,41 @@
|
||||
-- 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 = { },
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
local syntax = require "core.syntax"
|
||||
|
||||
syntax.add {
|
||||
files = { "%.py$", "%.pyw$" },
|
||||
files = { "%.py$", "%.pyw$", "%.rpy$" },
|
||||
headers = "^#!.*[ /]python",
|
||||
comment = "#",
|
||||
patterns = {
|
||||
|
||||
@@ -7,8 +7,7 @@ local draw_overlay = DocView.draw_overlay
|
||||
|
||||
function DocView:draw_overlay(...)
|
||||
local ns = ("n"):rep(config.line_limit)
|
||||
local ss = self:get_font():subpixel_scale()
|
||||
local offset = self:get_font():get_width_subpixel(ns) / ss
|
||||
local offset = self:get_font():get_width(ns)
|
||||
local x = self:get_line_screen_position(1) + offset
|
||||
local y = self.position.y
|
||||
local w = math.ceil(SCALE * 1)
|
||||
|
||||
@@ -92,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
|
||||
|
||||
@@ -108,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
|
||||
|
||||
|
||||
@@ -171,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
|
||||
|
||||
+11
-25
@@ -13,9 +13,6 @@ config.plugins.scale = {
|
||||
use_mousewheel = true
|
||||
}
|
||||
|
||||
local MINIMUM_SCALE = 0.25;
|
||||
|
||||
local scale_level = 0
|
||||
local scale_steps = 0.05
|
||||
|
||||
local current_scale = SCALE
|
||||
@@ -36,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
|
||||
|
||||
@@ -50,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
|
||||
@@ -69,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)
|
||||
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(math.max(default_scale + scale_level * scale_steps), MINIMUM_SCALE)
|
||||
local function dec_scale()
|
||||
set_scale(current_scale - scale_steps)
|
||||
end
|
||||
|
||||
|
||||
@@ -106,6 +90,8 @@ keymap.add {
|
||||
["ctrl+0"] = "scale:reset",
|
||||
["ctrl+-"] = "scale:decrease",
|
||||
["ctrl+="] = "scale:increase",
|
||||
["ctrl+wheelup"] = "scale:increase",
|
||||
["ctrl+wheeldown"] = "scale:decrease"
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user