Contents

_M.textadept.adeptsense

Code autocompletion and documentation support for programming languages.

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 and display their documentation 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,and fields are class or instance properties. For example a “Cat” class may have a “color” field and a “meow” 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 = _M.textadept.adeptsense.FUNCTION,
  F = _M.textadept.adeptsense.FIELD,
  m = _M.textadept.adeptsense.CLASS,
  t = _M.textadept.adeptsense.FIELD,
}

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 immediately. 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 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


CLASS (string)

Ctags kind for Adeptsense classes.


FIELD (string)

Ctags kind for Adeptsense fields.


FIELDS (string)

XPM image for Adeptsense fields.


FUNCTION (string)

Ctags kind for Adeptsense functions.


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 sense.completions['']. The default value is true.


Functions


add_trigger(sense, c, only_fields, only_functions)

Sets the trigger character(s) c for autocompletion. If only_fields is true, the trigger only completes fields. If only_functions is true, the trigger only completes functions.

Parameters:

Usage:


clear(sense)

Clears the Adeptsense for loading new Ctags or project files.

Parameters:


complete(sense, only_fields, only_functions)

Shows an autocompletion list of functions (unless only_fields is true) and fields (unless only_funcs is true) for the symbol behind the caret, returning true on success.

Parameters:

Return:

See also:


get_apidoc(sense, symbol)

Returns a list of apidocs for symbol name. The list contains a pos key with the index of the apidoc to show.

Parameters:

Return:


get_class(sense, symbol)

Returns the class name for symbol name. If symbol is sense.syntax.self and inside a class definition matching sense.syntax.class_definition, that class is returned. Otherwise the buffer is searched backwards for a type declaration of symbol according to the patterns in sense.syntax.type_declarations or a type assignment of symbol according to sense.syntax.type_assignments.

Parameters:

Return:

See also:


get_completions(sense, symbol, only_fields, only_functions)

Returns a list of function (unless only_fields is true) and field (unless only_funcs is true) completions for symbol name.

Parameters:

Return:


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'. Returns empty strings instead of nil.

Parameters:

Return:


goto_ctag(sense, kind, title)

Prompts the user to select a symbol to jump to from a list of all known symbols of kind kind (classes, functions, fields, etc.) shown in a filtered list dialog whose title text is title.

Parameters:


handle_clear(sense)

Called when clearing the Adeptsense. This function should be replaced with your own if you have any persistant objects that need to be deleted.

Parameters:


handle_ctag(sense, tag_name, file_name, ex_cmd, ext_fields)

Called by load_ctags() when a Ctag kind is not recognized. The parameters are extracted from Ctags' tag format. This method should be replaced with your own that is specific to the language.

Parameters:


load_ctags(sense, tag_file, nolocations)

Loads the Ctags file tag_file for autocompletions. If nolocations is true, sense:goto_ctag() cannot be used with this set of tags. 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 false.

Parameters:


new(lang)

Creates and returns a new Adeptsense for lang name. Only one sense can exist per language.

Parameters:

Usage:

Return:


show_apidoc(sense)

Shows a call tip with API documentation for the symbol behind the caret. If documentation is already being shown, cycles through multiple definitions.

Parameters:

Return:

See also:


Tables


api_files

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

A list containing 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

A map of Ctags kinds to Adeptsense kinds. Recognized kinds are FUNCTION, FIELD, and CLASS. 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:

See also:


inherited_classes

A map of classes and a list of their inherited classes, normally populated by load_ctags().


locations

A list of the locations of known symbols, normally populated by load_ctags().


syntax

Map of language-specific syntax settings.

Fields:

See also: