├── .gitignore ├── LICENSE ├── README.md ├── buffer_list.lua ├── core ├── buffer.lua ├── filteredlist.lua ├── indicator.lua ├── init.lua ├── list.lua ├── style.lua └── ui.lua ├── ctags.lua ├── docs ├── api.html ├── config.ld ├── images │ ├── buffer_list.png │ ├── bufferlist.gif │ ├── ctags_terminal.png │ ├── fs_fuzzy_matching.png │ ├── fs_listing.png │ ├── fs_snapopen.png │ └── run_command.png ├── index.html ├── ldoc.css ├── ldoc.ltp ├── modules │ ├── textredux.buffer_list.html │ ├── textredux.core.buffer.html │ ├── textredux.core.filteredlist.html │ ├── textredux.core.html │ ├── textredux.core.indicator.html │ ├── textredux.core.list.html │ ├── textredux.core.style.html │ ├── textredux.core.ui.html │ ├── textredux.ctags.html │ ├── textredux.fs.html │ ├── textredux.hijack.html │ ├── textredux.html │ ├── textredux.util.color.html │ └── textredux.util.matcher.html └── tour.html ├── examples ├── basic_list.lua ├── buffer_actions.lua ├── buffer_indicators.lua ├── buffer_styling.lua ├── git_ls_files.lua ├── init.lua ├── list_commands.lua ├── multi_column_list.lua └── styled_list.lua ├── fs.lua ├── init.lua └── util ├── color.lua └── matcher.lua /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2011-2012 Nils Nordman 2 | Copyright 2012-2013 Robert Gieseke 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | 'key.lua' contains code with the following copyright: 23 | 24 | Copyright (c) 2007-2011 Mitchell 25 | 26 | with the license being the same as the above. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Textredux is a module for the [Textadept editor](http://foicica.com/textadept/) 2 | that offers a set of text based replacement interfaces for core Textadept 3 | functionality. 4 | 5 | ![](docs/images/bufferlist.gif) 6 | 7 | Textredux' main branch usually follows Textadept's nightly development and started tracking the changes in Textadept 12 alpha. 8 | 9 | The API docs are generated with [ldoc](https://stevedonovan.github.io/ldoc/): 10 | 11 | ``` 12 | cd docs 13 | ldoc . 14 | ``` 15 | 16 | The Textredux module is released under the MIT license. 17 | Please visit the [homepage](http://rgieseke.github.io/textredux/) for 18 | more information. 19 | -------------------------------------------------------------------------------- /buffer_list.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2011-2012 Nils Nordman 2 | -- Copyright 2012-2014 Robert Gieseke 3 | -- License: MIT (see LICENSE) 4 | 5 | --[[-- 6 | The buffer list module provides a text based replacement for the standard 7 | Textadept buffer list. 8 | 9 | ## Usage 10 | 11 | Use the @{textredux.hijack} function or load the buffer list 12 | in your `~/.textadept/init.lua`: 13 | 14 | local textredux = require('textredux') 15 | keys["ctrl+b"] = textredux.buffer_list.show 16 | 17 | ## Features 18 | 19 | - Close a buffer from the buffer list (bound to `Ctrl + D` by default) 20 | - Close all files currently shown in the buffer list. 21 | (bound to `Ctrl + Shift + D` and `Meta + D` in Curses by default) 22 | - The list of buffers is sorted and the current buffer is pre-selected 23 | - The buffers to show can be specified using a function 24 | 25 | @module textredux.buffer_list 26 | ]] 27 | 28 | local reduxlist = require 'textredux.core.list' 29 | 30 | local M = {} 31 | 32 | --- The Textredux list instance used by the buffer list. 33 | M.list = nil 34 | 35 | --[[-- The key bindings for the buffer list. 36 | 37 | You can modifiy this to customise the key bindings to your liking. The key 38 | bindings are passed directly to the Textredux list, so note that the 39 | first argument to any function will be the Textredux list itself. 40 | You can read more about the Textredux list's keys in the 41 | [list documentation](./textredux.core.list.html#keys). 42 | 43 | If you like to add a custom key binding for closing a buffer or all buffers 44 | in the current selection you can bind the @{close_buffer} and @{close_selected} 45 | functions to a key of your choice. For other actions it's likely that you want to 46 | obtain the currently selected buffer - you can use the @{currently_selected_buffer} 47 | function for that. 48 | ]] 49 | M.keys = { 50 | ["ctrl+d"] = function(list) M.close_buffer(list) end, -- Default for `close buffer` 51 | [CURSES and 'meta+d' or 'ctrl+D'] = function(list) M.close_selected(list) end, 52 | } 53 | 54 | local buffer_source 55 | 56 | local function shorten_home_dir(directory) 57 | if not directory then return end 58 | local home_dir = os.getenv('HOME') or os.getenv('UserProfile') 59 | return directory:gsub(home_dir, '~') 60 | end 61 | 62 | local function buffer_title(buffer) 63 | local title = (buffer.filename or ''):match('[\\/]([^/\\]+)$') 64 | return title or buffer.filename or buffer._type or _L['Untitled'] 65 | end 66 | 67 | local function buffer_directory(buffer) 68 | if not buffer.filename then return nil end 69 | return shorten_home_dir(buffer.filename:match('^(.+[\\/])[^/\\]+$')) 70 | end 71 | 72 | local function get_buffer_items() 73 | local items = {} 74 | for _, buffer in ipairs(buffer_source()) do 75 | if M.list.buffer.target ~= buffer then 76 | local modified = buffer.modify and '*' or '' 77 | items[#items + 1] = { 78 | buffer_title(buffer) .. modified, 79 | buffer_directory(buffer), 80 | buffer = buffer 81 | } 82 | end 83 | end 84 | table.sort(items, function(a, b) 85 | if a[2] == b[2] then return a[1] < b[1] end 86 | if a[2] and b[2] then return a[2] < b[2] end 87 | return a[1] < b[1] 88 | end) 89 | return items 90 | end 91 | 92 | local function on_selection(list, item) 93 | list:close() 94 | local target = item.buffer 95 | if buffer ~= target then view:goto_buffer(target) end 96 | end 97 | 98 | --[[-- Returns the currently selected buffer in the list. 99 | @param list The Textredux list instance used by the buffer list. If not 100 | provided, then the global list is used automatically. 101 | @return The currently selected buffer, if any. 102 | @return The currently selected buffer's name, if any. 103 | ]] 104 | function M.currently_selected_buffer(list) 105 | list = list or M.list 106 | if not list then error('`list` must be provided', 2) end 107 | local item = list:get_current_selection() 108 | if item then return item.buffer, item[1] end 109 | end 110 | 111 | --[[-- Closes the currently selected buffer in the buffer list. 112 | @param list The Textredux list instance used by the buffer list. This function 113 | is ordinarily invoked as the result of a key binding, and you should thus not 114 | need to specify this yourself. If list isn't provided, the global list is 115 | automatically used. 116 | ]] 117 | function M.close_buffer(list) 118 | list = list or M.list 119 | if not list then error('`list` must be provided', 2) end 120 | local sel_buffer, name = M.currently_selected_buffer(list) 121 | if sel_buffer then 122 | ui.statusbar_text = 'Closing ' .. name .. '..' 123 | local current_pos = buffer.current_pos 124 | local current_search = list:get_current_search() 125 | view:goto_buffer(sel_buffer) 126 | local closed = sel_buffer:close() 127 | list.items = get_buffer_items() 128 | list:show() 129 | if closed then 130 | list:set_current_search(current_search) 131 | buffer.goto_pos(math.min(current_pos, buffer.length + 1)) 132 | buffer.home() 133 | ui.statusbar_text = 'Closed ' .. name 134 | else 135 | ui.statusbar_text = '' 136 | end 137 | end 138 | end 139 | 140 | --[[-- Closes all currently selected/filtered files in the buffer list. 141 | @param list The Textredux list instance used by the buffer list. This function 142 | is ordinarily invoked as the result of a key binding, and you should thus not 143 | need to specify this yourself. If list isn't provided, the global list is 144 | automatically used. 145 | ]] 146 | function M.close_selected(list) 147 | list = list or M.list 148 | if not list then error('`list` must be provided', 2) end 149 | local current_search = list:get_current_search() 150 | if not current_search then return end 151 | while true do 152 | local sel_buffer, _ = M.currently_selected_buffer(list) 153 | if not sel_buffer then break end 154 | local current_pos = buffer.current_pos 155 | view:goto_buffer(sel_buffer) 156 | local closed = sel_buffer:close() 157 | list.items = get_buffer_items() 158 | list:show() 159 | if closed then 160 | list:set_current_search(current_search) 161 | buffer.goto_pos(math.min(current_pos, buffer.length + 1)) 162 | buffer.home() 163 | end 164 | end 165 | list:set_current_search('') 166 | ui.statusbar_text = 'All selected buffers closed' 167 | end 168 | 169 | --- Shows a list of the specified buffers, or _G.BUFFERS if not specified. 170 | -- @param buffers Either nil, in which case all buffers within _G.BUFFERS 171 | -- are displayed, or a function returning a table of buffers to display. 172 | function M.show(buffers) 173 | buffer_source = buffers or function() return _BUFFERS end 174 | 175 | if not M.list then 176 | M.list = reduxlist.new('Buffer list') 177 | M.list.headers = {'Name', 'Directory'} 178 | M.list.on_selection = on_selection 179 | for k, v in pairs(M.keys) do 180 | M.list.keys[k] = v 181 | end 182 | end 183 | M.list.items = get_buffer_items() 184 | local buffer = buffer 185 | local active_buffer 186 | for index, item in ipairs(M.list.items) do 187 | if item.buffer == buffer then 188 | active_buffer = index 189 | break 190 | end 191 | end 192 | M.list:show() 193 | if active_buffer then 194 | local line = M.list.buffer.data.items_start_line + active_buffer 195 | M.list.buffer:goto_line(line - 1) 196 | end 197 | local short_cut = CURSES and '[Meta+D]' or '[Ctrl+Shift+D]' 198 | ui.statusbar_text = '[Enter] = open, [Ctrl+D] = close selected, '.. 199 | short_cut..' = close all buffers in current list' 200 | end 201 | 202 | 203 | return M 204 | -------------------------------------------------------------------------------- /core/filteredlist.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2011-2012 Nils Nordman 2 | -- Copyright 2012-2014 Robert Gieseke 3 | -- License: MIT (see LICENSE) 4 | 5 | --[[-- 6 | Filtered list wrapper, hijacking `ui.dialogs.list`. 7 | 8 | @module textredux.core.filteredlist 9 | ]] 10 | 11 | local list = require 'textredux.core.list' 12 | 13 | local M = {} 14 | 15 | local ui_filteredlist = ui.dialogs.list 16 | local current_coroutine 17 | 18 | local function convert_multi_column_table(nr_columns, items) 19 | local _items, _item = {}, {} 20 | for i, item in ipairs(items) do 21 | _item[#_item + 1] = item 22 | if i % nr_columns == 0 then 23 | _items[#_items + 1] = _item 24 | _item = {} 25 | end 26 | end 27 | return _items 28 | end 29 | 30 | local function index_of(element, table) 31 | for i, e in ipairs(table) do 32 | if e == element then return i end 33 | end 34 | end 35 | 36 | ui.dialogs.list = function(options) 37 | if not current_coroutine then 38 | return ui_filteredlist(options) 39 | end 40 | local co = current_coroutine 41 | local title = options.title or '' 42 | local columns = options.columns 43 | local items = options.items or {} 44 | if columns then 45 | columns = type(columns) == 'string' and { columns } or columns 46 | if #columns > 1 then items = convert_multi_column_table(#columns, items) end 47 | end 48 | 49 | local l = list.new(title, items) 50 | if columns then l.headers = columns end 51 | l.on_selection = function(l, item) 52 | local value = index_of(item, items) 53 | l:close() 54 | coroutine.resume(co, value) 55 | end 56 | l:show() 57 | return coroutine.yield() 58 | end 59 | 60 | -- Wrap 61 | function M.wrap(func) 62 | return function(...) 63 | current_coroutine = coroutine.create(func) 64 | local status, val = coroutine.resume(current_coroutine) 65 | current_coroutine = nil 66 | if not status then events.emit(events.ERROR, val) end 67 | end 68 | end 69 | 70 | return M 71 | -------------------------------------------------------------------------------- /core/indicator.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2011-2012 Nils Nordman 2 | -- Copyright 2012-2014 Robert Gieseke 3 | -- License: MIT (see LICENSE) 4 | 5 | --[[-- 6 | The indicator module provides support for indicators in your buffers. 7 | Indicators lets you visually mark a certain text range using various styles and 8 | colors. Using the event mechanism you can also receive events whenever the 9 | user clicks the marked text. 10 | 11 | ## The indicator definition 12 | 13 | An indicator is defined using a simple table with the properties listed below. 14 | For the most part, these properties maps directly to fields in the 15 | [buffer](http://foicica.com/textadept/api/buffer.html) API. 16 | 17 | - `style`: The style of the indicator. See `indic_style`. 18 | - `alpha`: Alpha transparency value from 0 to 255 (or 256 for no alpha), used 19 | for fill colors of rectangles . See `indic_alpha`. 20 | - `outline_alpha`: Alpha transparency value from 0 to 255 (or 256 for no alpha), 21 | used for outline colors of rectangles . See `indic_outline_alpha`. 22 | - `fore`: The foreground color of the indicator. The color should be specified 23 | in the `'#rrggbb'` notation. 24 | - `under`: Whether an indicator is drawn under text or over (default). Drawing 25 | under text works only when two phase drawing is enabled for the buffer (the 26 | default). 27 | 28 | A simple example: 29 | 30 | local reduxindicator = textredux.core.indicator 31 | reduxindicator.RED_BOX = {style = c.INDIC_BOX, fore = '#ff0000'} 32 | 33 | ## Using indicators 34 | 35 | Start with defining your indicators using the format described above. You can 36 | then either apply them against a range of text using apply, or pass them to 37 | one of the text insertion functions in @{textredux.core.buffer}. 38 | 39 | local text = 'Text for a red box indicator\n\n' 40 | buffer:add_text(text) 41 | reduxindicator.RED_BOX:apply(0, #text) 42 | 43 | buffer:add_text(text, nil, nil, reduxindicator.RED_BOX) 44 | 45 | Please also see the file `examples/buffer_indicators.lua`. 46 | 47 | @module textredux.core.indicator 48 | ]] 49 | 50 | local color = require 'textredux.util.color' 51 | 52 | local M = {} 53 | 54 | --- 55 | -- Applies the given indicator for the text range specified in the current 56 | -- buffer. 57 | -- @param self The indicator to apply 58 | -- @param position The start position 59 | -- @param length The length of the range to fill 60 | local function apply(self, position, length) 61 | local buffer = buffer 62 | buffer.indicator_current = self.number 63 | buffer:indicator_fill_range(position, length) 64 | end 65 | 66 | -- Called when a new table is added to the indicator module. 67 | local function define_indicator(t, name, properties) 68 | if not properties.number then 69 | local number = view.new_indic_number() 70 | properties.number = number 71 | end 72 | properties.apply = apply 73 | rawset(t, name, properties) 74 | end 75 | 76 | -- Called to set indicator styles in a new buffer or view. 77 | local function activate_indicators() 78 | local buffer = buffer 79 | for _, properties in pairs(M) do 80 | if type(properties) == 'table' then 81 | local number = properties.number 82 | if properties.style then buffer.indic_style[number] = properties.style end 83 | if properties.alpha then buffer.indic_alpha[number] = properties.alpha end 84 | if properties.outline_alpha then 85 | buffer.indic_outline_alpha[number] = properties.outline_alpha 86 | end 87 | if properties.fore then 88 | buffer.indic_fore[number] = color.string_to_color(properties.fore) 89 | end 90 | if properties.under then buffer.indic_under[number] = properties.under end 91 | end 92 | end 93 | end 94 | 95 | -- Ensure Textredux indicators are defined after switching buffers or views. 96 | events.connect(events.BUFFER_NEW, activate_indicators) 97 | events.connect(events.VIEW_NEW, activate_indicators) 98 | events.connect(events.VIEW_AFTER_SWITCH, activate_indicators) 99 | 100 | setmetatable(M, {__newindex=define_indicator}) 101 | 102 | return M 103 | -------------------------------------------------------------------------------- /core/init.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2011-2012 Nils Nordman 2 | -- Copyright 2012-2014 Robert Gieseke 3 | -- License: MIT (see LICENSE) 4 | 5 | --[[-- 6 | The Textredux core module allows you to easily create text based interfaces 7 | for the [Textadept](http://foicica.com/textadept/) editor. 8 | 9 | It currently consists of the following components: 10 | 11 | - The @{textredux.core.buffer} module that supports custom styling, buffer 12 | specific key bindings, hotspot support and generally makes it easy to 13 | create a text based interface buffer by taking care of the background 14 | gruntwork required. 15 | 16 | - The @{textredux.core.style} module that let's you easily define custom 17 | styles, as well as leveraging the default styles already provided by the 18 | user's theme. 19 | 20 | - The @{textredux.core.indicator} module that provides a convenient way of 21 | using indicators in your buffers. 22 | 23 | - The @{textredux.core.list} module that provides a versatile and extensible 24 | text based item listing for Textadept, featuring advanced search capabilities 25 | and styling. 26 | 27 | How to use 28 | ---------- 29 | 30 | After installing the Textredux module into your `modules` directory, you can 31 | either do 32 | 33 | local textredux = require('textredux') 34 | local reduxlist = textredux.core.list 35 | 36 | or you can just the modules that you want by something 37 | similar to 38 | 39 | local reduxstyle = require('textredux.core.style') 40 | local reduxbuffer = require('textredux.core.style') 41 | 42 | The examples provide an overview on how to use the various components and their 43 | features, and the documentation for each component provides more details. 44 | 45 | @module textredux.core 46 | ]] 47 | 48 | local M = { 49 | buffer = require 'textredux.core.buffer', 50 | filteredlist = require 'textredux.core.filteredlist', 51 | style = require 'textredux.core.style', 52 | list = require 'textredux.core.list', 53 | indicator = require 'textredux.core.indicator', 54 | } 55 | 56 | return M 57 | -------------------------------------------------------------------------------- /core/list.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2011-2012 Nils Nordman 2 | -- Copyright 2012-2014 Robert Gieseke 3 | -- License: MIT (see LICENSE) 4 | 5 | --[[-- 6 | The list module provides a text based item listing for Textadept, featuring 7 | advanced search capabilities and styling. 8 | 9 | ## How to use 10 | 11 | Create the list using @{new}, specify @{items} and other fields/callbacks 12 | (such as @{on_selection}) and invoke @{list:show}. 13 | 14 | Please see also the various list examples in `./examples`. 15 | 16 | ## Features 17 | 18 | - Support for multi-column table items, in addition to supporting the simpler 19 | case of just listing strings. 20 | - Fully customizable styling. You can either specify individual styles for 21 | different columns, or specify styles for each item dynamically using a 22 | callback. 23 | - Powerful search capabilities. The list class supports both exact matching and 24 | fuzzy matching, and will present best matches first. It also supports 25 | searching for multiple search strings (any text separated by whitespace is 26 | considered to be multiple search strings). Searches are done against all 27 | columns. 28 | - `Ctrl/Alt/Meta-Backspace` resets the current search. 29 | 30 | @module textredux.core.list 31 | ]] 32 | 33 | local M = {} 34 | local list = {} 35 | 36 | local reduxbuffer = require('textredux.core.buffer') 37 | local reduxstyle = require('textredux.core.style') 38 | local util_matcher = require('textredux.util.matcher') 39 | 40 | local string_rep = string.rep 41 | 42 | reduxstyle.list_header = {underlined = true} 43 | 44 | reduxstyle.list_match_highlight = reduxstyle['function']..{underlined = true} 45 | 46 | --- The default style to use for diplaying headers. 47 | -- This is by default the `style.list_header` style. It's possible to override 48 | -- this for a specific list by assigning another value to the instance itself. 49 | list.header_style = reduxstyle.list_header 50 | 51 | --- The style to use for indicating matches. 52 | -- You can turn off highlighing of matches by setting this to nil. 53 | -- It's possible to override this for a specific list by assigning another 54 | -- value to the instance itself. The default value is `style.default`. 55 | list.match_highlight_style = reduxstyle.list_match_highlight 56 | 57 | --- The default styles to use for different columns. This can be specified 58 | -- individually for each list as well. Values can either be explicit styles, 59 | -- defined using @{textredux.core.style}, or functions which returns 60 | -- explicit styles. In the latter case, the function will be invoked with the 61 | -- corresponding item and column index. The default styles contains styles for 62 | -- up to three columns, after which the default style will be used. 63 | list.column_styles = {reduxstyle.string, reduxstyle.keyword, reduxstyle.type} 64 | 65 | --- Whether searches are case insensitive or not. 66 | -- It's possible to override this for a specific list by assigning another 67 | -- value to the instance itself. The default value is `true`. 68 | list.search_case_insensitive = true 69 | 70 | --- Whether fuzzy searching should be in addition to explicit matches. 71 | -- It's possible to override this for a specific list by assigning another 72 | -- value to the instance itself. The default value is `true`. 73 | list.search_fuzzy = true 74 | 75 | --- List instance fields. 76 | -- These can be set only for a list instance, and not globally for the module. 77 | -- @section instance 78 | 79 | --- Optional headers for the list. 80 | -- If set, the headers must be a table with the same number of columns as 81 | -- @{items}. 82 | list.headers = nil 83 | 84 | --- A table of items to display in the list. 85 | -- Each table item can either be a table itself, in which case the list will 86 | -- be multi column, or a string in which case the list be single column. 87 | list.items = nil 88 | 89 | --[[- The handler/callback to call when the user has selected an item. 90 | The handler will be passed the following parameters: 91 | 92 | - `list`: the list itself 93 | - `item`: the item selected 94 | ]] 95 | list.on_selection = nil 96 | 97 | --[[- The handler/callback to call when the user has typed in text which 98 | doesn't match any item, and presses ``. 99 | 100 | The handler will be passed the following parameters: 101 | 102 | - `list`: the list itself 103 | - `search`: the current search of the list 104 | ]] 105 | list.on_new_selection = nil 106 | 107 | --- The underlying @{textredux.core.buffer} used by the list. 108 | list.buffer = nil 109 | 110 | --- 111 | -- A table of key commands for the list. 112 | -- This functions almost exactly the same as @{textredux.core.buffer.keys}. 113 | -- The one difference is that for function values, the parameter passed will be 114 | -- a reference to the list instead of a buffer reference. 115 | list.keys = nil 116 | 117 | --- A general purpose table that can be used for storing state associated 118 | -- with the list. Just like @{textredux.core.buffer.data}, the `data` table 119 | -- is special in the way that it will automatically be cleared whenever the user 120 | -- closes the buffer associated with the list. 121 | list.data = nil 122 | 123 | --- @section end 124 | 125 | --[[- Creates a new list. 126 | @param title The list title 127 | @param items The list items, see @{items}. Not required, items can be set later 128 | using the @{items} field. 129 | @param on_selection The on selection handler, see @{on_selection}. Not required, 130 | this can be specified later using the @{on_selection} field. 131 | @return The new list instance 132 | ]] 133 | function M.new(title, items, on_selection) 134 | if not title then error('no title specified', 2) end 135 | local l = { 136 | title = title, 137 | items = items or {}, 138 | on_selection = on_selection 139 | } 140 | setmetatable(l, {__index = list}) 141 | 142 | l:_create_buffer() 143 | return l 144 | end 145 | 146 | --- Shows the list. 147 | function list:show() 148 | self:_calculate_column_widths() 149 | self.buffer.data = { 150 | matcher = util_matcher.new( 151 | self.items, 152 | self.search_case_insensitive, 153 | self.search_fuzzy 154 | ), 155 | list = self 156 | } 157 | self.buffer:show() 158 | end 159 | 160 | --- Returns the currently selected item if any, or nil otherwise. 161 | function list:get_current_selection() 162 | local buffer = self.buffer 163 | if buffer:is_showing() then 164 | local data = buffer.data 165 | local current_line = buffer:line_from_position(buffer.current_pos) 166 | if current_line >= data.items_start_line 167 | and current_line <= data.items_end_line then 168 | return data.matching_items[current_line - data.items_start_line + 1] 169 | end 170 | end 171 | return nil 172 | end 173 | 174 | --- Closes the list. 175 | function list:close() 176 | self.buffer:close() 177 | end 178 | 179 | --- Returns the current user search if any, or nil otherwise. 180 | function list:get_current_search() 181 | local search = self.buffer.data.search 182 | return search and #search > 0 and search or nil 183 | end 184 | 185 | --- Sets the current user search. 186 | -- @param search The search string to use 187 | function list:set_current_search(search) 188 | self.buffer.data.search = search 189 | if self.buffer:is_active() then self.buffer:refresh() end 190 | end 191 | 192 | -- Begin private section. 193 | 194 | -- Calculates the column widths for the current items. 195 | function list:_calculate_column_widths() 196 | local column_widths = {} 197 | 198 | for i, header in ipairs(self.headers or {}) do 199 | column_widths[i] = #tostring(header) 200 | end 201 | for _, item in ipairs(self.items) do 202 | if type(item) ~= 'table' then item = {item} end 203 | for j, field in ipairs(item) do 204 | column_widths[j] = math.max(column_widths[j] or 0, #tostring(field)) 205 | end 206 | end 207 | self._column_widths = column_widths 208 | end 209 | 210 | -- Return style for column from {@colum_styles} table. 211 | function list:_column_style(item, column) 212 | local style = self.column_styles[column] 213 | if not style then return reduxstyle.default end 214 | return type(style) == 'function' and style(item, column) or style 215 | end 216 | 217 | -- Add text and padding. 218 | local function add_column_text(buffer, text, pad_to, style) 219 | buffer:add_text(text, style) 220 | local padding = (pad_to + 1) - #text 221 | if padding then buffer:add_text(string_rep(' ', padding)) end 222 | end 223 | 224 | -- Highlight matches. 225 | function highlight_matches(explanations, line_start, match_style) 226 | for _, explanation in ipairs(explanations) do 227 | for _, range in ipairs(explanation) do 228 | match_style:apply( 229 | line_start + range.start_pos - 1, 230 | range.length 231 | ) 232 | end 233 | end 234 | end 235 | 236 | -- Add items. 237 | function list:_add_items(items, start_index, end_index) 238 | local buffer = self.buffer 239 | local data = self.buffer.data 240 | local search = data.search 241 | local column_widths = self._column_widths 242 | 243 | for index = start_index, end_index do 244 | local item = items[index] 245 | if item == nil or index > end_index then break end 246 | local columns = type(item) == 'table' and item or { item } 247 | local line_start = buffer.current_pos 248 | for j, field in ipairs(columns) do 249 | local pad_to = j == nr_columns and 0 or column_widths[j] 250 | add_column_text(buffer, tostring(field), 251 | pad_to, self:_column_style(columns, j)) 252 | end 253 | 254 | if self.match_highlight_style then 255 | local explanations = data.matcher:explain(search, buffer:get_cur_line()) 256 | highlight_matches(explanations, line_start, self.match_highlight_style) 257 | end 258 | 259 | buffer:add_text('\n') 260 | if self.on_selection then 261 | local handler = function (shift, ctrl, alt, meta) 262 | self.on_selection(self, item, shift, ctrl, alt, meta) 263 | end 264 | buffer:add_hotspot(line_start, buffer.current_pos, handler) 265 | end 266 | end 267 | data.shown_items = end_index 268 | data.items_end_line = buffer:line_from_position(buffer.current_pos) - 1 269 | 270 | if #items > end_index then 271 | local message = string.format( 272 | "[..] (%d more items not shown, press here to see more)", 273 | #items - end_index 274 | ) 275 | buffer:add_text(message, reduxstyle.comment) 276 | end 277 | end 278 | 279 | -- Refresh list. 280 | function list:_refresh() 281 | local buffer = self.buffer 282 | local data = buffer.data 283 | data.matching_items = data.matcher:match(data.search) 284 | 285 | -- Header. 286 | buffer:add_text(self.title .. ' : ') 287 | buffer:add_text(#data.matching_items, reduxstyle.number) 288 | buffer:add_text('/') 289 | buffer:add_text(#self.items, reduxstyle.number) 290 | buffer:add_text(' items') 291 | if data.search and #data.search > 0 then 292 | buffer:add_text( ' matching ') 293 | buffer:add_text(data.search, reduxstyle.comment) 294 | end 295 | buffer:add_text('\n\n') 296 | 297 | -- Item listing. 298 | local column_widths = self._column_widths 299 | local nr_columns = #column_widths 300 | 301 | -- Headers. 302 | local headers = self.headers 303 | if headers then 304 | for i, header in ipairs(self.headers or {}) do 305 | local pad_to = i == nr_columns and 0 or column_widths[i] 306 | add_column_text(buffer, header, pad_to, self.header_style) 307 | end 308 | buffer:add_text('\n') 309 | end 310 | 311 | -- Items. 312 | data.items_start_line = buffer:line_from_position(buffer.current_pos) 313 | local nr_items = buffer.lines_on_screen - data.items_start_line - 1 314 | self:_add_items(data.matching_items, 1, nr_items) 315 | buffer:goto_line(data.items_start_line - 1) 316 | buffer:home() 317 | end 318 | 319 | -- Load more items. 320 | function list:_load_more_items() 321 | local buffer = self.buffer 322 | local data = buffer.data 323 | local start_index = data.shown_items + 1 324 | local end_index = start_index + buffer.lines_on_screen - 3 325 | buffer:goto_pos(buffer.length) 326 | buffer:home() 327 | 328 | buffer:update(function() 329 | buffer:del_line_right() 330 | self:_add_items(data.matching_items, start_index, end_index) 331 | end) 332 | buffer:goto_pos(buffer.length) 333 | end 334 | 335 | -- Create Textredux buffer to display the list. 336 | function list:_create_buffer() 337 | local listbuffer = reduxbuffer.new(self.title) 338 | listbuffer.on_refresh = function(...) self:_refresh(...) end 339 | 340 | self.buffer = listbuffer 341 | self.data = self.buffer.data 342 | 343 | listbuffer.on_deleted = function() 344 | self.data = {} 345 | end 346 | 347 | self.buffer.on_char_added = function(char) 348 | local search = self.get_current_search(self) or '' 349 | self.set_current_search(self, search..char) 350 | end 351 | 352 | listbuffer.keys['\b'] = function() 353 | local search = self.get_current_search(self) 354 | if search then self.set_current_search(self, search:sub(1, #search - 1)) end 355 | end 356 | 357 | local search_delete_word = function() 358 | local search = self:get_current_search() 359 | if search then 360 | self:set_current_search(search:gsub('%s*$', ''):match('^(.*%s)%S*$')) 361 | end 362 | end 363 | listbuffer.keys['ctrl+\b'] = search_delete_word 364 | listbuffer.keys['alt+\b'] = search_delete_word 365 | listbuffer.keys['cmd+\b'] = search_delete_word 366 | 367 | local key_wrapper = function(t, k, v) 368 | if type(v) == 'function' then 369 | listbuffer.keys[k] = function() v(self) end 370 | else 371 | listbuffer.keys[k] = v 372 | end 373 | end 374 | 375 | self.keys = setmetatable({}, { 376 | __index = listbuffer.keys, 377 | __newindex = key_wrapper 378 | }) 379 | return listbuffer 380 | end 381 | 382 | -- Limit movement to selectable lines and load more items for long lists. 383 | events.connect(events.UPDATE_UI, function(updated) 384 | if not updated then return end 385 | local buffer = buffer 386 | local reduxbuffer = buffer._textredux 387 | if not reduxbuffer then return end 388 | if not reduxbuffer.data.list then return end 389 | if buffer.UPDATE_SELECTION & updated == buffer.UPDATE_SELECTION then 390 | local line = buffer:line_from_position(buffer.current_pos) 391 | local start_line = reduxbuffer.data.items_start_line 392 | local end_line = reduxbuffer.data.items_end_line 393 | if reduxbuffer.data.shown_items < #reduxbuffer.data.matching_items and 394 | line > end_line then 395 | reduxbuffer.data.list:_load_more_items() 396 | buffer:goto_line(reduxbuffer.data.items_end_line) 397 | elseif line > end_line then 398 | buffer:goto_line(end_line) 399 | elseif line < start_line then 400 | buffer:goto_line(start_line) 401 | end 402 | end 403 | end) 404 | 405 | return M 406 | -------------------------------------------------------------------------------- /core/style.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2011-2012 Nils Nordman 2 | -- Copyright 2012-2014 Robert Gieseke 3 | -- License: MIT (see LICENSE) 4 | 5 | --[[-- 6 | The style module lets you define and use custom, non-lexer-based styles. 7 | 8 | ## What's a style? 9 | 10 | Textredux styling provides an abstraction layer over the lexer based style 11 | creation. A style is thus just a table with certain properties, almost exactly 12 | the same as for style created for a lexer or theme. Please see the documentation 13 | for 14 | [lexer.style](http://foicica.com/textadept/api/lexer.html#Styles.and.Styling) 15 | for information about the available fields. Colors should be defined in the 16 | standard `'#rrggbb'` notation. 17 | 18 | ## Defining styles 19 | 20 | You define a new style by assigning a table with its properties to the module: 21 | 22 | local reduxstyle = require 'textredux.core.style' 23 | reduxstyle.foo_header = { italics = true, fore = '#680000' } 24 | 25 | As has been previously said, it's often a good idea to base your custom styles 26 | on an existing default style. Similarily to defining a lexer style in Textadept 27 | you can achieve this by concatenating styles: 28 | 29 | reduxstyle.foo_header = style.string .. { underlined = true } 30 | 31 | *NB:* Watch out for the mistake of assigning the style to a local variable: 32 | 33 | local header = reduxstyle.string .. { underlined = true } 34 | 35 | This will _not_ work, as the style is not correctly defined with the style 36 | module, necessary to ensure styles are correctly defined when new buffers 37 | are created. 38 | 39 | In order to avoid name clashes, it's suggested that you name any custom styles 40 | by prefixing their name with the name of your module. E.g. if your module is 41 | named `awesome`, then name your style something like `style.awesome_style`. 42 | 43 | ## Updating styles 44 | 45 | To make them fit better with your theme or preferences you can change styles 46 | already set by overwriting their properties in your `init.lua`: 47 | 48 | local textredux = require('textredux') 49 | local reduxstyle = textredux.core.style 50 | reduxstyle.list_match_highlight.fore = reduxstyle.class.fore 51 | reduxstyle.fs_directory.italics = true 52 | 53 | ## Using styles 54 | 55 | You typically use a style by inserting text through 56 | @{textredux.core.buffer}'s text insertion methods, specifying the style. 57 | Please see also the example in `examples/buffer_styling.lua`. 58 | 59 | reduxbuffer:add_text('Foo header text', reduxstyle.foo_header) 60 | 61 | ## The default styles 62 | 63 | Textredux piggybacks on the default lexer styles defined by a user's theme, 64 | and makes them available for your Textredux interfaces. The big benefit of this 65 | is that by using those styles or basing your custom styles on them, your 66 | interface stands a much higher chance of blending in well with the color scheme 67 | used. As an example, your custom style with cyan foreground text might look 68 | great with your own dark theme, but may be pretty near invisible for some user 69 | with a light blue background. 70 | 71 | You can read more about the default lexer styles in the 72 | [Textadept lexer documentation](http://foicica.com/textadept/api/lexer.html). 73 | You access a default style (or any style for that matter), by indexing the 74 | style module, like so: `style.`. For reference, the default styles 75 | available are these: 76 | 77 | - style.nothing 78 | - style.whitespace 79 | - style.comment 80 | - style.string 81 | - style.number 82 | - style.keyword 83 | - style.identifier 84 | - style.operator 85 | - style.error 86 | - style.preproc 87 | - style.constant 88 | - style.variable 89 | - style.function 90 | - style.class 91 | - style.type 92 | - style.default 93 | - style.line_number 94 | - style.bracelight 95 | - style.bracebad 96 | - style.controlchar 97 | - style.indentguide 98 | - style.calltip 99 | 100 | @module textredux.core.style 101 | ]] 102 | 103 | local M = {} 104 | 105 | local color = require 'textredux.util.color' 106 | local string_to_color = color.string_to_color 107 | local color_to_string = color.color_to_string 108 | 109 | local STYLE_LASTPREDEFINED = buffer.STYLE_LASTPREDEFINED 110 | local STYLE_MAX = buffer.STYLE_MAX 111 | 112 | --- 113 | -- Applies a style. 114 | -- Attached to each style defined in the module. 115 | -- @param self The Style 116 | -- @param start_pos The start position 117 | -- @param length The number of chars to style 118 | local function apply(self, start_pos, length) 119 | local buffer = buffer 120 | buffer:start_styling(start_pos, 0xff) 121 | buffer:set_styling(length, self.number) 122 | end 123 | 124 | -- Copy a table. 125 | local function table_copy(table) 126 | local new = {} 127 | for k, v in pairs(table) do new[k] = v end 128 | return new 129 | end 130 | 131 | -- Overwrite fields in first style table with fields from second style table. 132 | local function style_merge(s1, s2) 133 | local new = table_copy(s1) 134 | for k, v in pairs(s2) do new[k] = v end 135 | new.number = nil 136 | return new 137 | end 138 | 139 | -- Set a property if it is set. 140 | local function set_style_property(t, number, value) 141 | if value ~= nil then t[number] = value end 142 | end 143 | 144 | -- Activate Textredux styles in a buffer. 145 | function M.activate_styles() 146 | if not buffer._textredux then return end 147 | for _, v in pairs(M) do 148 | if type(v) == 'table' then 149 | if v.number > STYLE_LASTPREDEFINED then 150 | set_style_property(buffer.style_size, v.number, v.size) 151 | set_style_property(buffer.style_bold, v.number, v.bold) 152 | set_style_property(buffer.style_italic, v.number, v.italics) 153 | set_style_property(buffer.style_underline, v.number, v.underlined) 154 | set_style_property(buffer.style_fore, v.number, string_to_color(v.fore)) 155 | set_style_property(buffer.style_back, v.number, string_to_color(v.back)) 156 | set_style_property(buffer.style_eol_filled, v.number, v.eolfilled) 157 | set_style_property(buffer.style_character_set, v.number, v.characterset) 158 | set_style_property(buffer.style_case, v.number, v.case) 159 | set_style_property(buffer.style_visible, v.number, v.visible) 160 | set_style_property(buffer.style_changeable, v.number, v.changeable) 161 | set_style_property(buffer.style_hot_spot, v.number, v.hotspot) 162 | set_style_property(buffer.style_font, v.number, v.font) 163 | end 164 | end 165 | end 166 | end 167 | 168 | -- Pre-defined style numbers. 169 | local default_styles = { 170 | nothing = 1, 171 | whitespace = 2, 172 | comment = 3, 173 | string = 4, 174 | number = 5, 175 | keyword = 6, 176 | identifier = 7, 177 | operator = 8, 178 | error = 9, 179 | preproc = 10, 180 | constant = 11, 181 | variable = 12, 182 | ['function'] = 13, 183 | class = 14, 184 | type = 15, 185 | default = 33, 186 | line_number = 34, 187 | bracelight = 35, 188 | bracebad = 36, 189 | controlchar = 37, 190 | indentguide = 38, 191 | calltip = 39 192 | } 193 | 194 | -- Set default styles by parsing buffer properties. 195 | for k, v in pairs(default_styles) do 196 | M[k] = {number = v, apply = apply} 197 | local style = buffer.property['style.'..k]:gsub('[$%%]%b()', function(key) 198 | return buffer.property[key:sub(3, -2)] 199 | end) 200 | local fore = style:match('fore:(%d+)') 201 | if fore then M[k]['fore'] = color_to_string(tonumber(fore)) end 202 | local back = style:match('back:(%d+)') 203 | if back then M[k]['back'] = color_to_string(tonumber(back)) end 204 | local font = style:match('font:([%a ]+)') 205 | if font then M[k]['font'] = font end 206 | local fontsize = style:match('fontsize:([%a ]+)') 207 | if fontsize then M[k]['fontsize'] = fontsize end 208 | -- Assuming "notbold" etc. are never used in default styles. 209 | if style:match('italics') then M[k]['italics'] = true end 210 | if style:match('bold') then M[k]['bold'] = true end 211 | if style:match('underlined') then M[k]['underlined'] = true end 212 | if style:match('eolfilled') then M[k]['eolfilled'] = true end 213 | setmetatable(M[k], {__concat=style_merge}) 214 | end 215 | 216 | -- Defines a new style using the given table of style properties. 217 | -- @param name The style name that should be used for the style 218 | -- @param properties The table describing the style 219 | local function define_style(t, name, properties) 220 | local properties = table_copy(properties) 221 | local count = 0 222 | for _, v in pairs(M) do 223 | if type(v) == 'table' then count = count + 1 end 224 | end 225 | local number = STYLE_LASTPREDEFINED + count - #default_styles + 1 226 | if (number > STYLE_MAX) then error('Maximum style number exceeded') end 227 | properties.number = number 228 | properties.apply = apply 229 | rawset(t, name, properties) 230 | end 231 | 232 | setmetatable(M, {__newindex=define_style}) 233 | 234 | -- Ensure Textredux styles are defined after switching buffers or views. 235 | events.connect(events.BUFFER_AFTER_SWITCH, M.activate_styles) 236 | events.connect(events.VIEW_NEW, M.activate_styles) 237 | events.connect(events.VIEW_AFTER_SWITCH, M.activate_styles) 238 | 239 | return M 240 | -------------------------------------------------------------------------------- /core/ui.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2011-2012 Nils Nordman 2 | -- Copyright 2012-2014 Robert Gieseke 3 | -- License: MIT (see LICENSE) 4 | 5 | --[[-- 6 | The ui module handles UI related operations for Textredux. 7 | 8 | @module textredux.core.ui 9 | ]] 10 | local M = {} 11 | 12 | --- 13 | -- Specifies the way that Textredux should split views. 14 | -- Possible values are: 15 | -- - `'horizontal'` : Prefer horizontal splits. 16 | -- - `'vertical'` : Prefer vertical splits. 17 | M.view_split_preference = 'vertical' 18 | 19 | function M.switch_to_other_view() 20 | if #_VIEWS ~= 1 then 21 | _G.ui.goto_view(1, true) 22 | else 23 | view:split(M.view_split_preference == 'vertical') 24 | end 25 | end 26 | 27 | return M 28 | -------------------------------------------------------------------------------- /ctags.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2012-2014 Robert Gieseke 2 | -- License: MIT (see LICENSE) 3 | 4 | --[[-- 5 | Displays a filtered list of symbols (functions, variables, …) in the current 6 | document using [Exuberant Ctags](http://ctags.sourceforge.net/). 7 | 8 | Usage 9 | ----- 10 | 11 | In your init.lua: 12 | 13 | local textredux = require 'textredux' 14 | keys.cg = textredux.ctags.goto_symbol -- Ctrl+G 15 | 16 | Requirements 17 | ------------ 18 | 19 | Exuberant Ctags needs to be installed and is available in the 20 | usual package managers. 21 | 22 | Debian/Ubuntu: 23 | 24 | sudo apt-get install exuberant-ctags 25 | 26 | Homebrew on OS X: 27 | 28 | brew install ctags 29 | 30 | Note that it is possible to add support for additional filetypes in your 31 | `~/.ctags`, for example LaTeX: 32 | 33 | --langdef=latex 34 | --langmap=latex:.tex 35 | --regex-latex=/\\label\{([^}]*)\}/\1/l,label/ 36 | --regex-latex=/\\section\{([^}]*)\}/\1/s,section/ 37 | --regex-latex=/\\subsection\{([^}]*)\}/\1/t,subsection/ 38 | --regex-latex=/\\subsubsection\{([^}]*)\}/\1/u,subsubsection/ 39 | --regex-latex=/\\section\*\{([^}]*)\}/\1/s,section/ 40 | --regex-latex=/\\subsection\*\{([^}]*)\}/\1/t,subsection/ 41 | --regex-latex=/\\subsubsection\*\{([^}]*)\}/\1/u,subsubsection/ 42 | 43 | This module is based on Mitchell's ctags code posted on the 44 | [Textadept wiki](http://foicica.com/wiki/ctags). 45 | 46 | @module textredux.ctags 47 | ]] 48 | 49 | local M = {} 50 | 51 | local reduxstyle = require 'textredux.core.style' 52 | local reduxlist = require 'textredux.core.list' 53 | 54 | --- 55 | -- Path and options for the ctags call can be defined in the `CTAGS` 56 | -- field. 57 | M.CTAGS = 'ctags --sort=yes --fields=+K-f' 58 | 59 | --- 60 | -- Mappings from Ctags kind to Textredux styles. 61 | M.styles = { 62 | class = reduxstyle.class, 63 | enum = reduxstyle['type'], 64 | enumerator = reduxstyle['type'], 65 | ['function'] = reduxstyle['function'], 66 | procedure = reduxstyle['function'], 67 | method = reduxstyle['function'], 68 | field = reduxstyle.variable, 69 | member = reduxstyle.variable, 70 | macro = reduxstyle.operator, 71 | namespace = reduxstyle.preproc, 72 | typedef = reduxstyle.keyword, 73 | variable = reduxstyle.variable 74 | } 75 | 76 | -- Close the Textredux list and jump to the selected line in the origin buffer. 77 | local function on_selection(list, item) 78 | local line = item[3] 79 | if line then 80 | ui.statusbar_text = line 81 | list:close() 82 | buffer:goto_line(tonumber(line) - 1) 83 | buffer:vertical_center_caret() 84 | end 85 | end 86 | 87 | -- Return color for Ctags kind. 88 | local function get_item_style(item, column_index) 89 | -- Use a capture to find fields like `function namespace:…`. 90 | local kind = item[2]:match('(%a+)%s?') 91 | return M.styles[kind] or reduxstyle.default 92 | end 93 | 94 | --- 95 | -- Goes to the selected symbol in a filtered list dialog. 96 | -- Requires [ctags]((http://ctags.sourceforge.net/)) to be installed. 97 | function M.goto_symbol() 98 | if not buffer.filename then return end 99 | local symbols = {} 100 | local p = os.spawn(M.CTAGS..' --sort=no --excmd=number -f - "'..buffer.filename..'"') 101 | for line in p:read('*all'):gmatch('[^\r\n]+') do 102 | local name, line, ext = line:match('^(%S+)\t[^\t]+\t([^;]+);"\t(.+)$') 103 | if name and line and ext then 104 | symbols[#symbols + 1] = {name, ext, line} 105 | end 106 | end 107 | if #symbols > 0 then 108 | local list = reduxlist.new('Go to symbol') 109 | list.items = symbols 110 | list.on_selection = on_selection 111 | list.column_styles = { reduxstyle.default, get_item_style } 112 | list:show() 113 | end 114 | end 115 | 116 | return M 117 | -------------------------------------------------------------------------------- /docs/api.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Textredux API Docs 7 | 8 | 9 | 10 | 11 | 12 | 13 | 44 | 45 |
46 |
47 | 48 | 49 |

Text-based interfaces for Textadept.

50 | 51 |

Modules

52 | 53 | 54 | 55 | 58 | 59 | 60 | 61 | 63 | 64 | 65 | 66 | 68 | 69 | 70 | 71 | 73 | 74 | 75 | 76 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 91 | 92 | 93 | 94 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |
textreduxThe Textredux module allows you to easily create text based interfaces for the 56 | Textadept editor and offers a set of text 57 | based interfaces.
textredux.fstextredux.fs provides a text based file browser and file system related 62 | functions for Textadept.
textredux.ctagsDisplays a filtered list of symbols (functions, variables, …) in the current 67 | document using Exuberant Ctags.
textredux.buffer_listThe buffer list module provides a text based replacement for the standard 72 | Textadept buffer list.
textredux.core.bufferTextredux buffers wrap Textadept buffers and extend them with support for 77 | custom styling, buffer specific key bindings and hotspot support.
textredux.core.filteredlistFiltered list wrapper, hijacking ui.dialogs.filteredlist.
textredux.core.indicatorThe indicator module provides support for indicators in your buffers.
textredux.coreThe Textredux core module allows you to easily create text based interfaces 90 | for the Textadept editor.
textredux.core.listThe list module provides a text based item listing for Textadept, featuring 95 | advanced search capabilities and styling.
textredux.core.styleThe style module lets you define and use custom, non-lexer-based styles.
textredux.core.uiThe ui module handles UI related operations for Textredux.
textredux.util.colorThe color module provides utility functions for color handling.
textredux.util.matcherThe matcher module provides easy and advanced matching of strings.
114 | 115 |
116 | Generated by LDoc 117 |
118 |
119 |
120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /docs/config.ld: -------------------------------------------------------------------------------- 1 | -- ldoc configuration file 2 | title = 'Textredux API Docs' 3 | project = 'Textredux' 4 | file = { 5 | '../init.lua', 6 | '../fs.lua', 7 | '../ctags.lua', 8 | '../buffer_list.lua', 9 | '../core', 10 | '../util' 11 | } 12 | format = 'markdown' 13 | style = true 14 | backtick_references = false 15 | description = [[ 16 | Text-based interfaces for Textadept. 17 | ]] 18 | output = 'api' 19 | template = '.' 20 | dir = '.' 21 | alias("p","param") 22 | -------------------------------------------------------------------------------- /docs/images/buffer_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rgieseke/textredux/c68ec44314febbf8bc3d761079918ac75c8e026b/docs/images/buffer_list.png -------------------------------------------------------------------------------- /docs/images/bufferlist.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rgieseke/textredux/c68ec44314febbf8bc3d761079918ac75c8e026b/docs/images/bufferlist.gif -------------------------------------------------------------------------------- /docs/images/ctags_terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rgieseke/textredux/c68ec44314febbf8bc3d761079918ac75c8e026b/docs/images/ctags_terminal.png -------------------------------------------------------------------------------- /docs/images/fs_fuzzy_matching.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rgieseke/textredux/c68ec44314febbf8bc3d761079918ac75c8e026b/docs/images/fs_fuzzy_matching.png -------------------------------------------------------------------------------- /docs/images/fs_listing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rgieseke/textredux/c68ec44314febbf8bc3d761079918ac75c8e026b/docs/images/fs_listing.png -------------------------------------------------------------------------------- /docs/images/fs_snapopen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rgieseke/textredux/c68ec44314febbf8bc3d761079918ac75c8e026b/docs/images/fs_snapopen.png -------------------------------------------------------------------------------- /docs/images/run_command.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rgieseke/textredux/c68ec44314febbf8bc3d761079918ac75c8e026b/docs/images/run_command.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Textredux 7 | 8 | 9 | 10 | 11 | 12 | 13 | 26 | 27 |
28 |
29 |

Textredux

30 | 31 |

Textredux is a module for the Textadept editor 32 | that offers text-based interfaces for core Textadept 33 | functionality.

34 | 35 | 36 |

Features

37 |
    38 |
  • A text-based file browser. Completely keyboard driven, with powerful search 39 | and seamless switching between traditional and 40 | snapopen (recursive) listings.
  • 41 |
  • A text-based replacement for the buffer list, also 42 | providing an easy way to close buffers and directories.
  • 43 |
  • A simple function call that automatically integrates all Textredux 44 | functionality in the Textadept editor. No need to configure anything further 45 | unless you want to – once you’ve loaded this you will be using Textredux. 46 | In addition to integrating the above modules, it will hook into Textadept and 47 | inject text based interfaces whereever it can (e.g. file open or theme selection).
  • 48 |
49 | 50 | 51 |

Installation

52 | 53 |

The Textredux module itself can be downloaded from the 54 | releases page or 55 | by getting the latest version on master 56 | as a zip file. 57 |

58 | 59 |

To install the module, just unpack the module into the

60 | 61 |
~/.textadept/modules/textredux
62 | 63 |

directory. 64 | Alternatively you can clone Textredux into it:

65 | 66 |
 67 | cd ~/.textadept/modules
 68 | git clone https://github.com/rgieseke/textredux.git
 69 | 
70 | 71 |

Usage

72 | 73 |

Having installed the module, there are two ways you can use it. Note that due to 74 | Textredux re-using colors defined in your theme file, loading Textredux should happen 75 | after a custom theme is set using ui.set_theme in your init.lua. 76 |

77 | 78 |

1) Cherrypick the functionality you want from the different modules by assigning 79 | key bindings to the desired functions. As an example, if you would like to use 80 | the text based file browser and normally open files using Ctrl-O, then the 81 | following code in your init.lua would do the trick:

