Compare commits

...

7 Commits

Author SHA1 Message Date
Francesco Abbate 5c8ba47f2c Add priority threads to keep editor busy 2021-10-18 23:26:33 +02:00
Francesco Abbate 7027f15d55 Add a mechanism for incremental loading 2021-10-18 23:24:23 +02:00
Francesco Abbate 1794765f07 Fix problem with treeview keeping the editor busy
Fix a problem introduced when fixing the dirty pixel problem, commit
cb08c5c. The node, when determining the layout was rounding the size
of the fixed-size view. In turns this latter was calling move_towards
to the default_size it wanted. If default_size was non-integer the
value vas never archieved because it was rounded during layout and
move_towars was keeping the editor busy by setting the
core.need_redraw flag.
2021-10-14 09:13:06 +02:00
Francesco Abbate bed103b7a6 Fix error introduced with 43fc35d7 2021-10-12 22:19:24 +02:00
Francesco Abbate 80db3cb027 Fix problem with treeview x resizing
The x size of the treeview plugin cannot really change expect if explicitly
resized.

The call to move_towards for x seems to raise a state where core.redraw is
always set to true and this prevent the application to go idle.

It is seen after the introduction of the dmon directory monitoring but it
is not clear why it wasn't seen before.
2021-10-12 18:06:00 +02:00
Francesco Abbate a0f4ac93e1 Do not use normalize_path when not needed 2021-10-12 16:08:06 +02:00
Francesco Abbate 43fc35d7dc First attempt to treat correctly network volumes
On windows paths belonging to network volumes will be gives like:

\\address\share-name\path

Now the code recognize these paths and treat them correctly.
2021-10-12 14:28:28 +02:00
3 changed files with 118 additions and 64 deletions

View File

@ -280,24 +280,61 @@ local function split_on_slash(s, sep_pattern)
end end
function common.normalize_path(filename) -- The filename argument given to the function is supposed to
-- come from system.absolute_path and as such should be an
-- absolute path without . or .. elements.
-- This function exists because on Windows the drive letter returned
-- by system.absolute_path is sometimes with a lower case and sometimes
-- with an upper case to we normalize to upper case.
function common.normalize_volume(filename)
if not filename then return end if not filename then return end
if PATHSEP == '\\' then
local drive, rem = filename:match('^([a-zA-Z]:\\)(.*)')
if drive then
return drive:upper() .. rem
end
end
return filename
end
function common.normalize_path(filename)
if not filename then return end
local volume
if PATHSEP == '\\' then if PATHSEP == '\\' then
filename = filename:gsub('[/\\]', '\\') filename = filename:gsub('[/\\]', '\\')
local drive, rem = filename:match('^([a-zA-Z])(:.*)') local drive, rem = filename:match('^([a-zA-Z]:\\)(.*)')
filename = drive and drive:upper() .. rem or filename if drive then
volume, filename = drive:upper(), rem
else
drive, rem = filename:match('^(\\\\[^\\]+\\[^\\]+\\)(.*)')
if drive then
volume, filename = drive, rem
end
end
else
local relpath = filename:match('^/(.+)')
if relpath then
volume, filename = "/", relpath
end
end end
local parts = split_on_slash(filename, PATHSEP) local parts = split_on_slash(filename, PATHSEP)
local accu = {} local accu = {}
for _, part in ipairs(parts) do for _, part in ipairs(parts) do
if part == '..' and #accu > 0 and accu[#accu] ~= ".." then if part == '..' then
table.remove(accu) if #accu > 0 and accu[#accu] ~= ".." then
table.remove(accu)
elseif volume then
error("invalid path " .. volume .. filename)
else
table.insert(accu, part)
end
elseif part ~= '.' then elseif part ~= '.' then
table.insert(accu, part) table.insert(accu, part)
end end
end end
local npath = table.concat(accu, PATHSEP) local npath = table.concat(accu, PATHSEP)
return npath == "" and PATHSEP or npath return (volume or "") .. (npath == "" and PATHSEP or npath)
end end

View File

