diff options
author | mitchell <70453897+667e-11@users.noreply.github.com> | 2007-08-06 05:00:21 -0400 |
---|---|---|
committer | mitchell <70453897+667e-11@users.noreply.github.com> | 2007-08-06 05:00:21 -0400 |
commit | 538767b22d4641ffe4c6c5d5d82445c9e036bab6 (patch) | |
tree | 7187d27ef2d85a902a4eb9f92ab81a893816bbf7 | |
parent | f23283b23db8dd67ac7c951ab4093b8b1d54ada4 (diff) |
Initial import of extension Lua files.
-rw-r--r-- | core/ext/find.lua | 107 | ||||
-rw-r--r-- | core/ext/mime_types.lua | 86 | ||||
-rw-r--r-- | core/ext/pm.lua | 95 | ||||
-rw-r--r-- | core/ext/pm/buffer_browser.lua | 57 | ||||
-rw-r--r-- | core/ext/pm/ctags_browser.lua | 246 | ||||
-rw-r--r-- | core/ext/pm/file_browser.lua | 64 |
6 files changed, 655 insertions, 0 deletions
diff --git a/core/ext/find.lua b/core/ext/find.lua new file mode 100644 index 00000000..bc33b3cd --- /dev/null +++ b/core/ext/find.lua @@ -0,0 +1,107 @@ +-- Copyright 2007 Mitchell mitchell<att>caladbolg.net. See LICENSE. + +local find = textadept.find + +--- +-- [Local table] Text escape sequences with their associated characters. +-- @class table +-- @name escapes +local escapes = { + ['\\a'] = '\a', ['\\b'] = '\b', ['\\f'] = '\f', ['\\n'] = '\n', + ['\\r'] = '\r', ['\\t'] = '\t', ['\\v'] = '\v', ['\\\\'] = '\\' +} + +--- +-- Finds and selects text in the current buffer. +-- This is used by the find dialog. It is recommended to use the buffer:find() +-- function for scripting. +-- @param text The text to find. +-- @param flags Search flags. This is a number mask of 3 flags: match case (2), +-- whole word (4), and Lua pattern (8) joined with binary AND. +-- @param next Boolean indicating search direction (next is forward). +-- @param wrapped Utility boolean indicating whether the search has wrapped or +-- not for displaying useful statusbar information. This flag is used and set +-- internally, and should not be set otherwise. +function find.find(text, flags, next, wrapped) + local buffer = buffer + local result + text = text:gsub('\\[abfnrtv\\]', escapes) + find.captures = nil + if flags < 8 then + if next then + buffer:goto_pos(buffer.current_pos + 1) + buffer:search_anchor() + result = buffer:search_next(flags, text) + else + buffer:goto_pos(buffer.anchor - 1) + buffer:search_anchor() + result = buffer:search_prev(flags, text) + end + if result then buffer:scroll_caret() end + else -- lua pattern search + local buffer_text = buffer:get_text(buffer.length) + local results = { buffer_text:find(text, buffer.anchor + 1) } + if #results > 0 then + result = results[1] + find.captures = { unpack(results, 3) } + buffer:set_sel(results[2], result - 1) + else + result = -1 + end + end + if result == -1 and not wrapped then -- wrap the search + local anchor, pos = buffer.anchor, buffer.current_pos + if next or flags >= 8 then + buffer:goto_pos(0) + else + buffer:goto_pos(buffer.length) + end + textadept.statusbar_text = 'Search wrapped' + result = find.find(text, flags, next, true) + if not result then + textadept.statusbar_text = 'No results found' + buffer:goto_pos(anchor) + end + return result + elseif result ~= -1 and not wrapped then + textadept.statusbar_text = '' + end + return result ~= -1 +end + +--- +-- Replaces found text. +-- This function is used by the find dialog. It is not recommended to call it +-- via scripts. +-- textadept.find.find is called first, to select any found text. The selected +-- text is then replaced by the specified replacement text. +-- @param rtext The text to replace found text with. It can contain Lua escape +-- sequences to use text captured by a Lua pattern. (%n where 1 <= n <= 9.) +function find.replace(rtext) + if #buffer:get_sel_text() == 0 then return end + local buffer = buffer + buffer:target_from_selection() + if find.captures then + for i, v in ipairs(find.captures) do + rtext = rtext:gsub('[^%%]?[^%%]?%%'..i, v) -- not entirely correct + end + end + buffer:replace_target( rtext:gsub('\\[abfnrtv\\]', escapes) ) +end + +--- +-- Replaces all found text. +-- This function is used by the find dialog. It is not recommended to call it +-- via scripts. +-- @param ftext The text to find. +-- @param rtext The text to replace found text with. +-- @param flags The number mask identical to the one in 'find'. +-- @see find.find +function find.replace_all(ftext, rtext, flags) + local count = 0 + while( find.find(ftext, flags, true) ) do + find.replace(rtext) + count = count + 1 + end + textadept.statusbar_text = tostring(count)..' replacement(s) made' +end diff --git a/core/ext/mime_types.lua b/core/ext/mime_types.lua new file mode 100644 index 00000000..bf17219f --- /dev/null +++ b/core/ext/mime_types.lua @@ -0,0 +1,86 @@ +-- Copyright 2007 Mitchell mitchell<att>caladbolg.net. See LICENSE. + +local languages = { + cpp = 'cpp', + css = 'css', + diff = 'diff', + html = 'html', + javascript = 'javascript', + lua = 'lua', + makefile = 'makefile', + php = 'php', + python = 'python', + rhtml = 'rhtml', + ruby = 'ruby', + xml = 'xml' +} + +local l = languages +local extensions = { + c = l.cpp, cpp = l.cpp, cxx = l.cpp, h = l.cpp, + css = l.css, + diff = l.diff, patch = l.diff, + html = l.html, htm = l.html, shtml = l.html, + iface = l.makefile, + js = l.javascript, + lua = l.lua, + mak = l.makefile, makefile = l.makefile, Makefile = l.makefile, + php = l.php, + py = l.python, pyw = l.python, + rhtml = l.rhtml, + rb = l.rb, rbw = l.rb, + xml = l.xml, xsl = l.xml, xslt = l.xml +} + +--- +-- [Local] Sets the buffer's lexer language based on a filename. +-- @param filename The filename used to set the lexer language. +local function set_lexer_from_filename(filename) + local lexer + if filename then + local ext = filename:match('[^/]+$'):match('[^.]+$') + lexer = extensions[ext] + end + buffer:set_lexer_language(lexer or 'container') +end + +--- +-- [Local] Loads a language module based on a filename (if it hasn't been +-- loaded already). +-- @param filename The filename used to load a language module from. +local function load_language_module_from_filename(filename) + if not filename then return end + local ext = filename:match('[^/]+$'):match('[^.]+$') + local lang = extensions[ext] + if lang then + local ret, err = pcall(require, lang) + if ret then + modules[lang].set_buffer_properties() + elseif not ret and not err:match("^module '"..lang.."' not found:") then + textadept.handlers.error(err) + end + end +end + +--- +-- [Local] Performs actions suitable for a new buffer. +-- Sets the lexer language and loads the language module based on the new +-- buffer's filename. +local function handle_new() + local buffer = buffer + set_lexer_from_filename(buffer.filename) + load_language_module_from_filename(buffer.filename) +end + +--- +-- [Local] Performs actions suitable for when buffers are switched. +-- Sets the lexer language based on the current buffer's filename. +local function handle_switch() + set_lexer_from_filename(buffer.filename) +end + +local handlers = textadept.handlers +handlers.add_function_to_handler('file_opened', handle_new) +handlers.add_function_to_handler('file_saved_as', handle_new) +handlers.add_function_to_handler('buffer_switch', handle_switch) +handlers.add_function_to_handler('view_new', handle_switch) diff --git a/core/ext/pm.lua b/core/ext/pm.lua new file mode 100644 index 00000000..82274066 --- /dev/null +++ b/core/ext/pm.lua @@ -0,0 +1,95 @@ +-- Copyright 2007 Mitchell mitchell<att>caladbolg.net. See LICENSE. + +--- +-- Browsers loaded by the project manager. +-- @class table +-- @name browsers +local browsers = { 'buffer_browser', 'file_browser', 'ctags_browser' } +for _, b in ipairs(browsers) do require('ext/pm.'..b) end + +local pm = textadept.pm + +--- +-- Requests treeview contents from browser that matches pm_entry's text. +-- This function is called internally and shouldn't be called by a script. +-- @param full_path A numerically indexed table of treeview item parents. The +-- first index contains the text of pm_entry. Subsequent indexes contain the +-- ID's of parents of the child requested for expanding (if any). +-- @param expanding Optional flag indicating if the contents of a parent are +-- being requested. Defaults to false. +-- @return table of tables to for display in the treeview (single level). +-- Each key in the return table is the treeview item's ID. The table value +-- has the following recognized fields: +-- parent - boolean value indicating if this entry can contain children. If +-- true, an expanding arrow is displayed next to the entry. +-- pixbuf - a string representing a GTK stock-id whose icon is displayed +-- next to an entry. +-- text - the entry's Pango marked-up display text. +-- Note that only a SINGLE level of data needs to be returned. When parents +-- are expanded, this function is called again to get that level of data. +function pm.get_contents_for(full_path, expanding) + for _, browser in pairs(pm.browsers) do + if browser.matches( full_path[1] ) then + return browser.get_contents_for(full_path, expanding) + end + end +end + +--- +-- Performs an action based on the selected treeview item. +-- This function is called internally and shouldn't be called by a script. +-- @param selected_item Identical to 'full_path' in pm.get_contents_for. +-- @see pm.get_contents_for +function pm.perform_action(selected_item) + for _, browser in pairs(pm.browsers) do + if browser.matches( selected_item[1] ) then + return browser.perform_action(selected_item) + end + end +end + +--- +-- Creates a context menu based on the selected treeview item. +-- This function is called internally and shouldn't be called by a script. +-- @param selected_item Identical to 'full_path' in pm.get_contents_for. +-- @return table of menu items. +-- The return table consists of an ordered list of strings to be used to +-- construct a context menu. The strings are handled as follows: +-- 'gtk-*' - a stock menu item is created based on the GTK stock-id. +-- 'separator' - a menu separator item is created. +-- Otherwise a regular menu item with a mnemonic is created. +-- @see pm.get_contents_for +function pm.get_context_menu(selected_item) + for _, browser in pairs(pm.browsers) do + if browser.matches( selected_item[1] ) then + return browser.get_context_menu(selected_item) + end + end +end + +--- +-- Performs an action based on the selected menu item. +-- This function is called internally and shouldn't be called by a script. +-- @param menu_item The label text of the menu item selected. +-- @param selected_item Identical to 'full_path' in pm.get_contents_for. +-- @see pm.get_contents_for +function pm.perform_menu_action(menu_item, selected_item) + for _, browser in pairs(pm.browsers) do + if browser.matches( selected_item[1] ) then + return browser.perform_menu_action(menu_item, selected_item) + end + end +end + +--- +-- Toggles the width of the project manager. +-- If the pm is visible, it's width is saved and then set to 0, effectively +-- hiding it. If it is hidden, the width is restored. +function pm.toggle_visible() + if pm.width > 0 then + pm.prev_width = pm.width + pm.width = 0 + else + pm.width = pm.prev_width or 150 + end +end diff --git a/core/ext/pm/buffer_browser.lua b/core/ext/pm/buffer_browser.lua new file mode 100644 index 00000000..56891fa9 --- /dev/null +++ b/core/ext/pm/buffer_browser.lua @@ -0,0 +1,57 @@ +-- Copyright 2007 Mitchell mitchell<att>caladbolg.net. See LICENSE. + +--- +-- Buffer browser for the Textadept project manager. +-- It is enabled with the prefix 'buffers' in the project manager entry field. +module('textadept.pm.browsers.buffer', package.seeall) + +function matches(entry_text) + return entry_text:sub(1, 7) == 'buffers' +end + +function get_contents_for() + local contents = {} + for index, buffer in ipairs(textadept.buffers) do + index = string.format("%02i", index) + contents[index] = { + pixbuf = buffer.dirty and 'gtk-edit' or 'gtk-file', + text = (buffer.filename or 'Untitled'):match('[^/]+$') + } + end + return contents +end + +function perform_action(selected_item) + local index = selected_item[2] + local buffer = textadept.buffers[ tonumber(index) ] + if buffer then view:goto_buffer(index) view:focus() end +end + +function get_context_menu(selected_item) + return { '_New', '_Open', '_Save', 'Save _As...', 'separator', '_Close' } +end + +function perform_menu_action(menu_item, selected_item) + if menu_item == 'New' then + textadept.new_buffer() + elseif menu_item == 'Open' then + textadept.io.open() + elseif menu_item == 'Save' then + textadept.buffers[ tonumber( selected_item[2] ) ]:save() + elseif menu_item == 'Save As...' then + textadept.buffers[ tonumber( selected_item[2] ) ]:save_as() + elseif menu_item == 'Close' then + textadept.buffers[ tonumber( selected_item[2] ) ]:close() + end + textadept.pm.activate() +end + +local add_function_to_handler = textadept.handlers.add_function_to_handler +local function update_view() + if matches(textadept.pm.entry_text) then textadept.pm.activate() end +end +add_function_to_handler('file_opened', update_view) +add_function_to_handler('buffer_new', update_view) +add_function_to_handler('buffer_deleted', update_view) +add_function_to_handler('save_point_reached', update_view) +add_function_to_handler('save_point_left', update_view) diff --git a/core/ext/pm/ctags_browser.lua b/core/ext/pm/ctags_browser.lua new file mode 100644 index 00000000..e5607e7a --- /dev/null +++ b/core/ext/pm/ctags_browser.lua @@ -0,0 +1,246 @@ +-- Copyright 2007 Mitchell mitchell<att>caladbolg.net. See LICENSE. + +--- +-- CTags Browser for the Textadept project manager. +-- It is enabled with the prefix 'ctags' in the project manager entry field +-- followed by either nothing or ':' and the path to a ctags file. If no path +-- is specified, the current file is parsed via ctags and its structure shown. +module('textadept.pm.browsers.ctags', package.seeall) + +local FILE_OUT = '/tmp/textadept_output' + +--- +-- The current ctags file and current directory. +-- When a ctags file is opened, current_dir is set to its dirname. +local current_file, current_dir + +--- +-- The table of ctags with property values. +-- Each key is the name of a ctags identifier (function, class, etc.) and the +-- value is a table containing: +-- * The GTK stock-id for the pixbuf to display next to the identifier in the +-- tree view (pixbuf key). +-- * The display text used for displaying the identifier in the tree view +-- (display_text key). +-- * Boolean parent value if the identifier is a container. +-- * The line number or pattern used to goto the identifier. +-- Note this table is returned by get_contents_for, but only 'pixbuf', +-- 'display_text' and 'parent' fields are read; all others are ignored. +-- @class table +-- @name tags +local tags + +--- +-- Table of associations of tag identifier types for specific languages with +-- GTK stock-id pixbufs. +-- @class table +-- @name pixbuf +local pixbuf = { + lua = { f = 'prog-method' }, + ruby = { + c = 'prog-class', + f = 'prog-method', + F = 'prog-method', + m = 'prog-namespace' + }, + cpp = { + c = 'prog-class', + e = 'prog-enum', + f = 'prog-method', + g = 'prog-enum', + m = 'prog-field', + n = 'prog-namespace', + s = 'prog-struct' + } +} + +--- +-- Table of associations of file extensions with languages. +-- @class table +-- @name language +local language = { + lua = 'lua', + rb = 'ruby', + h = 'cpp', c = 'cpp', cxx = 'cpp' -- C++ +} + +--- +-- Table used to determine if a tag kind is a container or not in a specific +-- language. +-- Top-level keys are language names from the languages table with table +-- values. These table values have tag kind keys with boolean values indicating +-- if they are containers or not. +-- @class table +-- @name container +-- @return true if the tag kind is a container. +-- @see language +local container = { + lua = {}, + ruby = { c = true, m = true }, + cpp = { c = true, g = true, s = true } +} + +--- +-- Table used to determine if a construct name is a container or not in a +-- specific language. +-- Top-level keys are language names from the languages table with table +-- values. These table values have construct name keys with boolean values +-- indicating if they are containers or not. +-- @class table +-- @name container_construct +-- @return true if the construct name is a container. +-- @see language +local container_construct = { + lua = {}, + ruby = { class = true, module = true }, + cpp = { class = true, enum = true, struct = true } +} + +--- Matches 'ctags:[/absolute/path/to/ctags/file]' +function matches(entry_text) + return entry_text:sub(1, 5) == 'ctags' and true or false +end + +--- +-- If not expanding, create the entire tree; otherwise return the child table +-- of the parent being expanded. +function get_contents_for(full_path, expanding) + local ctags_file = full_path[1]:sub(7) -- ignore 'ctags:' + local f + if #ctags_file == 0 then + tags = {} + current_file = nil + current_dir = '' -- ctags file will specify absolute paths + os.execute( 'ctags -o '..FILE_OUT..' '..(buffer.filename or '') ) + f = io.open(FILE_OUT) + if not f then return {} end + elseif not expanding then + tags = {} + current_file = ctags_file + current_dir = ctags_file:match('^.+/') -- ctags file dirname + f = io.open(ctags_file) + if not f then return {} end + else + local parent = tags + for i = 2, #full_path do + local identifier = full_path[i] + if not parent[identifier] then return {} end + parent = parent[identifier].children + end + return parent + end + for line in f:lines() do + if line:sub(1, 2) ~= '!_' then + -- Parse ctags line to get identifier attributes. + local name, filepath, pattern, line_num, ext + name, filepath, pattern, ext = + line:match('^([^\t]+)\t([^\t]+)\t/^(.+)$/;"\t(.*)$') + if not name then + name, filepath, line_num, ext = + line:match('^([^\t]+)\t([^\t]+)\t(%d+);"\t(.*)$') + end + -- If the ctag line is parsed correctly, create the entry. + if name and #name > 0 then + local entry = {} + local file_ext = filepath:match('%.([^.]+)$') + local lang = language[file_ext] + if lang then + -- Parse the extension fields for details on if this identifier is a + -- child or parent and where to put it. + local fields = {} + --print(ext) + for key, val in ext:gmatch('([^:%s]+):?(%S*)') do + if #val == 0 and #key == 1 then -- kind + if container[lang][key] then + -- This identifier is a container. Place it in the toplevel of + -- tags. + entry.parent = true + entry.children = {} + if tags[name] then + -- If previously defined by a child, preserve the children + -- field. + entry.children = tags[name].children + end + tags[name] = entry + entry.set = true + end + entry.pixbuf = pixbuf[lang][key] + elseif container_construct[lang][key] then + -- This identifier belongs to a container, so define the + -- container if it hasn't been already and place this identifier + -- in it. Just in case there is no ctag entry for container later + -- on, define 'parent' and 'display_text'. + if not tags[val] then + tags[val] = { parent = true, display_text = val } + end + local parent = tags[val] + if not parent.children then parent.children = {} end + parent.children[name] = entry -- add to parent + entry.set = true + end + end + entry.display_text = name + -- The following keys are ignored by caller. + entry.filepath = filepath:sub(1, 1) == '/' and + filepath or current_dir..filepath + entry.pattern = pattern + entry.line_num = line_num + if not entry.set then tags[name] = entry end + else + print('Extension "'..file_ext..'" not recognized.') + end + else print('unmatched ctag: '..line) end + end + end + f:close() + return tags +end + +function perform_action(selected_item) + local item = tags + for i = 2, #selected_item do + local identifier = selected_item[i] + item = item[identifier] + if item.children then item = item.children end + end + if item.pattern then + local buffer_text = buffer:get_text(buffer.length) + local search_text = item.pattern:gsub('\\/', '/') + local s = buffer_text:find(search_text, 1, true) + if s then + textadept.io.open(item.filepath) + local line = buffer:line_from_position(s) + buffer:ensure_visible_enforce_policy(line) + buffer:goto_line(line) + else + error(item.display_text..' not found.') + end + elseif item.line_num then + textadept.io.open(item.filepath) + buffer:goto_line(item.line_num - 1) + end + view:focus() +end + +function get_context_menu(selected_item) + +end + +function perform_menu_action(menu_item, selected_item) + +end + +local add_function_to_handler = textadept.handlers.add_function_to_handler +local function update_view() + if matches(textadept.pm.entry_text) then + if buffer.filename then + textadept.pm.activate() + else + textadept.pm.clear() + end + end +end +add_function_to_handler('file_opened', update_view) +add_function_to_handler('buffer_deleted', update_view) +add_function_to_handler('buffer_switch', update_view) +add_function_to_handler('save_point_reached', update_view) diff --git a/core/ext/pm/file_browser.lua b/core/ext/pm/file_browser.lua new file mode 100644 index 00000000..9acf9bec --- /dev/null +++ b/core/ext/pm/file_browser.lua @@ -0,0 +1,64 @@ +-- Copyright 2007 Mitchell mitchell<att>caladbolg.net. See LICENSE. + +--- +-- File browser for the Textadept project manager. +-- It is enabled by providing the absolute path to a directory in the project +-- manager entry field. +module('textadept.pm.browsers.file', package.seeall) + +function matches(entry_text) + return entry_text:sub(1, 1) == '/' +end + +function get_contents_for(full_path) + local dirpath = table.concat(full_path, '/') + local out = io.popen('ls -1p "'..dirpath..'"'):read('*all') + if #out == 0 then + error('No such directory: '..dirpath) + return {} + end + local dir = {} + for entry in out:gmatch('[^\n]+') do + if entry:sub(-1, -1) == '/' then + local name = entry:sub(1, -2) + dir[name] = { + parent = true, + display_text = name, + pixbuf = 'gtk-directory' + } + else + dir[entry] = { display_text = entry } + end + end + return dir +end + +function perform_action(selected_item) + local filepath = table.concat(selected_item, '/') + textadept.io.open(filepath) + view:focus() +end + +function get_context_menu(selected_item) + return { '_Change Directory', 'File _Details' } +end + +function perform_menu_action(menu_item, selected_item) + local filepath = table.concat(selected_item, '/') + if menu_item == 'Change Directory' then + textadept.pm.entry_text = filepath + textadept.pm.activate() + elseif menu_item == 'File Details' then + local out = io.popen('ls -dhl "'..filepath..'"'):read('*all') + local perms, num_dirs, owner, group, size, mod_date = + out:match('^(%S+) (%S+) (%S+) (%S+) (%S+) (%S+ %S)') + out = 'File details for:\n'..filepath..'\n'.. + 'Perms:\t'..perms..'\n'.. + '#Dirs:\t'..num_dirs..'\n'.. + 'Owner:\t'..owner..'\n'.. + 'Group:\t'..group..'\n'.. + 'Size:\t'..size..'\n'.. + 'Date:\t'..mod_date + text_input(out, nil, false, 250, 250) + end +end |