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.
This commit is contained in:
Francesco Abbate 2021-05-10 15:08:56 +02:00
parent 0d48f9e8b5
commit 527b11029e
5 changed files with 87 additions and 31 deletions

View File

@ -104,11 +104,20 @@ command.add(nil, {
end, function (text) end, function (text)
return common.home_encode_list(common.path_suggest(common.home_expand(text))) return common.home_encode_list(common.path_suggest(common.home_expand(text)))
end, nil, function(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 if err then
core.error("Cannot open file %q: %q", text, err) 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 elseif path_stat.type == 'dir' then
core.error("Cannot open %q, is a folder", text) core.error("Cannot open %s, is a folder", text)
else else
return true return true
end end

View File

@ -41,7 +41,12 @@ end
local function save(filename) 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 local saved_filename = doc().filename
core.on_doc_save(saved_filename) core.on_doc_save(saved_filename)
core.log("Saved \"%s\"", saved_filename) core.log("Saved \"%s\"", saved_filename)
@ -356,12 +361,14 @@ local commands = {
end end
core.command_view:set_text(old_filename) core.command_view:set_text(old_filename)
core.command_view:enter("Rename", function(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) core.log("Renamed \"%s\" to \"%s\"", old_filename, filename)
if filename ~= old_filename then if filename ~= old_filename then
os.remove(old_filename) os.remove(old_filename)
end end
end, common.path_suggest) end, function (text)
return common.home_encode_list(common.path_suggest(common.home_expand(text)))
end)
end, end,
} }

View File

@ -203,6 +203,12 @@ function common.basename(path)
end 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) function common.home_encode(text)
if HOME and string.find(text, HOME, 1, true) == 1 then if HOME and string.find(text, HOME, 1, true) == 1 then
local dir_pos = #HOME + 1 local dir_pos = #HOME + 1
@ -230,16 +236,6 @@ function common.home_expand(text)
end 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 function split_on_slash(s, sep_pattern)
local t = {} local t = {}
for fragment in string.gmatch(s, "([^/\\]+)") do for fragment in string.gmatch(s, "([^/\\]+)") do
@ -249,8 +245,27 @@ local function split_on_slash(s, sep_pattern)
end 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) 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 end

View File

@ -36,11 +36,15 @@ local function splice(t, at, remove, insert)
end end
function Doc:new(filename) function Doc:new(filename, abs_filename, new_file)
self.new_file = new_file
self:reset() self:reset()
if filename then if filename then
self:set_filename(filename, abs_filename)
if not new_file then
self:load(filename) self:load(filename)
end end
end
end end
@ -65,16 +69,15 @@ function Doc:reset_syntax()
end end
function Doc:set_filename(filename) function Doc:set_filename(filename, abs_filename)
self.filename = filename self.filename = filename
self.abs_filename = system.absolute_path(filename) self.abs_filename = abs_filename
end end
function Doc:load(filename) function Doc:load(filename)
local fp = assert( io.open(filename, "rb") ) local fp = assert( io.open(filename, "rb") )
self:reset() self:reset()
self:set_filename(filename)
self.lines = {} self.lines = {}
for line in fp:lines() do for line in fp:lines() do
if line:byte(-1) == 13 then if line:byte(-1) == 13 then
@ -91,17 +94,20 @@ function Doc:load(filename)
end end
function Doc:save(filename) function Doc:save(filename, abs_filename)
filename = filename or assert(self.filename, "no filename set to default to") 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") ) local fp = assert( io.open(filename, "wb") )
for _, line in ipairs(self.lines) do for _, line in ipairs(self.lines) do
if self.crlf then line = line:gsub("\n", "\r\n") end if self.crlf then line = line:gsub("\n", "\r\n") end
fp:write(line) fp:write(line)
end end
fp:close() fp:close()
if filename then self:set_filename(filename, abs_filename)
self:set_filename(filename) self.new_file = false
end
self:reset_syntax() self:reset_syntax()
self:clean() self:clean()
end end
@ -113,7 +119,7 @@ end
function Doc:is_dirty() 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 end

View File

@ -812,10 +812,30 @@ function core.normalize_to_project_dir(filename)
end 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) function core.open_doc(filename)
local new_file = not filename or not system.get_file_info(filename)
local abs_filename
if filename then if filename then
-- normalize filename and set absolute filename then
-- try to find existing doc for filename -- 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 for _, doc in ipairs(core.docs) do
if doc.abs_filename and abs_filename == doc.abs_filename then if doc.abs_filename and abs_filename == doc.abs_filename then
return doc return doc
@ -823,8 +843,7 @@ function core.open_doc(filename)
end end
end end
-- no existing doc for filename; create new -- no existing doc for filename; create new
filename = filename and core.normalize_to_project_dir(filename) local doc = Doc(filename, abs_filename, new_file)
local doc = Doc(filename)
table.insert(core.docs, doc) table.insert(core.docs, doc)
core.log_quiet(filename and "Opened doc \"%s\"" or "Opened new doc", filename) core.log_quiet(filename and "Opened doc \"%s\"" or "Opened new doc", filename)
return doc return doc