82 | 83 |
 84 | textredux = require 'textredux'
 85 | keys.co = textredux.fs.open_file
 86 | 
87 | 88 |

2) If you can’t get enough of text based interfaces and the joy they provide, 89 | then the Textredux hijack function is for you. Simple place this in your 90 | init.lua:

91 | 92 |
require('textredux').hijack()
 93 | 
94 | 95 |

As the name suggest, Textredux has now hijacked your environment. All your 96 | regular key bindings should now use Textredux where applicable. Clicking the 97 | menu will still open the standard GUI dialogs.

98 | 99 |

Regardless of the approach chosen, the 100 | module documentation contains more 101 | in-depth documentation for the various modules, including customization tips.

102 | 103 |

Also, make sure to take a brief look at the screen shots for some 104 | quick tips on using Textredux.

105 | 106 |

Code

107 | 108 |

Textredux is released under the MIT license (see the LICENSE file in the source 109 | for the full details). The source code 110 | is available from GitHub. It was written by 111 | Nils Nordman 112 | and is now maintained by Robert Gieseke.

113 | 114 |

Contribute

115 | 116 |

Any feedback, be it patches, feature request or bug reports is most welcome.

117 | 118 |

If you want to provide patches, the preferred way of doing so would be as a pull 119 | request via GitHub, or as a pull request from some other Git server. Should that 120 | not be an option patches are gladly accepted through other means as well.

121 | 122 |

If you have any bug reports or feature requests, please submit them to the 123 | Github issue tracker 124 | or send an email to Textadept’s mailing list.

125 | 126 |

Changelog

127 | 128 |

1.2 (2020-03-09)

129 | 130 |
    131 |
  • Release to be compatible with Textadept 10
  • 132 |
133 | 134 |

1.1 (2016-02-25)

135 | 136 |
    137 |
  • Don't hijack save_as dialog
  • 138 |
  • Allow drive selection on Windows
  • 139 |
  • Bug fixes, styling improvements
  • 140 |
141 | 142 |

1.0.3 (2014-09-08)

143 | 144 |
    145 |
  • Requires a nightly build with fixes for spawn on OS X and the terminal version
  • 146 |
  • Fix console window popup on Windows for Ctags list (Thanks to Feng Li)
  • 147 |
  • Add example file to show output of git ls-files
  • 148 |
149 | 150 | 151 |

1.0.2 (2014-08-15)

152 | 153 |
    154 |
  • Fix bug caused by loading lfs explicitly after Textadept switched to lfs v1.6.2
  • 155 |
156 | 157 |

1.0.1 (2014-06-27)

158 | 159 |
    160 |
  • Fix styling bugs
  • 161 |
  • Add note on updating styles
  • 162 |
163 | 164 |

1.0.0 (2014-06-24)

165 | 166 |
    167 |
  • Requires Textadept 7.4 or later
  • 168 |
  • Hijack is now a function, no longer a module
  • 169 |
  • Waiting for events.INITIALIZED is no longer necessary when requiring Textredux
  • 170 |
  • Keys use Textadept's keys.MODE under the hood
  • 171 |
  • Removed support for on_keypress events
  • 172 |
  • Add support for on_char_added events, see list.lua for an example
  • 173 |
  • Removed (flaky) support for modifiers on key presses and mouse clicks
  • 174 |
  • Removed reduxbuffer.newline function
  • 175 |
  • Hide Textadept's line numbers in Textredux buffers
  • 176 |
  • All Textredux buffers can be closed with Esc by default
  • 177 |
  • Indicators and styles are now set using an apply function bound to each table
  • 178 |
  • The Ctags list colorises the kind of symbol (class, function, …)
  • 179 |
  • The current search is kept when toggling between snapopen and directory view
  • 180 |
  • In lists scrolling is limited to the selectable items
  • 181 |
  • The search in lists can be reset using Ctrl/Alt/Meta-Backspace
  • 182 |
183 | 184 |

0.11 (2014-03-04)

185 | 186 |
    187 |
  • Workaround for bug on Windows
  • 188 |
189 | 190 |

0.10 (2013-10-29)

191 | 192 |
    193 |
  • New layout and updates for the documentation
  • 194 |
  • Renamed core.gui to core.ui 195 |
  • Fix using default filtered list when not hi-jacked
  • 196 |
  • Fix bug when opening buffers in a new view
  • 197 |
  • Fix indicator example
  • 198 |
199 | 200 | 201 |

0.9 (2013-10-01)

202 | 203 |
    204 |
  • Update for API changes until Textadept 7.0 beta 4
  • 205 |
206 | 207 | 208 |

0.8 (2013-07-11)

209 | 210 |
    211 |
  • Update for module name change to file_types in Textadept 7.0 beta
  • 212 |
213 | 214 | 215 |

0.7 (2013-07-02)

216 | 217 |
    218 |
  • Compatibility with Textadept 7.0 alpha 2
  • 219 |
  • Better workaround for item selection in Curses version 220 | (Thanks to Chris Emerson)
  • 221 |
222 | 223 | 224 |

0.6 (2013-06-08)

225 | 226 |
    227 |
  • Compatibility with Textadept 6.6 and 7.0 alpha
  • 228 |
  • Added ctags symbol filtered list search
  • 229 |
  • Removed workaround for Curses version
  • 230 |
231 | 232 | 233 |

0.5 (2013-05-02)

234 | 235 |
    236 |
  • Updates for API changes in Textadept 6.5 and 6.6 beta.
  • 237 |
238 | 239 | 240 |

0.4 (2013-03-05)

241 | 242 |
    243 |
  • Integrate TextUI module back into Textredux as the core submodule.
  • 244 |
  • Update to snapopen API change in Textadept 6.2.
  • 245 |
246 | 247 | 248 |

0.3 (2012-11-28)

249 | 250 |
    251 |
  • Updates for API changes until Textadept 6.
  • 252 |
  • Don’t hijack menu entries, use the default widgets when clicking the menu.
  • 253 |
254 | 255 | 256 |

0.2 (2012-03-08)

257 | 258 |
    259 |
  • Make ctrl+enter open entries in another view (buffer list / file browser)
  • 260 |
  • Make buffer list keys configurable
  • 261 |
  • Handle buffers with nil directories in the buffer list
  • 262 |
  • TextUI: 263 | 264 |
      265 |
    • Added support for indicators
    • 266 |
    • Improved auto-loading of not-shown list items
    • 267 |
    • Added highlighting of list matches
    • 268 |
    • Added mouse support for buffer hotspots
    • 269 |
    • Font names are now included for default styles
    • 270 |
    • List selection callbacks now receive modifiers as well
    • 271 |
    272 |
  • 273 |
274 | 275 | 276 |

0.1 (2012-01-20)

277 | 278 |

First public release.

