Textadept
- Home |
- Download |
- Lua API |
- Source |
- Language Modules |
- Stats |
- Wiki |
- Mailing List
Contents
_M.textadept.adeptsense
Language autocompletion support for the textadept module.
Overview
Adeptsense is a form of autocompletion for programming. It has the means to supply a list of potential completions for classes, member functions and fields, packages, etc. Adeptsense can also display documentation for such entities in the form of a calltip. This document provides the information necessary in order to write a new Adeptsense for a language. For illustrative purposes, an Adeptsense for Lua will be created. More advanced techniques are covered later.
Creating an Adeptsense
Adeptsenses exist per-language and are typically defined in a language-specific module. First check to see if the module for your language has an Adeptsense. If not, you will need to create one. The language modules included with Textadept have Adeptsenses so they can be used for reference. If your language is similar to any of those languages, you can copy and modify the existing language’s Adeptsense, saving some time and effort.
Terminology
Not all languages have “classes”, “functions”, and “fields” in the full sense of the word. Normally classes are referred to as objects in Object Oriented Programming (OOP), functions are class or instance methods a class can have, and fields are class or instance properties. For example an “Apple” class may have a “color” field and an “eat” function. To Adeptsense, the term “class” is simply a container for “function” and “field” completions. “functions” and “fields” are only distinguished by an icon in the completion list.
For Lua, consider modules and tables as “classes”, functions as “functions”, and module/table keys as “fields”.
Introduction
Open the language-specific module for editing and create a new instance of an Adeptsense.
$> # from either _HOME or _USERHOME:
$> cd modules/lua/
$> textadept init.lua
sense = _M.textadept.adeptsense.new('lua')
Where ‘lua’ is replaced by your language’s name.
Syntax Options
The syntax of different languages varies so the Adeptsense must be configured
for your language in order to function properly. See the syntax
table documentation for all options.
self
The first syntax option is syntax.self
. While Lua has a self
identifier,
it is not used in the usual sense for a class instance so it will just be
ignored.
class_definition
Next is syntax.class_definition
. Lua does not really have the “class”
concept most OOP programmers are used to, but modules do behave somewhat like
classes.
sense.syntax.class_definition = 'module%s*%(?%s*[\'"]([%w_%.]+)'
The “class”’s name is the identifier in quotes.
word_chars
Lua words already consist of the default %w_
so no changes are necessary.
symbol_chars
In addition to the usual [%w_%.]
symbol characters, Lua also allows symbols
to contain a :
.
sense.syntax.symbol_chars = '[%w_%.:]'
type_declarations
Since Lua has no type declarations (e.g. int x
in C), the
syntax.type_declarations
table should be empty:
sense.syntax.type_declarations = {}
type_declarations_exclude
Since Lua has no type declarations, no changes are necessary.
type_assignments
Sometimes a type can be inferred by the right side of a variable assignment.
In the Lua code local foo = 'bar'
, the variable foo
has type string
.
Similarly, in local foo = _M.textadept.adeptsense
, foo
has type
_M.textadept.adeptsense
.
sense.syntax.type_assignments = {
['^[\'"]'] = 'string', -- foo = 'bar' or foo = "bar"
['^([%w_%.]+)'] = '%1' -- foo = _M.textadept.adeptsense
}
Note the ^
in the pattern. The beginning of the right hand side of the
assignment should be matched, otherwise local foo = bar('baz')
could infer
an incorrect type.
Completion Lists
The completions
table contains the completion lists for
all classes. Each table entry key is the class’s name and the value is a
table of functions
and fields
lists. The general form is:
sense.completions = {
['class1'] = {
functions = { 'fun1', 'fun2', ...},
fields = { 'f1', 'f2', ... }
},
['class2'] = ...,
...
}
Obviously manually creating completion lists would be incredibly time-consuming so there is a shortcut method.
Ctags
Adeptsense recognizes the output from Ctags and can populate the
completions
table from it with a little bit of help. Ctags has a list of
“kinds” for every language. You can see them by running ctags --list-kinds
in your shell. Since Adeptsense only cares about classes, functions, and
fields, you need to let it know which kind of tag is which. Unfortunately,
Lua support in Ctags is not good at all. Instead, Textadept has a utility
(modules/lua/adeptsensedoc.lua
) to generate a fake set of tags that is more
useful. Functions are tagged 'f'
and should be recognized as such; table
keys are tagged 't'
and should be recognized as fields; module fields,
'F'
, should be fields; and modules, 'm'
, should be classes:
sense.ctags_kinds = {
f = 'functions',
F = 'fields',
m = 'classes',
t = 'fields',
}
To load a default set of Ctags, use load_ctags()
.
sense:load_ctags(_HOME..'/modules/lua/tags', true)
Textadept comes with a set of Ctags for its Lua API.
API Documentation
Adeptsense can show API documentation for symbols from files specified in its
api_files
table. See the previous link for documentation on
how the API file should be structured.
sense.api_files = { _HOME..'/modules/lua/api' }
Triggers
Triggers are characters or character sequences that trigger an autocompletion
list to be displayed. Lua has two characters that can do so: .
and :
. The
.
should autocomplete both fields and functions while :
should
autocomplete only functions. This is specified using
add_trigger()
.
sense:add_trigger('.')
sense:add_trigger(':', false, true)
User-Settings
Finally, you should allow the users of your Adeptsense to supply their own Ctags and API files in addition to any default ones you loaded or specified earlier:
-- Load user tags and apidoc.
if lfs.attributes(_USERHOME..'/modules/lua/tags') then
sense:load_ctags(_USERHOME..'/modules/lua/tags')
end
if lfs.attributes(_USERHOME..'/modules/lua/api') then
sense.api_files[#sense.api_files + 1] = _USERHOME..'/modules/lua/api'
end
Summary
The above method of setting syntax options, ctags kinds, and trigger characters for an Adeptsense is sufficient for most static and some dynamic languages. The rest of this document is devoted to more complex techniques.
Fine-Tuning
Sometimes Adeptsense’s default behavior is not sufficient. Maybe the
type_declarations
and type_assignments
tables used by the
get_class()
function are not granular enough. Maybe some
symbols can contain more than just the syntax.symbol_chars
used by
get_symbol()
. Adeptsense allows these functions to be
overridden.
function sense:get_class(symbol)
if condition then
return self.super.get_class(self, symbol) -- default behavior
else
-- different behavior
end
end
The default Adeptsense functions are called by using the self.super
reference.
Examples for Ruby
In Ruby, everything is an object – even numbers. Since numbers do not have
a type declaration, the get_class()
function should return
Integer
or Float
if the symbol is a number.
function sense:get_class(symbol)
local class = self.super.get_class(self, symbol)
if class then return class end
-- Integers and Floats.
if tonumber(symbol:match('^%d+%.?%d*$')) then
return symbol:find('%.') and 'Float' or 'Integer'
end
return nil
end
Note that there is no plus or minus prefix in the pattern. This is because
+
or -
characters are not a part of syntax.symbol_chars
so a symbol
will not contain either of them.
Like numbers, the syntax for constructing strings, arrays, hashes, and the
like should also be considered as symbols. [foo].
should show a completion
list with array instance methods:
function sense:get_symbol()
local line, p = buffer:get_cur_line()
if line:sub(1, p):match('%[.-%]%s*%.$') then return 'Array', '' end
return self.super.get_symbol(self)
end
The Ruby module Adeptsense has a more complicated version of this function that handles strings, hashes, symbols, and regexps as well. Please refer to it for more information.
Examples for Java
Autocomplete of Java import
statements is something nice to have. Ctags
produces a tag for packages so it is rather straightforward to build an
import completion list.
Since Adeptsense ignores any tags not mapped to classes
, functions
, or
fields
in ctags_kinds
, it passes an unknown tag to the
handle_ctag()
function. In this case, package (p
) tags
need to be handled.
function sense:handle_ctag(tag_name, file_name, ex_cmd, ext_fields)
if ext_fields:sub(1, 1) ~= 'p' then return end -- not a package
if not self.imports then self.imports = {} end
local import = self.imports
for package in tag_name:gmatch('[^.]+') do
if not import[package] then import[package] = {} end
import = import[package]
end
import[#import + 1] = file_name:match('([^/\\]-)%.java$')
end
Now that we have a list of import completions, it should be activated by the
normal trigger (.
), but only on a line that contains an import
statement.
The get_completions
function needs to be overridden to
use the import
table’s completions when necessary.
function sense:get_completions(symbol, ofields, ofunctions)
if not buffer:get_cur_line():find('^%s*import') then
return self.super.get_completions(self, symbol, ofields, ofunctions)
end
if symbol == 'import' then symbol = '' end -- top-level import
local c = {}
local import = self.imports or {}
for package in symbol:gmatch('[^%.]+') do
if not import[package] then return nil end
import = import[package]
end
for k, v in pairs(import) do
c[#c + 1] = type(v) == 'table' and k..'?1' or v..'?2'
end
table.sort(c)
return c
end
Note that '?1'
and '?2'
are appended to each completion entry. These tell
Adeptsense which icon to display in the autocompletion list. If no icon
information is given, no icon is displayed. 1
is for fields and 2
is for
functions. In this case, the icons are used only to distinguish between a
parent package and a package with no children since parents have an
additional list of completions.
Finally since an imports
table was created, it should be cleared when the
Adeptsense is cleared to free up memory. When this happens,
handle_clear()
is called.
function sense:handle_clear()
self.imports = {}
end
Child Language Adeptsenses
When Adeptsense completion is triggered, the Adeptsense for the language at
the current caret position is used, not necessarily the parent language’s
Adeptsense. For example, when editing CSS inside of an HTML file, the user
expects the CSS Adeptsense to be used. However, child language Adeptsenses
are not loaded automatically and must be loaded by the parent language
module. For the case of CSS in HTML, the HTML module’s init.lua
must
contain:
-- Load CSS Adeptsense.
if not _M.css then _M.css = require 'css' end
You will have to do something similar if you are writing an Adeptsense for a child lexer language.
Generating Lua Adeptsense
You can generate Lua Adeptsense for your own modules using the Lua language
module’s adeptsensedoc.lua
module with LuaDoc:
luadoc -d . --doclet _HOME/modules/lua/adeptsensedoc [module(s)]
where _HOME
is where Textadept is installed. The tags
and api
files are
output to the current directory and can be loaded via
load_ctags()
and api_files
respectively.
Module Fields
Not only does the Lua Adeptsense generator recognize functions and tables within modules, but it also recognizes module fields and their types with a certain syntax:
---
-- Module documentation.
-- @field field_name (type)
-- Field documentation.
or
---
-- Module documentation
-- * `field_name` (type)
-- Field documentation.
-- Multiple documentation lines must be indented.
Please note the latter -- * `field_name`
syntax can appear anywhere
inside a module, not just the module LuaDoc.
Fields
FIELDS
(string)
XPM image for Adeptsense fields.
FUNCTIONS
(string)
XPM image for Adeptsense functions.
always_show_globals
(bool)
Include globals in the list of completions offered.
Globals are classes, functions, and fields that do not belong to another
class. They are contained in completions['']
. The default value is
true
.
Functions
add_trigger
(sense, c, only_fields, only_functions)
Sets the trigger for autocompletion.
Parameters:
sense
: The Adeptsense returned byadeptsense.new()
.c
: The character(s) that triggers the autocompletion. You can have up to two characters.only_fields
: Iftrue
, this trigger only completes fields. The default value isfalse
.only_functions
: Iftrue
, this trigger only completes functions. The default value isfalse
.
Usage:
sense:add_trigger('.')
sense:add_trigger(':', false, true) -- only functions
sense:add_trigger('->')
clear
(sense)
Clears an Adeptsense. This is necessary for loading a new ctags file or completions from a different project.
Parameters:
sense
: The Adeptsense returned byadeptsense.new()
.
complete
(sense, only_fields, only_functions)
Shows an autocompletion list for the symbol behind the caret.
Parameters:
sense
: The Adeptsense returned byadeptsense.new()
.only_fields
: Iftrue
, returns list of only fields. The default value isfalse
.only_functions
: Iftrue
, returns list of only functions. The default value isfalse
.
Return:
true
on success orfalse
.
See also:
complete_symbol
()
Completes the symbol at the current position based on the current lexer’s
Adeptsense.
This should be called by key commands and menus instead of complete()
.
get_apidoc
(sense, symbol)
Returns a list of apidocs for the given symbol.
If there are multiple apidocs, the index of one to display is the value of
the pos
key in the returned list.
Parameters:
sense
: The Adeptsense returned byadeptsense.new()
.symbol
: The symbol to get apidocs for.
Return:
- apidoc_list or
nil
get_class
(sense, symbol)
Returns the class name for a given symbol.
If the symbol is sense.syntax.self
and a class definition using the
sense.syntax.class_definition
keyword is found, that class is returned.
Otherwise the buffer is searched backwards for a type declaration of the
symbol according to the patterns in sense.syntax.type_declarations
.
Parameters:
sense
: The Adeptsense returned byadeptsense.new()
.symbol
: The symbol to get the class of.
Return:
- class or
nil
See also:
get_completions
(sense, symbol, only_fields, only_functions)
Returns a list of completions for the given symbol.
Parameters:
sense
: The Adeptsense returned byadeptsense.new()
.symbol
: The symbol to get completions for.only_fields
: Iftrue
, returns list of only fields. The default value isfalse
.only_functions
: Iftrue
, returns list of only functions. The default value isfalse
.
Return:
- completion_list or
nil
get_symbol
(sense)
Returns a full symbol (if any) and current symbol part (if any) behind the
caret.
For example: buffer.cur
would return 'buffer'
and 'cur'
.
Parameters:
sense
: The Adeptsense returned byadeptsense.new()
.
Return:
- symbol or
''
- part or
''
goto_ctag
(sense, k, title)
Displays a filteredlist of all known symbols of the given kind (classes, functions, fields, etc.) and jumps to the source of the selected one.
Parameters:
sense
: The Adeptsense returned byadeptsense.new()
.k
: The ctag character kind (e.g.'f'
for a Lua function).title
: The title for the filteredlist dialog.
handle_clear
(sense)
Called when clearing an Adeptsense. This function should be replaced with your own if you have any persistant objects that need to be deleted.
Parameters:
sense
: The Adeptsense returned byadeptsense.new()
.
handle_ctag
(sense, tag_name, file_name, ex_cmd, ext_fields)
Called by load_ctags()
when a ctag kind is not recognized.
This method should be replaced with your own that is specific to the
language.
Parameters:
sense
: The Adeptsense returned byadeptsense.new()
.tag_name
: The tag name.file_name
: The name of the file the tag belongs to.ex_cmd
: Theex_cmd
returned by ctags.ext_fields
: Theext_fields
returned by ctags.
load_ctags
(sense, tag_file, nolocations)
Loads the given ctags file for autocompletion.
It is recommended to pass -n
to ctags in order to use line numbers instead
of text patterns to locate tags. This will greatly reduce memory usage for a
large number of symbols if nolocations
is not true
.
Parameters:
sense
: The Adeptsense returned byadeptsense.new()
.tag_file
: The path of the ctags file to load.nolocations
: Iftrue
, does not store the locations of the tags for use bygoto_ctag()
. The default value isfalse
.
new
(lang)
Creates a new Adeptsense for the given lexer language. Only one sense can exist per language.
Parameters:
lang
: The lexer language to create an Adeptsense for.
Usage:
local lua_sense = _M.textadept.adeptsense.new('lua')
Return:
- adeptsense
show_apidoc
(sense)
Shows a calltip with API documentation for the symbol behind the caret.
Parameters:
sense
: The Adeptsense returned byadeptsense.new()
.
Return:
true
on success orfalse
.
See also:
show_documentation
()
Shows API documentation for the symbol at the current position based on the
current lexer’s Adeptsense.
This should be called by key commands and menus instead of show_apidoc()
.
Tables
api_files
Contains a list of api files used by show_apidoc()
.
Each line in the api file contains a symbol name (not the full symbol)
followed by a space character and then the symbol’s documentation. Since
there may be many duplicate symbol names, it is recommended to put the full
symbol and arguments, if any, on the first line. (e.g. Class.function(arg1,
arg2, ...)
). This allows the correct documentation to be shown based on the
current context. In the documentation, newlines are represented with \n
. A
\
before \n
escapes the newline.
completions
Contains lists of possible completions for known symbols.
Each symbol key has a table value that contains a list of field completions
with a fields
key and a list of functions completions with a functions
key. This table is normally populated by load_ctags()
, but can also be set
by the user.
ctags_kinds
Contains a map of ctags kinds to Adeptsense kinds.
Recognized kinds are 'functions'
, 'fields'
, and 'classes'
. Classes are
quite simply containers for functions and fields so Lua modules would count
as classes. Any other kinds will be passed to handle_ctag()
for
user-defined handling.
Usage:
luasense.ctags_kinds = { 'f' = 'functions' }
csense.ctags_kinds = { 'm' = 'fields', 'f' = 'functions', c = 'classes', s = 'classes' }
javasense.ctags_kinds = { 'f' = 'fields', 'm' = 'functions', c = 'classes', i = 'classes' }
See also:
inherited_classes
Contains a map of classes and a list of their inherited classes.
locations
Contains the locations of known symbols.
This table is populated by load_ctags()
.
syntax
Contains syntax-specific values for the language.
Fields:
self
: The language’s syntax-equivalent ofself
. Default is'self'
.class_definition
: A Lua pattern representing the language’s class definition syntax. The first capture returned must be the class name. A second, optional capture contains the class' superclass (if any). If no completions are found for the class name, completions for the superclass are shown (if any). Completions will not be shown for both a class and superclass unless defined in a previously loaded ctags file. Also, multiple superclasses cannot be recognized by this pattern; use a ctags file instead. The default value is'class%s+([%w_]+)'
.word_chars
: A Lua pattern of characters allowed in a word. The default is'%w_'
.symbol_chars
: A Lua pattern of characters allowed in a symbol, including member operators. The pattern should be a character set. The default is'[%w_%.]'
.type_declarations
: A list of Lua patterns used for determining the class type of a symbol. The first capture returned must be the class name. Use%_
to match the symbol. The default value is'(%u[%w_%.]+)%s+%_'
.type_declarations_exclude
: A table of types to exclude, even if they match a type_declaration pattern. Each excluded type is a table key and has atrue
boolean value. For example,{ Foo = true }
excludes any type whose name isFoo
. The default value is{}
.type_assignments
: A map of Lua patterns to class types for variable assignments. This is typically used for dynamically typed languages. For example,sense.type_assignments['^"'] = 'string'
would recognize string assignments in Lua so thefoo
infoo = "bar"
would be recognized as typestring
. The class type value can contain pattern captures.
See also: