diff options
Diffstat (limited to 'core/file_io.lua')
-rw-r--r-- | core/file_io.lua | 173 |
1 files changed, 79 insertions, 94 deletions
diff --git a/core/file_io.lua b/core/file_io.lua index 6bd3692d..6760c4cd 100644 --- a/core/file_io.lua +++ b/core/file_io.lua @@ -27,12 +27,11 @@ -- Arguments: -- -- * _`filename`_: The filename of the file being saved. --- * _`saved_as`_: Whether or not the file was saved under a different --- filename. +-- * _`saved_as`_: Whether or not the file was saved under a different filename. -- @field _G.events.FILE_CHANGED (string) -- Emitted when Textadept detects that an open file was modified externally. --- When connecting to this event, connect with an index of 1 in order to --- override the default prompt to reload the file. +-- When connecting to this event, connect with an index of 1 in order to override the default +-- prompt to reload the file. -- Arguments: -- -- * _`filename`_: The filename externally modified. @@ -42,7 +41,9 @@ module('io')]] -- Events. +-- LuaFormatter off local file_io_events = {'file_opened','file_before_reload','file_after_reload','file_before_save','file_after_save','file_changed'} +-- LuaFormatter on for _, v in ipairs(file_io_events) do events[v:upper()] = v end io.quick_open_max = 1000 @@ -55,16 +56,15 @@ io.recent_files = {} --- -- List of encodings to attempt to decode files as. --- You should add to this list if you get a "Conversion failed" error when --- trying to open a file whose encoding is not recognized. Valid encodings are --- [GNU iconv's encodings][] and include: +-- You should add to this list if you get a "Conversion failed" error when trying to open a file +-- whose encoding is not recognized. Valid encodings are [GNU iconv's encodings][] and include: -- --- * European: ASCII, ISO-8859-{1,2,3,4,5,7,9,10,13,14,15,16}, KOI8-R, KOI8-U, --- KOI8-RU, CP{1250,1251,1252,1253,1254,1257}, CP{850,866,1131}, --- Mac{Roman,CentralEurope,Iceland,Croatian,Romania}, --- Mac{Cyrillic,Ukraine,Greek,Turkish}, Macintosh. --- * Unicode: UTF-8, UCS-2, UCS-2BE, UCS-2LE, UCS-4, UCS-4BE, UCS-4LE, UTF-16, --- UTF-16BE, UTF-16LE, UTF-32, UTF-32BE, UTF-32LE, UTF-7, C99, JAVA. +-- * European: ASCII, ISO-8859-{1,2,3,4,5,7,9,10,13,14,15,16}, KOI8-R, +-- KOI8-U, KOI8-RU, CP{1250,1251,1252,1253,1254,1257}, CP{850,866,1131}, +-- Mac{Roman,CentralEurope,Iceland,Croatian,Romania}, Mac{Cyrillic,Ukraine,Greek,Turkish}, +-- Macintosh. +-- * Unicode: UTF-8, UCS-2, UCS-2BE, UCS-2LE, UCS-4, UCS-4BE, UCS-4LE, UTF-16, UTF-16BE, +-- UTF-16LE, UTF-32, UTF-32BE, UTF-32LE, UTF-7, C99, JAVA. -- -- [GNU iconv's encodings]: https://www.gnu.org/software/libiconv/ -- @usage io.encodings[#io.encodings + 1] = 'UTF-32' @@ -73,14 +73,12 @@ io.recent_files = {} io.encodings = {'UTF-8', 'ASCII', 'CP1252', 'UTF-16'} --- --- Opens *filenames*, a string filename or list of filenames, or the --- user-selected filename(s). +-- Opens *filenames*, a string filename or list of filenames, or the user-selected filename(s). -- Emits a `FILE_OPENED` event. --- @param filenames Optional string filename or table of filenames to open. If --- `nil`, the user is prompted with a fileselect dialog. --- @param encodings Optional string encoding or table of encodings file contents --- are in (one encoding per file). If `nil`, encoding auto-detection is --- attempted via `io.encodings`. +-- @param filenames Optional string filename or table of filenames to open. If `nil`, the user +-- is prompted with a fileselect dialog. +-- @param encodings Optional string encoding or table of encodings file contents are in (one +-- encoding per file). If `nil`, encoding auto-detection is attempted via `io.encodings`. -- @see _G.events -- @name open_file function io.open_file(filenames, encodings) @@ -88,8 +86,7 @@ function io.open_file(filenames, encodings) if not assert_type(filenames, 'string/table/nil', 1) then filenames = ui.dialogs.fileselect{ title = _L['Open File'], select_multiple = true, - with_directory = (buffer.filename or ''):match('^.+[/\\]') or - lfs.currentdir(), + with_directory = (buffer.filename or ''):match('^.+[/\\]') or lfs.currentdir(), width = CURSES and ui.size[1] - 2 or nil } if not filenames then return end @@ -99,7 +96,10 @@ function io.open_file(filenames, encodings) for i = 1, #filenames do local filename = lfs.abspath((filenames[i]:gsub('^file://', ''))) for _, buf in ipairs(_BUFFERS) do - if filename == buf.filename then view:goto_buffer(buf) goto continue end + if filename == buf.filename then + view:goto_buffer(buf) + goto continue + end end local text = '' @@ -189,7 +189,10 @@ end -- LuaDoc is in core/.buffer.luadoc. local function save(buffer) if not buffer then buffer = _G.buffer end - if not buffer.filename then buffer:save_as() return end + if not buffer.filename then + buffer:save_as() + return + end events.emit(events.FILE_BEFORE_SAVE, buffer.filename) local text = buffer:get_text() if buffer.encoding then text = text:iconv(buffer.encoding, 'UTF-8') end @@ -221,9 +224,7 @@ end -- @see buffer.save -- @name save_all_files function io.save_all_files() - for _, buffer in ipairs(_BUFFERS) do - if buffer.filename and buffer.modify then buffer:save() end - end + for _, buffer in ipairs(_BUFFERS) do if buffer.filename and buffer.modify then buffer:save() end end end -- LuaDoc is in core/.buffer.luadoc. @@ -233,9 +234,8 @@ local function close(buffer, force) local filename = buffer.filename or buffer._type or _L['Untitled'] if buffer.filename then filename = filename:iconv('UTF-8', _CHARSET) end local button = ui.dialogs.msgbox{ - title = _L['Close without saving?'], - text = _L['There are unsaved changes in'], informative_text = filename, - icon = 'gtk-dialog-question', button1 = _L['Cancel'], + title = _L['Close without saving?'], text = _L['There are unsaved changes in'], + informative_text = filename, icon = 'gtk-dialog-question', button1 = _L['Cancel'], button2 = _L['Close without saving'], width = CURSES and #filename > 40 and ui.size[1] - 2 or nil } @@ -245,8 +245,8 @@ local function close(buffer, force) return true end --- Detects if the current file has been externally modified and, if so, emits a --- `FILE_CHANGED` event. +-- Detects if the current file has been externally modified and, if so, emits a `FILE_CHANGED` +-- event. local function update_modified_file() if not buffer.filename then return end local mod_time = lfs.attributes(buffer.filename, 'modification') @@ -261,8 +261,8 @@ events.connect(events.FOCUS, update_modified_file) events.connect(events.RESUME, update_modified_file) --- --- Closes all open buffers, prompting the user to continue if there are unsaved --- buffers, and returns `true` if the user did not cancel. +-- Closes all open buffers, prompting the user to continue if there are unsaved buffers, and +-- returns `true` if the user did not cancel. -- No buffers are saved automatically. They must be saved manually. -- @return `true` if user did not cancel; `nil` otherwise. -- @see buffer.close @@ -281,19 +281,16 @@ events.connect(events.BUFFER_NEW, function() buffer.save, buffer.save_as, buffer.close = save, save_as, close end) -- Export for later storage into the first buffer, which does not exist yet. --- Cannot rely on `events.BUFFER_NEW` because init scripts (e.g. menus and key --- bindings) can access buffer functions before the first `events.BUFFER_NEW` is --- emitted. +-- Cannot rely on `events.BUFFER_NEW` because init scripts (e.g. menus and key bindings) can +-- access buffer functions before the first `events.BUFFER_NEW` is emitted. io._reload, io._save, io._save_as, io._close = reload, save, save_as, close --- Prompts the user to reload the current file if it has been externally --- modified. +-- Prompts the user to reload the current file if it has been externally modified. events.connect(events.FILE_CHANGED, function(filename) local button = ui.dialogs.msgbox{ title = _L['Reload?'], text = _L['Reload modified file?'], - informative_text = string.format( - '"%s"\n%s', filename:iconv('UTF-8', _CHARSET), - _L['has been modified. Reload it?']), + informative_text = string.format('"%s"\n%s', filename:iconv('UTF-8', _CHARSET), + _L['has been modified. Reload it?']), -- LuaFormatter icon = 'gtk-dialog-question', button1 = _L['Yes'], button2 = _L['No'], width = CURSES and #filename > 40 and ui.size[1] - 2 or nil } @@ -332,23 +329,19 @@ end local vcs = {'.bzr', '.git', '.hg', '.svn', '_FOSSIL_'} --- --- Returns the root directory of the project that contains filesystem path --- *path*. --- In order to be recognized, projects must be under version control. Recognized --- VCSes are Bazaar, Fossil, Git, Mercurial, and SVN. --- @param path Optional filesystem path to a project or a file contained within --- a project. The default value is the buffer's filename or the current --- working directory. This parameter may be omitted. --- @param submodule Optional flag that indicates whether or not to return the --- root of the current submodule (if applicable). The default value is --- `false`. +-- Returns the root directory of the project that contains filesystem path *path*. +-- In order to be recognized, projects must be under version control. Recognized VCSes are +-- Bazaar, Fossil, Git, Mercurial, and SVN. +-- @param path Optional filesystem path to a project or a file contained within a project. The +-- default value is the buffer's filename or the current working directory. This parameter +-- may be omitted. +-- @param submodule Optional flag that indicates whether or not to return the root of the +-- current submodule (if applicable). The default value is `false`. -- @return string root or nil -- @name get_project_root function io.get_project_root(path, submodule) if type(path) == 'boolean' then path, submodule = nil, path end - if not assert_type(path, 'string/nil', 1) then - path = buffer.filename or lfs.currentdir() - end + if not assert_type(path, 'string/nil', 1) then path = buffer.filename or lfs.currentdir() end local dir = path:match('^(.+)[/\\]?') while dir do for i = 1, #vcs do @@ -368,38 +361,34 @@ end io.quick_open_filters = {} --- --- Prompts the user to select files to be opened from *paths*, a string --- directory path or list of directory paths, using a filtered list dialog. --- If *paths* is `nil`, uses the current project's root directory, which is --- obtained from `io.get_project_root()`. --- String or list *filter* determines which files to show in the dialog, with --- the default filter being `io.quick_open_filters[path]` (if it exists) or --- `lfs.default_filter`. A filter consists of Lua patterns that match file and --- directory paths to include or exclude. Patterns are inclusive by default. --- Exclusive patterns begin with a '!'. If no inclusive patterns are given, any --- path is initially considered. As a convenience, file extensions can be --- specified literally instead of as a Lua pattern (e.g. '.lua' vs. '%.lua$'), --- and '/' also matches the Windows directory separator ('[/\\]' is not needed). +-- Prompts the user to select files to be opened from *paths*, a string directory path or list +-- of directory paths, using a filtered list dialog. +-- If *paths* is `nil`, uses the current project's root directory, which is obtained from +-- `io.get_project_root()`. +-- String or list *filter* determines which files to show in the dialog, with the default +-- filter being `io.quick_open_filters[path]` (if it exists) or `lfs.default_filter`. A filter +-- consists of Lua patterns that match file and directory paths to include or exclude. Patterns +-- are inclusive by default. Exclusive patterns begin with a '!'. If no inclusive patterns are +-- given, any path is initially considered. As a convenience, file extensions can be specified +-- literally instead of as a Lua pattern (e.g. '.lua' vs. '%.lua$'), and '/' also matches the +-- Windows directory separator ('[/\\]' is not needed). -- The number of files in the list is capped at `quick_open_max`. -- If *filter* is `nil` and *paths* is ultimately a string, the filter from the --- `io.quick_open_filters` table is used. If that filter does not exist, --- `lfs.default_filter` is used. --- *opts* is an optional table of additional options for --- `ui.dialogs.filteredlist()`. --- @param paths Optional string directory path or table of directory paths to --- search. The default value is the current project's root directory, if --- available. --- @param filter Optional filter for files and directories to include and/or --- exclude. The default value is `lfs.default_filter` unless a filter for --- *paths* is defined in `io.quick_open_filters`. --- @param opts Optional table of additional options for --- `ui.dialogs.filteredlist()`. --- @usage io.quick_open(buffer.filename:match('^(.+)[/\\]')) -- list all files --- in the current file's directory, subject to the default filter --- @usage io.quick_open(io.get_current_project(), '.lua') -- list all Lua files --- in the current project --- @usage io.quick_open(io.get_current_project(), '!/build') -- list all files --- in the current project except those in the build directory +-- `io.quick_open_filters` table is used. If that filter does not exist, `lfs.default_filter` +-- is used. +-- *opts* is an optional table of additional options for `ui.dialogs.filteredlist()`. +-- @param paths Optional string directory path or table of directory paths to search. The +-- default value is the current project's root directory, if available. +-- @param filter Optional filter for files and directories to include and/or exclude. The +-- default value is `lfs.default_filter` unless a filter for *paths* is defined in +-- `io.quick_open_filters`. +-- @param opts Optional table of additional options for `ui.dialogs.filteredlist()`. +-- @usage io.quick_open(buffer.filename:match('^(.+)[/\\]')) -- list all files in the current +-- file's directory, subject to the default filter +-- @usage io.quick_open(io.get_current_project(), '.lua') -- list all Lua files in the current +-- project +-- @usage io.quick_open(io.get_current_project(), '!/build') -- list all files in the current +-- project except those in the build directory -- @see io.quick_open_filters -- @see lfs.default_filter -- @see quick_open_max @@ -423,24 +412,20 @@ function io.quick_open(paths, filter, opts) end if #utf8_list >= io.quick_open_max then ui.dialogs.msgbox{ - title = _L['File Limit Exceeded'], - text = string.format( - '%d %s %d', io.quick_open_max, + title = _L['File Limit Exceeded'], text = string.format('%d %s %d', io.quick_open_max, _L['files or more were found. Showing the first'], io.quick_open_max), icon = 'gtk-dialog-info' } end local options = { - title = _L['Open File'], columns = _L['Filename'], items = utf8_list, - button1 = _L['OK'], button2 = _L['Cancel'], select_multiple = true, - string_output = true, width = CURSES and ui.size[1] - 2 or nil + title = _L['Open File'], columns = _L['Filename'], items = utf8_list, button1 = _L['OK'], + button2 = _L['Cancel'], select_multiple = true, string_output = true, + width = CURSES and ui.size[1] - 2 or nil } if opts then for k, v in pairs(opts) do options[k] = v end end local button, utf8_filenames = ui.dialogs.filteredlist(options) if button ~= _L['OK'] or not utf8_filenames then return end local filenames = {} - for i = 1, #utf8_filenames do - filenames[i] = utf8_filenames[i]:iconv(_CHARSET, 'UTF-8') - end + for i = 1, #utf8_filenames do filenames[i] = utf8_filenames[i]:iconv(_CHARSET, 'UTF-8') end io.open_file(filenames) end |