279 |
280 |
281 | 282 | 283 | -------------------------------------------------------------------------------- /docs/ldoc.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.1.3 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /** 8 | * Correct `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | main, 20 | nav, 21 | section, 22 | summary { 23 | display: block; 24 | } 25 | 26 | /** 27 | * Correct `inline-block` display not defined in IE 8/9. 28 | */ 29 | 30 | audio, 31 | canvas, 32 | video { 33 | display: inline-block; 34 | } 35 | 36 | /** 37 | * Prevent modern browsers from displaying `audio` without controls. 38 | * Remove excess height in iOS 5 devices. 39 | */ 40 | 41 | audio:not([controls]) { 42 | display: none; 43 | height: 0; 44 | } 45 | 46 | /* ========================================================================== 47 | Base 48 | ========================================================================== */ 49 | 50 | /** 51 | * 1. Set default font family to sans-serif. 52 | * 2. Prevent iOS text size adjust after orientation change, without disabling 53 | * user zoom. 54 | */ 55 | 56 | html { 57 | font-family: sans-serif; /* 1 */ 58 | -ms-text-size-adjust: 100%; /* 2 */ 59 | -webkit-text-size-adjust: 100%; /* 2 */ 60 | } 61 | 62 | /** 63 | * Remove default margin. 64 | */ 65 | 66 | body { 67 | margin: 0; 68 | } 69 | 70 | /* ========================================================================== 71 | Links 72 | ========================================================================== */ 73 | 74 | /** 75 | * Remove the gray background color from active links in IE 10. 76 | */ 77 | 78 | a { 79 | background: transparent; 80 | } 81 | 82 | /** 83 | * Address `outline` inconsistency between Chrome and other browsers. 84 | */ 85 | 86 | a:focus { 87 | outline: thin dotted; 88 | } 89 | 90 | /** 91 | * Improve readability when focused and also mouse hovered in all browsers. 92 | */ 93 | 94 | a:active, 95 | a:hover { 96 | outline: 0; 97 | } 98 | 99 | /* ========================================================================== 100 | Typography 101 | ========================================================================== */ 102 | 103 | /** 104 | * Address variable `h1` font-size and margin within `section` and `article` 105 | * contexts in Firefox 4+, Safari 5, and Chrome. 106 | */ 107 | 108 | h1 { 109 | font-size: 2em; 110 | margin: 0.67em 0; 111 | } 112 | 113 | /** 114 | * Address styling not present in IE 8/9, Safari 5, and Chrome. 115 | */ 116 | 117 | abbr[title] { 118 | border-bottom: 1px dotted; 119 | } 120 | 121 | /** 122 | * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 123 | */ 124 | 125 | b, 126 | strong { 127 | font-weight: bold; 128 | } 129 | 130 | /** 131 | * Address differences between Firefox and other browsers. 132 | */ 133 | 134 | hr { 135 | -moz-box-sizing: content-box; 136 | box-sizing: content-box; 137 | height: 0; 138 | } 139 | 140 | /** 141 | * Address styling not present in IE 8/9. 142 | */ 143 | 144 | mark { 145 | background: #ff0; 146 | color: #000; 147 | } 148 | 149 | /** 150 | * Correct font family set oddly in Safari 5 and Chrome. 151 | */ 152 | 153 | code, 154 | kbd, 155 | pre, 156 | samp { 157 | font-family: monospace, serif; 158 | font-size: 1em; 159 | } 160 | 161 | /** 162 | * Improve readability of pre-formatted text in all browsers. 163 | */ 164 | 165 | pre { 166 | white-space: pre-wrap; 167 | } 168 | 169 | /** 170 | * Address inconsistent and variable font size in all browsers. 171 | */ 172 | 173 | small { 174 | font-size: 80%; 175 | } 176 | 177 | /** 178 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 179 | */ 180 | 181 | sub, 182 | sup { 183 | font-size: 75%; 184 | line-height: 0; 185 | position: relative; 186 | vertical-align: baseline; 187 | } 188 | 189 | sup { 190 | top: -0.5em; 191 | } 192 | 193 | sub { 194 | bottom: -0.25em; 195 | } 196 | 197 | /* ========================================================================== 198 | Embedded content 199 | ========================================================================== */ 200 | 201 | /** 202 | * Remove border when inside `a` element in IE 8/9. 203 | */ 204 | 205 | img { 206 | border: 0; 207 | } 208 | 209 | /* ========================================================================== 210 | Tables 211 | ========================================================================== */ 212 | 213 | /** 214 | * Remove most spacing between table cells. 215 | */ 216 | 217 | table { 218 | border-collapse: collapse; 219 | border-spacing: 0; 220 | } 221 | 222 | 223 | /* ========================================================================== 224 | Custom Textredux CSS 225 | ===========================================================================*/ 226 | 227 | * { 228 | -webkit-box-sizing: border-box; 229 | -moz-box-sizing: border-box; 230 | box-sizing: border-box; 231 | } 232 | 233 | body { 234 | background-color: #fefbec; 235 | font-size: 16px; 236 | line-height: 1.65; 237 | } 238 | 239 | #sidebar { 240 | margin-left: -280px; 241 | width: 280px; 242 | position: fixed; 243 | top: 0; 244 | left: 220px; 245 | bottom: 0; 246 | z-index: 1000; 247 | background-color: #e8e4cf; 248 | overflow-y: auto; 249 | text-align: right; 250 | padding-top: 20px; 251 | border-right: 1px solid lightgray; 252 | } 253 | 254 | .container { 255 | margin-top: 20px; 256 | padding-left: 240px; 257 | padding-right: 20px; 258 | } 259 | 260 | .content { 261 | max-width: 640px; 262 | } 263 | 264 | .container ul { 265 | padding-left: 20px; 266 | } 267 | 268 | #sidebar ul { 269 | padding-right: 20px; 270 | list-style: none; 271 | margin-top: 0; 272 | } 273 | 274 | a:link { 275 | text-decoration: none; 276 | color: #b58900; 277 | } 278 | a:visited { 279 | text-decoration: none; 280 | color: #b58900; 281 | } 282 | a:hover { 283 | color: #cb4b16; text-decoration: underline; 284 | } 285 | a:active { 286 | color: #cb4b16; 287 | } 288 | 289 | h1, h2 { 290 | font-family: 'Bowlby One SC'; 291 | letter-spacing: 0.03em; 292 | text-transform: uppercase; 293 | line-height: 1.4em; 294 | color: #b58900; 295 | } 296 | 297 | h1 { 298 | margin: 0; 299 | font-size: 5em; 300 | line-height: 0.9em; 301 | padding-bottom: 10px; 302 | } 303 | 304 | h2 { 305 | padding-top: 20px; 306 | } 307 | 308 | h2:first-of-type { 309 | padding-top: 0; 310 | } 311 | 312 | pre { 313 | font-family: Consolas, 'Liberation Mono', Courier, monospace; 314 | color: #333; 315 | background: rgb(250, 250, 250); 316 | padding: 10px; 317 | border: 1px solid lightgray; 318 | } 319 | 320 | table, td { 321 | border: 1px solid lightgray; 322 | } 323 | 324 | footer { 325 | padding-top: 40px; 326 | } 327 | 328 | dd { 329 | padding-bottom: 40px; 330 | } 331 | 332 | dd h3 { 333 | font-size: 16px; 334 | } 335 | 336 | img.border { 337 | border: 1px solid lightgray; 338 | border-radius: 5px; 339 | } 340 | -------------------------------------------------------------------------------- /docs/ldoc.ltp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | $(ldoc.title) 7 | 8 | 9 | 10 | 11 | 12 | # local no_spaces = ldoc.no_spaces 13 | # local use_li = ldoc.use_li 14 | # local display_name = ldoc.display_name 15 | # local iter = ldoc.modules.iter 16 | # local function M(txt,item) return ldoc.markup(txt,item,ldoc.plain) end 17 | # local nowrap = ldoc.wrap and '' or 'nowrap' 18 | 19 | 80 | 81 |
82 |
83 | 84 | # if ldoc.body then -- verbatim HTML as contents; 'non-code' entries 85 | $(ldoc.body) 86 | # elseif module then -- module documentation 87 |

$(ldoc.module_typename(module)) $(module.name)

88 |

$(M(module.summary,module))

89 |

$(M(module.description,module))

90 | # if module.usage then 91 | # local li,il = use_li(module.usage) 92 |

Usage:

93 |
    94 | # for usage in iter(module.usage) do 95 | $(li)
    $(ldoc.escape(usage))
    $(il) 96 | # end -- for 97 |
98 | # end -- if usage 99 | # if module.info then 100 |

Info:

101 |
    102 | # for tag, value in module.info:iter() do 103 |
  • $(tag): $(M(value,module))
  • 104 | # end 105 |
106 | # end -- if module.info 107 | 108 | 109 | # if not ldoc.no_summary then 110 | # -- bang out the tables of item types for this module (e.g Functions, Tables, etc) 111 | # for kind,items in module.kinds() do 112 |

$(kind)

113 | 114 | # for item in items() do 115 | 116 | 117 | 118 | 119 | # end -- for items 120 |
$(display_name(item))$(M(item.summary,item))
121 | #end -- for kinds 122 | 123 |
124 |
125 | 126 | #end -- if not no_summary 127 | 128 | # --- currently works for both Functions and Tables. The params field either contains 129 | # --- function parameters or table fields. 130 | # local show_return = not ldoc.no_return_or_parms 131 | # local show_parms = show_return 132 | # for kind, items in module.kinds() do 133 | # local kitem = module.kinds:get_item(kind) 134 |

$(kind)

135 | #-- $(M(module.kinds:get_section_description(kind),nil)) 136 | # if kitem then 137 | $(M(ldoc.descript(kitem),kitem)) 138 | # if kitem.usage then 139 |

Usage:

140 |
$(ldoc.prettify(kitem.usage[1]))
141 | # end 142 | # end 143 |
144 | # for item in items() do 145 |
146 | 147 | $(display_name(item)) 148 |
149 |
150 | $(M(ldoc.descript(item),item)) 151 | 152 | # if ldoc.custom_tags then 153 | # for custom in iter(ldoc.custom_tags) do 154 | # local tag = item.tags[custom[1]] 155 | # if tag and not custom.hidden then 156 | # local li,il = use_li(tag) 157 |

$(custom.title or custom[1]):

158 |
    159 | # for value in iter(tag) do 160 | $(li)$(custom.format and custom.format(value) or M(value))$(il) 161 | # end -- for 162 | # end -- if tag 163 |
164 | # end -- iter tags 165 | # end 166 | 167 | # if show_parms and item.params and #item.params > 0 then 168 | # local subnames = module.kinds:type_of(item).subnames 169 | # if subnames then 170 |

$(subnames):

171 | # end 172 |
    173 | # for parm in iter(item.params) do 174 | # local param,sublist = item:subparam(parm) 175 | # if sublist then 176 |
  • $(sublist)$(M(item.params.map[sublist],item)) 177 |
      178 | # end 179 | # for p in iter(param) do 180 | # local name,tp,def = item:display_name_of(p), ldoc.typename(item:type_of_param(p)), item:default_of_param(p) 181 |
    • $(name) 182 | # if tp ~= '' then 183 | $(tp) 184 | # end 185 | $(M(item.params.map[p],item)) 186 | # if def then 187 | (default $(def)) 188 | # end 189 | # if item:readonly(p) then 190 | readonly 191 | # end 192 |
    • 193 | # end 194 | # if sublist then 195 |
    196 | # end 197 | # end -- for 198 |
199 | # end -- if params 200 | 201 | # if show_return and item.retgroups then local groups = item.retgroups 202 |

Returns:

203 | # for i,group in ldoc.ipairs(groups) do local li,il = use_li(group) 204 |
    205 | # for r in group:iter() do local type, ctypes = item:return_type(r); local rt = ldoc.typename(type) 206 | $(li) 207 | # if rt ~= '' then 208 | $(rt) 209 | # end 210 | $(M(r.text,item))$(il) 211 | # if ctypes then 212 |
      213 | # for c in ctypes:iter() do 214 |
    • $(c.name) 215 | $(ldoc.typename(c.type)) 216 | $(M(c.comment,item))
    • 217 | # end 218 |
    219 | # end -- if ctypes 220 | # end -- for r 221 |
222 | # if i < #groups then 223 |

Or

224 | # end 225 | # end -- for group 226 | # end -- if returns 227 | 228 | # if show_return and item.raise then 229 |

Raises:

230 | $(M(item.raise,item)) 231 | # end 232 | 233 | # if item.see then 234 | # local li,il = use_li(item.see) 235 |

See also:

236 |
    237 | # for see in iter(item.see) do 238 | $(li)$(see.label)$(il) 239 | # end -- for 240 |
241 | # end -- if see 242 | 243 | # if item.usage then 244 | # local li,il = use_li(item.usage) 245 |

Usage:

246 |
    247 | # for usage in iter(item.usage) do 248 | $(li)
    $(ldoc.prettify(usage))
    $(il) 249 | # end -- for 250 |
251 | # end -- if usage 252 | 253 |
254 | # end -- for items 255 |
256 | # end -- for kinds 257 | 258 | # else -- if module; project-level contents 259 | 260 | # if ldoc.description then 261 |

$(M(ldoc.description,nil))

262 | # end 263 | # if ldoc.full_description then 264 |

$(M(ldoc.full_description,nil))

265 | # end 266 | 267 | # for kind, mods in ldoc.kinds() do 268 |

$(kind)

269 | # kind = kind:lower() 270 | 271 | # for m in mods() do 272 | 273 | 274 | 275 | 276 | # end -- for modules 277 |
$(m.name)$(M(m.summary,m))
278 | # end -- for kinds 279 | # end -- if module 280 | 281 |
282 | Generated by LDoc 283 |
284 |
285 |
286 | 287 | 288 | 289 | -------------------------------------------------------------------------------- /docs/modules/textredux.buffer_list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Textredux API Docs 7 | 8 | 9 | 10 | 11 | 12 | 13 | 49 | 50 |
51 |
52 | 53 |

Module textredux.buffer_list

54 |

The buffer list module provides a text based replacement for the standard 55 | Textadept buffer list.

56 |

57 | 58 | 59 |

Usage

60 | 61 |

Use the textredux.hijack function or load the buffer list 62 | in your ~/.textadept/init.lua:

63 | 64 | 65 |
 66 | local textredux = require('textredux')
 67 | keys["ctrl+b"] = textredux.buffer_list.show
 68 | 
69 | 70 |

Features

71 | 72 |
    73 |
  • Close a buffer from the buffer list (bound to Ctrl + D by default)
  • 74 |
  • Close all files in the directory of the selected buffer 75 | (bound to Ctrl + Shift + D and Meta + D in Curses by default)
  • 76 |
  • The list of buffers is sorted and the current buffer is pre-selected
  • 77 |
  • The buffers to show can be specified using a function
  • 78 |
79 |

80 | 81 | 82 |

Functions

83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 |
currently_selected_buffer (list)Returns the currently selected buffer in the list.
close_buffer (list)Closes the currently selected buffer in the buffer list.
close_selected (list)Closes all currently selected/filtered files in the buffer list.
show (buffers)Shows a list of the specified buffers, or _G.BUFFERS if not specified.
101 |

Tables

102 | 103 | 104 | 105 | 106 | 107 |
keysThe key bindings for the buffer list.
108 |

Fields

109 | 110 | 111 | 112 | 113 | 114 |
listThe Textredux list instance used by the buffer list.
115 | 116 |
117 |
118 | 119 | 120 |

Functions

121 |
122 |
123 | 124 | currently_selected_buffer (list) 125 |
126 |
127 | Returns the currently selected buffer in the list. 128 | 129 | 130 |

Parameters:

131 |
    132 |
  • list 133 | The Textredux list instance used by the buffer list. If not 134 | provided, then the global list is used automatically. 135 |
  • 136 |
137 | 138 |

Returns:

139 |
    140 |
  1. 141 | The currently selected buffer, if any.
  2. 142 |
  3. 143 | The currently selected buffer's name, if any.
  4. 144 |
145 | 146 | 147 | 148 | 149 |
150 |
151 | 152 | close_buffer (list) 153 |
154 |
155 | Closes the currently selected buffer in the buffer list. 156 | 157 | 158 |

Parameters:

159 |
    160 |
  • list 161 | The Textredux list instance used by the buffer list. This function 162 | is ordinarily invoked as the result of a key binding, and you should thus not 163 | need to specify this yourself. If list isn't provided, the global list is 164 | automatically used. 165 |
  • 166 |
167 | 168 | 169 | 170 | 171 | 172 |
173 |
174 | 175 | close_selected (list) 176 |
177 |
178 | Closes all currently selected/filtered files in the buffer list. 179 | 180 | 181 |

Parameters:

182 |
    183 |
  • list 184 | The Textredux list instance used by the buffer list. This function 185 | is ordinarily invoked as the result of a key binding, and you should thus not 186 | need to specify this yourself. If list isn't provided, the global list is 187 | automatically used. 188 |
  • 189 |
190 | 191 | 192 | 193 | 194 | 195 |
196 |
197 | 198 | show (buffers) 199 |
200 |
201 | Shows a list of the specified buffers, or _G.BUFFERS if not specified. 202 | 203 | 204 |

Parameters:

205 |
    206 |
  • buffers 207 | Either nil, in which case all buffers within _G.BUFFERS 208 | are displayed, or a function returning a table of buffers to display. 209 |
  • 210 |
211 | 212 | 213 | 214 | 215 | 216 |
217 |
218 |

Tables

219 |
220 |
221 | 222 | keys 223 |
224 |
225 | The key bindings for the buffer list.

226 | 227 |

You can modifiy this to customise the key bindings to your liking. The key 228 | bindings are passed directly to the Textredux list, so note that the 229 | first argument to any function will be the Textredux list itself. 230 | You can read more about the Textredux list's keys in the 231 | list documentation.

232 | 233 |

If you like to add a custom key binding for closing a buffer or all buffers 234 | in the current selection you can bind the close_buffer and close_selected 235 | functions to a key of your choice. For other actions it's likely that you want to 236 | obtain the currently selected buffer - you can use the currently_selected_buffer 237 | function for that. 238 | 239 | 240 |

Fields:

241 |
    242 |
  • [ctrl+d] 243 | Default for close buffer 244 |
  • 245 |
  • [CURSES] 246 | 247 | 248 | 249 |
  • 250 |
251 | 252 | 253 | 254 | 255 | 256 |
257 |
258 |

Fields

259 |
260 |
261 | 262 | list 263 |
264 |
265 | The Textredux list instance used by the buffer list. 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 |
274 |
275 | 276 | 277 |
278 | Generated by LDoc 279 |
280 |
281 |
282 | 283 | 284 | 285 | -------------------------------------------------------------------------------- /docs/modules/textredux.core.filteredlist.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Textredux API Docs 7 | 8 | 9 | 10 | 11 | 12 | 13 | 43 | 44 |
45 |
46 | 47 |

Module textredux.core.filteredlist

48 |

Filtered list wrapper, hijacking ui.dialogs.filteredlist.

49 |

50 | 51 |

52 | 53 | 54 | 55 |
56 |
57 | 58 | 59 | 60 | 61 |
62 | Generated by LDoc 63 |
64 |
65 |
66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /docs/modules/textredux.core.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Textredux API Docs 7 | 8 | 9 | 10 | 11 | 12 | 13 | 43 | 44 |
45 |
46 | 47 |

Module textredux.core

48 |

The Textredux core module allows you to easily create text based interfaces 49 | for the Textadept editor.

50 |

It currently consists of the following components:

51 | 52 |
    53 |
  • The textredux.core.buffer module that supports custom styling, buffer 54 | specific key bindings, hotspot support and generally makes it easy to 55 | create a text based interface buffer by taking care of the background 56 | gruntwork required.

  • 57 |
  • The textredux.core.style module that let's you easily define custom 58 | styles, as well as leveraging the default styles already provided by the 59 | user's theme.

  • 60 |
  • The textredux.core.indicator module that provides a convenient way of 61 | using indicators in your buffers.

  • 62 |
  • The textredux.core.list module that provides a versatile and extensible 63 | text based item listing for Textadept, featuring advanced search capabilities 64 | and styling.

  • 65 |
66 | 67 |

How to use

68 | 69 |

After installing the Textredux module into your modules directory, you can 70 | either do

71 | 72 | 73 |
 74 | local textredux = require('textredux')
 75 | local reduxlist = textredux.core.list
 76 | 
77 | 78 |

or you can just the modules that you want by something 79 | similar to

80 | 81 | 82 |
 83 | local reduxstyle = require('textredux.core.style')
 84 | local reduxbuffer = require('textredux.core.style')
 85 | 
86 | 87 |

The examples provide an overview on how to use the various components and their 88 | features, and the documentation for each component provides more details.

89 | 90 | 91 | 92 |
93 |
94 | 95 | 96 | 97 | 98 |
99 | Generated by LDoc 100 |
101 |
102 |
103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /docs/modules/textredux.core.indicator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Textredux API Docs 7 | 8 | 9 | 10 | 11 | 12 | 13 | 43 | 44 |
45 |
46 | 47 |

Module textredux.core.indicator

48 |

The indicator module provides support for indicators in your buffers.

49 |

Indicators lets you visually mark a certain text range using various styles and 50 | colors. Using the event mechanism you can also receive events whenever the 51 | user clicks the marked text.

52 | 53 |

The indicator definition

54 | 55 |

An indicator is defined using a simple table with the properties listed below. 56 | For the most part, these properties maps directly to fields in the 57 | buffer API.

58 | 59 |
    60 |
  • style: The style of the indicator. See indic_style.
  • 61 |
  • alpha: Alpha transparency value from 0 to 255 (or 256 for no alpha), used 62 | for fill colors of rectangles . See indic_alpha.
  • 63 |
  • outline_alpha: Alpha transparency value from 0 to 255 (or 256 for no alpha), 64 | used for outline colors of rectangles . See indic_outline_alpha.
  • 65 |
  • fore: The foreground color of the indicator. The color should be specified 66 | in the '#rrggbb' notation.
  • 67 |
  • under: Whether an indicator is drawn under text or over (default). Drawing 68 | under text works only when two phase drawing is enabled for the buffer (the 69 | default).
  • 70 |
71 | 72 |

A simple example:

73 | 74 | 75 |
 76 | local reduxindicator = textredux.core.indicator
 77 | reduxindicator.RED_BOX = {style = c.INDIC_BOX, fore = '#ff0000'}
 78 | 
79 | 80 |

Using indicators

81 | 82 |

Start with defining your indicators using the format described above. You can 83 | then either apply them against a range of text using apply, or pass them to 84 | one of the text insertion functions in textredux.core.buffer.

85 | 86 | 87 |
 88 | local text = 'Text for a red box indicator\n\n'
 89 | buffer:add_text(text)
 90 | reduxindicator.RED_BOX:apply(0, #text)
 91 | 
 92 | buffer:add_text(text, nil, nil, reduxindicator.RED_BOX)
 93 | 
94 | 95 |

Please also see the file examples/buffer_indicators.lua.

96 | 97 | 98 | 99 |
100 |
101 | 102 | 103 | 104 | 105 |
106 | Generated by LDoc 107 |
108 |
109 |
110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /docs/modules/textredux.core.list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Textredux API Docs 7 | 8 | 9 | 10 | 11 | 12 | 13 | 50 | 51 |
52 |
53 | 54 |

Module textredux.core.list

55 |

The list module provides a text based item listing for Textadept, featuring 56 | advanced search capabilities and styling.

57 |

58 | 59 | 60 |

How to use

61 | 62 |

Create the list using new, specify items and other fields/callbacks 63 | (such as on_selection) and invoke list:show.

64 | 65 |

Please see also the various list examples in ./examples.

66 | 67 |

Features

68 | 69 |
    70 |
  • Support for multi-column table items, in addition to supporting the simpler 71 | case of just listing strings.
  • 72 |
  • Fully customizable styling. You can either specify individual styles for 73 | different columns, or specify styles for each item dynamically using a 74 | callback.
  • 75 |
  • Powerful search capabilities. The list class supports both exact matching and 76 | fuzzy matching, and will present best matches first. It also supports 77 | searching for multiple search strings (any text separated by whitespace is 78 | considered to be multiple search strings). Searches are done against all 79 | columns.
  • 80 |
  • Ctrl/Alt/Meta-Backspace resets the current search.
  • 81 |
82 |

83 | 84 | 85 |

Functions

86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 |
new (title, items, on_selection)Creates a new list.
show ()Shows the list.
get_current_selection ()Returns the currently selected item if any, or nil otherwise.
close ()Closes the list.
get_current_search ()Returns the current user search if any, or nil otherwise.
set_current_search (search)Sets the current user search.
112 |

Tables

113 | 114 | 115 | 116 | 117 | 118 |
column_stylesThe default styles to use for different columns.
119 |

Fields

120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 |
header_styleThe default style to use for diplaying headers.
match_highlight_styleThe style to use for indicating matches.
search_case_insensitiveWhether searches are case insensitive or not.
search_fuzzyWhether fuzzy searching should be in addition to explicit matches.
138 |

List instance fields

139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 169 | 170 |
headersOptional headers for the list.
itemsA table of items to display in the list.
on_selectionThe handler/callback to call when the user has selected an item.
on_new_selectionThe handler/callback to call when the user has typed in text which 155 | doesn't match any item, and presses <enter>.
bufferThe underlying textredux.core.buffer used by the list.
keysA table of key commands for the list.
dataA general purpose table that can be used for storing state associated 168 | with the list.
171 | 172 |
173 |
174 | 175 | 176 |

Functions

177 |
178 |
179 | 180 | new (title, items, on_selection) 181 |
182 |
183 | Creates a new list. 184 | 185 | 186 |

Parameters:

187 |
    188 |
  • title 189 | The list title 190 |
  • 191 |
  • items 192 | The list items, see items. Not required, items can be set later 193 | using the items field. 194 |
  • 195 |
  • on_selection 196 | The on selection handler, see on_selection. Not required, 197 | this can be specified later using the on_selection field. 198 |
  • 199 |
200 | 201 |

Returns:

202 |
    203 | 204 | The new list instance 205 |
206 | 207 | 208 | 209 | 210 |
211 |
212 | 213 | show () 214 |
215 |
216 | Shows the list. 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 |
225 |
226 | 227 | get_current_selection () 228 |
229 |
230 | Returns the currently selected item if any, or nil otherwise. 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 |
239 |
240 | 241 | close () 242 |
243 |
244 | Closes the list. 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 |
253 |
254 | 255 | get_current_search () 256 |
257 |
258 | Returns the current user search if any, or nil otherwise. 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 |
267 |
268 | 269 | set_current_search (search) 270 |
271 |
272 | Sets the current user search. 273 | 274 | 275 |

Parameters:

276 |
    277 |
  • search 278 | The search string to use 279 |
  • 280 |
281 | 282 | 283 | 284 | 285 | 286 |
287 |
288 |

Tables

289 |
290 |
291 | 292 | column_styles 293 |
294 |
295 | The default styles to use for different columns. This can be specified 296 | individually for each list as well. Values can either be explicit styles, 297 | defined using textredux.core.style, or functions which returns 298 | explicit styles. In the latter case, the function will be invoked with the 299 | corresponding item and column index. The default styles contains styles for 300 | up to three columns, after which the default style will be used. 301 | 302 | 303 |

Fields:

304 |
    305 |
  • reduxstyle 306 | 307 | 308 | 309 |
  • 310 |
  • reduxstyle 311 | 312 | 313 | 314 |
  • 315 |
  • reduxstyle 316 | 317 | 318 | 319 |
  • 320 |
321 | 322 | 323 | 324 | 325 | 326 |
327 |
328 |

Fields

329 |
330 |
331 | 332 | header_style 333 |
334 |
335 | The default style to use for diplaying headers. 336 | This is by default the style.list_header style. It's possible to override 337 | this for a specific list by assigning another value to the instance itself. 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 |
346 |
347 | 348 | match_highlight_style 349 |
350 |
351 | The style to use for indicating matches. 352 | You can turn off highlighing of matches by setting this to nil. 353 | It's possible to override this for a specific list by assigning another 354 | value to the instance itself. The default value is style.default. 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 |
363 |
364 | 365 | search_case_insensitive 366 |
367 |
368 | Whether searches are case insensitive or not. 369 | It's possible to override this for a specific list by assigning another 370 | value to the instance itself. The default value is true. 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 |
379 |
380 | 381 | search_fuzzy 382 |
383 |
384 | Whether fuzzy searching should be in addition to explicit matches. 385 | It's possible to override this for a specific list by assigning another 386 | value to the instance itself. The default value is true. 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 |
395 |
396 |

List instance fields

397 | These can be set only for a list instance, and not globally for the module. 398 |
399 |
400 | 401 | headers 402 |
403 |
404 | Optional headers for the list. 405 | If set, the headers must be a table with the same number of columns as 406 | items. 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 |
415 |
416 | 417 | items 418 |
419 |
420 | A table of items to display in the list. 421 | Each table item can either be a table itself, in which case the list will 422 | be multi column, or a string in which case the list be single column. 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 |
431 |
432 | 433 | on_selection 434 |
435 |
436 | 437 |

The handler/callback to call when the user has selected an item. 438 | The handler will be passed the following parameters:

439 | 440 |
    441 |
  • list: the list itself
  • 442 |
  • item: the item selected
  • 443 |
444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 |
453 |
454 | 455 | on_new_selection 456 |
457 |
458 | 459 |

The handler/callback to call when the user has typed in text which 460 | doesn't match any item, and presses <enter>.

461 | 462 |

The handler will be passed the following parameters:

463 | 464 |
    465 |
  • list: the list itself
  • 466 |
  • search: the current search of the list
  • 467 |
468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 |
477 |
478 | 479 | buffer 480 |
481 |
482 | The underlying textredux.core.buffer used by the list. 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 |
491 |
492 | 493 | keys 494 |
495 |
496 | A table of key commands for the list. 497 | This functions almost exactly the same as textredux.core.buffer.keys. 498 | The one difference is that for function values, the parameter passed will be 499 | a reference to the list instead of a buffer reference. 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 |
508 |
509 | 510 | data 511 |
512 |
513 | A general purpose table that can be used for storing state associated 514 | with the list. Just like textredux.core.buffer.data, the data table 515 | is special in the way that it will automatically be cleared whenever the user 516 | closes the buffer associated with the list. 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 |
525 |
526 | 527 | 528 |
529 | Generated by LDoc 530 |
531 |
532 |
533 | 534 | 535 | 536 | -------------------------------------------------------------------------------- /docs/modules/textredux.core.style.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Textredux API Docs 7 | 8 | 9 | 10 | 11 | 12 | 13 | 43 | 44 |
45 |
46 | 47 |

Module textredux.core.style

48 |

The style module lets you define and use custom, non-lexer-based styles.

49 |

50 | 51 | 52 |

What's a style?

53 | 54 |

Textredux styling provides an abstraction layer over the lexer based style 55 | creation. A style is thus just a table with certain properties, almost exactly 56 | the same as for style created for a lexer or theme. Please see the documentation 57 | for 58 | lexer.style 59 | for information about the available fields. Colors should be defined in the 60 | standard '#rrggbb' notation.

61 | 62 |

Defining styles

63 | 64 |

You define a new style by assigning a table with its properties to the module:

65 | 66 | 67 |
 68 | local reduxstyle = require 'textredux.core.style'
 69 | reduxstyle.foo_header = { italics = true, fore = '#680000' }
 70 | 
71 | 72 |

As has been previously said, it's often a good idea to base your custom styles 73 | on an existing default style. Similarily to defining a lexer style in Textadept 74 | you can achieve this by concatenating styles:

75 | 76 | 77 |
 78 | reduxstyle.foo_header = style.string .. { underlined = true }
 79 | 
80 | 81 |

NB: Watch out for the mistake of assigning the style to a local variable:

82 | 83 | 84 |
 85 | local header = reduxstyle.string .. { underlined = true }
 86 | 
87 | 88 |

This will not work, as the style is not correctly defined with the style 89 | module, necessary to ensure styles are correctly defined when new buffers 90 | are created.

91 | 92 |

In order to avoid name clashes, it's suggested that you name any custom styles 93 | by prefixing their name with the name of your module. E.g. if your module is 94 | named awesome, then name your style something like style.awesome_style.

95 | 96 |

Updating styles

97 | 98 |

To make them fit better with your theme or preferences you can change styles 99 | already set by overwriting their properties in your init.lua:

100 | 101 | 102 |
103 | local textredux = require('textredux')
104 | local reduxstyle = textredux.core.style
105 | reduxstyle.list_match_highlight.fore = reduxstyle.class.fore
106 | reduxstyle.fs_directory.italics = true
107 | 
108 | 109 |

Using styles

110 | 111 |

You typically use a style by inserting text through 112 | textredux.core.buffer's text insertion methods, specifying the style. 113 | Please see also the example in examples/buffer_styling.lua.

114 | 115 | 116 |
117 | reduxbuffer:add_text('Foo header text', reduxstyle.foo_header)
118 | 
119 | 120 |

The default styles

121 | 122 |

Textredux piggybacks on the default lexer styles defined by a user's theme, 123 | and makes them available for your Textredux interfaces. The big benefit of this 124 | is that by using those styles or basing your custom styles on them, your 125 | interface stands a much higher chance of blending in well with the color scheme 126 | used. As an example, your custom style with cyan foreground text might look 127 | great with your own dark theme, but may be pretty near invisible for some user 128 | with a light blue background.

129 | 130 |

You can read more about the default lexer styles in the 131 | Textadept lexer documentation. 132 | You access a default style (or any style for that matter), by indexing the 133 | style module, like so: style.<name>. For reference, the default styles 134 | available are these:

135 | 136 |
    137 |
  • style.nothing
  • 138 |
  • style.whitespace
  • 139 |
  • style.comment
  • 140 |
  • style.string
  • 141 |
  • style.number
  • 142 |
  • style.keyword
  • 143 |
  • style.identifier
  • 144 |
  • style.operator
  • 145 |
  • style.error
  • 146 |
  • style.preproc
  • 147 |
  • style.constant
  • 148 |
  • style.variable
  • 149 |
  • style.function
  • 150 |
  • style.class
  • 151 |
  • style.type
  • 152 |
  • style.default
  • 153 |
  • style.line_number
  • 154 |
  • style.bracelight
  • 155 |
  • style.bracebad
  • 156 |
  • style.controlchar
  • 157 |
  • style.indentguide
  • 158 |
  • style.calltip
  • 159 |
160 |

161 | 162 | 163 | 164 |
165 |
166 | 167 | 168 | 169 | 170 |
171 | Generated by LDoc 172 |
173 |
174 |
175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /docs/modules/textredux.core.ui.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Textredux API Docs 7 | 8 | 9 | 10 | 11 | 12 | 13 | 47 | 48 |
49 |
50 | 51 |

Module textredux.core.ui

52 |

The ui module handles UI related operations for Textredux.

53 |

54 | 55 |

56 | 57 | 58 |

Fields

59 | 60 | 61 | 62 | 63 | 64 |
view_split_preferenceSpecifies the way that Textredux should split views.
65 | 66 |
67 |
68 | 69 | 70 |

Fields

71 |
72 |
73 | 74 | view_split_preference 75 |
76 |
77 | Specifies the way that Textredux should split views. 78 | Possible values are: 79 | - 'horizontal' : Prefer horizontal splits. 80 | - 'vertical' : Prefer vertical splits. 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 |
89 |
90 | 91 | 92 |
93 | Generated by LDoc 94 |
95 |
96 |
97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /docs/modules/textredux.ctags.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Textredux API Docs 7 | 8 | 9 | 10 | 11 | 12 | 13 | 49 | 50 |
51 |
52 | 53 |

Module textredux.ctags

54 |

Displays a filtered list of symbols (functions, variables, …) in the current 55 | document using Exuberant Ctags.

56 |

57 | 58 | 59 |

Usage

60 | 61 |

In your init.lua:

62 | 63 | 64 |
 65 | local textredux = require 'textredux'
 66 | keys.cg = textredux.ctags.goto_symbol -- Ctrl+G
 67 | 
68 | 69 |

Requirements

70 | 71 |

Exuberant Ctags needs to be installed and is available in the 72 | usual package managers.

73 | 74 |

Debian/Ubuntu:

75 | 76 | 77 |
 78 | sudo apt-get install exuberant-ctags
 79 | 
80 | 81 |

Homebrew on OS X:

82 | 83 | 84 |
 85 | brew install ctags
 86 | 
87 | 88 |

Note that it is possible to add support for additional filetypes in your 89 | ~/.ctags, for example LaTeX:

90 | 91 | 92 |
 93 | --langdef=latex
 94 | --langmap=latex:.tex
 95 | --regex-latex=/\\label\{([^}]*)\}/\1/l,label/
 96 | --regex-latex=/\\section\{([^}]*)\}/\1/s,section/
 97 | --regex-latex=/\\subsection\{([^}]*)\}/\1/t,subsection/
 98 | --regex-latex=/\\subsubsection\{([^}]*)\}/\1/u,subsubsection/
 99 | --regex-latex=/\\section\*\{([^}]*)\}/\1/s,section/
100 | --regex-latex=/\\subsection\*\{([^}]*)\}/\1/t,subsection/
101 | --regex-latex=/\\subsubsection\*\{([^}]*)\}/\1/u,subsubsection/
102 | 
103 | 104 |

This module is based on Mitchell's ctags code posted on the 105 | Textadept wiki.

106 |

107 | 108 | 109 |

Functions

110 | 111 | 112 | 113 | 114 | 115 |
goto_symbol ()Goes to the selected symbol in a filtered list dialog.
116 |

Tables

117 | 118 | 119 | 120 | 121 | 122 |
stylesMappings from Ctags kind to Textredux styles.
123 |

Fields

124 | 125 | 126 | 127 | 129 | 130 |
CTAGSPath and options for the ctags call can be defined in the CTAGS 128 | field.
131 | 132 |
133 |
134 | 135 | 136 |

Functions

137 |
138 |
139 | 140 | goto_symbol () 141 |
142 |
143 | Goes to the selected symbol in a filtered list dialog. 144 | Requires ctags to be installed. 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 |
153 |
154 |

Tables

155 |
156 |
157 | 158 | styles 159 |
160 |
161 | Mappings from Ctags kind to Textredux styles. 162 | 163 | 164 |

Fields:

165 |
    166 |
  • class 167 | 168 | 169 | 170 |
  • 171 |
  • enum 172 | 173 | 174 | 175 |
  • 176 |
  • enumerator 177 | 178 | 179 | 180 |
  • 181 |
  • [function] 182 | 183 | 184 | 185 |
  • 186 |
  • procedure 187 | 188 | 189 | 190 |
  • 191 |
  • method 192 | 193 | 194 | 195 |
  • 196 |
  • field 197 | 198 | 199 | 200 |
  • 201 |
  • member 202 | 203 | 204 | 205 |
  • 206 |
  • macro 207 | 208 | 209 | 210 |
  • 211 |
  • namespace 212 | 213 | 214 | 215 |
  • 216 |
  • typedef 217 | 218 | 219 | 220 |
  • 221 |
  • variable 222 | 223 | 224 | 225 |
  • 226 |
227 | 228 | 229 | 230 | 231 | 232 |
233 |
234 |

Fields

235 |
236 |
237 | 238 | CTAGS 239 |
240 |
241 | Path and options for the ctags call can be defined in the CTAGS 242 | field. 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 |
251 |
252 | 253 | 254 |
255 | Generated by LDoc 256 |
257 |
258 |
259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /docs/modules/textredux.fs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Textredux API Docs 7 | 8 | 9 | 10 | 11 | 12 | 13 | 48 | 49 |
50 |
51 | 52 |

Module textredux.fs

53 |

textredux.fs provides a text based file browser and file system related 54 | functions for Textadept.

55 |

It features traditional directory browsing, snapopen functionality, completely 56 | keyboard driven interaction, and provides powerful narrow to search 57 | functionality.

58 | 59 |

Some tips on using the file browser

60 | 61 |

Switching between traditional browsing and snapopen

62 | 63 |

As said above the file browser allows both traditional browsing as well as 64 | snapopen functionality. But it also allows you to seamlessly switch between 65 | the two modes (by default, Ctrl + S is assigned for this).

66 | 67 |

Quickly moving up one directory level

68 | 69 |

In traditional browsing mode, you can always select .. to move up one 70 | directory level. But a quicker way of doing the same is to press <backspace> 71 | when you have an empty search. This also works when in snapopen mode.

72 | 73 |

Opening a sub directory in snapopen mode

74 | 75 |

In contrast with Textadept snapopen, you will in snapopen mode also see sub 76 | directories in the listing. This is by design - you can select a sub directory 77 | to snapopen that directory.

78 | 79 |

Changing the styles used for different file types

80 | 81 |

If you don't like the default styles (colors, etc.) used by the file browser, 82 | you can easily change these by customizing any of the reduxstyle_<foo> entries 83 | using the Textredux style module. As an example, to make directory entries 84 | underlined you would do something like the following:

85 | 86 | 87 |
 88 | textredux.core.style.fs_directory = {underlined = true}
 89 | 
90 | 91 |

Please see the documentation for the Textredux style 92 | module for instructions on how to define styles.

93 | 94 | 95 |

Functions

96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 117 | 118 |
select_file (on_selection, start_directory, filter, depth, max_files)Opens a file browser and lets the user choose a file.
save_buffer_as ()Saves the current buffer under a new name.
save_buffer ()Saves the current buffer.
open_file (start_directory)Opens the specified directory for browsing.
snapopen (directory, filter, exclude_FILTER, depth)Opens a list of files in the specified directory, according to the given 116 | parameters.
119 |

Fields

120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 |
reduxstyle.fs_directoryThe style used for directory entries.
reduxstyle.fs_fileThe style used for ordinary file entries.
reduxstyle.fs_linkThe style used for link entries.
reduxstyle.fs_socketThe style used for socket entries.
reduxstyle.fs_pipeThe style used for pipe entries.
reduxstyle.fs_deviceThe style used for pipe entries.
146 | 147 |
148 |
149 | 150 | 151 |

Functions

152 |
153 |
154 | 155 | select_file (on_selection, start_directory, filter, depth, max_files) 156 |
157 |
158 | Opens a file browser and lets the user choose a file. 159 | 160 | 161 |

Parameters:

162 |
    163 |
  • on_selection 164 | The function to invoke when the user has choosen a file. 165 | The function will be called with following parameters:

    166 | 167 |
      168 |
    • path: The full path of the choosen file (UTF-8 encoded).
    • 169 |
    • exists: A boolean indicating whether the file exists or not.
    • 170 |
    • list: A reference to the Textredux list used by browser.
    • 171 |
    172 | 173 |

    The list will not be closed automatically, so close it explicitly using 174 | list:close() if desired. 175 |

  • 176 |
  • start_directory 177 | The initial directory to open, in UTF-8 encoding. If 178 | nil, the initial directory is determined automatically (preferred choice is to 179 | open the directory containing the current file). 180 |
  • 181 |
  • filter 182 | The filter to apply, if any. The structure and semantics are the 183 | same as for Textadept's 184 | snapopen. 185 |
  • 186 |
  • depth 187 | The number of directory levels to display in the list. Defaults to 188 | 1 if not specified, which results in a "normal" directory listing. 189 |
  • 190 |
  • max_files 191 | The maximum number of files to scan and display in the list. 192 | Defaults to 10000 if not specified. 193 |
  • 194 |
195 | 196 | 197 | 198 | 199 | 200 |
201 |
202 | 203 | save_buffer_as () 204 |
205 |
206 | Saves the current buffer under a new name. 207 | Open a browser and lets the user select a name. 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 |
216 |
217 | 218 | save_buffer () 219 |
220 |
221 | Saves the current buffer. 222 | Prompts the users for a filename if it's a new, previously unsaved buffer. 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 |
231 |
232 | 233 | open_file (start_directory) 234 |
235 |
236 | Opens the specified directory for browsing. 237 | 238 | 239 |

Parameters:

240 |
    241 |
  • start_directory 242 | The directory to open, in UTF-8 encoding 243 |
  • 244 |
245 | 246 | 247 | 248 | 249 | 250 |
251 |
252 | 253 | snapopen (directory, filter, exclude_FILTER, depth) 254 |
255 |
256 | 257 |

Opens a list of files in the specified directory, according to the given 258 | parameters. This works similarily to 259 | Textadept snapopen. 260 | The main differences are:

261 | 262 |
    263 |
  • it does not support opening multiple paths at once

  • 264 |
  • filter can contain functions as well as patterns (and can be a function as well). 265 | Functions will be passed a file object which is the same as the return from 266 | lfs.attributes, 267 | with the following additions:

    268 | 269 |
      270 |
    • rel_path: The path of the file relative to the currently 271 | displayed directory.
    • 272 |
    • hidden: Whether the path denotes a hidden file.
    • 273 |
  • 274 |
275 | 276 | 277 | 278 | 279 |

Parameters:

280 |
    281 |
  • directory 282 | The directory to open, in UTF-8 encoding. 283 |
  • 284 |
  • filter 285 | The filter to apply. The format and semantics are the same as for 286 | Textadept. 287 |
  • 288 |
  • exclude_FILTER 289 | Same as for Textadept: unless if not true then 290 | snapopen.FILTER will be automatically added to the filter. 291 | to snapopen.FILTER if not specified. 292 |
  • 293 |
  • depth 294 | The number of directory levels to scan. Defaults to DEFAULT_DEPTH 295 | if not specified. 296 |
  • 297 |
298 | 299 | 300 | 301 | 302 | 303 |
304 |
305 |

Fields

306 |
307 |
308 | 309 | reduxstyle.fs_directory 310 |
311 |
312 | The style used for directory entries. 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 |
321 |
322 | 323 | reduxstyle.fs_file 324 |
325 |
326 | The style used for ordinary file entries. 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 |
335 |
336 | 337 | reduxstyle.fs_link 338 |
339 |
340 | The style used for link entries. 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 |
349 |
350 | 351 | reduxstyle.fs_socket 352 |
353 |
354 | The style used for socket entries. 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 |
363 |
364 | 365 | reduxstyle.fs_pipe 366 |
367 |
368 | The style used for pipe entries. 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 |
377 |
378 | 379 | reduxstyle.fs_device 380 |
381 |
382 | The style used for pipe entries. 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 |
391 |
392 | 393 | 394 |
395 | Generated by LDoc 396 |
397 |
398 |
399 | 400 | 401 | 402 | -------------------------------------------------------------------------------- /docs/modules/textredux.hijack.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Textredux API Docs 7 | 8 | 9 | 10 | 11 | 12 | 13 | 43 | 44 |
45 |
46 | 47 |

Module textredux.hijack

48 |

The hijack module provides the easiest, and most invasive, way of getting 49 | Textredux functionality for Textadept.

50 |

51 |

It's a one-stop setup in the way that 52 | you don't really have to configure anything else to use the functionality of 53 | Textredux - the hijack module inserts itself anywhere it can and will 54 | automatically integrate with your existing key bindings.

55 | 56 |

How to use

57 | 58 |

After installing the Textredux module into your .textadept/modules directory, 59 | simply add the following to your .textadept/init.lua file:

60 | 61 |
require 'textredux.hijack'
62 | 
63 | 64 |

65 |

Info:

66 |
    67 |
  • Copyright: 2012
  • 68 |
  • License: MIT (see LICENSE)
  • 69 |
  • Author: Nils Nordman
  • 70 |
71 | 72 | 73 | 74 |
75 |
76 | 77 | 78 | 79 | 80 |
81 | Generated by LDoc 82 |
83 |
84 |
85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /docs/modules/textredux.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Textredux API Docs 7 | 8 | 9 | 10 | 11 | 12 | 13 | 47 | 48 |
49 |
50 | 51 |

Module textredux

52 |

The Textredux module allows you to easily create text based interfaces for the 53 | Textadept editor and offers a set of text 54 | based interfaces.

55 |

It currently contains the following modules:

56 | 57 |
    58 |
  • textredux.core. The core module provides basic components to create 59 | text based interfaces.
  • 60 |
  • textredux.fs. Contains text based interfaces for file io operations, 61 | like open file, save file as well as snapopen functionality.
  • 62 |
  • textredux.ctags. Displays a filtered list of symbols (functions, 63 | variables, …) in the current document using Exuberant Ctags.
  • 64 |
  • textredux.buffer_list. A text based buffer list replacement, which in 65 | addition to being text based also offers an easy way to close buffers 66 | directly from the list.
  • 67 |
68 | 69 |

How to use it

70 | 71 |

Download and put the Textredux module in your .textadept/modules/ 72 | directory.

73 | 74 |

Having installed it, there are two (mixable) ways you can use Textredux.

75 | 76 |

1) Select the functionality you want from the different modules by assigning 77 | keys to the desired functions.

78 | 79 | 80 |
 81 | local textredux = require('textredux')
 82 | keys.co = textredux.fs.open_file
 83 | keys.cS = textredux.fs.save_buffer_as
 84 | keys.cb = textredux.buffer_list.show
 85 | keys.cg = textredux.ctags.goto_symbol
 86 | 
87 | 88 |

2) If you can't get enough of text based interfaces and the joy they provide, 89 | then the hijack function is for you. Simply place this in your 90 | init.lua:

91 | 92 | 93 |
 94 | require('textredux').hijack()
 95 | 
96 | 97 |

As the name suggest, Textredux has now hijacked your environment. All your 98 | regular key bindings should now use Textredux where applicable. Clicking the 99 | menu will still open the standard GUI dialogs.

100 | 101 |

Customizing

102 | 103 |

Please see the modules documentation for more configuration settings.

104 | 105 | 106 |

Functions

107 | 108 | 109 | 110 | 112 | 113 |
hijack ()Hijacks Textadept, replacing all keyboard shortcuts with text based 111 | counterparts.
114 | 115 |
116 |
117 | 118 | 119 |

Functions

120 |
121 |
122 | 123 | hijack () 124 |
125 |
126 | Hijacks Textadept, replacing all keyboard shortcuts with text based 127 | counterparts. Additionally, it replaces the traditional filtered list 128 | with a Textredux list for a number of operations. 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 |
137 |
138 | 139 | 140 |
141 | Generated by LDoc 142 |
143 |
144 |
145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /docs/modules/textredux.util.color.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Textredux API Docs 7 | 8 | 9 | 10 | 11 | 12 | 13 | 47 | 48 |
49 |
50 | 51 |

Module textredux.util.color

52 |

The color module provides utility functions for color handling.

53 |

54 | 55 |

56 | 57 | 58 |

Functions

59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 |
string_to_color (rgb)Convert color in '#rrggbb' format to 'bbggrr'.
color_to_string (color)Convert color in hex 'bbggrr' format to string '#rrggbb'
69 | 70 |
71 |
72 | 73 | 74 |

Functions

75 |
76 |
77 | 78 | string_to_color (rgb) 79 |
80 |
81 | Convert color in '#rrggbb' format to 'bbggrr'. 82 | 83 | 84 |

Parameters:

85 |
    86 |
  • rgb 87 | 88 | 89 | 90 |
  • 91 |
92 | 93 | 94 | 95 | 96 | 97 |
98 |
99 | 100 | color_to_string (color) 101 |
102 |
103 | Convert color in hex 'bbggrr' format to string '#rrggbb' 104 | 105 | 106 |

Parameters:

107 |
    108 |
  • color 109 | 110 | 111 | 112 |
  • 113 |
114 | 115 | 116 | 117 | 118 | 119 |
120 |
121 | 122 | 123 |
124 | Generated by LDoc 125 |
126 |
127 |
128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /docs/modules/textredux.util.matcher.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Textredux API Docs 7 | 8 | 9 | 10 | 11 | 12 | 13 | 47 | 48 |
49 |
50 | 51 |

Module textredux.util.matcher

52 |

The matcher module provides easy and advanced matching of strings.

53 |

54 | 55 |

56 | 57 | 58 |

Functions

59 | 60 | 61 | 62 | 63 | 64 |
_matchers_for_search (search_string)Creates matches for the specified search
65 | 66 |
67 |
68 | 69 | 70 |

Functions

71 |
72 |
73 | 74 | _matchers_for_search (search_string) 75 |
76 |
77 | Creates matches for the specified search 78 | 79 | 80 |

Parameters:

81 |
    82 |
  • search_string 83 | The search string 84 |
  • 85 |
86 | 87 |

Returns:

88 |
    89 | 90 | A table of matcher functions, each taking a line as parameter and 91 | returning a score (or nil for no match). 92 |
93 | 94 | 95 | 96 | 97 |
98 |
99 | 100 | 101 |
102 | Generated by LDoc 103 |
104 |
105 |
106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /docs/tour.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Textredux 7 | 8 | 9 | 10 | 11 | 12 | 13 | 25 | 26 |
27 |
28 |

Screen Shots

29 | 30 |

Open file

31 | 32 |

Above you can see the open file screen for the Textadept directory, displaying a 33 | normal directory listing. To narrow the file list just type some characters. 34 | To change to the parent directory either select the “..” entry or press 35 | Backspace.

36 | 37 |

Snapopen file

38 | 39 |

Here is the same directory, but in snapopen mode. You can quickly toggle between 40 | normal listing and snapopen mode by pressing Ctrl+S. 41 | Pressing Backspace to 42 | go to the parent directory works fine when in snapopen mode as well.

43 | 44 |

Fuzzy matching

45 | 46 |

Here you can see fuzzy matching in action. Just type whatever you remember and 47 | see what pops up. Should you get to many matching entries and need to narrow 48 | further by matching on earlier parts of the paths, just add a space and type 49 | some additional search text.

50 | 51 |

Buffer list

52 | 53 |

The replacement buffer list. As the status bar says, you can close buffers or 54 | all files in a directory from within the list by pressing Ctrl+D 55 | or Ctrl+Shift+D (Meta+D in the terminal). 56 | As opposed to the stock buffer list you can also narrow entries by the directory name.

57 | 58 |

Run command

59 | 60 |

The above image shows nothing new interface-wise, but illustrates the use of 61 | the Textredux hijack function. When using the hijack function, Textredux 62 | replaces whatever it can of traditional interfaces with text based counterparts.

63 | 64 |

Ctags in terminal

65 | 66 |

Textredux’ ctags-based symbol list running in a terminal session of Textadept.

