Compare commits
2 Commits
Author | SHA1 | Date |
---|---|---|
George Sokianos | cb30b591d2 | |
George Sokianos | 9d13525af0 |
|
@ -5,7 +5,7 @@
|
|||
#
|
||||
|
||||
LiteXL_OBJ := \
|
||||
src/dirmonitor.o src/main.o src/rencache.o src/renderer.o \
|
||||
src/api/dirmonitor.o src/main.o src/rencache.o src/renderer.o \
|
||||
src/renwindow.o src/api/api.o src/api/regex.o \
|
||||
src/api/renderer.o src/api/system.o src/platform/amigaos4.o
|
||||
|
||||
|
@ -43,10 +43,10 @@ LiteXL: $(LiteXL_OBJ)
|
|||
@echo "Compiling $<"
|
||||
@$(compiler) -c $< -o $*.o $(CFLAGS) $(INCPATH) $(DFLAGS)
|
||||
|
||||
src/dirmonitor.o: src/dirmonitor.c src/platform/amigaos4.h
|
||||
src/dirmonitor.o: src/api/dirmonitor.c
|
||||
|
||||
src/main.o: src/main.c src/api/api.h src/rencache.h \
|
||||
src/renderer.h src/platform/amigaos4.h src/dirmonitor.h
|
||||
src/renderer.h src/platform/amigaos4.h
|
||||
|
||||
src/rencache.o: src/rencache.c
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ config.message_timeout = 5
|
|||
config.mouse_wheel_scroll = 50 * SCALE
|
||||
config.scroll_past_end = true
|
||||
config.file_size_limit = 10
|
||||
config.ignore_files = "^%."
|
||||
config.ignore_files = {"^%."}
|
||||
config.symbol_pattern = "[%a_][%w_]*"
|
||||
config.non_word_chars = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-"
|
||||
config.undo_merge_timeout = 0.3
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
local common = require "core.common"
|
||||
local dirwatch = {}
|
||||
|
||||
function dirwatch:__index(idx)
|
||||
local value = rawget(self, idx)
|
||||
if value ~= nil then return value end
|
||||
return dirwatch[idx]
|
||||
end
|
||||
|
||||
function dirwatch.new()
|
||||
local t = {
|
||||
scanned = {},
|
||||
watched = {},
|
||||
reverse_watched = {},
|
||||
monitor = dirmonitor.new(),
|
||||
windows_watch_top = nil,
|
||||
windows_watch_count = 0
|
||||
}
|
||||
setmetatable(t, dirwatch)
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
function dirwatch:scan(directory, bool)
|
||||
if bool == false then return self:unwatch(directory) end
|
||||
self.scanned[directory] = system.get_file_info(directory).modified
|
||||
end
|
||||
|
||||
-- Should be called on every directory in a subdirectory.
|
||||
-- In windows, this is a no-op for anything underneath a top-level directory,
|
||||
-- but code should be called anyway, so we can ensure that we have a proper
|
||||
-- experience across all platforms.
|
||||
function dirwatch:watch(directory, bool)
|
||||
if bool == false then return self:unwatch(directory) end
|
||||
if not self.watched[directory] and not self.scanned[directory] then
|
||||
if PLATFORM == "Windows" then
|
||||
if not self.windows_watch_top or directory:find(self.windows_watch_top, 1, true) ~= 1 then
|
||||
-- Get the highest level of directory that is common to this directory, and the original.
|
||||
local target = directory
|
||||
while self.windows_watch_top and self.windows_watch_top:find(target, 1, true) ~= 1 do
|
||||
target = common.dirname(target)
|
||||
end
|
||||
if target ~= self.windows_watch_top then
|
||||
local value = self.monitor:watch(target)
|
||||
if value and value < 0 then
|
||||
return self:scan(directory)
|
||||
end
|
||||
self.windows_watch_top = target
|
||||
self.windows_watch_count = self.windows_watch_count + 1
|
||||
end
|
||||
end
|
||||
self.watched[directory] = true
|
||||
elseif PLATFORM == "AmigaOS 4" then
|
||||
return self:scan(directory)
|
||||
else
|
||||
local value = self.monitor:watch(directory)
|
||||
-- If for whatever reason, we can't watch this directory, revert back to scanning.
|
||||
-- Don't bother trying to find out why, for now.
|
||||
if value and value < 0 then
|
||||
return self:scan(directory)
|
||||
end
|
||||
self.watched[directory] = value
|
||||
self.reverse_watched[value] = directory
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function dirwatch:unwatch(directory)
|
||||
if self.watched[directory] then
|
||||
if PLATFORM ~= "Windows" then
|
||||
self.monitor.unwatch(directory)
|
||||
self.reverse_watched[self.watched[directory]] = nil
|
||||
else
|
||||
self.windows_watch_count = self.windows_watch_count - 1
|
||||
if self.windows_watch_count == 0 then
|
||||
self.windows_watch_top = nil
|
||||
self.monitor.unwatch(directory)
|
||||
end
|
||||
end
|
||||
self.watched[directory] = nil
|
||||
elseif self.scanned[directory] then
|
||||
self.scanned[directory] = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- designed to be run inside a coroutine.
|
||||
function dirwatch:check(change_callback, scan_time, wait_time)
|
||||
self.monitor:check(function(id)
|
||||
if PLATFORM == "Windows" then
|
||||
change_callback(common.dirname(self.windows_watch_top .. PATHSEP .. id))
|
||||
elseif self.reverse_watched[id] then
|
||||
change_callback(self.reverse_watched[id])
|
||||
end
|
||||
end)
|
||||
local start_time = system.get_time()
|
||||
for directory, old_modified in pairs(self.scanned) do
|
||||
if old_modified then
|
||||
local new_modified = system.get_file_info(directory).modified
|
||||
if old_modified < new_modified then
|
||||
change_callback(directory)
|
||||
self.scanned[directory] = new_modified
|
||||
end
|
||||
end
|
||||
if system.get_time() - start_time > scan_time then
|
||||
coroutine.yield(wait_time)
|
||||
start_time = system.get_time()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return dirwatch
|
||||
|
|
@ -5,6 +5,7 @@ local config = require "core.config"
|
|||
local style = require "core.style"
|
||||
local command
|
||||
local keymap
|
||||
local dirwatch
|
||||
local RootView
|
||||
local StatusView
|
||||
local TitleView
|
||||
|
@ -106,70 +107,8 @@ local function get_project_file_info(root, file)
|
|||
end
|
||||
|
||||
|
||||
-- Predicate function to inhibit directory recursion in get_directory_files
|
||||
-- based on a time limit and the number of files.
|
||||
local function timed_max_files_pred(dir, filename, entries_count, t_elapsed)
|
||||
local n_limit = entries_count <= config.max_project_files
|
||||
local t_limit = t_elapsed < 20 / config.fps
|
||||
return n_limit and t_limit and core.project_subdir_is_shown(dir, filename)
|
||||
end
|
||||
|
||||
|
||||
-- "root" will by an absolute path without trailing '/'
|
||||
-- "path" will be a path starting with '/' and without trailing '/'
|
||||
-- or the empty string.
|
||||
-- It will identifies a sub-path within "root.
|
||||
-- The current path location will therefore always be: root .. path.
|
||||
-- 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)
|
||||
if begin_hook then begin_hook() end
|
||||
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)
|
||||
if info then
|
||||
table.insert(info.type == "dir" and dirs or files, info)
|
||||
entries_count = entries_count + 1
|
||||
end
|
||||
end
|
||||
|
||||
local recurse_complete = true
|
||||
table.sort(dirs, compare_file)
|
||||
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)
|
||||
recurse_complete = recurse_complete and complete
|
||||
entries_count = n
|
||||
else
|
||||
recurse_complete = false
|
||||
end
|
||||
end
|
||||
|
||||
table.sort(files, compare_file)
|
||||
for _, f in ipairs(files) do
|
||||
table.insert(t, f)
|
||||
end
|
||||
|
||||
return t, recurse_complete, entries_count
|
||||
end
|
||||
|
||||
|
||||
function core.project_subdir_set_show(dir, filename, show)
|
||||
dir.shown_subdir[filename] = show
|
||||
if dir.files_limit and PLATFORM == "Linux" 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")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
@ -183,8 +122,10 @@ local function show_max_files_warning(dir)
|
|||
"Filesystem is too slow: project files will not be indexed." or
|
||||
"Too many files in project directory: stopped reading at "..
|
||||
config.max_project_files.." files. For more information see "..
|
||||
"usage.md at github.com/lite-xl/lite-xl."
|
||||
core.status_view:show_message("!", style.accent, message)
|
||||
"usage.md at https://github.com/lite-xl/lite-xl."
|
||||
if core.status_view then
|
||||
core.status_view:show_message("!", style.accent, message)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
@ -270,64 +211,129 @@ local function project_subdir_bounds(dir, filename)
|
|||
end
|
||||
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 index, n = 0, #dir.files
|
||||
if filename_rooted ~= "" then
|
||||
local filename = strip_leading_path(filename_rooted)
|
||||
index, n = project_subdir_bounds(dir, filename)
|
||||
end
|
||||
|
||||
if not files_list_match(dir.files, index, n, new_files) then
|
||||
files_list_replace(dir.files, index, n, new_files)
|
||||
dir.is_dirty = true
|
||||
return true
|
||||
end
|
||||
-- Predicate function to inhibit directory recursion in get_directory_files
|
||||
-- based on a time limit and the number of files.
|
||||
local function timed_max_files_pred(dir, filename, entries_count, t_elapsed)
|
||||
local n_limit = entries_count <= config.max_project_files
|
||||
local t_limit = t_elapsed < 20 / config.fps
|
||||
return n_limit and t_limit and core.project_subdir_is_shown(dir, filename)
|
||||
end
|
||||
|
||||
|
||||
local function add_dir_scan_thread(dir)
|
||||
core.add_thread(function()
|
||||
while true do
|
||||
local has_changes = rescan_project_subdir(dir, "")
|
||||
if has_changes then
|
||||
core.redraw = true -- we run without an event, from a thread
|
||||
-- "root" will by an absolute path without trailing '/'
|
||||
-- "path" will be a path starting with '/' and without trailing '/'
|
||||
-- or the empty string.
|
||||
-- It will identifies a sub-path within "root.
|
||||
-- The current path location will therefore always be: root .. path.
|
||||
-- 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)
|
||||
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)
|
||||
if info and not common.match_pattern(common.basename(info.filename), config.ignore_files) then
|
||||
table.insert(info.type == "dir" and dirs or files, info)
|
||||
entries_count = entries_count + 1
|
||||
end
|
||||
end
|
||||
|
||||
local recurse_complete = true
|
||||
table.sort(dirs, compare_file)
|
||||
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)
|
||||
recurse_complete = recurse_complete and complete
|
||||
entries_count = n
|
||||
else
|
||||
recurse_complete = false
|
||||
end
|
||||
end
|
||||
|
||||
table.sort(files, compare_file)
|
||||
for _, f in ipairs(files) do
|
||||
table.insert(t, f)
|
||||
end
|
||||
|
||||
return t, recurse_complete, entries_count
|
||||
end
|
||||
|
||||
|
||||
-- Should be called on any directory that registers a change.
|
||||
-- Uses relative paths at the project root.
|
||||
local function refresh_directory(topdir, target, expanded)
|
||||
local index, n, directory
|
||||
if target == "" then
|
||||
index, n = 1, #topdir.files
|
||||
directory = ""
|
||||
else
|
||||
index, n = project_subdir_bounds(topdir, target)
|
||||
index = index + 1
|
||||
n = index + n - 1
|
||||
directory = (PATHSEP .. target)
|
||||
end
|
||||
if index then
|
||||
local files
|
||||
local change = false
|
||||
if topdir.files_limit then
|
||||
-- If we have the folders literally open on the side panel.
|
||||
files = expanded and get_directory_files(topdir, topdir.name, directory, {}, 0, core.project_subdir_is_shown) or {}
|
||||
change = true
|
||||
else
|
||||
-- If we're expecting to keep track of everything, go through the list and iteratively deal with directories.
|
||||
files = get_directory_files(topdir, topdir.name, directory, {}, 0, function() return false end)
|
||||
end
|
||||
|
||||
local new_idx, old_idx = 1, index
|
||||
local new_directories = {}
|
||||
local last_dir = nil
|
||||
while old_idx <= n or new_idx <= #files do
|
||||
local old_info, new_info = topdir.files[old_idx], files[new_idx]
|
||||
if not new_info or not old_info or not last_dir or old_info.filename:sub(1, #last_dir + 1) ~= last_dir .. "/" then
|
||||
if not new_info or not old_info or not files_info_equal(new_info, old_info) then
|
||||
change = true
|
||||
if not old_info or (new_info and system.path_compare(new_info.filename, new_info.type, old_info.filename, old_info.type)) then
|
||||
table.insert(topdir.files, old_idx, new_info)
|
||||
new_idx = new_idx + 1
|
||||
old_idx = old_idx + 1
|
||||
if new_info.type == "dir" then
|
||||
table.insert(new_directories, new_info)
|
||||
end
|
||||
n = n + 1
|
||||
else
|
||||
table.remove(topdir.files, old_idx)
|
||||
if old_info.type == "dir" then
|
||||
topdir.watch:unwatch(target .. PATHSEP .. old_info.filename)
|
||||
end
|
||||
n = n - 1
|
||||
end
|
||||
else
|
||||
new_idx = new_idx + 1
|
||||
old_idx = old_idx + 1
|
||||
end
|
||||
if old_info and old_info.type == "dir" then
|
||||
last_dir = old_info.filename
|
||||
end
|
||||
else
|
||||
old_idx = old_idx + 1
|
||||
end
|
||||
coroutine.yield(5)
|
||||
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")
|
||||
end
|
||||
local t, complete, entries_count = get_directory_files(dir, dir.name, "", {}, 0, timed_max_files_pred)
|
||||
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")
|
||||
for i, v in ipairs(new_directories) do
|
||||
topdir.watch:watch(target)
|
||||
if refresh_directory(topdir, target .. PATHSEP .. v.filename) then
|
||||
change = true
|
||||
end
|
||||
end
|
||||
if core.status_view then -- May be not yet initialized.
|
||||
show_max_files_warning(dir)
|
||||
if change then
|
||||
core.redraw = true
|
||||
topdir.is_dirty = true
|
||||
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
|
||||
core.dir_rescan_add_job(dir, ".")
|
||||
return change
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -337,29 +343,63 @@ function core.add_project_directory(path)
|
|||
-- will be simply the name of the directory, without its path.
|
||||
-- The field item.topdir will identify it as a top level directory.
|
||||
path = common.normalize_volume(path)
|
||||
local dir = {
|
||||
local topdir = {
|
||||
name = path,
|
||||
item = {filename = common.basename(path), type = "dir", topdir = true},
|
||||
files_limit = false,
|
||||
is_dirty = true,
|
||||
shown_subdir = {},
|
||||
watch_thread = nil,
|
||||
watch = dirwatch.new()
|
||||
}
|
||||
table.insert(core.project_directories, dir)
|
||||
scan_project_folder(#core.project_directories)
|
||||
table.insert(core.project_directories, topdir)
|
||||
|
||||
local fstype = PLATFORM == "Linux" and system.get_fs_type(topdir.name) or "unknown"
|
||||
topdir.force_scans = (fstype == "nfs" or fstype == "fuse")
|
||||
local t, complete, entries_count = get_directory_files(topdir, topdir.name, "", {}, 0, timed_max_files_pred)
|
||||
topdir.files = t
|
||||
if not complete then
|
||||
topdir.slow_filesystem = not complete and (entries_count <= config.max_project_files)
|
||||
topdir.files_limit = true
|
||||
show_max_files_warning(topdir)
|
||||
refresh_directory(topdir, "", true)
|
||||
else
|
||||
for i,v in ipairs(t) do
|
||||
if v.type == "dir" then topdir.watch:watch(path .. PATHSEP .. v.filename) end
|
||||
end
|
||||
end
|
||||
topdir.watch:watch(topdir.name)
|
||||
-- each top level directory gets a watch thread. if the project is small, or
|
||||
-- if the ablity to use directory watches hasn't been compromised in some way
|
||||
-- either through error, or amount of files, then this should be incredibly
|
||||
-- quick; essentially one syscall per check. Otherwise, this may take a bit of
|
||||
-- time; the watch will yield in this coroutine after 0.01 second, for 0.1 seconds.
|
||||
topdir.watch_thread = core.add_thread(function()
|
||||
while true do
|
||||
topdir.watch:check(function(target)
|
||||
if target == topdir.name then return refresh_directory(topdir, "", true) end
|
||||
local dirpath = target:sub(#topdir.name + 2)
|
||||
local abs_dirpath = topdir.name .. PATHSEP .. dirpath
|
||||
if dirpath then
|
||||
-- check if the directory is in the project files list, if not exit.
|
||||
local dir_index, dir_match = file_search(topdir.files, {filename = dirpath, type = "dir"})
|
||||
if not dir_match or not core.project_subdir_is_shown(topdir, topdir.files[dir_index].filename) then return end
|
||||
end
|
||||
return refresh_directory(topdir, dirpath, true)
|
||||
end, 0.01, 0.01)
|
||||
coroutine.yield(0.05)
|
||||
end
|
||||
end)
|
||||
if path == core.project_dir then
|
||||
core.project_files = dir.files
|
||||
core.project_files = topdir.files
|
||||
end
|
||||
core.redraw = true
|
||||
end
|
||||
|
||||
|
||||
function core.update_project_subdir(dir, filename, expanded)
|
||||
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 {}
|
||||
files_list_replace(dir.files, index, n, new_files)
|
||||
dir.is_dirty = true
|
||||
return true
|
||||
if dir.files_limit then
|
||||
refresh_directory(dir, filename, expanded)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -376,7 +416,7 @@ local function find_files_rec(root, path)
|
|||
info.filename = strip_leading_path(file)
|
||||
if info.type == "file" then
|
||||
coroutine.yield(root, info)
|
||||
else
|
||||
elseif not common.match_pattern(common.basename(info.filename), config.ignore_files) then
|
||||
find_files_rec(root, PATHSEP .. info.filename)
|
||||
end
|
||||
end
|
||||
|
@ -437,42 +477,6 @@ function core.project_files_number()
|
|||
end
|
||||
|
||||
|
||||
local function project_dir_by_watch_id(watch_id)
|
||||
for i = 1, #core.project_directories do
|
||||
if core.project_directories[i].watch_id == watch_id then
|
||||
return core.project_directories[i]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function project_scan_remove_file(dir, filepath)
|
||||
local fileinfo = { filename = filepath }
|
||||
for _, filetype in ipairs {"dir", "file"} do
|
||||
fileinfo.type = filetype
|
||||
local index, match = file_search(dir.files, fileinfo)
|
||||
if match then
|
||||
table.remove(dir.files, index)
|
||||
dir.is_dirty = true
|
||||
return
|
||||
end
|
||||
end
|
||||
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)
|
||||
if fileinfo then
|
||||
project_scan_add_entry(dir, fileinfo)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- create a directory using mkdir but may need to create the parent
|
||||
-- directories as well.
|
||||
local function create_user_directory()
|
||||
|
@ -594,6 +598,7 @@ end
|
|||
function core.init()
|
||||
command = require "core.command"
|
||||
keymap = require "core.keymap"
|
||||
dirwatch = require "core.dirwatch"
|
||||
RootView = require "core.rootview"
|
||||
StatusView = require "core.statusview"
|
||||
TitleView = require "core.titleview"
|
||||
|
@ -653,6 +658,8 @@ function core.init()
|
|||
core.blink_start = system.get_time()
|
||||
core.blink_timer = core.blink_start
|
||||
|
||||
local got_user_error, got_project_error = not core.load_user_directory()
|
||||
|
||||
local project_dir_abs = system.absolute_path(project_dir)
|
||||
local set_project_ok = project_dir_abs and core.set_project_dir(project_dir_abs)
|
||||
if set_project_ok then
|
||||
|
@ -664,7 +671,9 @@ function core.init()
|
|||
update_recents_project("remove", project_dir)
|
||||
end
|
||||
project_dir_abs = system.absolute_path(".")
|
||||
if not core.set_project_dir(project_dir_abs) then
|
||||
if not core.set_project_dir(project_dir_abs, function()
|
||||
got_project_error = not core.load_project_module()
|
||||
end) then
|
||||
system.show_fatal_error("Lite XL internal error", "cannot set project directory to cwd")
|
||||
os.exit(1)
|
||||
end
|
||||
|
@ -691,14 +700,12 @@ function core.init()
|
|||
cur_node = cur_node:split("down", core.status_view, {y = true})
|
||||
|
||||
command.add_defaults()
|
||||
local got_user_error = not core.load_user_directory()
|
||||
local plugins_success, plugins_refuse_list = core.load_plugins()
|
||||
|
||||
do
|
||||
local pdir, pname = project_dir_abs:match("(.*)[:/\\\\](.*)")
|
||||
core.log("Opening project %q from directory %s", pname, pdir)
|
||||
end
|
||||
local got_project_error = not core.load_project_module()
|
||||
|
||||
-- We assume we have just a single project directory here. Now that StatusView
|
||||
-- is there show max files warning if needed.
|
||||
|
@ -1105,76 +1112,6 @@ function core.has_pending_rescan()
|
|||
end
|
||||
|
||||
|
||||
function core.dir_rescan_add_job(dir, filepath)
|
||||
local dirpath = filepath:match("^(.+)[/\\].+$")
|
||||
local dirpath_rooted = dirpath and PATHSEP .. dirpath or ""
|
||||
local abs_dirpath = dir.name .. dirpath_rooted
|
||||
if dirpath then
|
||||
-- check if the directory is in the project files list, if not exit
|
||||
local dir_index, dir_match = file_search(dir.files, {filename = dirpath, type = "dir"})
|
||||
-- Note that is dir_match is false dir_index greaten than the last valid index.
|
||||
-- We use dir_index to index dir.files below only if dir_match is true.
|
||||
if not dir_match or not core.project_subdir_is_shown(dir, dir.files[dir_index].filename) then return end
|
||||
end
|
||||
local new_time = system.get_time() + 1
|
||||
|
||||
-- evaluate new rescan request versus existing rescan
|
||||
local remove_list = {}
|
||||
for _, rescan in pairs(scheduled_rescan) do
|
||||
if abs_dirpath == rescan.abs_path or common.path_belongs_to(abs_dirpath, rescan.abs_path) then
|
||||
-- abs_dirpath is a subpath of a scan already ongoing: skip
|
||||
rescan.time_limit = new_time
|
||||
return
|
||||
elseif common.path_belongs_to(rescan.abs_path, abs_dirpath) then
|
||||
-- abs_dirpath already cover this rescan: add to the list of rescan to be removed
|
||||
table.insert(remove_list, rescan.abs_path)
|
||||
end
|
||||
end
|
||||
for _, key_path in ipairs(remove_list) do
|
||||
scheduled_rescan[key_path] = nil
|
||||
end
|
||||
|
||||
scheduled_rescan[abs_dirpath] = {dir = dir, path = dirpath_rooted, abs_path = abs_dirpath, time_limit = new_time}
|
||||
core.add_thread(function()
|
||||
while true do
|
||||
local rescan = scheduled_rescan[abs_dirpath]
|
||||
if not rescan then return end
|
||||
if system.get_time() > rescan.time_limit then
|
||||
local has_changes = rescan_project_subdir(rescan.dir, rescan.path)
|
||||
if has_changes then
|
||||
core.redraw = true -- we run without an event, from a thread
|
||||
rescan.time_limit = new_time
|
||||
else
|
||||
scheduled_rescan[rescan.abs_path] = nil
|
||||
return
|
||||
end
|
||||
end
|
||||
coroutine.yield(0.2)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
-- no-op but can be overrided by plugins
|
||||
function core.on_dirmonitor_modify(dir, filepath)
|
||||
end
|
||||
|
||||
|
||||
function core.on_dir_change(watch_id, action, filepath)
|
||||
local dir = project_dir_by_watch_id(watch_id)
|
||||
if not dir then return end
|
||||
core.dir_rescan_add_job(dir, filepath)
|
||||
if action == "delete" then
|
||||
project_scan_remove_file(dir, filepath)
|
||||
elseif action == "create" then
|
||||
project_scan_add_file(dir, filepath)
|
||||
core.on_dirmonitor_modify(dir, filepath);
|
||||
elseif action == "modify" then
|
||||
core.on_dirmonitor_modify(dir, filepath);
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function core.on_event(type, ...)
|
||||
local did_keymap = false
|
||||
if type == "textinput" then
|
||||
|
@ -1214,8 +1151,6 @@ function core.on_event(type, ...)
|
|||
end
|
||||
elseif type == "focuslost" then
|
||||
core.root_view:on_focus_lost(...)
|
||||
elseif type == "dirchange" then
|
||||
core.on_dir_change(...)
|
||||
elseif type == "quit" then
|
||||
core.quit()
|
||||
end
|
||||
|
|
|
@ -577,7 +577,7 @@ command.add(function() return view.hovered_item ~= nil end, {
|
|||
system.exec(string.format("start \"\" %q", hovered_item.abs_filename))
|
||||
elseif string.find(PLATFORM, "Mac") then
|
||||
system.exec(string.format("open %q", hovered_item.abs_filename))
|
||||
elseif PLATFORM == "Linux" then
|
||||
elseif PLATFORM == "Linux" or string.find(PLATFORM, "BSD") then
|
||||
system.exec(string.format("xdg-open %q", hovered_item.abs_filename))
|
||||
end
|
||||
end,
|
||||
|
|
1595
lib/dmon/dmon.h
1595
lib/dmon/dmon.h
File diff suppressed because it is too large
Load Diff
|
@ -1,162 +0,0 @@
|
|||
#ifndef __DMON_EXTRA_H__
|
||||
#define __DMON_EXTRA_H__
|
||||
|
||||
//
|
||||
// Copyright 2021 Sepehr Taghdisian (septag@github). All rights reserved.
|
||||
// License: https://github.com/septag/dmon#license-bsd-2-clause
|
||||
//
|
||||
// Extra header functionality for dmon.h for the backend based on inotify
|
||||
//
|
||||
// Add/Remove directory functions:
|
||||
// dmon_watch_add: Adds a sub-directory to already valid watch_id. sub-directories are assumed to be relative to watch root_dir
|
||||
// dmon_watch_add: Removes a sub-directory from already valid watch_id. sub-directories are assumed to be relative to watch root_dir
|
||||
// Reason: The inotify backend does not work well when watching in recursive mode a root directory of a large tree, it may take
|
||||
// quite a while until all inotify watches are established, and events will not be received in this time. Also, since one
|
||||
// inotify watch will be established per subdirectory, it is possible that the maximum amount of inotify watches per user
|
||||
// will be reached. The default maximum is 8192.
|
||||
// When using inotify backend users may turn off the DMON_WATCHFLAGS_RECURSIVE flag and add/remove selectively the
|
||||
// sub-directories to be watched based on application-specific logic about which sub-directory actually needs to be watched.
|
||||
// The function dmon_watch_add and dmon_watch_rm are used to this purpose.
|
||||
//
|
||||
|
||||
#ifndef __DMON_H__
|
||||
#error "Include 'dmon.h' before including this file"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
DMON_API_DECL bool dmon_watch_add(dmon_watch_id id, const char* subdir);
|
||||
DMON_API_DECL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef DMON_IMPL
|
||||
#if DMON_OS_LINUX
|
||||
DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir)
|
||||
{
|
||||
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);
|
||||
|
||||
dmon__watch_state* watch = &_dmon.watches[id.id - 1];
|
||||
|
||||
// check if the directory exists
|
||||
// if watchdir contains absolute/root-included path, try to strip the rootdir from it
|
||||
// else, we assume that watchdir is correct, so save it as it is
|
||||
struct stat st;
|
||||
dmon__watch_subdir subdir;
|
||||
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) {
|
||||
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir + strlen(watch->rootdir));
|
||||
}
|
||||
} else {
|
||||
char fullpath[DMON_MAX_PATH];
|
||||
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);
|
||||
if (!skip_lock)
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return false;
|
||||
}
|
||||
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir);
|
||||
}
|
||||
|
||||
int dirlen = (int)strlen(subdir.rootdir);
|
||||
if (subdir.rootdir[dirlen - 1] != '/') {
|
||||
subdir.rootdir[dirlen] = '/';
|
||||
subdir.rootdir[dirlen + 1] = '\0';
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (!skip_lock)
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t inotify_mask = IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE | IN_MODIFY;
|
||||
char fullpath[DMON_MAX_PATH];
|
||||
dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir);
|
||||
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);
|
||||
if (!skip_lock)
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
stb_sb_push(watch->subdirs, subdir);
|
||||
stb_sb_push(watch->wds, wd);
|
||||
|
||||
if (!skip_lock)
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DMON_API_IMPL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir)
|
||||
{
|
||||
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);
|
||||
|
||||
dmon__watch_state* watch = &_dmon.watches[id.id - 1];
|
||||
|
||||
char subdir[DMON_MAX_PATH];
|
||||
dmon__strcpy(subdir, sizeof(subdir), watchdir);
|
||||
if (strstr(subdir, watch->rootdir) == subdir) {
|
||||
dmon__strcpy(subdir, sizeof(subdir), watchdir + strlen(watch->rootdir));
|
||||
}
|
||||
|
||||
int dirlen = (int)strlen(subdir);
|
||||
if (subdir[dirlen - 1] != '/') {
|
||||
subdir[dirlen] = '/';
|
||||
subdir[dirlen + 1] = '\0';
|
||||
}
|
||||
|
||||
int i, c = stb_sb_count(watch->subdirs);
|
||||
for (i = 0; i < c; i++) {
|
||||
if (strcmp(watch->subdirs[i].rootdir, subdir) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i >= c) {
|
||||
_DMON_LOG_ERRORF("Watch directory '%s' is not valid", watchdir);
|
||||
if (!skip_lock)
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return false;
|
||||
}
|
||||
inotify_rm_watch(watch->fd, watch->wds[i]);
|
||||
|
||||
/* Remove entry from subdirs and wds by swapping position with the last entry */
|
||||
watch->subdirs[i] = stb_sb_last(watch->subdirs);
|
||||
stb_sb_pop(watch->subdirs);
|
||||
|
||||
watch->wds[i] = stb_sb_last(watch->wds);
|
||||
stb_sb_pop(watch->wds);
|
||||
|
||||
if (!skip_lock)
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
return true;
|
||||
}
|
||||
#endif // DMON_OS_LINUX
|
||||
#endif // DMON_IMPL
|
||||
|
||||
#endif // __DMON_EXTRA_H__
|
||||
|
|
@ -1 +0,0 @@
|
|||
lite_includes += include_directories('.')
|
|
@ -22,33 +22,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
## septag/dmon
|
||||
|
||||
Copyright 2019 Sepehr Taghdisian. All rights reserved.
|
||||
|
||||
https://github.com/septag/dmon
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
EVENT SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
## Fira Sans
|
||||
|
||||
Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A.
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
#include "api.h"
|
||||
|
||||
|
||||
int luaopen_system(lua_State *L);
|
||||
int luaopen_renderer(lua_State *L);
|
||||
int luaopen_regex(lua_State *L);
|
||||
// int luaopen_process(lua_State *L);
|
||||
int luaopen_dirmonitor(lua_State* L);
|
||||
|
||||
static const luaL_Reg libs[] = {
|
||||
{ "system", luaopen_system },
|
||||
{ "renderer", luaopen_renderer },
|
||||
{ "regex", luaopen_regex },
|
||||
// { "process", luaopen_process },
|
||||
{ "system", luaopen_system },
|
||||
{ "renderer", luaopen_renderer },
|
||||
{ "regex", luaopen_regex },
|
||||
// { "process", luaopen_process },
|
||||
{ "dirmonitor", luaopen_dirmonitor },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
|
@ -18,4 +19,3 @@ void api_load_libs(lua_State *L) {
|
|||
for (int i = 0; libs[i].name; i++)
|
||||
luaL_requiref(L, libs[i].name, libs[i].func, 1);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#define API_TYPE_FONT "Font"
|
||||
#define API_TYPE_PROCESS "Process"
|
||||
#define API_TYPE_DIRMONITOR "Dirmonitor"
|
||||
|
||||
#define API_CONSTANT_DEFINE(L, idx, key, n) (lua_pushnumber(L, n), lua_setfield(L, idx - 1, key))
|
||||
|
||||
|
|
|
@ -0,0 +1,226 @@
|
|||
#include "api.h"
|
||||
#include <stdlib.h>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#elif __linux__
|
||||
#include <sys/inotify.h>
|
||||
#include <limits.h>
|
||||
#elif __amigaos4__
|
||||
#include "platform/amigaos4.h"
|
||||
#else
|
||||
#include <sys/event.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/*
|
||||
This is *slightly* a clusterfuck. Normally, we'd
|
||||
have windows wait on a list of handles like inotify,
|
||||
however, MAXIMUM_WAIT_OBJECTS is 64. Yes, seriously.
|
||||
So, for windows, we are recursive.
|
||||
*/
|
||||
struct dirmonitor {
|
||||
int fd;
|
||||
#if _WIN32
|
||||
HANDLE handle;
|
||||
char buffer[8192];
|
||||
OVERLAPPED overlapped;
|
||||
bool running;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct dirmonitor* init_dirmonitor() {
|
||||
printf("DBG: init_dirmonitor\n");
|
||||
struct dirmonitor* monitor = calloc(sizeof(struct dirmonitor), 1);
|
||||
#ifndef _WIN32
|
||||
#if __linux__
|
||||
monitor->fd = inotify_init1(IN_NONBLOCK);
|
||||
#elif __amigaos4__
|
||||
monitor->fd = notificationInit(); // TODO: This needs real implementation
|
||||
#else
|
||||
monitor->fd = kqueue();
|
||||
#endif
|
||||
#endif
|
||||
return monitor;
|
||||
}
|
||||
|
||||
#if _WIN32
|
||||
static void close_monitor_handle(struct dirmonitor* monitor) {
|
||||
if (monitor->handle) {
|
||||
if (monitor->running) {
|
||||
BOOL result = CancelIoEx(monitor->handle, &monitor->overlapped);
|
||||
DWORD error = GetLastError();
|
||||
if (result == TRUE || error != ERROR_NOT_FOUND) {
|
||||
DWORD bytes_transferred;
|
||||
GetOverlappedResult( monitor->handle, &monitor->overlapped, &bytes_transferred, TRUE );
|
||||
}
|
||||
monitor->running = false;
|
||||
}
|
||||
CloseHandle(monitor->handle);
|
||||
}
|
||||
monitor->handle = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
void deinit_dirmonitor(struct dirmonitor* monitor) {
|
||||
printf("DBG: deinit_dirmonitor\n");
|
||||
#if _WIN32
|
||||
close_monitor_handle(monitor);
|
||||
#elif __amigaos4__
|
||||
monitor->fd = 0; // TODO: This needs real implementation
|
||||
#else
|
||||
close(monitor->fd);
|
||||
#endif
|
||||
free(monitor);
|
||||
}
|
||||
|
||||
int check_dirmonitor(struct dirmonitor* monitor, int (*change_callback)(int, const char*, void*), void* data) {
|
||||
printf("DBG: check_dirmonitor\n");
|
||||
#if _WIN32
|
||||
if (!monitor->running) {
|
||||
if (ReadDirectoryChangesW(monitor->handle, monitor->buffer, sizeof(monitor->buffer), TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME, NULL, &monitor->overlapped, NULL) == 0)
|
||||
return GetLastError();
|
||||
monitor->running = true;
|
||||
}
|
||||
DWORD bytes_transferred;
|
||||
if (!GetOverlappedResult(monitor->handle, &monitor->overlapped, &bytes_transferred, FALSE)) {
|
||||
int error = GetLastError();
|
||||
return error == ERROR_IO_PENDING || error == ERROR_IO_INCOMPLETE ? 0 : error;
|
||||
}
|
||||
monitor->running = false;
|
||||
for (FILE_NOTIFY_INFORMATION* info = (FILE_NOTIFY_INFORMATION*)monitor->buffer; (char*)info < monitor->buffer + sizeof(monitor->buffer); info = (FILE_NOTIFY_INFORMATION*)((char*)info) + info->NextEntryOffset) {
|
||||
change_callback(info->FileNameLength, (char*)info->FileName, data);
|
||||
if (!info->NextEntryOffset)
|
||||
break;
|
||||
}
|
||||
monitor->running = false;
|
||||
return 0;
|
||||
#elif __linux__
|
||||
char buf[PATH_MAX + sizeof(struct inotify_event)];
|
||||
while (1) {
|
||||
ssize_t len = read(monitor->fd, buf, sizeof(buf));
|
||||
if (len == -1 && errno != EAGAIN)
|
||||
return errno;
|
||||
if (len <= 0)
|
||||
return 0;
|
||||
for (char *ptr = buf; ptr < buf + len; ptr += sizeof(struct inotify_event) + ((struct inotify_event*)ptr)->len)
|
||||
change_callback(((const struct inotify_event *) ptr)->wd, NULL, data);
|
||||
}
|
||||
#elif __amigaos4__
|
||||
return 0; // TODO: This needs real implementation
|
||||
#else
|
||||
struct kevent event;
|
||||
while (1) {
|
||||
struct timespec tm = {0};
|
||||
int nev = kevent(monitor->fd, NULL, 0, &event, 1, &tm);
|
||||
if (nev == -1)
|
||||
return errno;
|
||||
if (nev <= 0)
|
||||
return 0;
|
||||
change_callback(event.ident, NULL, data);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int add_dirmonitor(struct dirmonitor* monitor, const char* path) {
|
||||
printf("DBG: add_dirmonitor\n");
|
||||
#if _WIN32
|
||||
close_monitor_handle(monitor);
|
||||
monitor->handle = CreateFileA(path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
|
||||
if (monitor->handle && monitor->handle != INVALID_HANDLE_VALUE)
|
||||
return 1;
|
||||
monitor->handle = NULL;
|
||||
return -1;
|
||||
#elif __linux__
|
||||
return inotify_add_watch(monitor->fd, path, IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO);
|
||||
#elif __amigaos4__
|
||||
return -1; // TODO: This needs real implementation
|
||||
#else
|
||||
int fd = open(path, O_RDONLY);
|
||||
struct kevent change;
|
||||
EV_SET(&change, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_DELETE | NOTE_EXTEND | NOTE_WRITE | NOTE_ATTRIB | NOTE_LINK | NOTE_RENAME, 0, (void*)path);
|
||||
kevent(monitor->fd, &change, 1, NULL, 0, NULL);
|
||||
return fd;
|
||||
#endif
|
||||
}
|
||||
|
||||
void remove_dirmonitor(struct dirmonitor* monitor, int fd) {
|
||||
printf("DBG: remove_dirmonitor\n");
|
||||
#if _WIN32
|
||||
close_monitor_handle(monitor);
|
||||
#elif __linux__
|
||||
inotify_rm_watch(monitor->fd, fd);
|
||||
#elif __amigaos4__
|
||||
fd = 0; // TODO: This needs real implementation
|
||||
#else
|
||||
close(fd);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int f_check_dir_callback(int watch_id, const char* path, void* L) {
|
||||
printf("DBG: f_check_dir_callback\n");
|
||||
#if _WIN32
|
||||
char buffer[PATH_MAX*4];
|
||||
int count = WideCharToMultiByte(CP_UTF8, 0, (WCHAR*)path, watch_id, buffer, PATH_MAX*4 - 1, NULL, NULL);
|
||||
lua_pushlstring(L, buffer, count);
|
||||
#else
|
||||
lua_pushnumber(L, watch_id);
|
||||
#endif
|
||||
lua_pcall(L, 1, 1, 0);
|
||||
int result = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
return !result;
|
||||
}
|
||||
|
||||
static int f_dirmonitor_new(lua_State* L) {
|
||||
printf("DBG: f_dirmonitor_new\n");
|
||||
// struct dirmonitor** monitor = lua_newuserdata(L, sizeof(struct dirmonitor**));
|
||||
// *monitor = init_dirmonitor();
|
||||
// luaL_setmetatable(L, API_TYPE_DIRMONITOR);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int f_dirmonitor_gc(lua_State* L) {
|
||||
printf("DBG: f_dirmonitor_gc\n");
|
||||
// deinit_dirmonitor(*((struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int f_dirmonitor_watch(lua_State *L) {
|
||||
printf("DBG: f_dirmonitor_watch\n");
|
||||
// lua_pushnumber(L, add_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), luaL_checkstring(L, 2)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int f_dirmonitor_unwatch(lua_State *L) {
|
||||
printf("DBG: f_dirmonitor_unwatch\n");
|
||||
// remove_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), luaL_checknumber(L, 2));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int f_dirmonitor_check(lua_State* L) {
|
||||
printf("DBG: f_dirmonitor_check\n");
|
||||
// lua_pushnumber(L, check_dirmonitor(*(struct dirmonitor**)luaL_checkudata(L, 1, API_TYPE_DIRMONITOR), f_check_dir_callback, L));
|
||||
return 1;
|
||||
}
|
||||
static const luaL_Reg dirmonitor_lib[] = {
|
||||
{ "new", f_dirmonitor_new },
|
||||
{ "__gc", f_dirmonitor_gc },
|
||||
{ "watch", f_dirmonitor_watch },
|
||||
{ "unwatch", f_dirmonitor_unwatch },
|
||||
{ "check", f_dirmonitor_check },
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
int luaopen_dirmonitor(lua_State* L) {
|
||||
luaL_newmetatable(L, API_TYPE_DIRMONITOR);
|
||||
luaL_setfuncs(L, dirmonitor_lib, 0);
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setfield(L, -2, "__index");
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -6,12 +6,13 @@
|
|||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include "api.h"
|
||||
// #include "dirmonitor.h"
|
||||
#include "rencache.h"
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
#include <windows.h>
|
||||
#include <fileapi.h>
|
||||
#elif __amigaos4__
|
||||
#include "platform/amigaos4.h"
|
||||
#elif __linux__
|
||||
#include <sys/vfs.h>
|
||||
#endif
|
||||
|
@ -249,26 +250,6 @@ top:
|
|||
lua_pushnumber(L, e.wheel.y);
|
||||
return 2;
|
||||
|
||||
case SDL_USEREVENT:
|
||||
lua_pushstring(L, "dirchange");
|
||||
lua_pushnumber(L, e.user.code >> 16);
|
||||
// switch (e.user.code & 0xffff) {
|
||||
// case DMON_ACTION_DELETE:
|
||||
// lua_pushstring(L, "delete");
|
||||
// break;
|
||||
// case DMON_ACTION_CREATE:
|
||||
// lua_pushstring(L, "create");
|
||||
// break;
|
||||
// case DMON_ACTION_MODIFY:
|
||||
// lua_pushstring(L, "modify");
|
||||
// break;
|
||||
// default:
|
||||
// return luaL_error(L, "unknown dmon event action: %d", e.user.code & 0xffff);
|
||||
// }
|
||||
// lua_pushstring(L, e.user.data1);
|
||||
free(e.user.data1);
|
||||
return 4;
|
||||
|
||||
default:
|
||||
goto top;
|
||||
}
|
||||
|
@ -794,36 +775,6 @@ static int f_load_native_plugin(lua_State *L) {
|
|||
return result;
|
||||
}
|
||||
|
||||
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);
|
||||
uint32_t dmon_flags = 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"); }
|
||||
// lua_pushnumber(L, watch_id.id);
|
||||
lua_pushnumber(L, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#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));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int f_watch_dir_rm(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_rm(watch_id, subdir));
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#define PATHSEP '\\'
|
||||
#else
|
||||
|
@ -909,11 +860,8 @@ static const luaL_Reg lib[] = {
|
|||
{ "fuzzy_match", f_fuzzy_match },
|
||||
{ "set_window_opacity", f_set_window_opacity },
|
||||
{ "load_native_plugin", f_load_native_plugin },
|
||||
{ "watch_dir", f_watch_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 },
|
||||
#endif
|
||||
{ NULL, NULL }
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#define DMON_IMPL
|
||||
#include "dmon.h"
|
||||
#include "dmon_extra.h"
|
||||
|
||||
#include "dirmonitor.h"
|
||||
|
||||
static void send_sdl_event(dmon_watch_id watch_id, dmon_action action, const char *filepath) {
|
||||
SDL_Event ev;
|
||||
const int size = strlen(filepath) + 1;
|
||||
/* The string allocated below should be deallocated as soon as the event is
|
||||
treated in the SDL main loop. */
|
||||
char *new_filepath = malloc(size);
|
||||
if (!new_filepath) return;
|
||||
memcpy(new_filepath, filepath, size);
|
||||
#ifdef _WIN32
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (new_filepath[i] == '/') {
|
||||
new_filepath[i] = '\\';
|
||||
}
|
||||
}
|
||||
#endif
|
||||
SDL_zero(ev);
|
||||
ev.type = SDL_USEREVENT;
|
||||
ev.user.code = ((watch_id.id & 0xffff) << 16) | (action & 0xffff);
|
||||
ev.user.data1 = new_filepath;
|
||||
SDL_PushEvent(&ev);
|
||||
}
|
||||
|
||||
void dirmonitor_init() {
|
||||
dmon_init();
|
||||
/* In theory we should register our user event but since we
|
||||
have just one type of user event this is not really needed. */
|
||||
/* sdl_dmon_event_type = SDL_RegisterEvents(1); */
|
||||
}
|
||||
|
||||
void dirmonitor_deinit() {
|
||||
dmon_deinit();
|
||||
}
|
||||
|
||||
void dirmonitor_watch_callback(dmon_watch_id watch_id, dmon_action action, const char *rootdir,
|
||||
const char *filepath, const char *oldfilepath, void *user)
|
||||
{
|
||||
(void) rootdir;
|
||||
(void) user;
|
||||
switch (action) {
|
||||
case DMON_ACTION_MOVE:
|
||||
send_sdl_event(watch_id, DMON_ACTION_DELETE, oldfilepath);
|
||||
send_sdl_event(watch_id, DMON_ACTION_CREATE, filepath);
|
||||
break;
|
||||
default:
|
||||
send_sdl_event(watch_id, action, filepath);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
#ifndef DIRMONITOR_H
|
||||
#define DIRMONITOR_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "dmon.h"
|
||||
#include "dmon_extra.h"
|
||||
|
||||
void dirmonitor_init();
|
||||
void dirmonitor_deinit();
|
||||
void dirmonitor_watch_callback(dmon_watch_id watch_id, dmon_action action, const char *rootdir,
|
||||
const char *filepath, const char *oldfilepath, void *user);
|
||||
|
||||
#endif
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#elif __linux__
|
||||
#elif __linux__ || __FreeBSD__
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#elif __APPLE__
|
||||
|
@ -16,8 +16,6 @@
|
|||
#include "platform/amigaos4.h"
|
||||
#endif
|
||||
|
||||
#include "dirmonitor.h"
|
||||
|
||||
|
||||
SDL_Window *window;
|
||||
|
||||
|
@ -112,8 +110,6 @@ int main(int argc, char **argv) {
|
|||
SDL_DisplayMode dm;
|
||||
SDL_GetCurrentDisplayMode(0, &dm);
|
||||
|
||||
// dirmonitor_init();
|
||||
|
||||
window = SDL_CreateWindow(
|
||||
"", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, dm.w * 0.8, dm.h * 0.8,
|
||||
SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN);
|
||||
|
@ -201,7 +197,6 @@ init_lua:
|
|||
|
||||
lua_close(L);
|
||||
ren_free_window_resources();
|
||||
// dirmonitor_deinit();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
|
|
@ -59,3 +59,13 @@ char *_fullpath(const char *path)
|
|||
strcpy(result, getFullPath(path));
|
||||
return result;
|
||||
}
|
||||
|
||||
int notificationInit(void) {
|
||||
// struct NotifyRequest *notifyRequest;
|
||||
|
||||
// if ((notifyrequest = AllocMem(sizeof(struct NotifyRequest), MEMF_CLEAR))) {
|
||||
|
||||
// }
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ static CONST_STRPTR stack USED = "$STACK:102400";
|
|||
static CONST_STRPTR version USED = VERSTAG;
|
||||
|
||||
char *_fullpath(const char *);
|
||||
int notificationInit(void);
|
||||
|
||||
|
||||
#endif
|
||||
|
|
|
@ -246,6 +246,22 @@ float ren_font_group_get_width(RenFont **fonts, const char *text) {
|
|||
return width / surface_scale;
|
||||
}
|
||||
|
||||
uint32_t get_pixel(SDL_Surface *surface, uint32_t x, uint32_t y)
|
||||
{
|
||||
uint32_t pixel = 0;
|
||||
uint8_t *pointer = surface->pixels + y * surface->pitch;
|
||||
pointer += x * surface->format->BytesPerPixel;
|
||||
memcpy(&pixel, pointer, surface->format->BytesPerPixel);
|
||||
return pixel;
|
||||
}
|
||||
|
||||
void set_pixel(SDL_Surface *surface, uint32_t x, uint32_t y, uint32_t color)
|
||||
{
|
||||
uint8_t *pointer = surface->pixels + y * surface->pitch;
|
||||
pointer += x * surface->format->BytesPerPixel;
|
||||
memcpy(pointer, &color, surface->format->BytesPerPixel);
|
||||
}
|
||||
|
||||
float ren_draw_text(RenFont **fonts, const char *text, float x, int y, RenColor color) {
|
||||
SDL_Surface *surface = renwin_get_surface(&window_renderer);
|
||||
const RenRect clip = window_renderer.clip;
|
||||
|
@ -253,9 +269,9 @@ float ren_draw_text(RenFont **fonts, const char *text, float x, int y, RenColor
|
|||
const int surface_scale = renwin_surface_scale(&window_renderer);
|
||||
float pen_x = x * surface_scale;
|
||||
y *= surface_scale;
|
||||
int bytes_per_pixel = surface->format->BytesPerPixel;
|
||||
// int bytes_per_pixel = surface->format->BytesPerPixel;
|
||||
const char* end = text + strlen(text);
|
||||
unsigned char* destination_pixels = surface->pixels;
|
||||
// unsigned char* destination_pixels = surface->pixels;
|
||||
int clip_end_x = clip.x + clip.width, clip_end_y = clip.y + clip.height;
|
||||
|
||||
while (text < end) {
|
||||
|
@ -283,16 +299,36 @@ float ren_draw_text(RenFont **fonts, const char *text, float x, int y, RenColor
|
|||
start_x += offset;
|
||||
glyph_start += offset;
|
||||
}
|
||||
unsigned int* destination_pixel = (unsigned int*)&destination_pixels[surface->pitch * target_y + start_x * bytes_per_pixel];
|
||||
unsigned char* source_pixel = &source_pixels[line * set->surface->pitch + glyph_start * (font->antialiasing == FONT_ANTIALIASING_SUBPIXEL ? 3 : 1)];
|
||||
for (int x = glyph_start; x < glyph_end; ++x) {
|
||||
unsigned int destination_color = *destination_pixel;
|
||||
SDL_Color dst = { (destination_color & surface->format->Rmask) >> surface->format->Rshift, (destination_color & surface->format->Gmask) >> surface->format->Gshift, (destination_color & surface->format->Bmask) >> surface->format->Bshift, (destination_color & surface->format->Amask) >> surface->format->Ashift };
|
||||
SDL_Color src = { *(font->antialiasing == FONT_ANTIALIASING_SUBPIXEL ? source_pixel++ : source_pixel), *(font->antialiasing == FONT_ANTIALIASING_SUBPIXEL ? source_pixel++ : source_pixel), *source_pixel++ };
|
||||
// unsigned int* destination_pixel = (unsigned int*)&destination_pixels[surface->pitch * target_y + start_x * bytes_per_pixel];
|
||||
// unsigned char* source_pixel = &source_pixels[line * set->surface->pitch + glyph_start * (font->antialiasing == FONT_ANTIALIASING_SUBPIXEL ? 3 : 1)];
|
||||
// for (int x = glyph_start; x < glyph_end; ++x) {
|
||||
// unsigned int destination_color = *destination_pixel;
|
||||
// SDL_Color dst = { (destination_color & surface->format->Rmask) >> surface->format->Rshift, (destination_color & surface->format->Gmask) >> surface->format->Gshift, (destination_color & surface->format->Bmask) >> surface->format->Bshift, (destination_color & surface->format->Amask) >> surface->format->Ashift };
|
||||
// SDL_Color src = { *(font->antialiasing == FONT_ANTIALIASING_SUBPIXEL ? source_pixel++ : source_pixel), *(font->antialiasing == FONT_ANTIALIASING_SUBPIXEL ? source_pixel++ : source_pixel), *source_pixel++ };
|
||||
int px = start_x;
|
||||
uint8_t* source_pixel = &source_pixels[line * set->surface->pitch + glyph_start * (font->antialiasing == FONT_ANTIALIASING_SUBPIXEL ? 3 : 1)];
|
||||
for (int x = glyph_start; x < glyph_end; ++x, ++px) {
|
||||
uint32_t destination_color = get_pixel(surface, px, target_y);
|
||||
SDL_Color dst;
|
||||
SDL_Color src;
|
||||
SDL_GetRGBA(destination_color, surface->format, &dst.r, &dst.g, &dst.b, &dst.a);
|
||||
|
||||
if (font->antialiasing == FONT_ANTIALIASING_SUBPIXEL) {
|
||||
src.r = *(source_pixel++);
|
||||
src.g = *(source_pixel++);
|
||||
}
|
||||
else {
|
||||
src.r = *(source_pixel);
|
||||
src.g = *(source_pixel);
|
||||
}
|
||||
|
||||
src.b = *(source_pixel++);
|
||||
src.a = 0xFF;
|
||||
r = (color.r * src.r * color.a + dst.r * (65025 - src.r * color.a) + 32767) / 65025;
|
||||
g = (color.g * src.g * color.a + dst.g * (65025 - src.g * color.a) + 32767) / 65025;
|
||||
b = (color.b * src.b * color.a + dst.b * (65025 - src.b * color.a) + 32767) / 65025;
|
||||
*destination_pixel++ = dst.a << surface->format->Ashift | r << surface->format->Rshift | g << surface->format->Gshift | b << surface->format->Bshift;
|
||||
// *destination_pixel++ = dst.a << surface->format->Ashift | r << surface->format->Rshift | g << surface->format->Gshift | b << surface->format->Bshift;
|
||||
set_pixel(surface, px, target_y, SDL_MapRGBA(surface->format, r, g, b, dst.a));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -332,15 +368,16 @@ void ren_draw_rect(RenRect rect, RenColor color) {
|
|||
y2 = y2 > clip.y + clip.height ? clip.y + clip.height : y2;
|
||||
|
||||
SDL_Surface *surface = renwin_get_surface(&window_renderer);
|
||||
uint32_t *d = surface->pixels;
|
||||
// uint32_t *d = surface->pixels;
|
||||
|
||||
// #ifdef __amigaos4__
|
||||
// d += x1 + y1 * surface->pitch/sizeof(uint32_t);
|
||||
// int dr = surface->pitch/sizeof(uint32_t) - (x2 - x1);
|
||||
// #else
|
||||
// d += x1 + y1 * surface->w;
|
||||
// int dr = surface->w - (x2 - x1);
|
||||
// #endif
|
||||
|
||||
#ifdef __amigaos4__
|
||||
d += x1 + y1 * surface->pitch/sizeof(uint32_t);
|
||||
int dr = surface->pitch/sizeof(uint32_t) - (x2 - x1);
|
||||
#else
|
||||
d += x1 + y1 * surface->w;
|
||||
int dr = surface->w - (x2 - x1);
|
||||
#endif
|
||||
if (color.a == 0xff) {
|
||||
uint32_t translated = SDL_MapRGB(surface->format, color.r, color.g, color.b);
|
||||
SDL_Rect rect = { x1, y1, x2 - x1, y2 - y1 };
|
||||
|
@ -348,13 +385,19 @@ void ren_draw_rect(RenRect rect, RenColor color) {
|
|||
} else {
|
||||
RenColor current_color;
|
||||
RenColor blended_color;
|
||||
// printf("DBG: surface->w %d\tsurface->pitch %d\tsurface->pitch/4 %d\n", surface->w, surface->pitch, surface->pitch/4);
|
||||
|
||||
for (int j = y1; j < y2; j++) {
|
||||
for (int i = x1; i < x2; i++, d++) {
|
||||
SDL_GetRGB(*d, surface->format, ¤t_color.r, ¤t_color.g, ¤t_color.b);
|
||||
// for (int i = x1; i < x2; i++, d++) {
|
||||
for (int i = x1; i < x2; i++) {
|
||||
// SDL_GetRGB(*d, surface->format, ¤t_color.r, ¤t_color.g, ¤t_color.b);
|
||||
uint32_t p = get_pixel(surface, i, j);
|
||||
SDL_GetRGB(p, surface->format, ¤t_color.r, ¤t_color.g, ¤t_color.b);
|
||||
blended_color = blend_pixel(current_color, color);
|
||||
*d = SDL_MapRGB(surface->format, blended_color.r, blended_color.g, blended_color.b);
|
||||
// *d = SDL_MapRGB(surface->format, blended_color.r, blended_color.g, blended_color.b);
|
||||
set_pixel(surface, i, j, SDL_MapRGB(surface->format, blended_color.r, blended_color.g, blended_color.b));
|
||||
}
|
||||
d += dr;
|
||||
// d += dr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue