aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormitchell <70453897+667e-11@users.noreply.github.com>2013-03-25 01:48:47 -0400
committermitchell <70453897+667e-11@users.noreply.github.com>2013-03-25 01:48:47 -0400
commit2fb60bd197071eb6c1da78e65e00aaaa374bdce1 (patch)
tree7e62cb5c41e97d5879e2c54487403e44a682a8fc
parent8b8737ea7eab958fe5a9459f02471d6b6efa7021 (diff)
downloadtextadept-2fb60bd197071eb6c1da78e65e00aaaa374bdce1.tar.gz
textadept-2fb60bd197071eb6c1da78e65e00aaaa374bdce1.zip
Added lfs.dir_foreach() for allowing Find in Files to have a filter.
Also moved snapopen module into core as io.snapopen().
-rw-r--r--core/file_io.lua59
-rw-r--r--core/init.lua1
-rw-r--r--core/lfs_ext.lua97
-rw-r--r--core/locale.conf3
-rw-r--r--core/locales/locale.de.conf1
-rw-r--r--core/locales/locale.es.conf3
-rw-r--r--core/locales/locale.fr.conf3
-rw-r--r--core/locales/locale.ru.conf1
-rw-r--r--doc/06_AdeptEditing.md15
-rw-r--r--modules/textadept/find.lua80
-rw-r--r--modules/textadept/init.lua1
-rw-r--r--modules/textadept/keys.lua8
-rw-r--r--modules/textadept/menu.lua4
-rw-r--r--modules/textadept/snapopen.lua148
14 files changed, 217 insertions, 207 deletions
diff --git a/core/file_io.lua b/core/file_io.lua
index fd45ac4c..878188ed 100644
--- a/core/file_io.lua
+++ b/core/file_io.lua
@@ -54,6 +54,9 @@
-- * _`filename`_: The UTF-8-encoded filename.
--
-- [`buffer:save_as()`]: buffer.html#save_as
+-- @field SNAPOPEN_MAX (number)
+-- The maximum number of files to list in the snapopen dialog.
+-- The default value is `1000`.
module('io')]]
-- Events.
@@ -63,6 +66,8 @@ events.FILE_BEFORE_SAVE = 'file_before_save'
events.FILE_AFTER_SAVE = 'file_after_save'
events.FILE_SAVED_AS = 'file_saved_as'
+io.SNAPOPEN_MAX = 1000
+
---
-- List of recently opened files, the most recent being towards the top.
-- @class table
@@ -141,8 +146,8 @@ io.try_encodings = {'UTF-8', 'ASCII', 'ISO-8859-1', 'MacRoman'}
-- Opens *utf8_filenames*, a "\n" delimited string of UTF-8-encoded filenames,
-- or user-selected files.
-- Emits a `FILE_OPENED` event.
--- @param utf8_filenames Optional list of UTF-8-encoded filenames to open. If
--- `nil`, the user is prompted with a fileselect dialog.
+-- @param utf8_filenames Optional string list of UTF-8-encoded filenames to
+-- open. If `nil`, the user is prompted with a fileselect dialog.
-- @see _G.events
-- @name open_file
function io.open_file(utf8_filenames)
@@ -394,3 +399,53 @@ function io.open_recent_file()
NCURSES and {'--width', gui.size[1] - 2} or '')
if i then io.open_file(io.recent_files[i + 1]) end
end
+
+---
+-- Quickly open files from *utf8_paths*, a "\n" delimited string of
+-- UTF-8-encoded directory paths, using a filtered list dialog.
+-- Files shown in the dialog do not match any pattern in string or table
+-- *filter*, and, unless *exclude_FILTER* is `true`, `lfs.FILTER` as well. A
+-- filter table contains Lua patterns that match filenames to exclude, with
+-- patterns matching folders to exclude listed in a `folders` sub-table.
+-- Patterns starting with '!' exclude files and folders that do not match the
+-- pattern that follows. Use a table of raw file extensions assigned to an
+-- `extensions` key for fast filtering by extension. All strings must be encoded
+-- in `_G._CHARSET`, not UTF-8. The number of files in the list is capped at
+-- `SNAPOPEN_MAX`.
+-- @param utf8_paths String list of UTF-8-encoded directory paths to search.
+-- @param filter Optional filter for files and folders to exclude.
+-- @param exclude_FILTER Optional flag indicating whether or not to exclude the
+-- default filter `lfs.FILTER` in the search. If `false`, adds `lfs.FILTER` to
+-- *filter*.
+-- The default value is `false` to include the default filter.
+-- @param ... Optional additional parameters to pass to `gui.dialog()`.
+-- @usage io.snapopen(buffer.filename:match('^.+/')) -- list all files in the
+-- current file's directory, subject to the default filter
+-- @usage io.snapopen('/project', '!%.lua$') -- list all Lua files in a project
+-- directory
+-- @usage io.snapopen('/project', {folders = {'build'}}) -- list all source
+-- files in a project directory
+-- @see lfs.FILTER
+-- @see SNAPOPEN_MAX
+-- @name snapopen
+function io.snapopen(utf8_paths, filter, exclude_FILTER, ...)
+ local list = {}
+ for utf8_path in utf8_paths:gmatch('[^\n]+') do
+ lfs.dir_foreach(utf8_path, function(file)
+ if #list >= io.SNAPOPEN_MAX then return false end
+ list[#list + 1] = file:gsub('^%.[/\\]', '')
+ end, filter, exclude_FILTER)
+ end
+ if #list >= io.SNAPOPEN_MAX then
+ gui.dialog('ok-msgbox',
+ '--title', _L['File Limit Exceeded'],
+ '--text',
+ string.format('%d %s %d', io.SNAPOPEN_MAX,
+ _L['files or more were found. Showing the first'],
+ io.SNAPOPEN_MAX),
+ '--button1', _L['_OK'])
+ end
+ local width = NCURSES and {'--width', gui.size[1] - 2} or ''
+ io.open_file(gui.filteredlist(_L['Open'], _L['File'], list, false,
+ '--select-multiple', width, ...) or '')
+end
diff --git a/core/init.lua b/core/init.lua
index 5aa38d28..9a61379b 100644
--- a/core/init.lua
+++ b/core/init.lua
@@ -10,6 +10,7 @@ args = require 'args'
_L = require 'locale'
events = require 'events'
require 'file_io'
+require 'lfs_ext'
require 'gui'
keys = require 'keys'
diff --git a/core/lfs_ext.lua b/core/lfs_ext.lua
new file mode 100644
index 00000000..d79800a4
--- /dev/null
+++ b/core/lfs_ext.lua
@@ -0,0 +1,97 @@
+-- Copyright 2007-2013 Mitchell mitchell.att.foicica.com. See LICENSE.
+
+--[[ This comment is for LuaDoc.
+---
+-- Extends the `lfs` library to find files in directories.
+module('lfs')]]
+
+---
+-- Filter table containing common binary file extensions and version control
+-- folders to exclude when iterating over files and directories using
+-- `dir_foreach` when its `exclude_FILTER` argument is `false`.
+-- @see dir_foreach
+-- @class table
+-- @name FILTER
+lfs.FILTER = {
+ extensions = {
+ 'a', 'bmp', 'bz2', 'class', 'dll', 'exe', 'gif', 'gz', 'jar', 'jpeg', 'jpg',
+ 'o', 'png', 'so', 'tar', 'tgz', 'tif', 'tiff', 'zip'
+ },
+ folders = {'%.bzr$', '%.git$', '%.hg$', '%.svn$', 'CVS$'}
+}
+
+-- Determines whether or not the given file matches the given filter.
+-- @param file The filename.
+-- @param filter The filter table.
+-- @return boolean `true` or `false`.
+local function exclude(file, filter)
+ if not filter then return false end
+ local ext = filter.extensions
+ if ext and ext[file:match('[^%.]+$')] then return true end
+ for i = 1, #filter do
+ local patt = filter[i]
+ if patt:sub(1, 1) ~= '!' then
+ if file:find(patt) then return true end
+ else
+ if not file:find(patt:sub(2)) then return true end
+ end
+ end
+ return false
+end
+
+---
+-- Iterates over all files and sub-directories in the UTF-8-encoded directory
+-- *utf8_dir*, calling function *f* on each file found.
+-- Files *f* is called on do not match any pattern in string or table *filter*,
+-- and, unless *exclude_FILTER* is `true`, `FILTER` as well. A filter table
+-- contains Lua patterns that match filenames to exclude, with patterns matching
+-- folders to exclude listed in a `folders` sub-table. Patterns starting with
+-- '!' exclude files and folders that do not match the pattern that follows. Use
+-- a table of raw file extensions assigned to an `extensions` key for fast
+-- filtering by extension. All strings must be encoded in `_G._CHARSET`, not
+-- UTF-8.
+-- @param utf8_dir A UTF-8-encoded directory path to iterate over.
+-- @param f Function to call with each full file path found. File paths are
+-- **not** encoded in UTF-8, but in `_G._CHARSET`. If *f* returns `false`
+-- explicitly, iteration ceases.
+-- @param filter Optional filter for files and folders to exclude.
+-- @param exclude_FILTER Optional flag indicating whether or not to exclude the
+-- default filter `FILTER` in the search. If `false`, adds `FILTER` to
+-- *filter*.
+-- The default value is `false` to include the default filter.
+-- @param recursing Utility flag indicating whether or not this function has
+-- been recursively called. This flag is used and set internally, and should
+-- not be set otherwise.
+-- @see FILTER
+-- @name dir_foreach
+function lfs.dir_foreach(utf8_dir, f, filter, exclude_FILTER, recursing)
+ if not recursing then
+ -- Convert filter to a table from nil or string arguments.
+ if not filter then filter = {} end
+ if type(filter) == 'string' then filter = {filter} end
+ -- Add FILTER to filter unless specified otherwise.
+ if not exclude_FILTER then
+ for k, v in pairs(lfs.FILTER) do
+ if not filter[k] then filter[k] = {} end
+ local filter_k = filter[k]
+ for i = 1, #v do filter_k[#filter_k + 1] = v[i] end
+ end
+ end
+ -- Create file extension filter hash table for quick lookups.
+ local ext = filter.extensions
+ if ext then for i = 1, #ext do ext[ext[i]] = true end end
+ end
+ local dir = utf8_dir:iconv(_CHARSET, 'UTF-8')
+ local lfs_attributes = lfs.attributes
+ for file in lfs.dir(dir) do
+ if not file:find('^%.%.?$') then -- ignore . and ..
+ file = dir..(not WIN32 and '/' or '\\')..file
+ local type = lfs_attributes(file, 'mode')
+ if type == 'directory' and not exclude(file, filter.folders) then
+ lfs.dir_foreach(file, f, filter, nil, true)
+ elseif type == 'file' and not exclude(file, filter) then
+ if f(file) == false then return end
+ end
+ end
+ end
+end
diff --git a/core/locale.conf b/core/locale.conf
index 297d7bfc..f694e806 100644
--- a/core/locale.conf
+++ b/core/locale.conf
@@ -81,6 +81,7 @@ _Whole word = _Whole word
_Lua pattern = _Lua pattern
_In files = _In files
Find in Files = Find in Files
+Find: = Find:
No results found = No results found
[Files Found Buffer] = [Files Found Buffer]
Search wrapped = Search wrapped
@@ -90,7 +91,7 @@ An error occured: = An error occured:
%_Cancel = _Cancel
replacement(s) made = replacement(s) made
% For ncurses:
-Find: = Find:
+%Find: = Find:
Replace: = Replace:
[Next] = [Next]
[Prev] = [Prev]
diff --git a/core/locales/locale.de.conf b/core/locales/locale.de.conf
index fcbc4ac3..071c16d2 100644
--- a/core/locales/locale.de.conf
+++ b/core/locales/locale.de.conf
@@ -82,6 +82,7 @@ _Whole word = Vollständiges Wort
_Lua pattern = _Lua-Pattern
_In files = _In Dateien
Find in Files = In Dateien suchen
+Find: = Suchen:
No results found = Keine Treffer gefunden
[Files Found Buffer] = [Gefundene Dateien-Buffer]
Search wrapped = Suche beginnt von oben
diff --git a/core/locales/locale.es.conf b/core/locales/locale.es.conf
index eb7bb7ea..4ebf1152 100644
--- a/core/locales/locale.es.conf
+++ b/core/locales/locale.es.conf
@@ -82,6 +82,7 @@ _Whole word = Palabra _completa
_Lua pattern = Patrón de _Lua
_In files = En ficher_os
Find in Files = Buscar en ficheros
+Find: = Buscar:
No results found = No se han encontrado coincidencias
[Files Found Buffer] = [Buffer de búsqueda en ficheros]
Search wrapped = La búsqueda ha sobrepasado el final/inicio del documento
@@ -91,7 +92,7 @@ An error occured: = Ha ocurrido un error:
%_Cancel = _Cancelar
replacement(s) made = cambios(s) hecho(s)
% For ncurses:
-Find: = Buscar:
+%Find: = Buscar:
Replace: = Reemplazar:
[Next] = [Siguiente]
[Prev] = [Anterior]
diff --git a/core/locales/locale.fr.conf b/core/locales/locale.fr.conf
index 8ee12f62..907cf735 100644
--- a/core/locales/locale.fr.conf
+++ b/core/locales/locale.fr.conf
@@ -82,6 +82,7 @@ _Whole word = _Mot entier
_Lua pattern = Pattern _Lua
_In files = _Dans les fichiers
Find in Files = Rechercher dans les fichiers
+Find: = Rechercher:
No results found = Aucun résultat trouvé
[Files Found Buffer] = [Buffer des fichiers trouvés]
Search wrapped = La recherche à bouclée
@@ -91,7 +92,7 @@ An error occured: = Une erreur est survenue:
%_Cancel = _Annuler
replacement(s) made = remplacement(s) effectués
% For ncurses:
-Find: = Rechercher:
+%Find: = Rechercher:
Replace: = Remplacer:
[Next] = [Suivant]
[Prev] = [Précédent]
diff --git a/core/locales/locale.ru.conf b/core/locales/locale.ru.conf
index d0609613..c8ff6228 100644
--- a/core/locales/locale.ru.conf
+++ b/core/locales/locale.ru.conf
@@ -82,6 +82,7 @@ _Whole word = _Слово целиком
_Lua pattern = _Шаблон lua
_In files = _В файлах
Find in Files = Найти в файлах
+Find: = Найти:
No results found = Ничего не найдено
[Files Found Buffer] = [Буфер поиска в файлах]
Search wrapped = Искать по кругу
diff --git a/doc/06_AdeptEditing.md b/doc/06_AdeptEditing.md
index 3704e208..1ea88ad4 100644
--- a/doc/06_AdeptEditing.md
+++ b/doc/06_AdeptEditing.md
@@ -187,15 +187,18 @@ in ncurses) key bindings. Replace in Files is not supported. You will have to
"Find in Files" first, and then "Replace All" for each file a result is found
in. The "Match Case", "Whole Word", and "Lua pattern" flags still apply.
-_Warning_: currently, there is no way to specify a file-type filter, so Find in
-Files will scan **all** files, even binary ones, in **all** sub-directories.
-Searches also block Textadept from receiving additional input, making the
-interface temporarily unresponsive. Searching large directories or projects can
-be very time consuming and frustrating, so using a specialized, external tool
-such as [ack][] is recommended.
+_Warning_: currently, the only way to specify a file-type filter is through the
+[find API][] and even though the default filter excludes common binary files
+and version control folders from searches, Find in Files could still scan
+unrecognized binary files or large, unwanted sub-directories. Searches also
+block Textadept from receiving additional input, making the interface
+temporarily unresponsive. Searching large directories or projects can be very
+time consuming and frustrating, so using a specialized, external tool such as
+[ack][] is recommended.
![Find in Files](images/findinfiles.png)
+[find API]: api/gui.find.html#FILTER
[ack]: http://betterthangrep.com/
### Incremental Find
diff --git a/modules/textadept/find.lua b/modules/textadept/find.lua
index 0d316105..411b59c5 100644
--- a/modules/textadept/find.lua
+++ b/modules/textadept/find.lua
@@ -80,6 +80,22 @@ local MARK_FIND = _SCINTILLA.next_marker_number()
local MARK_FIND_COLOR = 0x4D9999
local preferred_view
+---
+-- Table of Lua patterns matching files and folders to exclude when finding in
+-- files.
+-- Each filter string is a pattern that matches filenames to exclude, with
+-- patterns matching folders to exclude listed in a `folders` sub-table.
+-- Patterns starting with '!' exclude files and folders that do not match the
+-- pattern that follows. Use a table of raw file extensions assigned to an
+-- `extensions` key for fast filtering by extension. All strings must be encoded
+-- in `_G._CHARSET`, not UTF-8.
+-- The default value is `lfs.FILTER`, a filter for common binary file extensions
+-- and version control folders.
+-- @see find_in_files
+-- @class table
+-- @name FILTER
+find.FILTER = lfs.FILTER
+
-- Text escape sequences with their associated characters.
-- @class table
-- @name escapes
@@ -92,9 +108,11 @@ local escapes = {
-- Searches the *utf8_dir* or user-specified directory for files that match
-- search text and options and prints the results to a buffer.
-- Use the `find_text`, `match_case`, `whole_word`, and `lua` fields to set the
--- search text and option flags, respectively.
--- @param utf8_dir Optional UTF-8-encoded directory name to search. If `nil`,
+-- search text and option flags, respectively. Use `FILTER` to set the search
+-- filter.
+-- @param utf8_dir Optional UTF-8-encoded directory path to search. If `nil`,
-- the user is prompted for one.
+-- @see FILTER
-- @name find_in_files
function find.find_in_files(utf8_dir)
if not utf8_dir then
@@ -105,46 +123,28 @@ function find.find_in_files(utf8_dir)
(buffer.filename or ''):match('^.+[/\\]') or '',
'--no-newline')
end
- if #utf8_dir > 0 then
- local text = find.find_entry_text
- if not find.lua then text = text:gsub('([().*+?^$%%[%]-])', '%%%1') end
- if not find.match_case then text = text:lower() end
- if find.whole_word then text = '%f[%w_]'..text..'%f[^%w_]' end
- local match_case, whole_word = find.match_case, find.whole_word
- local matches = {'Find: '..text}
- function search_file(file)
- local line_num = 1
- for line in io.lines(file) do
- local optimized_line = line
- if not match_case then optimized_line = line:lower() end
- if optimized_line:find(text) then
- file = file:iconv('UTF-8', _CHARSET)
- matches[#matches + 1] = ('%s:%s:%s'):format(file, line_num, line)
- end
- line_num = line_num + 1
+ if utf8_dir == '' then return end
+
+ local text = find.find_entry_text
+ if not find.lua then text = text:gsub('([().*+?^$%%[%]-])', '%%%1') end
+ if not find.match_case then text = text:lower() end
+ if find.whole_word then text = '%f[%w_]'..text..'%f[^%w_]' end
+ local matches = {_L['Find:']..' '..text}
+ lfs.dir_foreach(utf8_dir, function(file)
+ local match_case = find.match_case
+ local line_num = 1
+ for line in io.lines(file) do
+ if (match_case and line or line:lower()):find(text) then
+ file = file:iconv('UTF-8', _CHARSET)
+ matches[#matches + 1] = ('%s:%s:%s'):format(file, line_num, line)
end
+ line_num = line_num + 1
end
- local lfs_dir, lfs_attributes = lfs.dir, lfs.attributes
- function search_dir(directory)
- for file in lfs_dir(directory) do
- if not file:find('^%.%.?$') then -- ignore . and ..
- local path = directory..(not WIN32 and '/' or '\\')..file
- local type = lfs_attributes(path, 'mode')
- if type == 'directory' then
- search_dir(path)
- elseif type == 'file' then
- search_file(path)
- end
- end
- end
- end
- local dir = utf8_dir:iconv(_CHARSET, 'UTF-8')
- search_dir(dir)
- if #matches == 1 then matches[2] = _L['No results found'] end
- matches[#matches + 1] = ''
- if buffer._type ~= _L['[Files Found Buffer]'] then preferred_view = view end
- gui._print(_L['[Files Found Buffer]'], table.concat(matches, '\n'))
- end
+ end, find.FILTER, true)
+ if #matches == 1 then matches[2] = _L['No results found'] end
+ matches[#matches + 1] = ''
+ if buffer._type ~= _L['[Files Found Buffer]'] then preferred_view = view end
+ gui._print(_L['[Files Found Buffer]'], table.concat(matches, '\n'))
end
local c = _SCINTILLA.constants
diff --git a/modules/textadept/init.lua b/modules/textadept/init.lua
index 838c0c42..951c27b4 100644
--- a/modules/textadept/init.lua
+++ b/modules/textadept/init.lua
@@ -18,7 +18,6 @@ M.filter_through = require 'textadept.filter_through'
M.mime_types = require 'textadept.mime_types'
M.run = require 'textadept.run'
M.session = require 'textadept.session'
-M.snapopen = require 'textadept.snapopen'
M.snippets = require 'textadept.snippets'
-- These need to be loaded last.
diff --git a/modules/textadept/keys.lua b/modules/textadept/keys.lua
index 2951c2b3..a11f09b4 100644
--- a/modules/textadept/keys.lua
+++ b/modules/textadept/keys.lua
@@ -224,9 +224,7 @@ M.utils = {
end,
select_command = function() _M.textadept.menu.select_command() end,
snapopen_filedir = function()
- if buffer.filename then
- _M.textadept.snapopen.open(buffer.filename:match('^(.+)[/\\]'))
- end
+ if buffer.filename then io.snapopen(buffer.filename:match('^(.+)[/\\]')) end
end,
show_style = function()
local buffer = buffer
@@ -494,8 +492,8 @@ keys.f2 = m_bookmarks.goto_next
keys[not NCURSES and 'sf2' or 'f3'] = m_bookmarks.goto_prev
keys[not NCURSES and 'af2' or 'f4'] = m_bookmarks.goto_bookmark
-- Snapopen.
-keys[not OSX and 'cu' or 'mu'] = {m_textadept.snapopen.open, _USERHOME}
--- TODO: {m_textadept.snapopen.open, _HOME}
+keys[not OSX and 'cu' or 'mu'] = {io.snapopen, _USERHOME}
+-- TODO: {io.snapopen, _HOME}
keys[not OSX and (not NCURSES and 'caO' or 'mO')
or 'cmO'] = utils.snapopen_filedir
if not NCURSES then keys[not OSX and 'ci' or 'mi'] = utils.show_style end
diff --git a/modules/textadept/menu.lua b/modules/textadept/menu.lua
index 6cc6d447..b1ffd0c2 100644
--- a/modules/textadept/menu.lua
+++ b/modules/textadept/menu.lua
@@ -138,8 +138,8 @@ M.menubar = {
{_L['_Goto Bookmark...'], m_bookmarks.goto_bookmark},
},
{ title = _L['Snap_open'],
- {_L['Snapopen _User Home'], {m_textadept.snapopen.open, _USERHOME}},
- {_L['Snapopen _Textadept Home'], {m_textadept.snapopen.open, _HOME}},
+ {_L['Snapopen _User Home'], {io.snapopen, _USERHOME}},
+ {_L['Snapopen _Textadept Home'], {io.snapopen, _HOME}},
{_L['Snapopen _Current Directory'], utils.snapopen_filedir},
},
{ title = _L['_Snippets'],
diff --git a/modules/textadept/snapopen.lua b/modules/textadept/snapopen.lua
deleted file mode 100644
index 04e60002..00000000
--- a/modules/textadept/snapopen.lua
+++ /dev/null
@@ -1,148 +0,0 @@
--- Copyright 2007-2013 Mitchell mitchell.att.foicica.com. See LICENSE.
-
-local M = {}
-
---[[ This comment is for LuaDoc.
----
--- Quickly open files in a set of directories using a filtered list dialog.
--- @field DEFAULT_DEPTH (number)
--- The maximum directory depth to search.
--- The default value is `99`.
--- @field MAX (number)
--- The maximum number of files to list.
--- The default value is `1000`.
-module('_M.textadept.snapopen')]]
-
----
--- The default filter table containing common binary file extensions and version
--- control folders to exclude from snapopen file lists.
--- @class table
--- @name FILTER
-M.FILTER = {
- extensions = {
- 'a', 'bmp', 'bz2', 'class', 'dll', 'exe', 'gif', 'gz', 'jar', 'jpeg', 'jpg',
- 'o', 'png', 'so', 'tar', 'tgz', 'tif', 'tiff', 'zip'
- },
- folders = {'%.bzr$', '%.git$', '%.hg$', '%.svn$', 'CVS$'}
-}
-M.DEFAULT_DEPTH = 99
-M.MAX = 1000
-
-local lfs_dir, lfs_attributes = lfs.dir, lfs.attributes
-local DEPTH = M.DEFAULT_DEPTH
-
--- Determines whether or not the given file matches the given filter.
--- @param file The filename.
--- @param filter The filter table.
--- @return boolean `true` or `false`.
-local function exclude(file, filter)
- if not filter then return false end
- local string_match, string_sub = string.match, string.sub
- local utf8_file = file:iconv('UTF-8', _CHARSET)
- local ext = filter.extensions
- if ext and ext[utf8_file:match('[^%.]+$')] then return true end
- for i = 1, #filter do
- local patt = filter[i]
- if string_sub(patt, 1, 1) ~= '!' then
- if string_match(utf8_file, patt) then return true end
- else
- if not string_match(utf8_file, string_sub(patt, 2)) then return true end
- end
- end
- return false
-end
-
--- Adds a directory's contents to a list of files.
--- @param utf8_dir The UTF-8 directory to open.
--- @param list The list of files to add dir's contents to.
--- @param depth The current depth of nested folders.
--- @param filter The filter table.
-local function add_directory(utf8_dir, list, depth, filter)
- local string_match, string_gsub, MAX = string.match, string.gsub, M.MAX
- local dir = utf8_dir:iconv(_CHARSET, 'UTF-8')
- for file in lfs_dir(dir) do
- if not string_match(file, '^%.%.?$') then
- file = dir..(not WIN32 and '/' or '\\')..file
- if lfs_attributes(file, 'mode') == 'directory' then
- if not exclude(file, filter.folders) and depth < DEPTH then
- add_directory(file, list, depth + 1, filter)
- end
- elseif not exclude(file, filter) then
- if #list >= MAX then return end
- list[#list + 1] = string_gsub(file, '^%.[/\\]', '')
- end
- end
- end
-end
-
----
--- Quickly open files from the set of directories *utf8_paths* using a filtered
--- list dialog.
--- Files shown in the dialog do not match any pattern in string or table
--- *filter*, and, unless *exclude_FILTER* is `true`, `FILTER` as well. A filter
--- table contains Lua patterns that match filenames to exclude. Patterns
--- starting with '!' exclude files that do not match the pattern that follows.
--- The filter may also contain an `extensions` key whose value is a table of
--- file extensions to exclude. Additionally, it may contain a `folders` key
--- whose value is a table of folder names to exclude. Extensions and folder
--- names must be encoded in UTF-8. The number of files in the list is capped at
--- `MAX`.
--- @param utf8_paths A UTF-8 string directory path or table of UTF-8 directory
--- paths to search.
--- @param filter Optional filter for files and folders to exclude.
--- @param exclude_FILTER Optional flag indicating whether or not to exclude the
--- default filter `FILTER` in the search. If `false`, adds `FILTER` to
--- *filter*.
--- The default value is `false` to include the default filter.
--- @param depth Number of directories to recurse into for finding files.
--- The default value is `DEFAULT_DEPTH`.
--- @usage _M.textadept.snapopen.open(buffer.filename:match('^.+/')) -- list all
--- files in the current file's directory, subject to the default filter
--- @usage _M.textadept.snapopen.open('/project', '!%.lua$') -- list all Lua
--- files in a project directory
--- @usage _M.textadept.snapopen.open('/project', {folders = {'build'}}) -- list
--- all source files in a project directory
--- @see FILTER
--- @see DEFAULT_DEPTH
--- @see MAX
--- @name open
-function M.open(utf8_paths, filter, exclude_FILTER, depth)
- -- Convert utf8_paths to a table from nil or string arguments.
- if not utf8_paths then utf8_paths = {} end
- if type(utf8_paths) == 'string' then utf8_paths = {utf8_paths} end
- -- Convert filter to a table from nil or string arguments.
- if not filter then filter = {} end
- if type(filter) == 'string' then filter = {filter} end
- -- Add FILTER to filter unless specified otherwise.
- if not exclude_FILTER then
- for k, v in pairs(M.FILTER) do
- if not filter[k] then filter[k] = {} end
- local filter_k = filter[k]
- for i = 1, #v do filter_k[#filter_k + 1] = v[i] end
- end
- end
- DEPTH = depth or M.DEFAULT_DEPTH
-
- -- Create file extension filter hash table for quick lookups.
- local ext = filter.extensions
- if ext then for i = 1, #ext do ext[ext[i]] = true end end
-
- -- Create the file list, prompt the user to choose a file, then open the file.
- local list = {}
- for _, path in ipairs(utf8_paths) do add_directory(path, list, 1, filter) end
- if #list >= M.MAX then
- gui.dialog('ok-msgbox',
- '--title', _L['File Limit Exceeded'],
- '--text',
- string.format('%d %s %d', M.MAX,
- _L['files or more were found. Showing the first'],
- M.MAX),
- '--button1', _L['_OK'])
- end
- local width = NCURSES and {'--width', gui.size[1] - 2} or ''
- local utf8_filenames = gui.filteredlist(_L['Open'], _L['File'], list, false,
- '--select-multiple', width) or ''
- for filename in utf8_filenames:gmatch('[^\n]+') do io.open_file(filename) end
-end
-
-return M