diff options
Diffstat (limited to 'modules/textadept')
-rw-r--r-- | modules/textadept/history.lua | 175 | ||||
-rw-r--r-- | modules/textadept/init.lua | 1 | ||||
-rw-r--r-- | modules/textadept/keys.lua | 81 | ||||
-rw-r--r-- | modules/textadept/macros.lua | 2 | ||||
-rw-r--r-- | modules/textadept/menu.lua | 8 |
5 files changed, 230 insertions, 37 deletions
diff --git a/modules/textadept/history.lua b/modules/textadept/history.lua new file mode 100644 index 00000000..4729322c --- /dev/null +++ b/modules/textadept/history.lua @@ -0,0 +1,175 @@ +-- Copyright 2019-2020 Mitchell. See LICENSE. + +local M = {} + +--[[ This comment is for LuaDoc. +--- +-- Records buffer positions within Textadept views over time and allows for +-- navigating through that history. +-- +-- This module listens for text edit events and buffer switch events. Each time +-- an insertion or deletion occurs, its location is recorded in the current +-- view's location history. If the edit is close enough to the previous record, +-- the previous record is amended. Each time a buffer switch occurs, the before +-- and after locations are also recorded. +-- @field minimum_line_distance (number) +-- The minimum number of lines between distinct history records. +-- The default value is `3`. +-- @field maximum_history_size (number) +-- The maximum number of history records to keep per view. +-- The default value is `100`. +module('textadept.history')]] + +M.minimum_line_distance = 3 +M.maximum_history_size = 100 + +-- Map of views to their history records. +-- Each record has a `pos` field that points to the current history position in +-- the associated view. +-- @class table +-- @name view_history +local view_history = setmetatable({}, {__index = function(t, view) + t[view] = {pos = 0} + return t[view] +end}) + +-- Listens for text insertion and deletion events and records their locations. +events.connect(events.MODIFIED, function(position, mod_type, text, length) + local buffer = buffer + -- Only interested in text insertion or deletion. + if mod_type & buffer.MOD_INSERTTEXT > 0 then + if length == buffer.length then return end -- ignore file loading + position = position + length + elseif mod_type & buffer.MOD_DELETETEXT > 0 then + if buffer.length == 0 then return end -- ignore replacing buffer contents + else + return + end + -- Ignore undo/redo. + if mod_type & (buffer.PERFORMED_UNDO | buffer.PERFORMED_REDO) > 0 then + return + end + M.record(nil, buffer:line_from_position(position), buffer.column[position]) +end) + +-- Do not record positions during buffer switches when jumping backwards or +-- forwards. +local jumping = false + +-- Jumps to the current position in the current view's history after adjusting +-- that position backwards or forwards. +local function goto_record() + jumping = true + local history = view_history[view] + local record = history[history.pos] + local filename, line, column = record.filename, record.line, record.column + if lfs.attributes(filename) then + io.open_file(filename) + else + for _, buffer in ipairs(_BUFFERS) do + if buffer.filename == filename or buffer._type == filename or + not buffer.filename and not buffer._type and + filename == _L['Untitled'] then + view:goto_buffer(buffer) + break + end + end + end + buffer:goto_pos(buffer:find_column(line, column)) + jumping = false +end + +--- +-- Navigates backwards through the current view's history. +-- @name back +function M.back() + local history = view_history[view] + if #history == 0 then return end -- nothing to do + local record = history[history.pos] + local line = buffer:line_from_position(buffer.current_pos) + if buffer.filename ~= record.filename or + math.abs(record.line - line) > M.minimum_line_distance then + -- When navigated away from the most recent record, and if that record is + -- not a soft record, jump back to it first, then navigate backwards. + if not record.soft then goto_record() return end + -- Otherwise, update the soft record with the current position and + -- immediately navigate backwards. + M.record(record.filename, nil, nil, record.soft) + end + if history.pos > 1 then history.pos = history.pos - 1 end + goto_record() +end + +--- +-- Navigates forwards through the current view's history. +-- @name forward +function M.forward() + local history = view_history[view] + if history.pos == #history then return end -- nothing to do + local record = history[history.pos] + if record.soft then M.record(record.filename, nil, nil, record.soft) end + history.pos = history.pos + 1 + goto_record() +end + +--- +-- Records the given location in the current view's history. +-- @param filename Optional string filename, buffer type, or identifier of the +-- buffer to store. If `nil`, uses the current buffer. +-- @param line Optional Integer line number to store. If `nil`, uses the current +-- line. +-- @param column Optional integer column number on line *line* to store. If +-- `nil`, uses the current column. +-- @param soft Optional flag that indicates whether or not this record should be +-- skipped when navigating backward towards it, and updated when navigating +-- away from it. The default value is `false`. +-- @name record +function M.record(filename, line, column, soft) + if not assert_type(filename, 'string/nil', 1) then + filename = buffer.filename or buffer._type or _L['Untitled'] + end + if not assert_type(line, 'number/nil', 2) then + line = buffer:line_from_position(buffer.current_pos) + end + if not assert_type(column, 'number/nil', 3) then + column = buffer.column[buffer.current_pos] + end + local history = view_history[view] + if #history > 0 then + local record = history[history.pos] + if filename == record.filename and + (math.abs(record.line - line) <= M.minimum_line_distance or + record.soft) then + -- If the most recent record is close enough (distance-wise), or if that + -- record is a soft record, update it instead of recording a new one. + record.line, record.column = line, column + record.soft = soft and record.soft + return + end + end + if history.pos < #history then + for i = history.pos + 1, #history do history[i] = nil end -- clear forward + end + history[#history + 1] = { + filename = filename, line = line, column = column, soft = soft + } + if #history > M.maximum_history_size then table.remove(history, 1) end + history.pos = #history +end + +-- Softly record positions when switching between buffers. +local function record_switch() + if not jumping then M.record(nil, nil, nil, true) end +end +events.connect(events.BUFFER_BEFORE_SWITCH, record_switch) +events.connect(events.BUFFER_AFTER_SWITCH, record_switch) +events.connect(events.FILE_OPENED, record_switch) + +--- +-- Clears all view history. +-- @name clear +function M.clear() + for view in pairs(view_history) do view_history[view] = {pos = 0} end +end + +return M diff --git a/modules/textadept/init.lua b/modules/textadept/init.lua index a70afb6a..435b34dd 100644 --- a/modules/textadept/init.lua +++ b/modules/textadept/init.lua @@ -14,6 +14,7 @@ require('textadept.command_entry') M.editing = require('textadept.editing') M.file_types = require('textadept.file_types') require('textadept.find') +M.history = require('textadept.history') M.macros = require('textadept.macros') M.run = require('textadept.run') M.session = require('textadept.session') diff --git a/modules/textadept/keys.lua b/modules/textadept/keys.lua index e9cc2c29..25db537b 100644 --- a/modules/textadept/keys.lua +++ b/modules/textadept/keys.lua @@ -25,41 +25,45 @@ local M = {} -- None |None |None |Load session... -- None |None |None |Save session... -- Ctrl+Q |⌘Q |^Q |Quit --- **Edit** | | | --- Ctrl+Z<br/>Alt+Bksp |⌘Z |^Z^(†)<br/>M-Z|Undo --- Ctrl+Y<br/>Ctrl+Shift+Z |⌘⇧Z |^Y<br/>M-S-Z |Redo --- Ctrl+X<br/>Shift+Del |⌘X<br/>⇧⌦|^X |Cut --- Ctrl+C<br/>Ctrl+Ins |⌘C |^C |Copy --- Ctrl+V<br/>Shift+Ins |⌘V |^V |Paste --- Ctrl+Shift+V |⌘⇧V |M-V |Paste Reindent --- Ctrl+D |⌘D |None |Duplicate line --- Del |⌦<br/>^D |Del<br/>^D |Delete --- Alt+Del |^⌦ |M-Del<br/>M-D |Delete word --- Ctrl+A |⌘A |M-A |Select all --- Ctrl+M |^M |M-M |Match brace --- Ctrl+Enter |^Esc |M-Enter^(‡) |Complete word --- Ctrl+/ |^/ |M-/ |Toggle block comment --- Ctrl+T |^T |^T |Transpose characters --- Ctrl+Shift+J |^J |M-J |Join lines --- Ctrl+| |⌘| |^\ |Filter text through --- Ctrl+Shift+M |^⇧M |M-S-M |Select between delimiters --- Ctrl+< |⌘< |M-< |Select between XML tags --- Ctrl+> |⌘> |None |Select in XML tag --- Ctrl+Shift+D |⌘⇧D |M-S-W |Select word --- Ctrl+Shift+N |⌘⇧N |M-S-N |Select line --- Ctrl+Shift+P |⌘⇧P |M-S-P |Select paragraph --- Ctrl+Alt+U |^U |M-^U |Upper case selection --- Ctrl+Alt+Shift+U |^⇧U |M-^L |Lower case selection --- Alt+< |^< |M-> |Enclose as XML tags --- Alt+> |^> |None |Enclose as single XML tag --- Alt+" |^" |None |Enclose in double quotes --- Alt+' |^' |None |Enclose in single quotes --- Alt+( |^( |M-) |Enclose in parentheses --- Alt+[ |^[ |M-] |Enclose in brackets --- Alt+{ |^{ |M-} |Enclose in braces --- Ctrl+Shift+Up |^⇧⇡ |S-^Up |Move selected lines up --- Ctrl+Shift+Down |^⇧⇣ |S-^Down |Move selected lines down --- Ctrl+P |⌘, |M-~ |Preferences +-- **Edit** | | | +-- Ctrl+Z<br/>Alt+Bksp |⌘Z |^Z^(†)<br/>M-Z|Undo +-- Ctrl+Y<br/>Ctrl+Shift+Z|⌘⇧Z |^Y<br/>M-S-Z |Redo +-- Ctrl+X<br/>Shift+Del |⌘X<br/>⇧⌦|^X |Cut +-- Ctrl+C<br/>Ctrl+Ins |⌘C |^C |Copy +-- Ctrl+V<br/>Shift+Ins |⌘V |^V |Paste +-- Ctrl+Shift+V |⌘⇧V |M-V |Paste Reindent +-- Ctrl+D |⌘D |None |Duplicate line +-- Del |⌦<br/>^D |Del<br/>^D |Delete +-- Alt+Del |^⌦ |M-Del<br/>M-D |Delete word +-- Ctrl+A |⌘A |M-A |Select all +-- Ctrl+M |^M |M-M |Match brace +-- Ctrl+Enter |^Esc |M-Enter^(‡) |Complete word +-- Ctrl+/ |^/ |M-/ |Toggle block comment +-- Ctrl+T |^T |^T |Transpose characters +-- Ctrl+Shift+J |^J |M-J |Join lines +-- Ctrl+| |⌘| |^\ |Filter text through +-- Ctrl+Shift+M |^⇧M |M-S-M |Select between delimiters +-- Ctrl+< |⌘< |M-< |Select between XML tags +-- Ctrl+> |⌘> |None |Select in XML tag +-- Ctrl+Shift+D |⌘⇧D |M-S-W |Select word +-- Ctrl+Shift+N |⌘⇧N |M-S-N |Select line +-- Ctrl+Shift+P |⌘⇧P |M-S-P |Select paragraph +-- Ctrl+Alt+U |^U |M-^U |Upper case selection +-- Ctrl+Alt+Shift+U |^⇧U |M-^L |Lower case selection +-- Alt+< |^< |M-> |Enclose as XML tags +-- Alt+> |^> |None |Enclose as single XML tag +-- Alt+" |^" |None |Enclose in double quotes +-- Alt+' |^' |None |Enclose in single quotes +-- Alt+( |^( |M-) |Enclose in parentheses +-- Alt+[ |^[ |M-] |Enclose in brackets +-- Alt+{ |^{ |M-} |Enclose in braces +-- Ctrl+Shift+Up |^⇧⇡ |S-^Up |Move selected lines up +-- Ctrl+Shift+Down |^⇧⇣ |S-^Down |Move selected lines down +-- Alt+, |^, |M-, |Navigate backward +-- Alt+. |^. |M-. |Navigate forward +-- None |None |None |Record location +-- None |None |None |Clear navigation history +-- Ctrl+P |⌘, |M-~ |Preferences -- **Search** | | | -- Ctrl+F |⌘F |M-F<br/>M-S-F|Find -- Ctrl+G<br/>F3 |⌘G |M-G |Find next @@ -336,7 +340,12 @@ local bindings = { {'ctrl+shift+up', 'ctrl+shift+up', 'ctrl+shift+up'}, [buffer.move_selected_lines_down] = {'ctrl+shift+down', 'ctrl+shift+down', 'ctrl+shift+down'}, - -- Preferences + -- History. + [textadept.history.back] = {'alt+,', 'ctrl+,', 'meta+,'}, + [textadept.history.forward] = {'alt+.', 'ctrl+.', 'meta+.'}, + -- TODO: textadept.history.record + -- TODO: textadept.history.clear + -- Preferences. [m_edit[_L['Preferences']][2]] = {'ctrl+p', 'cmd+,', 'meta+~'}, -- Search. diff --git a/modules/textadept/macros.lua b/modules/textadept/macros.lua index 2a165c36..3428ef11 100644 --- a/modules/textadept/macros.lua +++ b/modules/textadept/macros.lua @@ -1,4 +1,4 @@ --- Copyright 2018 Mitchell. See LICENSE. +-- Copyright 2018-2020 Mitchell. See LICENSE. --[[ This comment is for LuaDoc. --- diff --git a/modules/textadept/menu.lua b/modules/textadept/menu.lua index b69a9f7b..1c7aa4b1 100644 --- a/modules/textadept/menu.lua +++ b/modules/textadept/menu.lua @@ -131,6 +131,14 @@ local default_menubar = { {_L['Move Selected Lines Up'], buffer.move_selected_lines_up}, {_L['Move Selected Lines Down'], buffer.move_selected_lines_down} }, + { + title = _L['History'], + {_L['Navigate Backward'], textadept.history.back}, + {_L['Navigate Forward'], textadept.history.forward}, + {_L['Record Location'], textadept.history.record}, + SEPARATOR, + {_L['Clear History'], textadept.history.clear} + }, SEPARATOR, {_L['Preferences'], function() io.open_file(_USERHOME .. '/init.lua') end} }, |