aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--core/file_io.lua4
-rw-r--r--core/lfs_ext.lua6
-rw-r--r--core/ui.lua3
-rw-r--r--modules/textadept/editing.lua202
4 files changed, 99 insertions, 116 deletions
diff --git a/core/file_io.lua b/core/file_io.lua
index 88db6907..aca5af2b 100644
--- a/core/file_io.lua
+++ b/core/file_io.lua
@@ -99,8 +99,8 @@ 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
diff --git a/core/lfs_ext.lua b/core/lfs_ext.lua
index ada0431e..59318da8 100644
--- a/core/lfs_ext.lua
+++ b/core/lfs_ext.lua
@@ -81,8 +81,7 @@ function lfs.dir_foreach(dir, f, filter, n, include_dirs, level)
end
for basename in lfs.dir(dir) do
if basename:find('^%.%.?$') then goto continue end -- ignore . and ..
- local filename = string.format(
- '%s%s%s', dir, dir ~= '/' and '/' or '', basename)
+ local filename = dir .. (dir ~= '/' and '/' or '') .. basename
local mode = lfs.attributes(filename, 'mode')
if mode ~= 'directory' and mode ~= 'file' then goto continue end
local include
@@ -129,8 +128,7 @@ function lfs.abspath(filename, prefix)
if not filename:find(not WIN32 and '^/' or '^%a:[/\\]') and
not (WIN32 and filename:find('^\\\\')) then
if not prefix then prefix = lfs.currentdir() end
- filename = string.format(
- '%s%s%s', prefix, not WIN32 and '/' or '\\', filename)
+ filename = prefix .. (not WIN32 and '/' or '\\') .. filename
end
filename = filename:gsub('%f[^/\\]%.[/\\]', '') -- clean up './'
while filename:find('[^/\\]+[/\\]%.%.[/\\]') do
diff --git a/core/ui.lua b/core/ui.lua
index a90bd9b6..43101a96 100644
--- a/core/ui.lua
+++ b/core/ui.lua
@@ -320,8 +320,7 @@ end)
-- Sets buffer statusbar text.
events_connect(events.UPDATE_UI, function(updated)
if updated and updated & 3 == 0 then return end -- ignore scrolling
- local text =
- not CURSES and '%s %d/%d %s %d %s %s %s %s' or
+ local text = not CURSES and '%s %d/%d %s %d %s %s %s %s' or
'%s %d/%d %s %d %s %s %s %s'
local pos = buffer.selection_n_caret[buffer.main_selection]
local line, max = buffer:line_from_position(pos) + 1, buffer.line_count
diff --git a/modules/textadept/editing.lua b/modules/textadept/editing.lua
index 1c9fbcb3..15074bae 100644
--- a/modules/textadept/editing.lua
+++ b/modules/textadept/editing.lua
@@ -158,10 +158,10 @@ end, 1) -- need index of 1 because default key handler halts propagation
events.connect(events.UPDATE_UI, function(updated)
if updated and updated & 3 == 0 then return end -- ignore scrolling
local pos = buffer.selection_n_caret[buffer.main_selection]
- local match = M.brace_matches[buffer.char_at[pos]] and
- buffer:brace_match(pos, 0) or -1
- local f = buffer[match ~= -1 and 'brace_highlight' or 'brace_bad_light']
- f(pos, match)
+ local match =
+ M.brace_matches[buffer.char_at[pos]] and buffer:brace_match(pos, 0) or -1
+ local f = match ~= -1 and buffer.brace_highlight or buffer.brace_bad_light
+ f(buffer, pos, match)
end)
-- Moves over typeover characters when typed, taking multiple selections into
@@ -176,7 +176,7 @@ events.connect(events.KEYPRESS, function(code)
handled = true
end
end
- if handled then return true end
+ if handled then return true end -- prevent typing
end
end)
@@ -185,7 +185,7 @@ events.connect(events.CHAR_ADDED, function(code)
if not M.auto_indent or code ~= 10 then return end
local line = buffer:line_from_position(buffer.current_pos)
if line > 0 and buffer:get_line(line - 1):find('^[\r\n]+$') and
- buffer:get_line(line):find('^[^\r\n]') then
+ buffer:get_line(line):find('^[^\r\n]') then
return -- do not auto-indent when pressing enter from start of previous line
end
local i = line - 1
@@ -218,26 +218,22 @@ if CURSES and not WIN32 then
end)
end
--- Prepares the buffer for saving to a file.
+-- Prepares the buffer for saving to a file by stripping trailing whitespace,
+-- ensuring a final newline, and normalizing line endings.
events.connect(events.FILE_BEFORE_SAVE, function()
if not M.strip_trailing_spaces then return end
- local buffer = buffer
buffer:begin_undo_action()
-- Strip trailing whitespace.
for line = 0, buffer.line_count - 1 do
local s, e = buffer:position_from_line(line), buffer.line_end_position[line]
local i, byte = e - 1, buffer.char_at[e - 1]
- while i >= s and (byte == 9 or byte == 32) do
+ while i >= s and (byte == 9 or byte == 32) do -- '\t' or ' '
i, byte = i - 1, buffer.char_at[i - 1]
end
if i < e - 1 then buffer:delete_range(i + 1, e - i - 1) end
end
- -- Ensure ending newline.
- local e = buffer:position_from_line(buffer.line_count)
- if buffer.line_count == 1 or
- e > buffer:position_from_line(buffer.line_count - 1) then
- buffer:insert_text(e, '\n')
- end
+ -- Ensure final newline.
+ if buffer.char_at[buffer.length - 1] ~= 10 then buffer:append_text('\n') end
-- Convert non-consistent EOLs
buffer:convert_eols(buffer.eol_mode)
buffer:end_undo_action()
@@ -256,19 +252,20 @@ function M.paste_reindent()
text = text:gsub('^\n', '\r\n'):gsub('([^\r])\n', '%1\r\n')
end
local lead = text:match('^[ \t]*')
- if lead ~= '' then text = text:sub(#lead + 1):gsub('\n'..lead, '\n') end
+ if lead ~= '' then text = text:sub(#lead + 1):gsub('\n' .. lead, '\n') end
-- Change indentation to match buffer indentation settings.
local tab_width = math.huge
text = text:gsub('\n([ \t]+)', function(indentation)
if indentation:find('^\t') then
- if buffer.use_tabs then return '\n'..indentation end
- return '\n'..indentation:gsub('\t', string.rep(' ', buffer.tab_width))
+ return buffer.use_tabs and '\n' .. indentation or
+ '\n' .. indentation:gsub('\t', string.rep(' ', buffer.tab_width))
else
tab_width = math.min(tab_width, #indentation)
local indent = math.floor(#indentation / tab_width)
local spaces = string.rep(' ', math.fmod(#indentation, tab_width))
- if buffer.use_tabs then return '\n'..string.rep('\t', indent)..spaces end
- return '\n'..string.rep(' ', buffer.tab_width):rep(indent)..spaces
+ return string.format(
+ '\n%s%s', buffer.use_tabs and string.rep('\t', indent) or
+ string.rep(' ', buffer.tab_width):rep(indent), spaces)
end
end)
-- Re-indent according to whichever of the current and preceding lines has the
@@ -279,15 +276,15 @@ function M.paste_reindent()
if i < 0 or buffer.line_indentation[i] < buffer.line_indentation[line] then
i = line
end
- local indentation = buffer:text_range(buffer:position_from_line(i),
- buffer.line_indent_position[i])
- local fold_header = i ~= line and
- buffer.fold_level[i] & buffer.FOLDLEVELHEADERFLAG > 0
+ local indentation = buffer:text_range(
+ buffer:position_from_line(i), buffer.line_indent_position[i])
+ local fold_header =
+ i ~= line and buffer.fold_level[i] & buffer.FOLDLEVELHEADERFLAG > 0
if fold_header then
- indentation = indentation..(buffer.use_tabs and '\t' or
- string.rep(' ', buffer.tab_width))
+ indentation = indentation ..
+ (buffer.use_tabs and '\t' or string.rep(' ', buffer.tab_width))
end
- text = text:gsub('\n', '\n'..indentation)
+ text = text:gsub('\n', '\n' .. indentation)
-- Paste the text and adjust first and last line indentation accordingly.
local start_indent = buffer.line_indentation[i]
if fold_header then start_indent = start_indent + buffer.tab_width end
@@ -312,7 +309,6 @@ end
-- @see comment_string
-- @name block_comment
function M.block_comment()
- local buffer = buffer
local comment = M.comment_string[buffer:get_lexer(true)] or ''
local prefix, suffix = comment:match('^([^|]+)|?([^|]*)$')
if not prefix then return end
@@ -323,22 +319,21 @@ function M.block_comment()
buffer:begin_undo_action()
for line = s, not ignore_last_line and e or e - 1 do
local p = buffer.line_indent_position[line]
- if buffer:text_range(p, p + #prefix) == prefix then
- buffer:delete_range(p, #prefix)
+ local uncomment = buffer:text_range(p, p + #prefix) == prefix
+ if not uncomment then
+ buffer:insert_text(p, prefix)
if suffix ~= '' then
- p = buffer.line_end_position[line]
- buffer:delete_range(p - #suffix, #suffix)
- if line == s then anchor = anchor - #suffix end
- if line == e then pos = pos - #suffix end
+ buffer:insert_text(buffer.line_end_position[line], suffix)
end
else
- buffer:insert_text(p, prefix)
+ buffer:delete_range(p, #prefix)
if suffix ~= '' then
- buffer:insert_text(buffer.line_end_position[line], suffix)
- if line == s then anchor = anchor + #suffix end
- if line == e then pos = pos + #suffix end
+ p = buffer.line_end_position[line]
+ buffer:delete_range(p - #suffix, #suffix)
end
end
+ if line == s then anchor = anchor + #suffix * (uncomment and -1 or 1) end
+ if line == e then pos = pos + #suffix * (uncomment and -1 or 1) end
end
buffer:end_undo_action()
anchor, pos = buffer.line_end_position[s] - anchor, buffer.length - pos
@@ -367,8 +362,8 @@ function M.goto_line(line)
buffer:ensure_visible_enforce_policy(line)
buffer:goto_line(line)
end
-args.register('-l', '--line', 1, function(line) M.goto_line(line - 1) end,
- 'Go to line')
+args.register(
+ '-l', '--line', 1, function(line) M.goto_line(line - 1) end, 'Go to line')
---
-- Transposes characters intelligently.
@@ -377,14 +372,13 @@ args.register('-l', '--line', 1, function(line) M.goto_line(line - 1) end,
-- @name transpose_chars
function M.transpose_chars()
if buffer.current_pos == 0 then return end
- local pos, byte = buffer.current_pos, buffer.char_at[buffer.current_pos]
- if byte == 10 or byte == 13 or pos == buffer.length then
- pos = buffer:position_before(pos)
- end
+ local pos = buffer.current_pos
+ local line_end = buffer.line_end_position[buffer:line_from_position(pos)]
+ if pos == line_end then pos = buffer:position_before(pos) end
local pos1, pos2 = buffer:position_before(pos), buffer:position_after(pos)
local ch1, ch2 = buffer:text_range(pos1, pos), buffer:text_range(pos, pos2)
buffer:set_target_range(pos1, pos2)
- buffer:replace_target(ch2..ch1)
+ buffer:replace_target(ch2 .. ch1)
buffer:goto_pos(pos2)
end
@@ -417,12 +411,11 @@ function M.enclose(left, right)
for i = 0, buffer.selections - 1 do
local s, e = buffer.selection_n_start[i], buffer.selection_n_end[i]
if s == e then
- buffer:set_target_range(buffer:word_start_position(s, true),
- buffer:word_end_position(e, true))
- else
- buffer:set_target_range(s, e)
+ s = buffer:word_start_position(s, true)
+ e = buffer:word_end_position(e, true)
end
- buffer:replace_target(left..buffer.target_text..right)
+ buffer:set_target_range(s, e)
+ buffer:replace_target(left .. buffer.target_text .. right)
buffer.selection_n_start[i] = buffer.target_end
buffer.selection_n_end[i] = buffer.target_end
end
@@ -448,24 +441,23 @@ function M.select_enclosed(left, right)
s, e = buffer:search_prev(0, left), buffer:search_next(0, right)
elseif M.auto_pairs then
s = buffer.selection_start
- local char_at, style_at = buffer.char_at, buffer.style_at
while s >= 0 do
- local match = M.auto_pairs[char_at[s]]
- if match then
- left, right = string.char(char_at[s]), match
- if buffer:brace_match(s, 0) >= buffer.selection_end - 1 then
- e = buffer:brace_match(s, 0)
+ local match = M.auto_pairs[buffer.char_at[s]]
+ if not match then goto continue end
+ left, right = string.char(buffer.char_at[s]), match
+ if buffer:brace_match(s, 0) >= buffer.selection_end - 1 then
+ e = buffer:brace_match(s, 0)
+ break
+ elseif M.brace_matches[buffer.char_at[s]] or
+ buffer.style_at[s] == buffer.style_at[buffer.selection_start] then
+ buffer.search_flags = 0
+ buffer:set_target_range(s + 1, buffer.length)
+ if buffer:search_in_target(match) >= buffer.selection_end - 1 then
+ e = buffer.target_end - 1
break
- elseif M.brace_matches[char_at[s]] or
- style_at[s] == style_at[buffer.selection_start] then
- buffer.search_flags = 0
- buffer:set_target_range(s + 1, buffer.length)
- if buffer:search_in_target(match) >= buffer.selection_end - 1 then
- e = buffer.target_end - 1
- break
- end
end
end
+ ::continue::
s = s - 1
end
end
@@ -492,7 +484,7 @@ function M.select_word(all)
buffer.search_flags = buffer.search_flags + buffer.FIND_WHOLEWORD
if all then buffer:multiple_select_add_next() end -- select word first
end
- buffer['multiple_select_add_'..(not all and 'next' or 'each')](buffer)
+ buffer['multiple_select_add_' .. (not all and 'next' or 'each')](buffer)
end
---
@@ -521,7 +513,6 @@ end
-- @see buffer.use_tabs
-- @name convert_indentation
function M.convert_indentation()
- local buffer = buffer
buffer:begin_undo_action()
for line = 0, buffer.line_count - 1 do
local s = buffer:position_from_line(line)
@@ -531,7 +522,7 @@ function M.convert_indentation()
if buffer.use_tabs then
local tabs = indent // buffer.tab_width
local spaces = math.fmod(indent, buffer.tab_width)
- new_indentation = string.rep('\t', tabs)..string.rep(' ', spaces)
+ new_indentation = string.rep('\t', tabs) .. string.rep(' ', spaces)
else
new_indentation = string.rep(' ', indent)
end
@@ -570,8 +561,8 @@ function M.highlight_word()
buffer.search_flags = buffer.FIND_WHOLEWORD + buffer.FIND_MATCHCASE
buffer:target_whole_document()
while buffer:search_in_target(word) > -1 do
- buffer:indicator_fill_range(buffer.target_start,
- buffer.target_end - buffer.target_start)
+ buffer:indicator_fill_range(
+ buffer.target_start, buffer.target_end - buffer.target_start)
buffer:set_target_range(buffer.target_end, buffer.length)
end
end
@@ -582,13 +573,13 @@ end
-- standard output (stdout). *command* may contain pipes.
-- Standard input is as follows:
--
--- 1. If text is selected and spans multiple lines, all text on the lines that
+-- 1. If no text is selected, the entire buffer is used.
+-- 2. If text is selected and spans a single line, only the selected text is
+-- used.
+-- 3. If text is selected and spans multiple lines, all text on the lines that
-- have text selected is passed as stdin. However, if the end of the selection
-- is at the beginning of a line, only the line ending delimiters from the
-- previous line are included. The rest of the line is excluded.
--- 2. If text is selected and spans a single line, only the selected text is
--- used.
--- 3. If no text is selected, the entire buffer is used.
-- @param command The Linux, BSD, Mac OSX, or Windows shell command to filter
-- text through. May contain pipes.
-- @name filter_through
@@ -596,7 +587,10 @@ function M.filter_through(command)
assert(not (WIN32 and CURSES), 'not implemented in this environment')
assert_type(command, 'string', 1)
local s, e = buffer.selection_start, buffer.selection_end
- if s ~= e then
+ if s == e then
+ -- Use the whole buffer as input.
+ buffer:target_whole_document()
+ else
-- Use the selected lines as input.
local i, j = buffer:line_from_position(s), buffer:line_from_position(e)
if i < j then
@@ -604,15 +598,12 @@ function M.filter_through(command)
if buffer.column[e] > 0 then e = buffer:position_from_line(j + 1) end
end
buffer:set_target_range(s, e)
- else
- -- Use the whole buffer as input.
- buffer:target_whole_document()
end
local commands = lpeg.match(lpeg.Ct(lpeg.P{
lpeg.C(lpeg.V('command')) * ('|' * lpeg.C(lpeg.V('command')))^0,
command = (1 - lpeg.S('"\'|') + lpeg.V('str'))^1,
str = '"' * (1 - lpeg.S('"\\') + lpeg.P('\\') * 1)^0 * lpeg.P('"')^-1 +
- "'" * (1 - lpeg.S("'\\") + lpeg.P('\\') * 1)^0 * lpeg.P("'")^-1,
+ "'" * (1 - lpeg.S("'\\") + lpeg.P('\\') * 1)^0 * lpeg.P("'")^-1,
}), command)
local output = buffer.target_text
for i = 1, #commands do
@@ -621,17 +612,14 @@ function M.filter_through(command)
p:close()
output = p:read('a') or ''
if p:wait() ~= 0 then
- ui.statusbar_text = string.format('"%s" %s', commands[i],
- _L['returned non-zero status'])
+ ui.statusbar_text = string.format(
+ '"%s" %s', commands[i], _L['returned non-zero status'])
return
end
end
buffer:replace_target(output:iconv('UTF-8', _CHARSET))
- if s ~= e then
- buffer:set_sel(buffer.target_start, buffer.target_end)
- else
- buffer:goto_pos(s)
- end
+ if s == e then buffer:goto_pos(s) return end
+ buffer:set_sel(buffer.target_start, buffer.target_end)
end
---
@@ -646,13 +634,13 @@ function M.autocomplete(name)
local len_entered, list = M.autocompleters[name]()
if not len_entered or not list or #list == 0 then return end
buffer.auto_c_order = buffer.ORDER_PERFORMSORT
- buffer:auto_c_show(len_entered,
- table.concat(list, string.char(buffer.auto_c_separator)))
+ buffer:auto_c_show(
+ len_entered, table.concat(list, string.char(buffer.auto_c_separator)))
return true
end
--- Returns for the word behind the caret a list of completions constructed from
--- the current buffer or all open buffers (depending on
+-- Returns for the word part behind the caret a list of whole word completions
+-- constructed from the current buffer or all open buffers (depending on
-- `M.autocomplete_all_words`).
-- @see buffer.word_chars
-- @see autocomplete
@@ -660,26 +648,22 @@ M.autocompleters.word = function()
local list, matches = {}, {}
local s = buffer:word_start_position(buffer.current_pos, true)
if s == buffer.current_pos then return end
- local word = buffer:text_range(s, buffer.current_pos)
+ local word_part = buffer:text_range(s, buffer.current_pos)
for _, buffer in ipairs(_BUFFERS) do
if buffer == _G.buffer or M.autocomplete_all_words then
- buffer.search_flags = buffer.FIND_WORDSTART
- if not buffer.auto_c_ignore_case then
- buffer.search_flags = buffer.search_flags + buffer.FIND_MATCHCASE
- end
+ buffer.search_flags = buffer.FIND_WORDSTART + buffer.FIND_MATCHCASE
buffer:target_whole_document()
- while buffer:search_in_target(word) > -1 do
+ while buffer:search_in_target(word_part) > -1 do
local e = buffer:word_end_position(buffer.target_end, true)
local match = buffer:text_range(buffer.target_start, e)
- if #match > #word and not matches[match] then
+ if #match > #word_part and not matches[match] then
list[#list + 1], matches[match] = match, true
end
buffer:set_target_range(e, buffer.length)
end
end
end
- if #list == 0 then return nil end
- return #word, list
+ return #word_part, list
end
local api_docs
@@ -698,8 +682,8 @@ local api_docs
-- @see buffer.word_chars
function M.show_documentation(pos, case_insensitive)
if buffer:call_tip_active() then events.emit(events.CALL_TIP_CLICK) return end
- local lang = buffer:get_lexer(true)
- if not M.api_files[lang] then return end
+ local api_files = M.api_files[buffer:get_lexer(true)]
+ if not api_files then return end
if not assert_type(pos, 'number/nil', 1) then pos = buffer.current_pos end
local s = buffer:word_start_position(pos, true)
local e = buffer:word_end_position(pos, true)
@@ -708,18 +692,18 @@ function M.show_documentation(pos, case_insensitive)
api_docs = {pos = pos, i = 1}
::lookup::
if symbol ~= '' then
- local symbol_patt = '^'..symbol:gsub('(%p)', '%%%1')
+ local symbol_patt = '^' .. symbol:gsub('(%p)', '%%%1')
if case_insensitive then
symbol_patt = symbol_patt:gsub('%a', function(ch)
return string.format('[%s%s]', ch:upper(), ch:lower())
end)
end
- for _, file in ipairs(M.api_files[lang]) do
+ for _, file in ipairs(api_files) do
if type(file) == 'function' then file = file() end
if file and lfs.attributes(file) then
for line in io.lines(file) do
if line:find(symbol_patt) then
- api_docs[#api_docs + 1] = line:match(symbol_patt..'%s+(.+)$')
+ api_docs[#api_docs + 1] = line:match(symbol_patt .. '%s+(.+)$')
end
end
end
@@ -727,8 +711,7 @@ function M.show_documentation(pos, case_insensitive)
end
-- Search backwards for an open function call and show API documentation for
-- that function as well.
- local char_at = buffer.char_at
- while s >= 0 and char_at[s] ~= 40 do s = s - 1 end
+ while s > 0 and buffer.char_at[s] ~= 40 do s = s - 1 end -- '('
e = buffer:brace_match(s, 0)
if s > 0 and (e == -1 or e >= pos) then
s, e = buffer:word_start_position(s - 1, true), s - 1
@@ -736,12 +719,15 @@ function M.show_documentation(pos, case_insensitive)
goto lookup
end
- if #api_docs == 0 then return end
+ if #api_docs == 0 then
+ api_docs = nil -- prevent the call tip click handler below from running
+ return
+ end
for i = 1, #api_docs do
local doc = api_docs[i]:gsub('%f[\\]\\n', '\n'):gsub('\\\\', '\\')
if #api_docs > 1 then
- if not doc:find('\n') then doc = doc..'\n' end
- doc = '\001'..doc:gsub('\n', '\n\002', 1)
+ if not doc:find('\n') then doc = doc .. '\n' end
+ doc = '\001' .. doc:gsub('\n', '\n\002', 1)
end
api_docs[i] = doc
end