@ -36,7 +36,7 @@ end
local function update_recents_project(action, dir_path_abs) local function update_recents_project(action, dir_path_abs)
local dirname = common.normalize_path(dir_path_abs) local dirname = common.normalize_volume(dir_path_abs)
if not dirname then return end if not dirname then return end
local recents = core.recent_projects local recents = core.recent_projects
local n = #recents local n = #recents
@ -56,7 +56,7 @@ function core.set_project_dir(new_dir, change_project_fn)
local chdir_ok = pcall(system.chdir, new_dir) local chdir_ok = pcall(system.chdir, new_dir)
if chdir_ok then if chdir_ok then
if change_project_fn then change_project_fn() end if change_project_fn then change_project_fn() end
core.project_dir = common.normalize_path(new_dir) core.project_dir = common.normalize_volume(new_dir)
core.project_directories = {} core.project_directories = {}
core.add_project_directory(new_dir) core.add_project_directory(new_dir)
return true return true
@ -173,45 +173,10 @@ local function show_max_files_warning()
) )
end end
-- Populate a project folder top directory by scanning the filesystem.
local function scan_project_folder(index)
local dir = core.project_directories[index]
local t, entries_count = get_directory_files(dir, dir.name, "", {}, nil, config.max_project_files)
if entries_count > config.max_project_files then
dir.files_limit = true
-- 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")
if core.status_view then -- May be not yet initialized.
show_max_files_warning()
end
else
dir.watch_id = system.watch_dir(dir.name, true)
end
dir.files = t
core.dir_rescan_add_job(dir, ".")
end
local function watch_folder(dir)
function core.add_project_directory(path) local rec = dir.entries_count <= config.max_project_files or PLATFORM ~= "Linux"
-- top directories has a file-like "item" but the item.filename dir.watch_id = system.watch_dir(dir.name, rec)
-- 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_path(path)
local dir = {
name = path,
item = {filename = common.basename(path), type = "dir", topdir = true},
files_limit = false,
is_dirty = true,
shown_subdir = {},
}
table.insert(core.project_directories, dir)
scan_project_folder(#core.project_directories)
if path == core.project_dir then
core.project_files = dir.files
end
core.redraw = true
end end
@ -304,12 +269,59 @@ local function rescan_project_subdir(dir, filename_rooted)
local filename = strip_leading_path(filename_rooted) local filename = strip_leading_path(filename_rooted)
index, n = project_subdir_bounds(dir, filename) index, n = project_subdir_bounds(dir, filename)
end end
if not files_list_match(dir.files, index, n, new_files) then if not files_list_match(dir.files, index, n, new_files) then
files_list_replace(dir.files, index, n, new_files) files_list_replace(dir.files, index, n, new_files)
dir.is_dirty = true dir.is_dirty = true
return true
end end
return #new_files - n
end
-- Populate a project folder top directory by scanning the filesystem.
local function scan_project_folder(index)
local dir = core.project_directories[index]
dir.files, dir.entries_count = get_directory_files(dir, dir.name, "", {}, nil, 0)
local dirs_list = {}
for _, info in pairs(dir.files) do
if info.type == 'dir' then
dirs_list[#dirs_list + 1] = info
end
end
core.add_thread(function()
for _, info in pairs(dirs_list) do
local n = rescan_project_subdir(dir, PATHSEP .. info.filename)
dir.entries_count = dir.entries_count + n
if dir.entries_count > config.max_project_files then
dir.files_limit = true
show_max_files_warning()
break
end
coroutine.yield()
end
watch_folder(dir)
core.dir_rescan_add_job(dir, ".")
end, nil, true)
end
function core.add_project_directory(path)
-- top directories has a file-like "item" but the item.filename
-- 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 = {
name = path,
item = {filename = common.basename(path), type = "dir", topdir = true},
files_limit = false,
is_dirty = true,
shown_subdir = {},
}
table.insert(core.project_directories, dir)
scan_project_folder(#core.project_directories)
if path == core.project_dir then
core.project_files = dir.files
end
core.redraw = true
end end
@ -572,9 +584,9 @@ function core.init()
Doc = require "core.doc" Doc = require "core.doc"
if PATHSEP == '\\' then if PATHSEP == '\\' then
USERDIR = common.normalize_path(USERDIR) USERDIR = common.normalize_volume(USERDIR)
DATADIR = common.normalize_path(DATADIR) DATADIR = common.normalize_volume(DATADIR)
EXEDIR = common.normalize_path(EXEDIR) EXEDIR = common.normalize_volume(EXEDIR)
end end
do do
@ -919,10 +931,17 @@ function core.show_title_bar(show)
end end
function core.add_thread(f, weak_ref) function core.has_priority_threads()
for _, t in ipairs(core.threads) do
if t.prio then return true end
end
end
function core.add_thread(f, weak_ref, priority)
local key = weak_ref or #core.threads + 1 local key = weak_ref or #core.threads + 1
local fn = function() return core.try(f) end local fn = function() return core.try(f) end
core.threads[key] = { cr = coroutine.create(fn), wake = 0 } core.threads[key] = { cr = coroutine.create(fn), wake = 0, prio = priority }
return key return key
end end
@ -1066,12 +1085,6 @@ end
local scheduled_rescan = {} local scheduled_rescan = {}
function core.has_pending_rescan()
for _ in pairs(scheduled_rescan) do
return true
end
end
function core.dir_rescan_add_job(dir, filepath) function core.dir_rescan_add_job(dir, filepath)
local dirpath = filepath:match("^(.+)[/\\].+$") local dirpath = filepath:match("^(.+)[/\\].+$")
@ -1108,8 +1121,8 @@ function core.dir_rescan_add_job(dir, filepath)
local rescan = scheduled_rescan[abs_dirpath] local rescan = scheduled_rescan[abs_dirpath]
if not rescan then return end if not rescan then return end
if system.get_time() > rescan.time_limit then if system.get_time() > rescan.time_limit then
local has_changes = rescan_project_subdir(rescan.dir, rescan.path) local n_changes = rescan_project_subdir(rescan.dir, rescan.path)
if has_changes then if n_changes > 0 then
core.redraw = true -- we run without an event, from a thread core.redraw = true -- we run without an event, from a thread
rescan.time_limit = new_time rescan.time_limit = new_time
else else
@ -1119,7 +1132,7 @@ function core.dir_rescan_add_job(dir, filepath)
end end
coroutine.yield(0.2) coroutine.yield(0.2)
end end
end) end, nil, true)
end end
@ -1286,7 +1299,7 @@ function core.run()
while true do while true do
core.frame_start = system.get_time() core.frame_start = system.get_time()
local did_redraw = core.step() local did_redraw = core.step()
local need_more_work = run_threads() or core.has_pending_rescan() local need_more_work = run_threads() or core.has_priority_threads()
if core.restart_request or core.quit_request then break end if core.restart_request or core.quit_request then break end
if not did_redraw and not need_more_work then if not did_redraw and not need_more_work then
idle_iterations = idle_iterations + 1 idle_iterations = idle_iterations + 1

View File

@ -412,7 +412,7 @@ end
-- axis are swapped; this function lets us use the same code for both -- axis are swapped; this function lets us use the same code for both
local function calc_split_sizes(self, x, y, x1, x2, y1, y2) local function calc_split_sizes(self, x, y, x1, x2, y1, y2)
local ds = ((x1 and x1 < 1) or (x2 and x2 < 1)) and 0 or style.divider_size local ds = ((x1 and x1 < 1) or (x2 and x2 < 1)) and 0 or style.divider_size
local n = math.floor(x1 and x1 + ds or (x2 and self.size[x] - x2 or self.size[x] * self.divider)) local n = x1 and x1 + ds or (x2 and self.size[x] - x2 or math.floor(self.size[x] * self.divider))
self.a.position[x] = self.position[x] self.a.position[x] = self.position[x]
self.a.position[y] = self.position[y] self.a.position[y] = self.position[y]
self.a.size[x] = n - ds self.a.size[x] = n - ds
@ -675,6 +675,10 @@ end
function Node:resize(axis, value) function Node:resize(axis, value)
-- the application works fine with non-integer values but to have pixel-perfect
-- placements of view elements, like the scrollbar, we round the value to be
-- an integer.
value = math.floor(value)
if self.type == 'leaf' then if self.type == 'leaf' then
-- If it is not locked we don't accept the -- If it is not locked we don't accept the
-- resize operation here because for proportional panes the resize is -- resize operation here because for proportional panes the resize is