67 |
68 |
69 | 70 | 71 | -------------------------------------------------------------------------------- /examples/basic_list.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2011-2012 Nils Nordman 2 | -- Copyright 2012-2014 Robert Gieseke 3 | -- License: MIT (see LICENSE) 4 | 5 | -- Very basic example on how to use a Textredux list. 6 | 7 | local M = {} 8 | 9 | local textredux = require 'textredux' 10 | 11 | function M.show_simple_list() 12 | -- Create the list. 13 | local list = textredux.core.list.new( 14 | 'Simple list', -- list title 15 | {'one', 'two', 'three'}, -- list items 16 | function (list, item) -- on selection callback 17 | ui.statusbar_text = 'You selected ' .. item 18 | end 19 | ) 20 | 21 | -- Show the list. 22 | list:show() 23 | end 24 | 25 | return M 26 | -------------------------------------------------------------------------------- /examples/buffer_actions.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2011-2012 Nils Nordman 2 | -- Copyright 2012-2014 Robert Gieseke 3 | -- License: MIT (see LICENSE) 4 | 5 | --[[ 6 | Example on various ways to make the buffer interactive, that is responding to 7 | user input. This example illustrates the use of implicit and explicit hotspots 8 | using either function or table commands, as well as key commands and responding 9 | directly to key presses. 10 | ]] 11 | 12 | local M = {} 13 | 14 | local textredux = require 'textredux' 15 | 16 | local reduxstyle = textredux.core.style 17 | reduxstyle.action_style = reduxstyle['function']..{underlined = true} 18 | 19 | local function on_refresh(buffer) 20 | buffer:add_text('Press Ctrl-T to show a message box.') 21 | buffer:add_text('\n\n') 22 | buffer:add_text('Table command: ') 23 | buffer:add_text('Quickopen Textadept config directory', reduxstyle.action_style, 24 | { io.quick_open, _USERHOME }) 25 | 26 | buffer:add_text('\n\nExplicit hotspot: ') 27 | local start_pos = buffer.current_pos 28 | buffer:add_text('Click here somewhere\nto show the command selection list', 29 | reduxstyle.action_style) 30 | buffer:add_hotspot(start_pos, buffer.current_pos, textadept.menu.select_command) 31 | end 32 | 33 | function M.create_action_buffer() 34 | local buffer = textredux.core.buffer.new('Action buffer') 35 | buffer.on_refresh = on_refresh 36 | 37 | -- Bind Control+T to show a simple message box. 38 | buffer.keys['ctrl+t'] = function() 39 | ui.dialogs.msgbox{title='Testredux', text='Test 1, 2, 3!'} 40 | end 41 | 42 | buffer:show() 43 | end 44 | 45 | return M 46 | -------------------------------------------------------------------------------- /examples/buffer_indicators.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2011-2012 Nils Nordman 2 | -- Copyright 2012-2014 Robert Gieseke 3 | -- License: MIT (see LICENSE) 4 | 5 | --[[ 6 | Example on how to use indicators with a Textredux buffer. This example shows 7 | how to define custom indicators and apply them for selected text. Additionally, 8 | available indicator styles are listed. 9 | ]] 10 | 11 | local M = {} 12 | 13 | local textredux = require 'textredux' 14 | local reduxstyle = require 'textredux.core.style' 15 | 16 | local constants = _SCINTILLA.constants 17 | local indicator = textredux.core.indicator 18 | 19 | indicator.RED_BOX = {style = constants.INDIC_BOX, fore = '#ff0000'} 20 | indicator.BLUE_FILL = {style = constants.INDIC_ROUNDBOX, fore = '#0000ff'} 21 | 22 | local indicator_styles = { 23 | INDIC_PLAIN = 'An underline', 24 | INDIC_SQUIGGLE = 'A squiggly underline 3 pixels in height', 25 | INDIC_TT = 'An underline of small `T` shapes', 26 | INDIC_DIAGONAL = 'An underline of diagonal hatches', 27 | INDIC_STRIKE = 'Strike out', 28 | INDIC_HIDDEN = 'Invisible - no visual effect', 29 | INDIC_BOX = 'A rectangular bounding box', 30 | INDIC_ROUNDBOX = 'A translucent box with rounded corners around the text', 31 | INDIC_STRAIGHTBOX = 'Similar to INDIC_ROUNDBOX but with sharp corners', 32 | INDIC_DASH = 'A dashed underline', 33 | INDIC_DOTS = 'A dotted underline', 34 | INDIC_SQUIGGLELOW = 'A squiggly underline 2 pixels in height', 35 | INDIC_DOTBOX = 'Similar to INDIC_STRAIGHTBOX but with a dotted outline', 36 | INDIC_SQUIGGLEPIXMAP = 'Identical to INDIC_SQUIGGLE but draws faster by using\n'.. 37 | 'a pixmap instead of multiple line segments', 38 | INDIC_COMPOSITIONTHICK = 'A 2-pixel thick underline at the bottom of the line\n'.. 39 | 'inset by 1 pixel on on either side. Similar in appearance to Asian\n'.. 40 | 'language input composition' 41 | } 42 | for k, v in pairs(indicator_styles) do 43 | indicator[k] = {style = constants[k]} 44 | end 45 | 46 | local function on_refresh(buffer) 47 | buffer:add_text('Indicators:\n\n') 48 | 49 | local start_pos = buffer.current_pos 50 | buffer:add_text('Text for manual red box indicator\n\n') 51 | indicator.RED_BOX:apply(start_pos, buffer.current_pos - start_pos) 52 | 53 | buffer:add_text('Text for buffer inserted blue indicator\n\n', 54 | nil, nil, indicator.BLUE_FILL) 55 | 56 | buffer:add_text('Indicator styles:\n\n') 57 | 58 | for k, v in pairs(indicator_styles) do 59 | buffer:add_text(k, nil, nil, indicator[k]) 60 | buffer:add_text('\n'..v..'\n\n') 61 | end 62 | 63 | end 64 | 65 | function M.create_indicator_buffer() 66 | local buffer = textredux.core.buffer.new('Indicator buffer') 67 | buffer.on_refresh = on_refresh 68 | buffer:show() 69 | end 70 | 71 | return M 72 | -------------------------------------------------------------------------------- /examples/buffer_styling.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2011-2012 Nils Nordman 2 | -- Copyright 2012-2014 Robert Gieseke 3 | -- License: MIT (see LICENSE) 4 | 5 | --[[ 6 | Example on how to use custom styling with a TextUI buffer. This example shows 7 | how to define custom styles and use them when inserting content. 8 | ]] 9 | 10 | local M = {} 11 | 12 | local textredux = require 'textredux' 13 | 14 | local reduxstyle = textredux.core.style 15 | 16 | -- define a custom style based on a default style 17 | reduxstyle.example_style1 = reduxstyle.number..{underlined=true, bold=true} 18 | 19 | -- define a custom style from scratch 20 | reduxstyle.example_style2 = {italics = true, fore = '#0000ff', back='#ffffff'} 21 | 22 | local function on_refresh(buffer) 23 | -- add some ordinary unstyled text. we can specify the newline directly here 24 | -- as '\n' since the buffer will always be in eol mode LF. 25 | buffer:add_text('Unstyled text\n') 26 | 27 | -- add some text using one the default styles from the user's theme 28 | buffer:add_text('Keyword style\n', reduxstyle.keyword) 29 | 30 | -- add some lines with custom styles 31 | buffer:add_text('Custom style based on default style\n', 32 | reduxstyle.example_style1) 33 | buffer:add_text('Custom style from scratch', 34 | reduxstyle.example_style2) 35 | buffer:add_text('\n') 36 | end 37 | 38 | function M.create_styled_buffer() 39 | local buffer = textredux.core.buffer.new('Example buffer') 40 | buffer.on_refresh = on_refresh 41 | buffer:show() 42 | end 43 | 44 | return M 45 | -------------------------------------------------------------------------------- /examples/git_ls_files.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2012-2014 Robert Gieseke 2 | -- License: MIT (see LICENSE) 3 | 4 | --[[ 5 | Display a list of files under revision control in a Git repository. Uses 6 | Textadept's `io.get_project_root` function to find the root directory. 7 | ]] 8 | 9 | local M = {} 10 | 11 | local textredux = require('textredux') 12 | local reduxlist = textredux.core.list 13 | 14 | function M.show_files() 15 | if not buffer.filename then return end 16 | local files = {} 17 | local current_dir = lfs.currentdir() 18 | local working_dir = io.get_project_root() 19 | if not working_dir then return end 20 | lfs.chdir(working_dir) 21 | ui.statusbar_text = lfs.currentdir() 22 | local p = spawn('git ls-files') 23 | for line in p:read('*all'):gmatch('[^\r\n]+') do 24 | files[#files + 1] = line 25 | end 26 | lfs.chdir(current_dir) 27 | if #files > 0 then 28 | local list = reduxlist.new('Git: '..working_dir) 29 | list.items = files 30 | list.on_selection = function(list, item) 31 | if item then 32 | io.open_file(working_dir..'/'..item) 33 | list:close() 34 | end 35 | end 36 | list:show() 37 | end 38 | end 39 | 40 | return M 41 | -------------------------------------------------------------------------------- /examples/init.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2011-2012 Nils Nordman 2 | -- Copyright 2012-2014 Robert Gieseke 3 | -- License: MIT (see LICENSE) 4 | 5 | --[[ 6 | This directory contains a few examples that show how to build text based 7 | interfaces with the modules in `textredux.core`. 8 | 9 | When Textredux is installed you can paste the following line in 10 | Textadept's command entry to show a list of examples: 11 | 12 | examples = require('textredux.examples').show_examples() 13 | 14 | to select from a list of examples. 15 | ]] 16 | 17 | local M = {} 18 | 19 | local textredux = require 'textredux' 20 | 21 | M.basic_list = require 'textredux.examples.basic_list' 22 | M.buffer_actions = require 'textredux.examples.buffer_actions' 23 | M.buffer_indicators = require 'textredux.examples.buffer_indicators' 24 | M.buffer_styling = require 'textredux.examples.buffer_styling' 25 | M.list_commands = require 'textredux.examples.list_commands' 26 | M.multi_column_list = require 'textredux.examples.multi_column_list' 27 | M.styled_list = require 'textredux.examples.styled_list' 28 | 29 | examples = { 30 | ['Buffer styling'] = M.buffer_styling.create_styled_buffer, 31 | ['Basic list'] = M.basic_list.show_simple_list, 32 | ['Buffer indicators'] = M.buffer_indicators.create_indicator_buffer, 33 | ['Styled list'] = M.styled_list.show_styled_list, 34 | ['List commands'] = M.list_commands.show_action_list, 35 | ['Multi column list'] = M.multi_column_list.show_multi_column_list, 36 | ['Buffer actions'] = M.buffer_actions.create_action_buffer 37 | } 38 | 39 | local keys = {} 40 | for k, v in pairs(examples) do 41 | keys[#keys+1] = k 42 | end 43 | 44 | local function on_selection(list, item) 45 | ui.statusbar_text = item 46 | examples[item]() 47 | end 48 | 49 | function M.show_examples() 50 | local list = textredux.core.list.new( 51 | 'Textredux examples', 52 | keys, 53 | on_selection 54 | ) 55 | list:show() 56 | end 57 | 58 | return M 59 | -------------------------------------------------------------------------------- /examples/list_commands.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2011-2012 Nils Nordman 2 | -- Copyright 2012-2014 Robert Gieseke 3 | -- License: MIT (see LICENSE) 4 | 5 | --[[ 6 | This example shows the use of additional commands for a list, using the keys 7 | table. 8 | ]] 9 | 10 | local M = {} 11 | 12 | local textredux = require 'textredux' 13 | 14 | function M.show_action_list() 15 | local list = textredux.core.list.new( 16 | 'List with additional commands (press "4", "5", "6")') 17 | list.items = {'one', 'two', 'three'} 18 | 19 | -- Assign snapopen user home as a table command to `3`. 20 | list.keys['4'] = { io.snapopen, _USERHOME } 21 | 22 | -- Print the currently selected item to the buffer when `5` is pressed. 23 | list.keys['5'] = function(list) 24 | local sel = tostring(list:get_current_selection()) 25 | list.buffer:update(function(buf) 26 | local pos = buf.current_pos 27 | buf:document_end() 28 | buf:add_text('\n'..buf.title..': '..sel) 29 | buf:goto_pos(pos) 30 | end) 31 | end 32 | 33 | -- Print the currently selected item when `6` is pressed. 34 | list.keys['6'] = function(list) 35 | ui.statusbar_text = 'Currently selected: ' .. 36 | tostring(list:get_current_selection()) 37 | end 38 | list:show() 39 | end 40 | 41 | return M 42 | -------------------------------------------------------------------------------- /examples/multi_column_list.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2011-2012 Nils Nordman 2 | -- Copyright 2012-2014 Robert Gieseke 3 | -- License: MIT (see LICENSE) 4 | 5 | --[[ 6 | This example shows how to use the list with multi column items. It also 7 | illustrates the optional use of headers, as well as the fact that the 8 | `on_selection` callback will be passed the item exactly as it was given 9 | which lets you use any arbitrary field in the selection logic. 10 | ]] 11 | 12 | local M = {} 13 | 14 | local textredux = require 'textredux' 15 | local reduxstyle = textredux.core.style 16 | 17 | local function on_selection(list, item) 18 | -- We will get back exactly what we specified, so the code below will print 19 | -- the IDs we specify further down (1, 2 or 3) 20 | ui.statusbar_text = 'You selected ' .. item.id 21 | end 22 | 23 | function M.show_multi_column_list() 24 | local list = textredux.core.list.new('Multi column list') 25 | 26 | -- Set the headers. This is optional and can be skipped if so desired 27 | list.headers = { 'Thing', 'Is' } 28 | 29 | -- List of multi column items, with additional named fields 30 | list.items = { 31 | {'Volvo', 'Vehicle', id = 1}, 32 | {'Dog', 'Animal', id = 2}, 33 | {'Hell','Other people', id = 3} 34 | } 35 | list.column_styles = {reduxstyle.number, reduxstyle.operator} 36 | list.on_selection = on_selection 37 | list:show() 38 | end 39 | 40 | return M 41 | -------------------------------------------------------------------------------- /examples/styled_list.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2011-2012 Nils Nordman 2 | -- Copyright 2012-2014 Robert Gieseke 3 | -- License: MIT (see LICENSE) 4 | 5 | --[[ 6 | This example shows how to use custom styling with a Textredux list. It 7 | illustrates both using one specific style for a column, and also how to specify 8 | styles dynamically based on the item. For conciseness of this example there is 9 | no `on_selection` handler specified. 10 | ]] 11 | 12 | local M = {} 13 | 14 | local textredux = require 'textredux' 15 | 16 | local reduxstyle = textredux.core.style 17 | 18 | -- Define some custom styles for use with the list. 19 | reduxstyle.example_red = {fore = '#FF0000'} 20 | reduxstyle.example_green = {fore = '#00FF00'} 21 | reduxstyle.example_blue = {fore = '#0000FF'} 22 | reduxstyle.example_code = {italics = true, underlined = true} 23 | 24 | local function get_item_style(item, column_index) 25 | -- Choose style based on the color name. 26 | local color = item[1] 27 | if color == 'Red' then return reduxstyle.example_red 28 | elseif color == 'Green' then return reduxstyle.example_green 29 | elseif color == 'Blue' then return reduxstyle.example_blue 30 | end 31 | end 32 | 33 | function M.show_styled_list() 34 | local list = textredux.core.list.new('Styled list') 35 | list.headers = {'Color', 'Code'} 36 | list.items = { 37 | {'Red', '#FF0000'}, 38 | {'Blue', '#0000FF'}, 39 | {'Green', '#00FF00'} 40 | } 41 | -- Specify the column styles; for the first column we'll determine the style 42 | -- to use dynamically, and for the second column we'll always use the custom 43 | -- style `style.example_code`. 44 | list.column_styles = { get_item_style, reduxstyle.example_code } 45 | list.match_highlight_style = reduxstyle.class 46 | list:show() 47 | end 48 | 49 | return M 50 | -------------------------------------------------------------------------------- /fs.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2011-2012 Nils Nordman 2 | -- Copyright 2012-2014 Robert Gieseke 3 | -- License: MIT (see LICENSE) 4 | 5 | --[[-- 6 | textredux.fs provides a text based file browser and file system related 7 | functions for Textadept. 8 | 9 | It features traditional directory browsing, snapopen functionality, completely 10 | keyboard driven interaction, and provides powerful narrow to search 11 | functionality. 12 | 13 | ## Some tips on using the file browser 14 | 15 | *Switching between traditional browsing and snapopen* 16 | 17 | As said above the file browser allows both traditional browsing as well as 18 | snapopen functionality. But it also allows you to seamlessly switch between 19 | the two modes (by default, `Ctrl + S` is assigned for this). 20 | 21 | *Quickly moving up one directory level* 22 | 23 | In traditional browsing mode, you can always select `..` to move up one 24 | directory level. But a quicker way of doing the same is to press `` 25 | when you have an empty search. This also works when in snapopen mode. 26 | Additionally, `Alt + Up` will always go up one directory level 27 | even if the search is not empty. 28 | While the file selection dialog is active it is also possible to quickly select 29 | either the filesystem root or user home directory. To do so press `/` or `~` 30 | respectively. 31 | 32 | *Opening a sub directory in snapopen mode* 33 | 34 | In contrast with Textadept snapopen, you will in snapopen mode also see sub 35 | directories in the listing. This is by design - you can select a sub directory 36 | to snapopen that directory. 37 | 38 | *Opening a file forcefully* 39 | The open function can be used to create new files. However, sometimes 40 | this will not work because the query matches an existing item. 41 | In that case it is possible to force the creation of the file 42 | by using `Ctrl + Enter`. 43 | 44 | *Changing the styles used for different file types* 45 | 46 | If you don't like the default styles (colors, etc.) used by the file browser, 47 | you can easily change these by customizing any of the `reduxstyle_` entries 48 | using the Textredux style module. As an example, to make directory entries 49 | underlined you would do something like the following: 50 | 51 | textredux.core.style.fs_directory = {underlined = true} 52 | 53 | Please see the documentation for the [Textredux style 54 | module](./textredux.core.style.html) for instructions on how to define styles. 55 | 56 | @module textredux.fs 57 | ]] 58 | 59 | local reduxlist = require 'textredux.core.list' 60 | local reduxstyle = require 'textredux.core.style' 61 | 62 | local string_match, string_sub = string.match, string.sub 63 | 64 | local user_home = os.getenv('HOME') or os.getenv('UserProfile') 65 | local fs_attributes = WIN32 and lfs.attributes or lfs.symlinkattributes 66 | local separator = WIN32 and '\\' or '/' 67 | local updir_pattern = '%.%.?$' 68 | 69 | local M = {} 70 | 71 | --- The style used for directory entries. 72 | reduxstyle.fs_directory = reduxstyle.operator 73 | 74 | --- The style used for ordinary file entries. 75 | reduxstyle.fs_file = reduxstyle.string 76 | 77 | --- The style used for link entries. 78 | reduxstyle.fs_link = reduxstyle.operator 79 | 80 | --- The style used for socket entries. 81 | reduxstyle.fs_socket = reduxstyle.error 82 | 83 | --- The style used for pipe entries. 84 | reduxstyle.fs_pipe = reduxstyle.error 85 | 86 | --- The style used for pipe entries. 87 | reduxstyle.fs_device = reduxstyle.error 88 | 89 | local file_styles = { 90 | directory = reduxstyle.fs_directory, 91 | file = reduxstyle.fs_file, 92 | link = reduxstyle.fs_link, 93 | socket = reduxstyle.fs_socket, 94 | ['named pipe'] = reduxstyle.fs_pipe, 95 | ['char device'] = reduxstyle.fs_device, 96 | ['block device'] = reduxstyle.fs_device, 97 | other = reduxstyle.default 98 | } 99 | 100 | local DEFAULT_DEPTH = 99 101 | 102 | -- Splits a path into its components 103 | local function split_path(path) 104 | local parts = {} 105 | for part in path:gmatch('[^' .. separator .. ']+') do 106 | parts[#parts + 1] = part 107 | end 108 | return parts 109 | end 110 | 111 | -- Joins path components into a path 112 | local function join_path(components) 113 | local start = WIN32 and '' or separator 114 | return start .. table.concat(components, separator) 115 | end 116 | 117 | -- Returns the dir part of path 118 | local function dirname(path) 119 | local parts = split_path(path) 120 | table.remove(parts) 121 | local dir = join_path(parts) 122 | if #dir == 0 then return path end -- win32 root 123 | return dir 124 | end 125 | 126 | -- Normalizes the path. This will deconstruct and reconstruct the 127 | -- path's components, while removing any relative parent references 128 | local function normalize_path(path) 129 | local parts = split_path(path) 130 | local normalized = {} 131 | for _, part in ipairs(parts) do 132 | if part == '..' then 133 | table.remove(normalized) 134 | else 135 | normalized[#normalized + 1] = part 136 | end 137 | end 138 | if #normalized == 1 and WIN32 then -- TODO: win hack 139 | normalized[#normalized + 1] = '' 140 | end 141 | return join_path(normalized) 142 | end 143 | 144 | -- Normalizes a path denoting a directory. This will do the same as 145 | -- normalize_path, but will in addition ensure that the path ends 146 | -- with a trailing separator 147 | local function normalize_dir_path(directory) 148 | local path = normalize_path(directory) 149 | return path:gsub("[\\/]?%.?[\\/]?$", separator) 150 | end 151 | 152 | local function parse_filters(filter) 153 | local filters = {} 154 | for _, restriction in ipairs(filter) do 155 | if type(restriction) == 'string' then 156 | local negated = restriction:match('^!(.+)') 157 | local filter_pattern = negated or restriction 158 | 159 | restriction = function(path) 160 | if path:match(filter_pattern) then 161 | if not negated then return true end 162 | elseif negated then return true 163 | else return false end 164 | end 165 | end 166 | filters[#filters + 1] = restriction 167 | end 168 | return filters 169 | end 170 | 171 | local function add_extensions_filter(filters, extensions) 172 | if extensions and #extensions > 0 then 173 | local exts = {} 174 | for _, ext in ipairs(extensions) do exts[ext] = true end 175 | filters[#filters + 1] = function(path) 176 | return exts[string_match(path, '%.(%a+)$')] ~= nil 177 | end 178 | end 179 | end 180 | 181 | -- Given the patterns, returns a function returning true if 182 | -- the path should be filtered, and false otherwise 183 | local function create_filter(filter) 184 | local filters = parse_filters(filter) 185 | add_extensions_filter(filters, filter.extensions) 186 | filters.directory = parse_filters(filter.folders or {}) 187 | return function(file) 188 | local filters = filters[file.mode] or filters 189 | for _, filter in ipairs(filters) do 190 | if filter(file.path) then return true end 191 | end 192 | return false 193 | end 194 | end 195 | 196 | -- create a filter for quick_open based on lfs.default_filter 197 | local function create_qopen_filter(filter) 198 | filter['folders'] = filter['folders'] or {} 199 | filter['extensions'] = filter['extensions'] or {} 200 | 201 | for _, pattern in pairs(lfs.default_filter) do 202 | pattern = pattern:match('..(.+)') 203 | local pattern_type = '' 204 | if pattern:match('%$$') then 205 | pattern_type = 'folders' 206 | else 207 | pattern_type = 'extensions' 208 | end 209 | filter[pattern_type][#filter[pattern_type] + 1] = pattern 210 | end 211 | 212 | return filter 213 | end 214 | 215 | local function file(path, name, parent) 216 | local file, error = fs_attributes(path) 217 | if error then file = { mode = 'error' } end 218 | local suffix = file.mode == 'directory' and separator or '' 219 | file.path = path 220 | file.hidden = name and string_sub(name, 1, 1) == '.' 221 | if parent then 222 | file.rel_path = parent.rel_path .. name .. suffix 223 | file.depth = parent.depth + 1 224 | else 225 | file.rel_path = '' 226 | file.depth = 1 227 | end 228 | file[1] = file.rel_path 229 | return file 230 | end 231 | 232 | local function find_files(directory, filter, depth, max_files) 233 | if not directory then error('Missing argument #1 (directory)', 2) end 234 | if not depth then error('Missing argument #3 (depth)', 2) end 235 | 236 | if type(filter) ~= 'function' then filter = create_filter(filter) end 237 | local files = {} 238 | 239 | directory = normalize_path(directory) 240 | local directories = { file(directory) } 241 | while #directories > 0 do 242 | local dir = table.remove(directories) 243 | if dir.depth > 1 then files[#files + 1] = dir end 244 | if dir.depth <= depth then 245 | local status, entries, dir_obj = pcall(lfs.dir, dir.path) 246 | if status then 247 | for entry in entries, dir_obj do 248 | local file = file(dir.path .. separator .. entry, entry, dir) 249 | if not filter(file) then 250 | if file.mode == 'directory' and entry ~= '..' and entry ~= '.' then 251 | table.insert(directories, 1, file) 252 | else 253 | if max_files and #files == max_files then return files, false end 254 | -- Workaround check for top-level (virtual) Windows drive 255 | if not (WIN32 and #dir.path == 3 and entry == '..') then 256 | files[#files + 1] = file 257 | end 258 | end 259 | end 260 | end 261 | end 262 | end 263 | end 264 | return files, true 265 | end 266 | 267 | local function sort_items(items) 268 | table.sort(items, function (a, b) 269 | local self_path = '.' .. separator 270 | local parent_path = '..' .. separator 271 | if a.rel_path == self_path then return true 272 | elseif b.rel_path == self_path then return false 273 | elseif a.rel_path == parent_path then return true 274 | elseif b.rel_path == parent_path then return false 275 | elseif a.hidden ~= b.hidden then return b.hidden 276 | elseif b.mode == 'directory' and a.mode ~= 'directory' then return false 277 | elseif a.mode == 'directory' and b.mode ~= 'directory' then return true 278 | end 279 | -- Strip trailing separator from directories for correct sorting, 280 | -- e.g. `foo` before `foo-bar` 281 | local trailing = separator.."$" 282 | return a.rel_path:gsub(trailing, "") < b.rel_path:gsub(trailing, "") 283 | end) 284 | end 285 | 286 | local function chdir(list, directory) 287 | directory = normalize_path(directory) 288 | local data = list.data 289 | local items, complete = find_files(directory, data.filter, data.depth, data.max_files) 290 | if data.depth == 1 then sort_items(items) end 291 | list.title = directory:gsub(user_home, '~') 292 | list.items = items 293 | data.directory = directory 294 | list:show() 295 | if #items > 1 and items[1].rel_path:match('^%.%..?$') then 296 | list.buffer:line_down() 297 | end 298 | if not complete then 299 | local status = 'Number of entries limited to ' .. 300 | data.max_files .. ' as per io.quick_open_max' 301 | ui.statusbar_text = status 302 | else 303 | ui.statusbar_text = '' 304 | end 305 | end 306 | 307 | local function open_selected_file(path, exists, list) 308 | if not exists then 309 | local retval = ui.dialogs.message 310 | { 311 | title = 'Create new file', 312 | text = path .. "\ndoes not exist, do you want to create it?", 313 | icon = 'dialog-question', 314 | button1 = 'Cancel', 315 | button2 = 'Create file' 316 | } 317 | if retval ~= 2 then 318 | return 319 | end 320 | local file, error = io.open(path, 'wb') 321 | if not file then 322 | ui.statusbar_text = 'Could not create ' .. path .. ': ' .. error 323 | return 324 | end 325 | file:close() 326 | end 327 | list:close() 328 | io.open_file(path) 329 | end 330 | 331 | local function get_initial_directory() 332 | local filename = _G.buffer.filename 333 | if filename then return dirname(filename) end 334 | return user_home 335 | end 336 | 337 | local function get_file_style(item, index) 338 | return file_styles[item.mode] or reduxstyle.default 339 | end 340 | 341 | local function toggle_snap(list) 342 | -- Don't toggle for list of Windows drives 343 | if WIN32 and list.data.directory == "" then return end 344 | local data = list.data 345 | local depth = data.depth 346 | local search = list:get_current_search() 347 | 348 | if data.prev_depth then 349 | data.depth = data.prev_depth 350 | else 351 | data.depth = data.depth == 1 and DEFAULT_DEPTH or 1 352 | end 353 | 354 | local filter = data.filter 355 | if data.depth == 1 then -- remove updir filter 356 | if type(filter.folders) == 'table' then 357 | for i, restriction in ipairs(filter.folders) do 358 | if restriction == updir_pattern then 359 | table.remove(filter.folders, i) 360 | break 361 | end 362 | end 363 | end 364 | else -- add updir filter 365 | filter.folders = filter.folders or {} 366 | filter.folders[#filter.folders + 1] = updir_pattern 367 | end 368 | filter = create_qopen_filter(filter) 369 | data.prev_depth = depth 370 | chdir(list, data.directory) 371 | list:set_current_search(search) 372 | end 373 | 374 | local function get_windows_drives() 375 | local letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 376 | local drives = {} 377 | for i=1, #letters do 378 | local drive = letters:sub(i, i)..":\\" 379 | if fs_attributes(drive) then 380 | drives[#drives + 1] = file(drive, drive) 381 | drives[#drives][1] = drive 382 | end 383 | end 384 | return drives 385 | end 386 | 387 | local function display_windows_root(list) 388 | list.items = get_windows_drives() 389 | list.data.directory = "" 390 | list.data.depth = 1 391 | list.title = "Drives" 392 | list:show() 393 | end 394 | 395 | local function updir(list) 396 | local parent = dirname(list.data.directory) 397 | if WIN32 and #list.data.directory == 3 then 398 | display_windows_root(list) 399 | return true 400 | elseif parent ~= list.data.directory then 401 | chdir(list, parent) 402 | return true 403 | end 404 | end 405 | 406 | local function create_list(directory, filter, depth, max_files) 407 | local list = reduxlist.new(directory) 408 | local data = list.data 409 | list.column_styles = {get_file_style} 410 | list.keys["ctrl+s"] = toggle_snap 411 | list.keys['/'] = function() 412 | if WIN32 then 413 | display_windows_root(list) 414 | else 415 | chdir(list, '/') 416 | end 417 | end 418 | list.keys['~'] = function() 419 | if user_home then chdir(list, user_home) end 420 | end 421 | list.keys['alt+up'] = function() 422 | updir(list) 423 | end 424 | list.keys['\b'] = function() 425 | local search = list:get_current_search() 426 | if not search then 427 | updir(list) 428 | else 429 | list:set_current_search(search:sub(1, -2)) 430 | end 431 | end 432 | list.keys['\n'] = function() 433 | local search = list:get_current_search() 434 | if #list.buffer.data.matching_items > 0 then 435 | list.buffer._on_user_select(list.buffer, list.buffer.current_pos) 436 | elseif #search > 0 then 437 | if list.on_new_selection then 438 | list:on_new_selection(search) 439 | end 440 | end 441 | end 442 | list.keys["right"] = function() 443 | local search = list:get_current_search() 444 | if not search then return end 445 | local found = false 446 | 447 | for _, item in ipairs(list.buffer.data.matching_items) do 448 | if item[1] == search then 449 | found = true 450 | break 451 | end 452 | end 453 | 454 | if found then 455 | list.buffer._on_user_select(list.buffer, list.buffer.current_pos) 456 | else 457 | list:on_new_selection(search) 458 | end 459 | end 460 | 461 | data.directory = directory 462 | data.filter = filter 463 | data.depth = depth 464 | data.max_files = max_files 465 | return list 466 | end 467 | 468 | --[[- Opens a file browser and lets the user choose a file. 469 | @param on_selection The function to invoke when the user has choosen a file. 470 | The function will be called with following parameters: 471 | 472 | - `path`: The full path of the choosen file (UTF-8 encoded). 473 | - `exists`: A boolean indicating whether the file exists or not. 474 | - `list`: A reference to the Textredux list used by browser. 475 | 476 | The list will not be closed automatically, so close it explicitly using 477 | `list:close()` if desired. 478 | 479 | @param start_directory The initial directory to open, in UTF-8 encoding. If 480 | nil, the initial directory is determined automatically (preferred choice is to 481 | open the directory containing the current file). 482 | @param filter The filter to apply, if any. The structure and semantics are the 483 | same as for Textadept's 484 | [snapopen](http://foicica.com/textadept/api/io.html#snapopen). 485 | @param depth The number of directory levels to display in the list. Defaults to 486 | 1 if not specified, which results in a "normal" directory listing. 487 | @param max_files The maximum number of files to scan and display in the list. 488 | Defaults to 10000 if not specified. 489 | ]] 490 | function M.select_file(on_selection, start_directory, filter, depth, max_files) 491 | start_directory = start_directory or get_initial_directory() 492 | if not filter then filter = {} 493 | elseif type(filter) == 'string' then filter = { filter } end 494 | 495 | filter.folders = filter.folders or {} 496 | filter.folders[#filter.folders + 1] = separator .. '%.$' 497 | 498 | -- Prevent opening another list from an already opened Textredux buffer. 499 | if buffer._textredux then return false end 500 | local list = create_list(start_directory, filter, depth or 1, 501 | max_files or 10000) 502 | 503 | list.on_selection = function(list, item) 504 | local path, mode = item.path, item.mode 505 | if mode == 'link' then 506 | mode = lfs.attributes(path, 'mode') 507 | end 508 | if mode == 'directory' then 509 | chdir(list, path) 510 | else 511 | on_selection(path, true, list, shift, ctrl, alt, meta) 512 | end 513 | end 514 | 515 | list.on_new_selection = function(list, name, shift, ctrl, alt, meta) 516 | local path = split_path(list.data.directory) 517 | path[#path + 1] = name 518 | on_selection(join_path(path), false, list, shift, ctrl, alt, meta) 519 | end 520 | 521 | chdir(list, start_directory) 522 | end 523 | 524 | function M.select_directory(on_selection, start_directory, filter, depth, max_files) 525 | start_directory = start_directory or get_initial_directory() 526 | if not filter then filter = {} 527 | elseif type(filter) == 'string' then filter = { filter } end 528 | filter[#filter + 1] = '.' 529 | 530 | filter.folders = filter.folders or {} 531 | 532 | local list = create_list(start_directory, filter, depth or 1, 533 | max_files or 10000) 534 | 535 | list.on_selection = function(list, item) 536 | local path, mode = item.path, item.mode 537 | if mode == 'link' then 538 | mode = lfs.attributes(path, 'mode') 539 | end 540 | if mode == 'directory' then 541 | if path:match("[/\\]%.$") then 542 | return 543 | end 544 | chdir(list, path) 545 | end 546 | end 547 | 548 | list.on_new_selection = function(list, name, shift, ctrl, alt, meta) 549 | local path = list.data.directory .. separator .. name:gsub("[/\\]*", "") 550 | on_selection(normalize_dir_path(path), false, list, shift, ctrl, alt, meta) 551 | end 552 | 553 | list.keys["right"] = function() 554 | local selected_dir = list:get_current_selection() 555 | if not selected_dir then 556 | return 557 | end 558 | 559 | local path = selected_dir.path 560 | if path:match("[/\\]%.$") then 561 | path = path:sub(1, -2) 562 | elseif path:match("[/\\]%..$") then 563 | return 564 | end 565 | 566 | on_selection(normalize_dir_path(path), false, list, shift, ctrl, alt, meta) 567 | end 568 | 569 | chdir(list, start_directory) 570 | end 571 | 572 | --- Saves the current buffer under a new name. 573 | -- Open a browser and lets the user select a name. 574 | function M.save_buffer_as() 575 | local buffer = _G.buffer 576 | local confirm_path 577 | 578 | local function set_file_name(path, exists, list) 579 | if not exists or path == confirm_path then 580 | list:close() 581 | _G.view:goto_buffer(_G._BUFFERS[buffer], false) 582 | buffer:save_as(path) 583 | ui.statusbar_text = '' 584 | else 585 | ui.statusbar_text = 'File exists (' .. path .. 586 | '): Press enter to overwrite.' 587 | confirm_path = path 588 | end 589 | end 590 | local filter = { folders = { separator .. '%.$' } } 591 | M.select_file(set_file_name, nil, filter, 1) 592 | ui.statusbar_text = 'Save file: select file name to save as..' 593 | 594 | end 595 | 596 | --- Saves the current buffer. 597 | -- Prompts the users for a filename if it's a new, previously unsaved buffer. 598 | function M.save_buffer() 599 | local buffer = _G.buffer 600 | if buffer.filename then 601 | buffer:save() 602 | else 603 | buffer:save_as() 604 | end 605 | end 606 | 607 | --- Opens the specified directory for browsing. 608 | -- @param start_directory The directory to open, in UTF-8 encoding 609 | function M.open_file(start_directory) 610 | local filter = { folders = { separator .. '%.$' } } 611 | M.select_file(open_selected_file, start_directory, filter, 1, io.quick_open_max) 612 | ui.statusbar_text = '[/] = jump to filesystem root, [~] = jump to userhome' 613 | end 614 | 615 | 616 | --[[- 617 | Opens a list of files in the specified directory, according to the given 618 | parameters. This works similarily to 619 | [Textadept snapopen](http://foicica.com/textadept/api/io.html#snapopen). 620 | The main differences are: 621 | 622 | - it does not support opening multiple paths at once 623 | - filter can contain functions as well as patterns (and can be a function as well). 624 | Functions will be passed a file object which is the same as the return from 625 | [lfs.attributes](http://keplerproject.github.com/luafilesystem/manual.html#attributes), 626 | with the following additions: 627 | 628 | - `rel_path`: The path of the file relative to the currently 629 | displayed directory. 630 | - `hidden`: Whether the path denotes a hidden file. 631 | 632 | @param directory The directory to open, in UTF-8 encoding. 633 | @param filter The filter to apply. The format and semantics are the same as for 634 | Textadept. 635 | @param exclude_FILTER Same as for Textadept: unless if not true then 636 | snapopen.FILTER will be automatically added to the filter. 637 | to snapopen.FILTER if not specified. 638 | @param depth The number of directory levels to scan. Defaults to DEFAULT_DEPTH 639 | if not specified. 640 | ]] 641 | function M.snapopen(directory, filter, exclude_FILTER, depth) 642 | if not directory then error('directory not specified', 2) end 643 | if not depth then depth = DEFAULT_DEPTH end 644 | filter = filter or {} 645 | if type(filter) == 'string' then filter = { filter } end 646 | filter.folders = filter.folders or {} 647 | filter.folders[#filter.folders + 1] = updir_pattern 648 | 649 | if not exclude_FILTER then 650 | filter = create_qopen_filter(filter) 651 | end 652 | 653 | M.select_file(open_selected_file, directory, filter, depth, io.quick_open_max) 654 | end 655 | 656 | return M 657 | -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2011-2012 Nils Nordman 2 | -- Copyright 2012-2014 Robert Gieseke 3 | -- License: MIT (see LICENSE) 4 | 5 | --[[-- 6 | The Textredux module allows you to easily create text based interfaces for the 7 | [Textadept](http://foicica.com/textadept/) editor and offers a set of text 8 | based interfaces. 9 | 10 | It currently contains the following modules: 11 | 12 | - @{textredux.core}. The core module provides basic components to create 13 | text based interfaces. 14 | - @{textredux.fs}. Contains text based interfaces for file io operations, 15 | like open file, save file as well as snapopen functionality. 16 | - @{textredux.ctags}. Displays a filtered list of symbols (functions, 17 | variables, …) in the current document using Exuberant Ctags. 18 | - @{textredux.buffer_list}. A text based buffer list replacement, which in 19 | addition to being text based also offers an easy way to close buffers 20 | directly from the list. 21 | 22 | ## How to use it 23 | 24 | Download and put the Textredux module in your `.textadept/modules/` 25 | directory. 26 | 27 | Having installed it, there are two (mixable) ways you can use Textredux. 28 | 29 | 1) Select the functionality you want from the different modules by assigning 30 | keys to the desired functions. 31 | 32 | local textredux = require('textredux') 33 | keys.co = textredux.fs.open_file 34 | keys.cS = textredux.fs.save_buffer_as 35 | keys.cb = textredux.buffer_list.show 36 | keys.cg = textredux.ctags.goto_symbol 37 | 38 | 2) If you can't get enough of text based interfaces and the joy they provide, 39 | then the @{hijack} function is for you. Simply place this in your 40 | `init.lua`: 41 | 42 | require('textredux').hijack() 43 | 44 | As the name suggest, Textredux has now hijacked your environment. All your 45 | regular key bindings should now use Textredux where applicable. Clicking the 46 | menu will still open the standard GUI dialogs. 47 | 48 | ## Customizing 49 | 50 | Please see the modules documentation for more configuration settings. 51 | 52 | @module textredux 53 | ]] 54 | 55 | local M = { 56 | core = require 'textredux.core', 57 | buffer_list = require 'textredux.buffer_list', 58 | ctags = require 'textredux.ctags', 59 | fs = require 'textredux.fs' 60 | } 61 | 62 | -- Set new key bindings. 63 | local function patch_keys(replacements) 64 | local _keys = {} 65 | for k, v in pairs(keys) do 66 | _keys[k] = v 67 | end 68 | for k, command_id in pairs(_keys) do 69 | local replacement = replacements[command_id] 70 | if replacement ~= nil then 71 | keys[k] = replacement 72 | end 73 | end 74 | end 75 | 76 | --- 77 | -- Hijacks Textadept, replacing all keyboard shortcuts with text based 78 | -- counterparts. Additionally, it replaces the traditional filtered list 79 | -- with a Textredux list for a number of operations. 80 | function M.hijack() 81 | local m_file = textadept.menu.menubar[_L['File']] 82 | local m_tools = textadept.menu.menubar[_L['Tools']] 83 | local m_buffer = textadept.menu.menubar[_L['Buffer']] 84 | local m_bookmark = m_tools[_L['Bookmarks']] 85 | 86 | local io_open = m_file[_L['Open']][2] 87 | 88 | local replacements = {} 89 | 90 | local io_quick_open = io.quick_open 91 | local function snapopen_compat(utf8_paths, filter, exclude_FILTER, ...) 92 | if not utf8_paths then utf8_paths = io.get_project_root() end 93 | if not utf8_paths and buffer.filename then 94 | utf8_paths = buffer.filename:match('^(.+)[/\\]') 95 | end 96 | if not utf8_paths or 97 | (type(utf8_paths) == 'table' and #utf8_paths ~= 1) 98 | then 99 | return io_quick_open(utf8_paths, filter, exclude_FILTER, ...) 100 | end 101 | local directory = type(utf8_paths) == 'table' and utf8_paths[1] or utf8_paths 102 | M.fs.snapopen(directory, filter, exclude_FILTER) 103 | end 104 | 105 | local io_open_file = io.open_file 106 | local function open_file_compat(utf8_filenames) 107 | if utf8_filenames then return io_open_file(utf8_filenames) end 108 | M.fs.open_file() 109 | end 110 | replacements[io_open] = open_file_compat 111 | 112 | -- Hijack filteredlist for the below functions. 113 | local select_lexer = m_buffer[_L['Select Lexer...']][2] 114 | local select_command = m_tools[_L['Select Command']][2] 115 | local goto_mark = m_bookmark[_L['Go To Bookmark...']][2] 116 | local fl_funcs = { 117 | select_lexer, 118 | io.open_recent_file, 119 | select_command, 120 | goto_mark 121 | } 122 | 123 | for _, target in ipairs(fl_funcs) do 124 | local wrap = M.core.filteredlist.wrap(target) 125 | replacements[target] = wrap 126 | end 127 | 128 | -- Hijack buffer list. 129 | replacements[ui.switch_buffer] = M.buffer_list.show 130 | 131 | -- Hijack snapopen. 132 | replacements[io.quick_open] = snapopen_compat 133 | io.quick_open = snapopen_compat 134 | 135 | -- Finalize by patching keys. 136 | patch_keys(replacements) 137 | end 138 | 139 | return M 140 | -------------------------------------------------------------------------------- /util/color.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2011-2012 Nils Nordman 2 | -- Copyright 2012-2014 Robert Gieseke 3 | -- License: MIT (see LICENSE) 4 | 5 | --[[-- 6 | The color module provides utility functions for color handling. 7 | 8 | @module textredux.util.color 9 | ]] 10 | 11 | local M = {} 12 | 13 | --- 14 | -- Convert color in '#rrggbb' format to 'bbggrr'. 15 | function M.string_to_color(rgb) 16 | if not rgb then return nil end 17 | local r, g, b = rgb:match('^#?(%x%x)(%x%x)(%x%x)$') 18 | if not r then error("Invalid color specification '" .. rgb .. "'", 2) end 19 | return tonumber(b .. g .. r, 16) 20 | end 21 | 22 | --- 23 | -- Convert color in hex 'bbggrr' format to string '#rrggbb' 24 | function M.color_to_string(color) 25 | local hex = string.format('%.6x', color) 26 | local b, g, r = hex:match('^(%x%x)(%x%x)(%x%x)$') 27 | if not r then return '?' end 28 | return '#' .. r .. g .. b 29 | end 30 | 31 | return M 32 | -------------------------------------------------------------------------------- /util/matcher.lua: -------------------------------------------------------------------------------- 1 | -- Copyright 2011-2012 Nils Nordman 2 | -- Copyright 2012-2014 Robert Gieseke 3 | -- License: MIT (see LICENSE) 4 | 5 | --[[-- 6 | The matcher module provides easy and advanced matching of strings. 7 | 8 | @module textredux.util.matcher 9 | ]] 10 | 11 | local append = table.insert 12 | 13 | local M = {} 14 | 15 | --[[ Constructs a new matcher. 16 | @param candidates The candidates to consider for matching. A table of either 17 | string, or tables containing strings. 18 | @param search_case_insensitive Whether searches are case insensitive or not. 19 | Defaults to `true`. 20 | @param search_fuzzy Whether fuzzy searching should be used in addition to 21 | explicit matching. Defaults to `true`. 22 | ]] 23 | function M.new(candidates, search_case_insensitive, search_fuzzy) 24 | local m = { 25 | search_case_insensitive = search_case_insensitive, 26 | search_fuzzy = search_fuzzy 27 | } 28 | setmetatable(m, { __index = M }) 29 | m:_set_candidates(candidates) 30 | return m 31 | end 32 | 33 | -- Applies search matchers on a line. 34 | -- @param line The line to match 35 | -- @param matchers The search matchers to apply 36 | -- @return A numeric score if the line matches or nil otherwise. For scoring, 37 | -- lower is better. 38 | local function match_score(line, matchers) 39 | local score = 0 40 | 41 | for _, matcher in ipairs(matchers) do 42 | local matcher_score = matcher(line) 43 | if not matcher_score then return nil end 44 | score = score + matcher_score 45 | end 46 | return score 47 | end 48 | 49 | --[[ Explains the match for a given search. 50 | @param search The search string to match 51 | @param text The text to match against 52 | @return A list of explanation tables. Each explanation table contains the 53 | following fields: 54 | `score`: The score for the match 55 | `start_pos`: The start position of the best match 56 | `end_pos`: The end position of the best match 57 | `1..n`: Tables of matching positions with the field start_pos and length 58 | ]] 59 | function M:explain(search, text) 60 | if not search or #search == 0 then return {} end 61 | if self.search_case_insensitive then 62 | search = search:lower() 63 | text = text:lower() 64 | end 65 | local matchers = self:_matchers_for_search(search) 66 | local explanations = {} 67 | 68 | for _, matcher in ipairs(matchers) do 69 | local score, start_pos, end_pos, search = matcher(text) 70 | if not score then return {} end 71 | local explanation = { score = score, start_pos = start_pos, end_pos = end_pos } 72 | local _, s_index = 1, 1 73 | local l_start, l_index = start_pos, start_pos 74 | while s_index <= #search do 75 | repeat 76 | s_index = s_index + 1 77 | l_index = l_index + 1 78 | until search:sub(s_index, s_index) ~= text:sub(l_index, l_index) or s_index > #search 79 | append(explanation, { start_pos = l_start, length = l_index - l_start }) 80 | if s_index > #search then break end 81 | repeat 82 | l_index = l_index + 1 83 | until search:sub(s_index, s_index) == text:sub(l_index, l_index) or l_index > end_pos 84 | l_start = l_index 85 | end 86 | append(explanations, explanation) 87 | end 88 | 89 | return explanations 90 | end 91 | 92 | -- Matches search against the candidates. 93 | -- @param search The search string to match 94 | -- @return A table of matching candidates, ordered by relevance. 95 | function M:match(search) 96 | if not search or #search == 0 then return self.candidates end 97 | local cache = self.cache 98 | if self.search_case_insensitive then search = search:lower() end 99 | local matches = cache.matches[search] or {} 100 | if #matches > 0 then return matches end 101 | local lines = cache.lines[string.sub(search, 1, -2)] or self.lines 102 | local matchers = self:_matchers_for_search(search) 103 | 104 | local matching_lines = {} 105 | for _, line in ipairs(lines) do 106 | local score = match_score(line.text, matchers) 107 | if score then 108 | matches[#matches + 1] = { index = line.index, score = score } 109 | matching_lines[#matching_lines + 1] = line 110 | end 111 | end 112 | cache.lines[search] = matching_lines 113 | 114 | table.sort(matches, function(a ,b) return a.score < b.score end) 115 | local matching_candidates = {} 116 | for _, match in ipairs(matches) do 117 | matching_candidates[#matching_candidates + 1] = self.candidates[match.index] 118 | end 119 | self.cache.matches[search] = matching_candidates 120 | return matching_candidates 121 | end 122 | 123 | function M:_set_candidates(candidates) 124 | self.candidates = candidates 125 | self.cache = { 126 | lines = {}, 127 | matches = {} 128 | } 129 | local lines = {} 130 | local fuzzy_score_penalty = 0 131 | 132 | for i, candidate in ipairs(candidates) do 133 | if type(candidate) ~= 'table' then candidate = { candidate } end 134 | local text = table.concat(candidate, ' ') 135 | if self.search_case_insensitive then text = text:lower() end 136 | lines[#lines + 1] = { 137 | text = text, 138 | index = i 139 | } 140 | fuzzy_score_penalty = math.max(fuzzy_score_penalty, #text) 141 | end 142 | self.lines = lines 143 | self.fuzzy_score_penalty = fuzzy_score_penalty 144 | end 145 | 146 | local pattern_escapes = {} 147 | for c in string.gmatch('^$()%.[]*+-?', '.') do pattern_escapes[c] = '%' .. c end 148 | 149 | local function fuzzy_search_pattern(search) 150 | local pattern = '' 151 | for i = 1, #search do 152 | local c = search:sub(i, i) 153 | c = pattern_escapes[c] or c 154 | pattern = pattern .. c .. '.-' 155 | end 156 | return pattern 157 | end 158 | 159 | --- Creates matches for the specified search 160 | -- @param search_string The search string 161 | -- @return A table of matcher functions, each taking a line as parameter and 162 | -- returning a score (or nil for no match). 163 | function M:_matchers_for_search(search_string) 164 | local fuzzy = self.search_fuzzy 165 | local fuzzy_penalty = self.fuzzy_score_penalty 166 | local groups = {} 167 | for part in search_string:gmatch('%S+') do groups[#groups + 1] = part end 168 | local matchers = {} 169 | 170 | for _, search in ipairs(groups) do 171 | local fuzzy_pattern = fuzzy and fuzzy_search_pattern(search) 172 | matchers[#matchers + 1] = function(line) 173 | local start_pos, end_pos = line:find(search, 1, true) 174 | local score = start_pos 175 | if not start_pos and fuzzy then 176 | start_pos, end_pos = line:find(fuzzy_pattern) 177 | if start_pos then 178 | score = (end_pos - start_pos) + fuzzy_penalty 179 | end 180 | end 181 | if score then 182 | return score + #line, start_pos, end_pos, search 183 | end 184 | end 185 | end 186 | return matchers 187 | end 188 | 189 | return M 190 | --------------------------------------------------------------------------------