commit
0a70b13a73
50
changelog.md
50
changelog.md
|
@ -1,5 +1,55 @@
|
|||
This files document the changes done in Lite XL for each release.
|
||||
|
||||
### 2.0.5
|
||||
|
||||
Revamp the project's user module so that modifications are immediately applied.
|
||||
|
||||
Add a mechanism to ignore files or directory based on their project's path.
|
||||
The new mechanism is backward compatible.*
|
||||
|
||||
Essentially there are two mechanisms:
|
||||
|
||||
- if a '/' or a '/$' appear at the end of the pattern it will match only directories
|
||||
- if a '/' appears anywhere in the pattern except at the end the pattern will be
|
||||
applied to the path
|
||||
|
||||
In the first case, when the pattern corresponds to a directory, a '/' will be
|
||||
appended to the name of each directory before checking the pattern.
|
||||
|
||||
In the second case, when the pattern corresponds to a path, the complete path of
|
||||
the file or directory will be used with an initial '/' added to the path.
|
||||
|
||||
Fix several problems with the directory monitoring library.
|
||||
Now the application should no longer assert when some related system call fails
|
||||
and we fallback to rescan when an error happens.
|
||||
On linux no longer use the recursive monitoring which was a source of problem.
|
||||
|
||||
Directory monitoring is now aware of symlinks and treat them appropriately.
|
||||
|
||||
Fix problem when encountering special files type on linux.
|
||||
|
||||
Improve directory monitoring so that the related thread actually waits without using
|
||||
any CPU time when there are no events.
|
||||
|
||||
Improve the suggestion when changing project folder or opening a new one.
|
||||
Now the previously used directory are suggested but if the path is changed the
|
||||
actual existing directories that match the pattern are suggested.
|
||||
In addition always use the text entered in the command view even if a suggested entry
|
||||
is highlighted.
|
||||
|
||||
The NagView warning window now no longer moves the document content.
|
||||
|
||||
### 2.0.4
|
||||
|
||||
Fix some bugs related to newly introduced directory monitoring using the dmon library.
|
||||
|
||||
Fix a problem with plain text search using Lua patterns by error.
|
||||
|
||||
Fix a problem with visualization of UTF-8 characters that caused garbage characters
|
||||
visualization.
|
||||
|
||||
Other fixes and improvements contributed by @Guldoman.
|
||||
|
||||
### 2.0.3
|
||||
|
||||
Replace periodic rescan of project folder with a notification based system using the
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -45,6 +45,7 @@ function TreeView:new()
|
|||
|
||||
self.item_icon_width = 0
|
||||
self.item_text_spacing = 0
|
||||
|
||||
self:add_core_hooks()
|
||||
end
|
||||
|
||||
|
@ -95,7 +96,7 @@ function TreeView:get_cached(dir, item, dirname)
|
|||
end
|
||||
t.name = basename
|
||||
t.type = item.type
|
||||
t.dir = dir -- points to top level "dir" item
|
||||
t.dir_name = dir.name -- points to top level "dir" item
|
||||
dir_cache[cache_name] = t
|
||||
end
|
||||
return t
|
||||
|
@ -233,11 +234,14 @@ function TreeView:on_mouse_pressed(button, x, y, clicks)
|
|||
if keymap.modkeys["ctrl"] and button == "left" then
|
||||
create_directory_in(hovered_item)
|
||||
else
|
||||
hovered_item.expanded = not hovered_item.expanded
|
||||
if hovered_item.dir.files_limit then
|
||||
core.update_project_subdir(hovered_item.dir, hovered_item.filename, hovered_item.expanded)
|
||||
core.project_subdir_set_show(hovered_item.dir, hovered_item.filename, hovered_item.expanded)
|
||||
local hovered_dir = core.project_dir_by_name(hovered_item.dir_name)
|
||||
if hovered_dir and hovered_dir.files_limit then
|
||||
if not core.project_subdir_set_show(hovered_dir, hovered_item.filename, not hovered_item.expanded) then
|
||||
return
|
||||
end
|
||||
core.update_project_subdir(hovered_dir, hovered_item.filename, not hovered_item.expanded)
|
||||
end
|
||||
hovered_item.expanded = not hovered_item.expanded
|
||||
end
|
||||
else
|
||||
core.try(function()
|
||||
|
@ -451,6 +455,12 @@ function RootView:draw(...)
|
|||
menu:draw()
|
||||
end
|
||||
|
||||
local on_quit_project = core.on_quit_project
|
||||
function core.on_quit_project()
|
||||
view.cache = {}
|
||||
on_quit_project()
|
||||
end
|
||||
|
||||
local function is_project_folder(path)
|
||||
return core.project_dir == path
|
||||
end
|
||||
|
|
208
lib/dmon/dmon.h
208
lib/dmon/dmon.h
|
@ -44,9 +44,6 @@
|
|||
// DMON_ASSERT:
|
||||
// define this to provide your own assert
|
||||
// default is 'assert'
|
||||
// DMON_LOG_ERROR:
|
||||
// define this to provide your own logging mechanism
|
||||
// default implementation logs to stdout and breaks the program
|
||||
// DMON_LOG_DEBUG
|
||||
// define this to provide your own extra debug logging mechanism
|
||||
// default implementation logs to stdout in DEBUG and does nothing in other builds
|
||||
|
@ -104,10 +101,22 @@ typedef enum dmon_action_t {
|
|||
DMON_ACTION_MOVE
|
||||
} dmon_action;
|
||||
|
||||
typedef enum dmon_error_enum {
|
||||
DMON_SUCCESS = 0,
|
||||
DMON_ERROR_WATCH_DIR,
|
||||
DMON_ERROR_OPEN_DIR,
|
||||
DMON_ERROR_MONITOR_FAIL,
|
||||
DMON_ERROR_UNSUPPORTED_SYMLINK,
|
||||
DMON_ERROR_SUBDIR_LOCATION,
|
||||
DMON_ERROR_END
|
||||
} dmon_error;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
DMON_API_DECL const char *dmon_error_str(dmon_error err);
|
||||
|
||||
DMON_API_DECL void dmon_init(void);
|
||||
DMON_API_DECL void dmon_deinit(void);
|
||||
|
||||
|
@ -115,7 +124,7 @@ DMON_API_DECL dmon_watch_id dmon_watch(const char* rootdir,
|
|||
void (*watch_cb)(dmon_watch_id watch_id, dmon_action action,
|
||||
const char* rootdir, const char* filepath,
|
||||
const char* oldfilepath, void* user),
|
||||
uint32_t flags, void* user_data);
|
||||
uint32_t flags, void* user_data, dmon_error *error_code);
|
||||
DMON_API_DECL void dmon_unwatch(dmon_watch_id id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@ -150,10 +159,6 @@ DMON_API_DECL void dmon_unwatch(dmon_watch_id id);
|
|||
# define NOMINMAX
|
||||
# endif
|
||||
# include <windows.h>
|
||||
# include <intrin.h>
|
||||
# ifdef _MSC_VER
|
||||
# pragma intrinsic(_InterlockedExchange)
|
||||
# endif
|
||||
#elif DMON_OS_LINUX
|
||||
# ifndef __USE_MISC
|
||||
# define __USE_MISC
|
||||
|
@ -169,6 +174,9 @@ DMON_API_DECL void dmon_unwatch(dmon_watch_id id);
|
|||
# include <time.h>
|
||||
# include <unistd.h>
|
||||
# include <stdlib.h>
|
||||
/* Recursive removed for Lite XL when using inotify. */
|
||||
# define LITE_XL_DISABLE_INOTIFY_RECURSIVE
|
||||
# define DMON_LOG_DEBUG(s)
|
||||
#elif DMON_OS_MACOS
|
||||
# include <pthread.h>
|
||||
# include <CoreServices/CoreServices.h>
|
||||
|
@ -189,11 +197,6 @@ DMON_API_DECL void dmon_unwatch(dmon_watch_id id);
|
|||
# define DMON_ASSERT(e) assert(e)
|
||||
#endif
|
||||
|
||||
#ifndef DMON_LOG_ERROR
|
||||
# include <stdio.h>
|
||||
# define DMON_LOG_ERROR(s) do { puts(s); DMON_ASSERT(0); } while(0)
|
||||
#endif
|
||||
|
||||
#ifndef DMON_LOG_DEBUG
|
||||
# ifndef NDEBUG
|
||||
# include <stdio.h>
|
||||
|
@ -223,10 +226,6 @@ DMON_API_DECL void dmon_unwatch(dmon_watch_id id);
|
|||
|
||||
#include <string.h>
|
||||
|
||||
#ifndef _DMON_LOG_ERRORF
|
||||
# define _DMON_LOG_ERRORF(str, ...) do { char msg[512]; snprintf(msg, sizeof(msg), str, __VA_ARGS__); DMON_LOG_ERROR(msg); } while(0);
|
||||
#endif
|
||||
|
||||
#ifndef _DMON_LOG_DEBUGF
|
||||
# define _DMON_LOG_DEBUGF(str, ...) do { char msg[512]; snprintf(msg, sizeof(msg), str, __VA_ARGS__); DMON_LOG_DEBUG(msg); } while(0);
|
||||
#endif
|
||||
|
@ -356,6 +355,20 @@ static void * stb__sbgrowf(void *arr, int increment, int itemsize)
|
|||
// watcher callback (same as dmon.h's decleration)
|
||||
typedef void (dmon__watch_cb)(dmon_watch_id, dmon_action, const char*, const char*, const char*, void*);
|
||||
|
||||
static const char *dmon__errors[] = {
|
||||
"Success",
|
||||
"Error watching directory",
|
||||
"Error opening directory",
|
||||
"Error enabling monitoring",
|
||||
"Error support for symlink disabled",
|
||||
"Error not a subdirectory",
|
||||
};
|
||||
|
||||
DMON_API_IMPL const char *dmon_error_str(dmon_error err) {
|
||||
DMON_ASSERT(err >= 0 && err < DMON_ERROR_END);
|
||||
return dmon__errors[(int) err];
|
||||
}
|
||||
|
||||
#if DMON_OS_WINDOWS
|
||||
// IOCP (windows)
|
||||
#ifdef UNICODE
|
||||
|
@ -389,9 +402,11 @@ typedef struct dmon__state {
|
|||
dmon__watch_state watches[DMON_MAX_WATCHES];
|
||||
HANDLE thread_handle;
|
||||
CRITICAL_SECTION mutex;
|
||||
volatile LONG modify_watches;
|
||||
volatile int modify_watches;
|
||||
CRITICAL_SECTION modify_watches_mutex;
|
||||
dmon__win32_event* events;
|
||||
bool quit;
|
||||
HANDLE wake_event;
|
||||
} dmon__state;
|
||||
|
||||
static bool _dmon_init;
|
||||
|
@ -474,17 +489,25 @@ _DMON_PRIVATE void dmon__win32_process_events(void)
|
|||
stb_sb_reset(_dmon.events);
|
||||
}
|
||||
|
||||
static int dmon__safe_get_modify_watches() {
|
||||
EnterCriticalSection(&_dmon.modify_watches_mutex);
|
||||
const int value = _dmon.modify_watches;
|
||||
LeaveCriticalSection(&_dmon.modify_watches_mutex);
|
||||
return value;
|
||||
}
|
||||
|
||||
_DMON_PRIVATE DWORD WINAPI dmon__thread(LPVOID arg)
|
||||
{
|
||||
_DMON_UNUSED(arg);
|
||||
HANDLE wait_handles[DMON_MAX_WATCHES];
|
||||
HANDLE wait_handles[DMON_MAX_WATCHES + 1];
|
||||
|
||||
SYSTEMTIME starttm;
|
||||
GetSystemTime(&starttm);
|
||||
uint64_t msecs_elapsed = 0;
|
||||
|
||||
while (!_dmon.quit) {
|
||||
if (_dmon.modify_watches || !TryEnterCriticalSection(&_dmon.mutex)) {
|
||||
if (dmon__safe_get_modify_watches() ||
|
||||
!TryEnterCriticalSection(&_dmon.mutex)) {
|
||||
Sleep(10);
|
||||
continue;
|
||||
}
|
||||
|
@ -500,14 +523,17 @@ _DMON_PRIVATE DWORD WINAPI dmon__thread(LPVOID arg)
|
|||
wait_handles[i] = watch->overlapped.hEvent;
|
||||
}
|
||||
|
||||
DWORD wait_result = WaitForMultipleObjects(_dmon.num_watches, wait_handles, FALSE, 10);
|
||||
DMON_ASSERT(wait_result != WAIT_FAILED);
|
||||
if (wait_result != WAIT_TIMEOUT) {
|
||||
const int n = _dmon.num_watches;
|
||||
wait_handles[n] = _dmon.wake_event;
|
||||
const int n_pending = stb_sb_count(_dmon.events);
|
||||
DWORD wait_result = WaitForMultipleObjects(n + 1, wait_handles, FALSE, n_pending > 0 ? 10 : INFINITE);
|
||||
// NOTE: maybe we should check for WAIT_ABANDONED_<n> values if that can happen.
|
||||
if (wait_result >= WAIT_OBJECT_0 && wait_result < WAIT_OBJECT_0 + n) {
|
||||
dmon__watch_state* watch = &_dmon.watches[wait_result - WAIT_OBJECT_0];
|
||||
DMON_ASSERT(HasOverlappedIoCompleted(&watch->overlapped));
|
||||
|
||||
DWORD bytes;
|
||||
if (GetOverlappedResult(watch->dir_handle, &watch->overlapped, &bytes, FALSE)) {
|
||||
if (HasOverlappedIoCompleted(&watch->overlapped) &&
|
||||
GetOverlappedResult(watch->dir_handle, &watch->overlapped, &bytes, FALSE)) {
|
||||
char filepath[DMON_MAX_PATH];
|
||||
PFILE_NOTIFY_INFORMATION notify;
|
||||
size_t offset = 0;
|
||||
|
@ -566,18 +592,37 @@ DMON_API_IMPL void dmon_init(void)
|
|||
{
|
||||
DMON_ASSERT(!_dmon_init);
|
||||
InitializeCriticalSection(&_dmon.mutex);
|
||||
InitializeCriticalSection(&_dmon.modify_watches_mutex);
|
||||
|
||||
_dmon.thread_handle =
|
||||
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)dmon__thread, NULL, 0, NULL);
|
||||
_dmon.wake_event = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
DMON_ASSERT(_dmon.thread_handle);
|
||||
_dmon_init = true;
|
||||
}
|
||||
|
||||
static void dmon__enter_critical_wakeup(void) {
|
||||
EnterCriticalSection(&_dmon.modify_watches_mutex);
|
||||
_dmon.modify_watches = 1;
|
||||
if (TryEnterCriticalSection(&_dmon.mutex) == 0) {
|
||||
SetEvent(_dmon.wake_event);
|
||||
EnterCriticalSection(&_dmon.mutex);
|
||||
}
|
||||
LeaveCriticalSection(&_dmon.modify_watches_mutex);
|
||||
}
|
||||
|
||||
static void dmon__leave_critical_wakeup(void) {
|
||||
EnterCriticalSection(&_dmon.modify_watches_mutex);
|
||||
_dmon.modify_watches = 0;
|
||||
LeaveCriticalSection(&_dmon.modify_watches_mutex);
|
||||
LeaveCriticalSection(&_dmon.mutex);
|
||||
}
|
||||
|
||||
DMON_API_IMPL void dmon_deinit(void)
|
||||
{
|
||||
DMON_ASSERT(_dmon_init);
|
||||
_dmon.quit = true;
|
||||
dmon__enter_critical_wakeup();
|
||||
if (_dmon.thread_handle != INVALID_HANDLE_VALUE) {
|
||||
WaitForSingleObject(_dmon.thread_handle, INFINITE);
|
||||
CloseHandle(_dmon.thread_handle);
|
||||
|
@ -587,7 +632,9 @@ DMON_API_IMPL void dmon_deinit(void)
|
|||
dmon__unwatch(&_dmon.watches[i]);
|
||||
}
|
||||
|
||||
dmon__leave_critical_wakeup();
|
||||
DeleteCriticalSection(&_dmon.mutex);
|
||||
DeleteCriticalSection(&_dmon.modify_watches_mutex);
|
||||
stb_sb_free(_dmon.events);
|
||||
_dmon_init = false;
|
||||
}
|
||||
|
@ -596,13 +643,12 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir,
|
|||
void (*watch_cb)(dmon_watch_id watch_id, dmon_action action,
|
||||
const char* dirname, const char* filename,
|
||||
const char* oldname, void* user),
|
||||
uint32_t flags, void* user_data)
|
||||
uint32_t flags, void* user_data, dmon_error *error_code)
|
||||
{
|
||||
DMON_ASSERT(watch_cb);
|
||||
DMON_ASSERT(rootdir && rootdir[0]);
|
||||
|
||||
_InterlockedExchange(&_dmon.modify_watches, 1);
|
||||
EnterCriticalSection(&_dmon.mutex);
|
||||
dmon__enter_critical_wakeup();
|
||||
|
||||
DMON_ASSERT(_dmon.num_watches < DMON_MAX_WATCHES);
|
||||
|
||||
|
@ -630,24 +676,21 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir,
|
|||
FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME |
|
||||
FILE_NOTIFY_CHANGE_SIZE;
|
||||
watch->overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
DMON_ASSERT(watch->overlapped.hEvent != INVALID_HANDLE_VALUE);
|
||||
|
||||
if (!dmon__refresh_watch(watch)) {
|
||||
if (watch->overlapped.hEvent == INVALID_HANDLE_VALUE ||
|
||||
!dmon__refresh_watch(watch)) {
|
||||
dmon__unwatch(watch);
|
||||
DMON_LOG_ERROR("ReadDirectoryChanges failed");
|
||||
LeaveCriticalSection(&_dmon.mutex);
|
||||
_InterlockedExchange(&_dmon.modify_watches, 0);
|
||||
*error_code = DMON_ERROR_WATCH_DIR;
|
||||
dmon__leave_critical_wakeup();
|
||||
return dmon__make_id(0);
|
||||
}
|
||||
} else {
|
||||
_DMON_LOG_ERRORF("Could not open: %s", rootdir);
|
||||
LeaveCriticalSection(&_dmon.mutex);
|
||||
_InterlockedExchange(&_dmon.modify_watches, 0);
|
||||
*error_code = DMON_ERROR_OPEN_DIR;
|
||||
dmon__leave_critical_wakeup();
|
||||
return dmon__make_id(0);
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&_dmon.mutex);
|
||||
_InterlockedExchange(&_dmon.modify_watches, 0);
|
||||
dmon__leave_critical_wakeup();
|
||||
return dmon__make_id(id);
|
||||
}
|
||||
|
||||
|
@ -655,8 +698,7 @@ DMON_API_IMPL void dmon_unwatch(dmon_watch_id id)
|
|||
{
|
||||
DMON_ASSERT(id.id > 0);
|
||||
|
||||
_InterlockedExchange(&_dmon.modify_watches, 1);
|
||||
EnterCriticalSection(&_dmon.mutex);
|
||||
dmon__enter_critical_wakeup();
|
||||
|
||||
int index = id.id - 1;
|
||||
DMON_ASSERT(index < _dmon.num_watches);
|
||||
|
@ -667,8 +709,7 @@ DMON_API_IMPL void dmon_unwatch(dmon_watch_id id)
|
|||
}
|
||||
--_dmon.num_watches;
|
||||
|
||||
LeaveCriticalSection(&_dmon.mutex);
|
||||
_InterlockedExchange(&_dmon.modify_watches, 0);
|
||||
dmon__leave_critical_wakeup();
|
||||
}
|
||||
|
||||
#elif DMON_OS_LINUX
|
||||
|
@ -704,12 +745,21 @@ typedef struct dmon__state {
|
|||
int num_watches;
|
||||
pthread_t thread_handle;
|
||||
pthread_mutex_t mutex;
|
||||
volatile int wait_flag;
|
||||
pthread_mutex_t wait_flag_mutex;
|
||||
int wake_event_pipe[2];
|
||||
bool quit;
|
||||
} dmon__state;
|
||||
|
||||
static bool _dmon_init;
|
||||
static dmon__state _dmon;
|
||||
|
||||
/* Implementation of recursive monitoring was removed on Linux for the Lite XL
|
||||
* application. It is never used with recent version of Lite XL starting from 2.0.5
|
||||
* and recursive monitoring with inotify was always problematic and half-broken.
|
||||
* Do not cover the new calling signature with error_code because not used by
|
||||
* Lite XL. */
|
||||
#ifndef LITE_XL_DISABLE_INOTIFY_RECURSIVE
|
||||
_DMON_PRIVATE void dmon__watch_recursive(const char* dirname, int fd, uint32_t mask,
|
||||
bool followlinks, dmon__watch_state* watch)
|
||||
{
|
||||
|
@ -764,6 +814,7 @@ _DMON_PRIVATE void dmon__watch_recursive(const char* dirname, int fd, uint32_t m
|
|||
}
|
||||
closedir(dir);
|
||||
}
|
||||
#endif
|
||||
|
||||
_DMON_PRIVATE const char* dmon__find_subdir(const dmon__watch_state* watch, int wd)
|
||||
{
|
||||
|
@ -777,6 +828,7 @@ _DMON_PRIVATE const char* dmon__find_subdir(const dmon__watch_state* watch, int
|
|||
return NULL;
|
||||
}
|
||||
|
||||
#ifndef LITE_XL_DISABLE_INOTIFY_RECURSIVE
|
||||
_DMON_PRIVATE void dmon__gather_recursive(dmon__watch_state* watch, const char* dirname)
|
||||
{
|
||||
struct dirent* entry;
|
||||
|
@ -809,6 +861,7 @@ _DMON_PRIVATE void dmon__gather_recursive(dmon__watch_state* watch, const char*
|
|||
}
|
||||
closedir(dir);
|
||||
}
|
||||
#endif
|
||||
|
||||
_DMON_PRIVATE void dmon__inotify_process_events(void)
|
||||
{
|
||||
|
@ -919,6 +972,7 @@ _DMON_PRIVATE void dmon__inotify_process_events(void)
|
|||
}
|
||||
|
||||
if (ev->mask & IN_CREATE) {
|
||||
# ifndef LITE_XL_DISABLE_INOTIFY_RECURSIVE
|
||||
if (ev->mask & IN_ISDIR) {
|
||||
if (watch->watch_flags & DMON_WATCHFLAGS_RECURSIVE) {
|
||||
char watchdir[DMON_MAX_PATH];
|
||||
|
@ -948,6 +1002,7 @@ _DMON_PRIVATE void dmon__inotify_process_events(void)
|
|||
ev = &_dmon.events[i]; // gotta refresh the pointer because it may be relocated
|
||||
}
|
||||
}
|
||||
# endif
|
||||
watch->watch_cb(ev->watch_id, DMON_ACTION_CREATE, watch->rootdir, ev->filepath, NULL, watch->user_data);
|
||||
}
|
||||
else if (ev->mask & IN_MODIFY) {
|
||||
|
@ -971,6 +1026,13 @@ _DMON_PRIVATE void dmon__inotify_process_events(void)
|
|||
stb_sb_reset(_dmon.events);
|
||||
}
|
||||
|
||||
_DMON_PRIVATE int dmon__safe_get_wait_flag() {
|
||||
pthread_mutex_lock(&_dmon.wait_flag_mutex);
|
||||
const int value = _dmon.wait_flag;
|
||||
pthread_mutex_unlock(&_dmon.wait_flag_mutex);
|
||||
return value;
|
||||
}
|
||||
|
||||
static void* dmon__thread(void* arg)
|
||||
{
|
||||
_DMON_UNUSED(arg);
|
||||
|
@ -986,21 +1048,36 @@ static void* dmon__thread(void* arg)
|
|||
|
||||
while (!_dmon.quit) {
|
||||
nanosleep(&req, &rem);
|
||||
if (_dmon.num_watches == 0 || pthread_mutex_trylock(&_dmon.mutex) != 0) {
|
||||
if (_dmon.num_watches == 0 ||
|
||||
dmon__safe_get_wait_flag() ||
|
||||
pthread_mutex_trylock(&_dmon.mutex) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create read FD set
|
||||
fd_set rfds;
|
||||
FD_ZERO(&rfds);
|
||||
for (int i = 0; i < _dmon.num_watches; i++) {
|
||||
const int n = _dmon.num_watches;
|
||||
int nfds = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
dmon__watch_state* watch = &_dmon.watches[i];
|
||||
FD_SET(watch->fd, &rfds);
|
||||
if (watch->fd > nfds)
|
||||
nfds = watch->fd;
|
||||
}
|
||||
int wake_fd = _dmon.wake_event_pipe[0];
|
||||
FD_SET(wake_fd, &rfds);
|
||||
if (wake_fd > nfds)
|
||||
nfds = wake_fd;
|
||||
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = 100000;
|
||||
if (select(FD_SETSIZE, &rfds, NULL, NULL, &timeout)) {
|
||||
const int n_pending = stb_sb_count(_dmon.events);
|
||||
if (select(nfds + 1, &rfds, NULL, NULL, n_pending > 0 ? &timeout : NULL)) {
|
||||
if (FD_ISSET(wake_fd, &rfds)) {
|
||||
char read_char;
|
||||
read(wake_fd, &read_char, 1);
|
||||
}
|
||||
for (int i = 0; i < _dmon.num_watches; i++) {
|
||||
dmon__watch_state* watch = &_dmon.watches[i];
|
||||
if (FD_ISSET(watch->fd, &rfds)) {
|
||||
|
@ -1050,6 +1127,18 @@ static void* dmon__thread(void* arg)
|
|||
return 0x0;
|
||||
}
|
||||
|
||||
_DMON_PRIVATE void dmon__mutex_wakeup_lock(void) {
|
||||
pthread_mutex_lock(&_dmon.wait_flag_mutex);
|
||||
_dmon.wait_flag = 1;
|
||||
if (pthread_mutex_trylock(&_dmon.mutex) != 0) {
|
||||
char send_char = 1;
|
||||
write(_dmon.wake_event_pipe[1], &send_char, 1);
|
||||
pthread_mutex_lock(&_dmon.mutex);
|
||||
}
|
||||
_dmon.wait_flag = 0;
|
||||
pthread_mutex_unlock(&_dmon.wait_flag_mutex);
|
||||
}
|
||||
|
||||
_DMON_PRIVATE void dmon__unwatch(dmon__watch_state* watch)
|
||||
{
|
||||
close(watch->fd);
|
||||
|
@ -1063,6 +1152,9 @@ DMON_API_IMPL void dmon_init(void)
|
|||
DMON_ASSERT(!_dmon_init);
|
||||
pthread_mutex_init(&_dmon.mutex, NULL);
|
||||
|
||||
_dmon.wait_flag = 0;
|
||||
int ret_pipe = pipe(_dmon.wake_event_pipe);
|
||||
DMON_ASSERT(ret_pipe == 0);
|
||||
int r = pthread_create(&_dmon.thread_handle, NULL, dmon__thread, NULL);
|
||||
_DMON_UNUSED(r);
|
||||
DMON_ASSERT(r == 0 && "pthread_create failed");
|
||||
|
@ -1073,12 +1165,14 @@ DMON_API_IMPL void dmon_deinit(void)
|
|||
{
|
||||
DMON_ASSERT(_dmon_init);
|
||||
_dmon.quit = true;
|
||||
dmon__mutex_wakeup_lock();
|
||||
pthread_join(_dmon.thread_handle, NULL);
|
||||
|
||||
for (int i = 0; i < _dmon.num_watches; i++) {
|
||||
dmon__unwatch(&_dmon.watches[i]);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
pthread_mutex_destroy(&_dmon.mutex);
|
||||
stb_sb_free(_dmon.events);
|
||||
_dmon_init = false;
|
||||
|
@ -1088,12 +1182,12 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir,
|
|||
void (*watch_cb)(dmon_watch_id watch_id, dmon_action action,
|
||||
const char* dirname, const char* filename,
|
||||
const char* oldname, void* user),
|
||||
uint32_t flags, void* user_data)
|
||||
uint32_t flags, void* user_data, dmon_error *error_code)
|
||||
{
|
||||
DMON_ASSERT(watch_cb);
|
||||
DMON_ASSERT(rootdir && rootdir[0]);
|
||||
|
||||
pthread_mutex_lock(&_dmon.mutex);
|
||||
dmon__mutex_wakeup_lock();
|
||||
|
||||
DMON_ASSERT(_dmon.num_watches < DMON_MAX_WATCHES);
|
||||
|
||||
|
@ -1107,7 +1201,7 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir,
|
|||
struct stat root_st;
|
||||
if (stat(rootdir, &root_st) != 0 || !S_ISDIR(root_st.st_mode) ||
|
||||
(root_st.st_mode & S_IRUSR) != S_IRUSR) {
|
||||
_DMON_LOG_ERRORF("Could not open/read directory: %s", rootdir);
|
||||
*error_code = DMON_ERROR_OPEN_DIR;
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return dmon__make_id(0);
|
||||
}
|
||||
|
@ -1122,8 +1216,7 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir,
|
|||
|
||||
dmon__strcpy(watch->rootdir, sizeof(watch->rootdir) - 1, linkpath);
|
||||
} else {
|
||||
_DMON_LOG_ERRORF("symlinks are unsupported: %s. use DMON_WATCHFLAGS_FOLLOW_SYMLINKS",
|
||||
rootdir);
|
||||
*error_code = DMON_ERROR_UNSUPPORTED_SYMLINK;
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return dmon__make_id(0);
|
||||
}
|
||||
|
@ -1140,7 +1233,7 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir,
|
|||
|
||||
watch->fd = inotify_init();
|
||||
if (watch->fd < -1) {
|
||||
DMON_LOG_ERROR("could not create inotify instance");
|
||||
*error_code = DMON_ERROR_MONITOR_FAIL;
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return dmon__make_id(0);
|
||||
}
|
||||
|
@ -1148,7 +1241,7 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir,
|
|||
uint32_t inotify_mask = IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE | IN_MODIFY;
|
||||
int wd = inotify_add_watch(watch->fd, watch->rootdir, inotify_mask);
|
||||
if (wd < 0) {
|
||||
_DMON_LOG_ERRORF("Error watching directory '%s'. (inotify_add_watch:err=%d)", watch->rootdir, errno);
|
||||
*error_code = DMON_ERROR_WATCH_DIR;
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return dmon__make_id(0);
|
||||
}
|
||||
|
@ -1158,11 +1251,12 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir,
|
|||
stb_sb_push(watch->wds, wd);
|
||||
|
||||
// recursive mode: enumarate all child directories and add them to watch
|
||||
#ifndef LITE_XL_DISABLE_INOTIFY_RECURSIVE
|
||||
if (flags & DMON_WATCHFLAGS_RECURSIVE) {
|
||||
dmon__watch_recursive(watch->rootdir, watch->fd, inotify_mask,
|
||||
(flags & DMON_WATCHFLAGS_FOLLOW_SYMLINKS) ? true : false, watch);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return dmon__make_id(id);
|
||||
|
@ -1172,7 +1266,7 @@ DMON_API_IMPL void dmon_unwatch(dmon_watch_id id)
|
|||
{
|
||||
DMON_ASSERT(id.id > 0);
|
||||
|
||||
pthread_mutex_lock(&_dmon.mutex);
|
||||
dmon__mutex_wakeup_lock();
|
||||
|
||||
int index = id.id - 1;
|
||||
DMON_ASSERT(index < _dmon.num_watches);
|
||||
|
@ -1479,7 +1573,7 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir,
|
|||
void (*watch_cb)(dmon_watch_id watch_id, dmon_action action,
|
||||
const char* dirname, const char* filename,
|
||||
const char* oldname, void* user),
|
||||
uint32_t flags, void* user_data)
|
||||
uint32_t flags, void* user_data, dmon_error *error_code)
|
||||
{
|
||||
DMON_ASSERT(watch_cb);
|
||||
DMON_ASSERT(rootdir && rootdir[0]);
|
||||
|
@ -1499,7 +1593,7 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir,
|
|||
struct stat root_st;
|
||||
if (stat(rootdir, &root_st) != 0 || !S_ISDIR(root_st.st_mode) ||
|
||||
(root_st.st_mode & S_IRUSR) != S_IRUSR) {
|
||||
_DMON_LOG_ERRORF("Could not open/read directory: %s", rootdir);
|
||||
*error_code = DMON_ERROR_OPEN_DIR;
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
__sync_lock_test_and_set(&_dmon.modify_watches, 0);
|
||||
return dmon__make_id(0);
|
||||
|
@ -1514,7 +1608,7 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir,
|
|||
|
||||
dmon__strcpy(watch->rootdir, sizeof(watch->rootdir) - 1, linkpath);
|
||||
} else {
|
||||
_DMON_LOG_ERRORF("symlinks are unsupported: %s. use DMON_WATCHFLAGS_FOLLOW_SYMLINKS", rootdir);
|
||||
*error_code = DMON_ERROR_UNSUPPORTED_SYMLINK;
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
__sync_lock_test_and_set(&_dmon.modify_watches, 0);
|
||||
return dmon__make_id(0);
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
DMON_API_DECL bool dmon_watch_add(dmon_watch_id id, const char* subdir);
|
||||
DMON_API_DECL bool dmon_watch_add(dmon_watch_id id, const char* subdir, dmon_error *error_code);
|
||||
DMON_API_DECL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@ -36,14 +36,15 @@ DMON_API_DECL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir);
|
|||
|
||||
#ifdef DMON_IMPL
|
||||
#if DMON_OS_LINUX
|
||||
DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir)
|
||||
DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir, dmon_error *error_code)
|
||||
{
|
||||
DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES);
|
||||
|
||||
bool skip_lock = pthread_self() == _dmon.thread_handle;
|
||||
|
||||
if (!skip_lock)
|
||||
pthread_mutex_lock(&_dmon.mutex);
|
||||
if (!skip_lock) {
|
||||
dmon__mutex_wakeup_lock();
|
||||
}
|
||||
|
||||
dmon__watch_state* watch = &_dmon.watches[id.id - 1];
|
||||
|
||||
|
@ -52,6 +53,8 @@ DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir)
|
|||
// else, we assume that watchdir is correct, so save it as it is
|
||||
struct stat st;
|
||||
dmon__watch_subdir subdir;
|
||||
// FIXME: check if it is a symlink and respect DMON_WATCHFLAGS_FOLLOW_SYMLINKS
|
||||
// to resolve the link.
|
||||
if (stat(watchdir, &st) == 0 && (st.st_mode & S_IFDIR)) {
|
||||
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir);
|
||||
if (strstr(subdir.rootdir, watch->rootdir) == subdir.rootdir) {
|
||||
|
@ -62,7 +65,7 @@ DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir)
|
|||
dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir);
|
||||
dmon__strcat(fullpath, sizeof(fullpath), watchdir);
|
||||
if (stat(fullpath, &st) != 0 || (st.st_mode & S_IFDIR) == 0) {
|
||||
_DMON_LOG_ERRORF("Watch directory '%s' is not valid", watchdir);
|
||||
*error_code = DMON_ERROR_UNSUPPORTED_SYMLINK;
|
||||
if (!skip_lock)
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return false;
|
||||
|
@ -79,7 +82,7 @@ DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir)
|
|||
// check that the directory is not already added
|
||||
for (int i = 0, c = stb_sb_count(watch->subdirs); i < c; i++) {
|
||||
if (strcmp(subdir.rootdir, watch->subdirs[i].rootdir) == 0) {
|
||||
_DMON_LOG_ERRORF("Error watching directory '%s', because it is already added.", watchdir);
|
||||
*error_code = DMON_ERROR_SUBDIR_LOCATION;
|
||||
if (!skip_lock)
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return false;
|
||||
|
@ -92,7 +95,7 @@ DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir)
|
|||
dmon__strcat(fullpath, sizeof(fullpath), subdir.rootdir);
|
||||
int wd = inotify_add_watch(watch->fd, fullpath, inotify_mask);
|
||||
if (wd == -1) {
|
||||
_DMON_LOG_ERRORF("Error watching directory '%s'. (inotify_add_watch:err=%d)", watchdir, errno);
|
||||
*error_code = DMON_ERROR_WATCH_DIR;
|
||||
if (!skip_lock)
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return false;
|
||||
|
@ -113,8 +116,9 @@ DMON_API_IMPL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir)
|
|||
|
||||
bool skip_lock = pthread_self() == _dmon.thread_handle;
|
||||
|
||||
if (!skip_lock)
|
||||
pthread_mutex_lock(&_dmon.mutex);
|
||||
if (!skip_lock) {
|
||||
dmon__mutex_wakeup_lock();
|
||||
}
|
||||
|
||||
dmon__watch_state* watch = &_dmon.watches[id.id - 1];
|
||||
|
||||
|
@ -137,7 +141,6 @@ DMON_API_IMPL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir)
|
|||
}
|
||||
}
|
||||
if (i >= c) {
|
||||
_DMON_LOG_ERRORF("Watch directory '%s' is not valid", watchdir);
|
||||
if (!skip_lock)
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return false;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
project('lite-xl',
|
||||
['c'],
|
||||
version : '2.0.3',
|
||||
version : '2.0.5',
|
||||
license : 'MIT',
|
||||
meson_version : '>= 0.42',
|
||||
default_options : [
|
||||
|
|
|
@ -560,6 +560,14 @@ static int f_get_file_info(lua_State *L) {
|
|||
}
|
||||
lua_setfield(L, -2, "type");
|
||||
|
||||
#if __linux__
|
||||
if (S_ISDIR(s.st_mode)) {
|
||||
if (lstat(path, &s) == 0) {
|
||||
lua_pushboolean(L, S_ISLNK(s.st_mode));
|
||||
lua_setfield(L, -2, "symlink");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -600,6 +608,11 @@ static int f_get_fs_type(lua_State *L) {
|
|||
lua_pushstring(L, "unknown");
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
static int f_return_unknown(lua_State *L) {
|
||||
lua_pushstring(L, "unknown");
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
@ -794,20 +807,47 @@ static int f_load_native_plugin(lua_State *L) {
|
|||
|
||||
static int f_watch_dir(lua_State *L) {
|
||||
const char *path = luaL_checkstring(L, 1);
|
||||
const int recursive = lua_toboolean(L, 2);
|
||||
uint32_t dmon_flags = (recursive ? DMON_WATCHFLAGS_RECURSIVE : 0);
|
||||
dmon_watch_id watch_id = dmon_watch(path, dirmonitor_watch_callback, dmon_flags, NULL);
|
||||
if (watch_id.id == 0) { luaL_error(L, "directory monitoring watch failed"); }
|
||||
/* On linux we watch non-recursively and we add/remove each sub-directory explicitly
|
||||
* using the function system.watch_dir_add/rm. On other systems we watch recursively
|
||||
* and system.watch_dir_add/rm are dummy functions that always returns true. */
|
||||
#if __linux__
|
||||
const uint32_t dmon_flags = DMON_WATCHFLAGS_FOLLOW_SYMLINKS;
|
||||
#elif __APPLE__
|
||||
const uint32_t dmon_flags = DMON_WATCHFLAGS_FOLLOW_SYMLINKS | DMON_WATCHFLAGS_RECURSIVE;
|
||||
#else
|
||||
const uint32_t dmon_flags = DMON_WATCHFLAGS_RECURSIVE;
|
||||
#endif
|
||||
dmon_error error;
|
||||
dmon_watch_id watch_id = dmon_watch(path, dirmonitor_watch_callback, dmon_flags, NULL, &error);
|
||||
if (watch_id.id == 0) {
|
||||
lua_pushnil(L);
|
||||
lua_pushstring(L, dmon_error_str(error));
|
||||
return 2;
|
||||
}
|
||||
lua_pushinteger(L, watch_id.id);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int f_unwatch_dir(lua_State *L) {
|
||||
dmon_watch_id watch_id;
|
||||
watch_id.id = luaL_checkinteger(L, 1);
|
||||
dmon_unwatch(watch_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if __linux__
|
||||
static int f_watch_dir_add(lua_State *L) {
|
||||
dmon_watch_id watch_id;
|
||||
watch_id.id = luaL_checkinteger(L, 1);
|
||||
const char *subdir = luaL_checkstring(L, 2);
|
||||
lua_pushboolean(L, dmon_watch_add(watch_id, subdir));
|
||||
dmon_error error_code;
|
||||
int success = dmon_watch_add(watch_id, subdir, &error_code);
|
||||
if (!success) {
|
||||
lua_pushboolean(L, 0);
|
||||
lua_pushstring(L, dmon_error_str(error_code));
|
||||
return 2;
|
||||
}
|
||||
lua_pushboolean(L, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -818,6 +858,11 @@ static int f_watch_dir_rm(lua_State *L) {
|
|||
lua_pushboolean(L, dmon_watch_rm(watch_id, subdir));
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
static int f_return_true(lua_State *L) {
|
||||
lua_pushboolean(L, 1);
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
|
@ -906,11 +951,16 @@ static const luaL_Reg lib[] = {
|
|||
{ "set_window_opacity", f_set_window_opacity },
|
||||
{ "load_native_plugin", f_load_native_plugin },
|
||||
{ "watch_dir", f_watch_dir },
|
||||
{ "unwatch_dir", f_unwatch_dir },
|
||||
{ "path_compare", f_path_compare },
|
||||
#if __linux__
|
||||
{ "watch_dir_add", f_watch_dir_add },
|
||||
{ "watch_dir_rm", f_watch_dir_rm },
|
||||
{ "get_fs_type", f_get_fs_type },
|
||||
#else
|
||||
{ "watch_dir_add", f_return_true },
|
||||
{ "watch_dir_rm", f_return_true },
|
||||
{ "get_fs_type", f_return_unknown },
|
||||
#endif
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue