From 708c2983ef8175e791b3749003d03e9507d9362c Mon Sep 17 00:00:00 2001 From: Francesco Abbate Date: Mon, 10 May 2021 15:08:56 +0200 Subject: [PATCH] Create new document if file doesn't exist If a non-existing file is specified with the command "core:open-file" a new document is opened with the given filename provided the directory already exists. The flag new_file is set to true in the Doc instance. The file will be actually created only when the "save" command is used. The document will be marked with the "*" event when no changes are done to mean that it is a new file and is not yet saved. The function common.normalize_path now process the .. and . in the filename. Before was not needed because system.absolute_path already get rid of them but now we need to have the absolute path of files that not yet exists so we cannot use system.absolute_path. --- data/core/commands/core.lua | 17 +++++++++++++---- data/core/commands/doc.lua | 13 ++++++++++--- data/core/common.lua | 37 ++++++++++++++++++++++++++----------- data/core/doc/init.lua | 28 +++++++++++++++++----------- data/core/init.lua | 25 ++++++++++++++++++++++--- 5 files changed, 88 insertions(+), 32 deletions(-) diff --git a/data/core/commands/core.lua b/data/core/commands/core.lua index c8233062..18b76bc9 100644 --- a/data/core/commands/core.lua +++ b/data/core/commands/core.lua @@ -95,11 +95,20 @@ command.add(nil, { end, function (text) return common.home_encode_list(common.path_suggest(common.home_expand(text))) end, nil, function(text) - local path_stat, err = system.get_file_info(common.home_expand(text)) + local filename = common.home_expand(text) + local path_stat, err = system.get_file_info(filename) if err then - core.error("Cannot open file %q: %q", text, err) - elseif path_stat.type == 'dir' then - core.error("Cannot open %q, is a folder", text) + if err:find("No such file", 1, true) then + -- check if the containing directory exists + local dirname = common.dirname(filename) + local dir_stat = dirname and system.get_file_info(dirname) + if not dirname or (dir_stat and dir_stat.type == 'dir') then + return true + end + end + core.error("Cannot open file %s: %s", text, err) + elseif path_stat.type == 'dir' then + core.error("Cannot open %s, is a folder", text) else return true end diff --git a/data/core/commands/doc.lua b/data/core/commands/doc.lua index 965f3451..9b6ba9af 100644 --- a/data/core/commands/doc.lua +++ b/data/core/commands/doc.lua @@ -68,7 +68,12 @@ end local function save(filename) - doc():save(filename and core.normalize_to_project_dir(filename)) + local abs_filename + if filename then + filename = core.normalize_to_project_dir(filename) + abs_filename = core.project_absolute_path(filename) + end + doc():save(filename, abs_filename) local saved_filename = doc().filename core.on_doc_save(saved_filename) core.log("Saved \"%s\"", saved_filename) @@ -323,12 +328,14 @@ local commands = { end core.command_view:set_text(old_filename) core.command_view:enter("Rename", function(filename) - doc():save(filename) + save(common.home_expand(filename)) core.log("Renamed \"%s\" to \"%s\"", old_filename, filename) if filename ~= old_filename then os.remove(old_filename) end - end, common.path_suggest) + end, function (text) + return common.home_encode_list(common.path_suggest(common.home_expand(text))) + end) end, } diff --git a/data/core/common.lua b/data/core/common.lua index 022dd1dc..b47cdbbb 100644 --- a/data/core/common.lua +++ b/data/core/common.lua @@ -203,6 +203,12 @@ function common.basename(path) end +-- can return nil if there is no directory part in the path +function common.dirname(path) + return path:match("(.+)[\\/][^\\/]+$") +end + + function common.home_encode(text) if HOME and string.find(text, HOME, 1, true) == 1 then local dir_pos = #HOME + 1 @@ -228,16 +234,6 @@ function common.home_expand(text) end -function common.normalize_path(filename) - if PATHSEP == '\\' then - filename = filename:gsub('[/\\]', '\\') - local drive, rem = filename:match('^([a-zA-Z])(:.*)') - return drive and drive:upper() .. rem or filename - end - return filename -end - - local function split_on_slash(s, sep_pattern) local t = {} for fragment in string.gmatch(s, "([^/\\]+)") do @@ -247,8 +243,27 @@ local function split_on_slash(s, sep_pattern) end +function common.normalize_path(filename) + if PATHSEP == '\\' then + filename = filename:gsub('[/\\]', '\\') + local drive, rem = filename:match('^([a-zA-Z])(:.*)') + filename = drive and drive:upper() .. rem or filename + end + local parts = split_on_slash(filename, PATHSEP) + local accu = {} + for _, part in ipairs(parts) do + if part == '..' then + table.remove(accu) + elseif part ~= '.' then + table.insert(accu, part) + end + end + return table.concat(accu, PATHSEP) +end + + function common.path_belongs_to(filename, path) - return filename and string.find(filename, path .. PATHSEP, 1, true) == 1 + return string.find(filename, path .. PATHSEP, 1, true) == 1 end diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index 39fae9ca..ca41cdad 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -36,10 +36,14 @@ local function splice(t, at, remove, insert) end -function Doc:new(filename) +function Doc:new(filename, abs_filename, new_file) + self.new_file = new_file self:reset() if filename then - self:load(filename) + self:set_filename(filename, abs_filename) + if not new_file then + self:load(filename) + end end end @@ -65,16 +69,15 @@ function Doc:reset_syntax() end -function Doc:set_filename(filename) +function Doc:set_filename(filename, abs_filename) self.filename = filename - self.abs_filename = system.absolute_path(filename) + self.abs_filename = abs_filename end function Doc:load(filename) local fp = assert( io.open(filename, "rb") ) self:reset() - self:set_filename(filename) self.lines = {} for line in fp:lines() do if line:byte(-1) == 13 then @@ -91,17 +94,20 @@ function Doc:load(filename) end -function Doc:save(filename) - filename = filename or assert(self.filename, "no filename set to default to") +function Doc:save(filename, abs_filename) + if not filename then + assert(self.filename, "no filename set to default to") + filename = self.filename + abs_filename = self.abs_filename + end local fp = assert( io.open(filename, "wb") ) for _, line in ipairs(self.lines) do if self.crlf then line = line:gsub("\n", "\r\n") end fp:write(line) end fp:close() - if filename then - self:set_filename(filename) - end + self:set_filename(filename, abs_filename) + self.new_file = false self:reset_syntax() self:clean() end @@ -113,7 +119,7 @@ end function Doc:is_dirty() - return self.clean_change_id ~= self:get_change_id() + return self.clean_change_id ~= self:get_change_id() or self.new_file end diff --git a/data/core/init.lua b/data/core/init.lua index 96187167..f008004c 100644 --- a/data/core/init.lua +++ b/data/core/init.lua @@ -733,10 +733,30 @@ function core.normalize_to_project_dir(filename) end +-- The function below works like system.absolute_path except it +-- doesn't fail if the file does not exist. We consider that the +-- current dir is core.project_dir so relative filename are considered +-- to be in core.project_dir. +-- Please note that .. or . in the filename are not taken into account. +-- This function should get only filenames normalized using +-- common.normalize_path function. +function core.project_absolute_path(filename) + if filename:match('^%a:\\') or filename:find('/', 1, true) then + return filename + else + return core.project_dir .. PATHSEP .. filename + end +end + + function core.open_doc(filename) + local new_file = not filename or not system.get_file_info(filename) + local abs_filename if filename then + -- normalize filename and set absolute filename then -- try to find existing doc for filename - local abs_filename = system.absolute_path(filename) + filename = core.normalize_to_project_dir(filename) + abs_filename = core.project_absolute_path(filename) for _, doc in ipairs(core.docs) do if doc.abs_filename and abs_filename == doc.abs_filename then return doc @@ -744,8 +764,7 @@ function core.open_doc(filename) end end -- no existing doc for filename; create new - filename = filename and core.normalize_to_project_dir(filename) - local doc = Doc(filename) + local doc = Doc(filename, abs_filename, new_file) table.insert(core.docs, doc) core.log_quiet(filename and "Opened doc \"%s\"" or "Opened new doc", filename) return doc