Merge pull request #809 from lite-xl/merge-master-2.0

Merge master 2.0
This commit is contained in:
Adam
2022-01-28 14:38:22 -05:00
committed by GitHub
11 changed files with 704 additions and 177 deletions
+39 -24
View File
@@ -10,8 +10,18 @@ local restore_title_view = false
local function suggest_directory(text)
text = common.home_expand(text)
return common.home_encode_list((text == "" or text == common.home_expand(common.dirname(core.project_dir)))
and core.recent_projects or common.dir_path_suggest(text))
local basedir = common.dirname(core.project_dir)
return common.home_encode_list((basedir and text == basedir .. PATHSEP or text == "") and
core.recent_projects or common.dir_path_suggest(text))
end
local function check_directory_path(path)
local abs_path = system.absolute_path(path)
local info = abs_path and system.get_file_info(abs_path)
if not info or info.type ~= 'dir' then
return nil
end
return abs_path
end
command.add(nil, {
@@ -141,46 +151,51 @@ command.add(nil, {
end,
["core:open-project-module"] = function()
local filename = ".lite_project.lua"
if system.get_file_info(filename) then
core.root_view:open_doc(core.open_doc(filename))
else
local doc = core.open_doc()
core.root_view:open_doc(doc)
doc:save(filename)
if not system.get_file_info(".lite_project.lua") then
core.try(core.write_init_project_module, ".lite_project.lua")
end
local doc = core.open_doc(".lite_project.lua")
core.root_view:open_doc(doc)
doc:save()
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))
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
local path_stat = system.get_file_info(text)
if not path_stat or path_stat.type ~= 'dir' then
core.error("Cannot open folder %q", text)
core.command_view:enter("Change Project Folder", function(text)
local path = common.home_expand(text)
local abs_path = check_directory_path(path)
if not abs_path then
core.error("Cannot open directory %q", path)
return
end
core.confirm_close_docs(core.docs, core.open_folder_project, text)
if abs_path == core.project_dir then return end
core.confirm_close_docs(core.docs, function(dirpath)
core.close_current_project()
core.open_folder_project(dirpath)
end, abs_path)
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))
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)
if not path_stat or path_stat.type ~= 'dir' then
core.error("Cannot open folder %q", text)
core.command_view:enter("Open Project", function(text)
local path = common.home_expand(text)
local abs_path = check_directory_path(path)
if not abs_path then
core.error("Cannot open directory %q", path)
return
end
system.exec(string.format("%q %q", EXEFILE, text))
if abs_path == core.project_dir then
core.error("Directory %q is currently opened", abs_path)
return
end
system.exec(string.format("%q %q", EXEFILE, abs_path))
end, suggest_directory)
end,
+1 -1
View File
@@ -489,7 +489,7 @@ local commands = {
end
for i,docview in ipairs(core.get_views_referencing_doc(doc())) do
local node = core.root_view.root_node:get_node_for_view(docview)
node:close_view(core.root_view, docview)
node:close_view(core.root_view.root_node, docview)
end
os.remove(filename)
core.log("Removed \"%s\"", filename)
+2
View File
@@ -84,6 +84,8 @@ function Doc:save(filename, abs_filename)
assert(self.filename, "no filename set to default to")
filename = self.filename
abs_filename = self.abs_filename
else
assert(self.filename or abs_filename, "calling save on unnamed doc without absolute path")
end
local fp = assert( io.open(filename, "wb") )
for _, line in ipairs(self.lines) do
+327 -62
View File
@@ -58,20 +58,56 @@ function core.set_project_dir(new_dir, change_project_fn)
if change_project_fn then change_project_fn() end
core.project_dir = common.normalize_volume(new_dir)
core.project_directories = {}
core.add_project_directory(new_dir)
return true
end
return false
return chdir_ok
end
function core.close_current_project()
-- When using system.unwatch_dir we need to pass the watch_id provided by dmon.
-- In reality when unwatching a directory the dmon library shifts the other watch_id
-- values so the actual watch_id changes. To workaround this problem we assume the
-- first watch_id is always 1 and the watch_id are continguous and we unwatch the
-- first watch_id repeateadly up to the number of watch_ids.
local watch_id_max = 0
for _, project_dir in ipairs(core.project_directories) do
if project_dir.watch_id and project_dir.watch_id > watch_id_max then
watch_id_max = project_dir.watch_id
end
end
for i = 1, watch_id_max do
system.unwatch_dir(1)
end
end
local function reload_customizations()
-- The logic is:
-- - the core.style and config modules are reloaded with the purpose of applying
-- the new user's and project's module configs
-- - inside the core.config the existing fields in config.plugins are preserved
-- because they are reserved to plugins configuration and plugins are already
-- loaded.
-- - plugins are not reloaded or unloaded
local plugins_save = {}
for k, v in pairs(config.plugins) do
plugins_save[k] = v
end
core.reload_module("core.style")
core.reload_module("core.config")
core.load_user_directory()
core.load_project_module()
for k, v in pairs(plugins_save) do
config.plugins[k] = v
end
end
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()
reload_customizations()
update_recents_project("add", dir_path_abs)
if not core.load_project_module() then
command.perform("core:open-log")
end
core.add_project_directory(dir_path_abs)
core.on_enter_project(dir_path_abs)
end
end
@@ -93,15 +129,57 @@ local function compare_file(a, b)
end
-- inspect config.ignore_files patterns and prepare ready to use entries.
local function compile_ignore_files()
local ipatterns = config.ignore_files
local compiled = {}
-- config.ignore_files could be a simple string...
if type(ipatterns) ~= "table" then ipatterns = {ipatterns} end
for i, pattern in ipairs(ipatterns) do
-- we ignore malformed pattern that raise an error
if pcall(string.match, "a", pattern) then
table.insert(compiled, {
use_path = pattern:match("/[^/$]"), -- contains a slash but not at the end
-- An '/' or '/$' at the end means we want to match a directory.
match_dir = pattern:match(".+/%$?$"), -- to be used as a boolen value
pattern = pattern -- get the actual pattern
})
end
end
return compiled
end
local function fileinfo_pass_filter(info, ignore_compiled)
if info.size >= config.file_size_limit * 1e6 then return false end
local basename = common.basename(info.filename)
-- replace '\' with '/' for Windows where PATHSEP = '\'
local fullname = "/" .. info.filename:gsub("\\", "/")
for _, compiled in ipairs(ignore_compiled) do
local test = compiled.use_path and fullname or basename
if compiled.match_dir then
if info.type == "dir" and string.match(test .. "/", compiled.pattern) then
return false
end
else
if string.match(test, compiled.pattern) then
return false
end
end
end
return true
end
-- compute a file's info entry completed with "filename" to be used
-- in project scan or falsy if it shouldn't appear in the list.
local function get_project_file_info(root, file)
local function get_project_file_info(root, file, ignore_compiled)
local info = system.get_file_info(root .. file)
if info then
-- info can be not nil but info.type may be nil if is neither a file neither
-- a directory, for example for /dev/* entries on linux.
if info and info.type then
info.filename = strip_leading_path(file)
return (info.size < config.file_size_limit * 1e6 and
not common.match_pattern(common.basename(info.filename), config.ignore_files)
and info)
return fileinfo_pass_filter(info, ignore_compiled) and info
end
end
@@ -123,15 +201,16 @@ end
-- When recursing "root" will always be the same, only "path" will change.
-- Returns a list of file "items". In eash item the "filename" will be the
-- complete file path relative to "root" *without* the trailing '/'.
local function get_directory_files(dir, root, path, t, entries_count, recurse_pred, begin_hook)
local function get_directory_files(dir, root, path, t, ignore_compiled, entries_count, recurse_pred, begin_hook)
if begin_hook then begin_hook() end
ignore_compiled = ignore_compiled or compile_ignore_files()
local t0 = system.get_time()
local all = system.list_dir(root .. path) or {}
local t_elapsed = system.get_time() - t0
local dirs, files = {}, {}
for _, file in ipairs(all) do
local info = get_project_file_info(root, path .. PATHSEP .. file)
local info = get_project_file_info(root, path .. PATHSEP .. file, ignore_compiled)
if info then
table.insert(info.type == "dir" and dirs or files, info)
entries_count = entries_count + 1
@@ -143,7 +222,7 @@ local function get_directory_files(dir, root, path, t, entries_count, recurse_pr
for _, f in ipairs(dirs) do
table.insert(t, f)
if recurse_pred(dir, f.filename, entries_count, t_elapsed) then
local _, complete, n = get_directory_files(dir, root, PATHSEP .. f.filename, t, entries_count, recurse_pred, begin_hook)
local _, complete, n = get_directory_files(dir, root, PATHSEP .. f.filename, t, ignore_compiled, entries_count, recurse_pred, begin_hook)
recurse_complete = recurse_complete and complete
entries_count = n
else
@@ -161,15 +240,14 @@ end
function core.project_subdir_set_show(dir, filename, show)
dir.shown_subdir[filename] = show
if dir.files_limit and PLATFORM == "Linux" then
if dir.files_limit and not dir.force_rescan then
local fullpath = dir.name .. PATHSEP .. filename
local watch_fn = show and system.watch_dir_add or system.watch_dir_rm
local success = watch_fn(dir.watch_id, fullpath)
if not success then
core.log("Internal warning: error calling system.watch_dir_%s", show and "add" or "rm")
if not (show and system.watch_dir_add or system.watch_dir_rm)(dir.watch_id, fullpath) then
return false
end
end
dir.shown_subdir[filename] = show
return true
end
@@ -209,15 +287,6 @@ local function file_search(files, info)
end
local function project_scan_add_entry(dir, fileinfo)
local index, match = file_search(dir.files, fileinfo)
if not match then
table.insert(dir.files, index, fileinfo)
dir.is_dirty = true
end
end
local function files_info_equal(a, b)
return a.filename == b.filename and a.type == b.type
end
@@ -234,7 +303,7 @@ local function files_list_match(a, i1, n, b)
end
-- arguments like for files_list_match
local function files_list_replace(as, i1, n, bs)
local function files_list_replace(as, i1, n, bs, hook)
local m = #bs
local i, j = 1, 1
while i <= m or i <= n do
@@ -244,7 +313,9 @@ local function files_list_replace(as, i1, n, bs)
then
table.insert(as, i1 + i, b)
i, j, n = i + 1, j + 1, n + 1
if hook and hook.insert then hook.insert(b) end
elseif j > m or system.path_compare(a.filename, a.type, b.filename, b.type) then
if hook and hook.remove then hook.remove(as[i1 + i]) end
table.remove(as, i1 + i)
n = n - 1
else
@@ -253,6 +324,29 @@ local function files_list_replace(as, i1, n, bs)
end
end
local function project_scan_add_entry(dir, fileinfo)
assert(not dir.force_rescan, "should be used only when force_rescan is false")
local index, match = file_search(dir.files, fileinfo)
if not match then
table.insert(dir.files, index, fileinfo)
if fileinfo.type == "dir" and not dir.files_limit then
-- ASSUMPTION: dir.force_rescan is FALSE
system.watch_dir_add(dir.watch_id, dir.name .. PATHSEP .. fileinfo.filename)
if fileinfo.symlink then
local new_files = get_directory_files(dir, dir.name, PATHSEP .. fileinfo.filename, {}, nil, 0, core.project_subdir_is_shown)
files_list_replace(dir.files, index, 0, new_files, {insert = function(info)
if info.type == "dir" then
system.watch_dir_add(dir.watch_id, dir.name .. PATHSEP .. info.filename)
end
end})
end
end
dir.is_dirty = true
end
end
local function project_subdir_bounds(dir, filename)
local index, n = 0, #dir.files
for i, file in ipairs(dir.files) do
@@ -271,7 +365,7 @@ local function project_subdir_bounds(dir, filename)
end
local function rescan_project_subdir(dir, filename_rooted)
local new_files = get_directory_files(dir, dir.name, filename_rooted, {}, 0, core.project_subdir_is_shown, coroutine.yield)
local new_files = get_directory_files(dir, dir.name, filename_rooted, {}, nil, 0, core.project_subdir_is_shown, coroutine.yield)
local index, n = 0, #dir.files
if filename_rooted ~= "" then
local filename = strip_leading_path(filename_rooted)
@@ -279,7 +373,19 @@ local function rescan_project_subdir(dir, filename_rooted)
end
if not files_list_match(dir.files, index, n, new_files) then
files_list_replace(dir.files, index, n, new_files)
-- Since we are modifying the list of files we may add new directories and
-- when dir.files_limit is false we need to add a watch for each subdirectory.
-- We are therefore passing a insert hook function to the purpose of adding
-- a watch.
-- Note that the hook shold almost never be called, it happens only if
-- we missed some directory creation event from the directory monitoring which
-- almost never happens. With inotify is at least theoretically possible.
local need_subdir_watches = not dir.files_limit and not dir.force_rescan
files_list_replace(dir.files, index, n, new_files, need_subdir_watches and {insert = function(fileinfo)
if fileinfo.type == "dir" then
system.watch_dir_add(dir.watch_id, dir.name .. PATHSEP .. fileinfo.filename)
end
end})
dir.is_dirty = true
return true
end
@@ -295,38 +401,62 @@ local function add_dir_scan_thread(dir)
end
coroutine.yield(5)
end
end)
end, dir)
end
local function folder_add_subdirs_watch(dir)
for _, fileinfo in ipairs(dir.files) do
if fileinfo.type == "dir" then
system.watch_dir_add(dir.watch_id, dir.name .. PATHSEP .. fileinfo.filename)
end
end
end
-- Populate a project folder top directory by scanning the filesystem.
local function scan_project_folder(index)
local dir = core.project_directories[index]
if PLATFORM == "Linux" then
local fstype = system.get_fs_type(dir.name)
dir.force_rescan = (fstype == "nfs" or fstype == "fuse")
local fstype = system.get_fs_type(dir.name)
dir.force_rescan = (fstype == "nfs" or fstype == "fuse")
if not dir.force_rescan then
local watch_err
dir.watch_id, watch_err = system.watch_dir(dir.name)
if not dir.watch_id then
core.log("Watch directory %s: %s", dir.name, watch_err)
dir.force_rescan = true
end
end
local t, complete, entries_count = get_directory_files(dir, dir.name, "", {}, 0, timed_max_files_pred)
local t, complete, entries_count = get_directory_files(dir, dir.name, "", {}, nil, 0, timed_max_files_pred)
-- If dir.files_limit is set to TRUE it means that:
-- * we will not read recursively all the project files and we don't index them
-- * we read only the files for the subdirectories that are opened/expanded in the
-- TreeView
-- * we add a subdirectory watch only to the directories that are opened/expanded
-- * we set the values in the shown_subdir table
--
-- If dir.files_limit is set to FALSE it means that:
-- * we will read recursively all the project files and we index them
-- * all the list of project files is always complete and kept updated when
-- changes happen on the disk
-- * all the subdirectories at any depth must have a watch using system.watch_dir_add
-- * we DO NOT set the values in the shown_subdir table
--
-- * If force_rescan is set to TRUE no watch are used in any case.
if not complete then
dir.slow_filesystem = not complete and (entries_count <= config.max_project_files)
dir.files_limit = true
if not dir.force_rescan then
-- Watch non-recursively on Linux only.
-- The reason is recursively watching with dmon on linux
-- doesn't work on very large directories.
dir.watch_id = system.watch_dir(dir.name, PLATFORM ~= "Linux")
end
if core.status_view then -- May be not yet initialized.
show_max_files_warning(dir)
end
else
if not dir.force_rescan then
dir.watch_id = system.watch_dir(dir.name, true)
end
end
dir.files = t
if dir.force_rescan then
add_dir_scan_thread(dir)
else
if not dir.files_limit then
folder_add_subdirs_watch(dir)
end
core.dir_rescan_add_job(dir, ".")
end
end
@@ -350,13 +480,73 @@ function core.add_project_directory(path)
core.project_files = dir.files
end
core.redraw = true
return dir
end
-- The function below is needed to reload the project directories
-- when the project's module changes.
local function rescan_project_directories()
local save_project_dirs = {}
local n = #core.project_directories
for i = 1, n do
local dir = core.project_directories[i]
save_project_dirs[i] = {name = dir.name, shown_subdir = dir.shown_subdir}
end
core.close_current_project() -- ensure we unwatch directories
core.project_directories = {}
for i = 1, n do -- add again the directories in the project
local dir = core.add_project_directory(save_project_dirs[i].name)
if dir.files_limit then
-- We need to sort the list of shown subdirectories so that higher level
-- directories are populated first. We use the function system.path_compare
-- because it order the entries in the appropriate order.
-- TODO: we may consider storing the table shown_subdir as a sorted table
-- since the beginning.
local subdir_list = {}
for subdir in pairs(save_project_dirs[i].shown_subdir) do
table.insert(subdir_list, subdir)
end
table.sort(subdir_list, function(a, b) return system.path_compare(a, "dir", b, "dir") end)
for _, subdir in ipairs(subdir_list) do
local show = save_project_dirs[i].shown_subdir[subdir]
for j = 1, #dir.files do
if dir.files[j].filename == subdir then
-- The instructions below match when happens in TreeView:on_mouse_pressed.
-- We perform the operations only once iff the subdir is in dir.files.
-- In theory set_show below may fail and return false but is it is listed
-- there it means it succeeded before so we are optimistically assume it
-- will not fail for the sake of simplicity.
core.project_subdir_set_show(dir, subdir, show)
core.update_project_subdir(dir, subdir, show)
break
end
end
end
end
end
end
function core.project_dir_by_name(name)
for i = 1, #core.project_directories do
if core.project_directories[i].name == name then
return core.project_directories[i]
end
end
end
function core.update_project_subdir(dir, filename, expanded)
assert(dir.files_limit, "function should be called only when directory is in files limit mode")
local index, n, file = project_subdir_bounds(dir, filename)
if index then
local new_files = expanded and get_directory_files(dir, dir.name, PATHSEP .. filename, {}, 0, core.project_subdir_is_shown) or {}
local new_files = expanded and get_directory_files(dir, dir.name, PATHSEP .. filename, {}, nil, 0, core.project_subdir_is_shown) or {}
-- ASSUMPTION: core.update_project_subdir is called only when dir.files_limit is true
-- NOTE: we may add new directories below but we do not need to call
-- system.watch_dir_add because the current function is called only
-- in dir.files_limit mode and in this latter case we don't need to
-- add watch to new, unfolded, subdirectories.
files_list_replace(dir.files, index, n, new_files)
dir.is_dirty = true
return true
@@ -452,6 +642,21 @@ local function project_scan_remove_file(dir, filepath)
fileinfo.type = filetype
local index, match = file_search(dir.files, fileinfo)
if match then
if filetype == "dir" then
-- If the directory is a symlink it may get deleted and we will
-- never get dirmonitor events for the removal the files it contains.
-- We proceed to remove all the files that belong to the directory.
local _, n_subdir = project_subdir_bounds(dir, filepath)
files_list_replace(dir.files, index, n_subdir, {}, {
remove= function(fileinfo)
if fileinfo.type == "dir" then
system.watch_dir_rm(dir.watch_id, dir.name .. PATHSEP .. filepath)
end
end})
if dir.files_limit then
dir.shown_subdir[filepath] = nil
end
end
table.remove(dir.files, index)
dir.is_dirty = true
return
@@ -461,13 +666,18 @@ end
local function project_scan_add_file(dir, filepath)
for fragment in string.gmatch(filepath, "([^/\\]+)") do
if common.match_pattern(fragment, config.ignore_files) then
return
end
end
local fileinfo = get_project_file_info(dir.name, PATHSEP .. filepath)
local ignore = compile_ignore_files()
local fileinfo = get_project_file_info(dir.name, PATHSEP .. filepath, ignore)
if fileinfo then
-- on Windows and MacOS we can get events from directories we are not following:
-- check if each parent directories pass the ignore_files rules.
repeat
filepath = common.dirname(filepath)
local parent_info = filepath and get_project_file_info(dir.name, PATHSEP .. filepath, ignore)
if filepath and not parent_info then
return -- parent directory does match ignore_files rules: stop there
end
until not parent_info
project_scan_add_entry(dir, fileinfo)
end
end
@@ -548,6 +758,48 @@ local style = require "core.style"
end
function core.write_init_project_module(init_filename)
local init_file = io.open(init_filename, "w")
if not init_file then error("cannot create file: \"" .. init_filename .. "\"") end
init_file:write([[
-- Put project's module settings here.
-- This module will be loaded when opening a project, after the user module
-- configuration.
-- It will be automatically reloaded when saved.
local config = require "core.config"
-- you can add some patterns to ignore files within the project
-- config.ignore_files = {"^%.", <some-patterns>}
-- Patterns are normally applied to the file's or directory's name, without
-- its path. See below about how to apply filters on a path.
--
-- Here some examples:
--
-- "^%." match any file of directory whose basename begins with a dot.
--
-- When there is an '/' or a '/$' at the end the pattern it will only match
-- directories. When using such a pattern a final '/' will be added to the name
-- of any directory entry before checking if it matches.
--
-- "^%.git/" matches any directory named ".git" anywhere in the project.
--
-- If a "/" appears anywhere in the pattern except if it appears at the end or
-- is immediately followed by a '$' then the pattern will be applied to the full
-- path of the file or directory. An initial "/" will be prepended to the file's
-- or directory's path to indicate the project's root.
--
-- "^/node_modules/" will match a directory named "node_modules" at the project's root.
-- "^/build.*/" match any top level directory whose name begins with "build"
-- "^/subprojects/.+/" match any directory inside a top-level folder named "subprojects".
-- You may activate some plugins on a pre-project base to override the user's settings.
-- config.plugins.trimwitespace = true
]])
init_file:close()
end
function core.load_user_directory()
return core.try(function()
@@ -577,15 +829,25 @@ function core.remove_project_directory(path)
return false
end
local function reload_on_user_module_save()
local function configure_borderless_window()
system.set_window_bordered(not config.borderless)
core.title_view:configure_hit_test(config.borderless)
core.title_view.visible = config.borderless
end
local function add_config_files_hooks()
-- auto-realod style when user's module is saved by overriding Doc:Save()
local doc_save = Doc.save
local user_filename = system.absolute_path(USERDIR .. PATHSEP .. "init.lua")
function Doc:save(filename, abs_filename)
local module_filename = system.absolute_path(".lite_project.lua")
doc_save(self, filename, abs_filename)
if self.abs_filename == user_filename then
core.reload_module("core.style")
core.load_user_directory()
if self.abs_filename == user_filename or self.abs_filename == module_filename then
reload_customizations()
rescan_project_directories()
configure_borderless_window()
end
end
end
@@ -656,6 +918,8 @@ function core.init()
core.blink_timer = core.blink_start
local project_dir_abs = system.absolute_path(project_dir)
-- We prevent set_project_dir below to effectively add and scan the directory becaese tha
-- project module and its ignore files is not yet loaded.
local set_project_ok = project_dir_abs and core.set_project_dir(project_dir_abs)
if set_project_ok then
if project_dir_explicit then
@@ -702,6 +966,9 @@ function core.init()
end
local got_project_error = not core.load_project_module()
-- We add the project directory now because the project's module is loaded.
core.add_project_directory(project_dir_abs)
-- We assume we have just a single project directory here. Now that StatusView
-- is there show max files warning if needed.
if core.project_directories[1].files_limit then
@@ -720,9 +987,7 @@ function core.init()
command.perform("core:open-log")
end
system.set_window_bordered(not config.borderless)
core.title_view:configure_hit_test(config.borderless)
core.title_view.visible = config.borderless
configure_borderless_window()
if #plugins_refuse_list.userdir.plugins > 0 or #plugins_refuse_list.datadir.plugins > 0 then
local opt = {
@@ -746,7 +1011,7 @@ function core.init()
end)
end
reload_on_user_module_save()
add_config_files_hooks()
end
+50 -12
View File
@@ -16,6 +16,7 @@ local NagView = View:extend()
function NagView:new()
NagView.super.new(self)
self.size.y = 0
self.show_height = 0
self.force_focus = false
self.queue = {}
end
@@ -50,16 +51,16 @@ function NagView:update()
NagView.super.update(self)
if core.active_view == self and self.title then
self:move_towards(self.size, "y", self:get_target_height())
self:move_towards(self, "show_height", self:get_target_height())
self:move_towards(self, "underline_progress", 1)
else
self:move_towards(self.size, "y", 0)
self:move_towards(self, "show_height", 0)
end
end
function NagView:draw_overlay()
function NagView:dim_window_content()
local ox, oy = self:get_content_offset()
oy = oy + self.size.y
oy = oy + self.show_height
local w, h = core.root_view.size.x, core.root_view.size.y - oy
core.root_view:defer_draw(function()
renderer.draw_rect(ox, oy, w, h, style.nagbar_dim)
@@ -81,7 +82,7 @@ function NagView:each_option()
bh = self:get_buttons_height()
ox,oy = self:get_content_offset()
ox = ox + self.size.x
oy = oy + self.size.y - bh - style.padding.y
oy = oy + self.show_height - bh - style.padding.y
for i = #self.options, 1, -1 do
opt = self.options[i]
@@ -103,13 +104,38 @@ function NagView:on_mouse_moved(mx, my, ...)
end
end
-- Used to store saved value for RootView.on_view_mouse_pressed
local on_view_mouse_pressed
local function capture_mouse_pressed(nag_view)
-- RootView is loaded locally to avoid NagView and RootView being
-- mutually recursive
local RootView = require "core.rootview"
on_view_mouse_pressed = RootView.on_view_mouse_pressed
RootView.on_view_mouse_pressed = function(button, x, y, clicks)
local handled = NagView.on_mouse_pressed(nag_view, button, x, y, clicks)
return handled or on_view_mouse_pressed(button, x, y, clicks)
end
end
local function release_mouse_pressed()
local RootView = require "core.rootview"
if on_view_mouse_pressed then
RootView.on_view_mouse_pressed = on_view_mouse_pressed
on_view_mouse_pressed = nil
end
end
function NagView:on_mouse_pressed(button, mx, my, clicks)
if NagView.super.on_mouse_pressed(self, button, mx, my, clicks) then return end
if NagView.super.on_mouse_pressed(self, button, mx, my, clicks) then return true end
for i, _, x,y,w,h in self:each_option() do
if mx >= x and my >= y and mx < x + w and my < y + h then
self:change_hovered(i)
command.perform "dialog:select"
break
return true
end
end
end
@@ -123,19 +149,21 @@ function NagView:on_text_input(text)
end
function NagView:draw()
if self.size.y <= 0 or not self.title then return end
local function draw_nagview_message(self)
if self.show_height <= 0 or not self.title then return end
self:draw_overlay()
self:draw_background(style.nagbar)
self:dim_window_content()
-- draw message's background
local ox, oy = self:get_content_offset()
renderer.draw_rect(ox, oy, self.size.x, self.show_height, style.nagbar)
ox = ox + style.padding.x
-- if there are other items, show it
if #self.queue > 0 then
local str = string.format("[%d]", #self.queue)
ox = common.draw_text(style.font, style.nagbar_text, str, "left", ox, oy, self.size.x, self.size.y)
ox = common.draw_text(style.font, style.nagbar_text, str, "left", ox, oy, self.size.x, self.show_height)
ox = ox + style.padding.x
end
@@ -170,6 +198,10 @@ function NagView:draw()
end
end
function NagView:draw()
core.root_view:defer_draw(draw_nagview_message, self)
end
function NagView:get_message_height()
local h = 0
for str in string.gmatch(self.message, "(.-)\n") do
@@ -195,6 +227,12 @@ function NagView:next()
self.force_focus = self.message ~= nil
core.set_active_view(self.message ~= nil and self or
core.next_active_view or core.last_active_view)
if self.message ~= nil and self then
-- We add a hook to manage all the mouse_pressed events.
capture_mouse_pressed(self)
else
release_mouse_pressed()
end
end
function NagView:show(title, message, options, on_select)