View CodeSense
CodeSense is a contextually-aware system for providing auto-completion for your Sugar. Adding CodeSense to your Sugar is easy provided that you have a well-written syntax with descriptive, granular syntax zones.
There are two components for CodeSense: libraries and providers.
CodeSense Libraries
XML files within your CodeSenseLibraries folder define the possible completions for your Sugar, grouped into sets. The library contains no logic about when various sets should be shown for auto-completion, but it does offer the ability to specify text snippets for use with specific sets of strings.
A typical CodeSenseLibraries XML file looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<codesense version="1.0">
<!-- The behavior defined in the root codesense element serves is used for all sets,
unless the set defines its own behavior -->
<behavior>
<!-- The append-static element defines a snippet that gets appended after the
completion string. -->
<append-static> is a fruit</append-static>
</behavior>
<set name="com.yourdomain.yoursugar.fruits">
<completion string="apple" />
<completion string="pear" />
<completion string="orange" />
</set>
<set name="com.yourdomain.yoursugar.vegetables">
<!-- A behavior defined in a set applies only to that set. -->
<behavior>
<append-static> is a vegetable</append-static>
</behavior>
<completion string="carrot" />
<completion string="cabbage" />
<completion string="potato" />
</set>
</codesense>
Sets and their completions should be straight-forward; the set has a name (which will be used in the CodeSense Provider to determine when to use these completions), and each completion has a string attribute that is the completion. By convention, the name for each set should be in reverse domain format.
Behaviors are used to append text snippets after the completion. In the simple example above, when the user uses CodeSense to insert “apple” what will actually be inserted is “apple is a fruit”. Behaviors are powerful because they allow you to conditionally insert things based on what comes after the completion, and also use text snippets to create tab stops.
The above example showcases how to use a static snippet to be appended to the completion string, but for more advanced behaviors, you can use additional settings and a dynamic snippet:
<behavior>
<!-- The <append-dynamic> element allows you to dynamically generate a snippet
to append after the actual completion. -->
<append-dynamic>
<!-- The matched suffix allows you to capture information about the text
after the completion. Specify a regular expression here. -->
<matched-suffix>(\s+)(is a vegetable)?</matched-suffix>
<!-- The transform-into element is a regex replacement expression, and can use
the captured strings from matched-suffix to generate a conditional snippet. -->
<transform-into>(?2:\1is a vegetable:\1is fantastic and )</transform-into>
</append-dynamic>
<!-- (optional) The confirm-characters element specifies additional characters that
trigger the confirmation of a completion. -->
<confirm characters=" " />
<!-- (optional) The confirm-partial element specifies characters that are contained
within the completion. When they're pressed, the completion will confirm up to
that character (including the character itself) and continue completing. This
is useful for things like CSS properties or PHP functions. -->
<confirm-partial characters="-" />
</behavior>
The example above may be confusing at first glance. The matched-suffix should be comprehensible (it’s a simple regex that with two capture groups that grabs whitespace immediately after the completion and then looks for the optional string “is a vegetable”). However, the transform-into expression uses a conditional that outputs different contents based on whether the second capture is empty or not. The syntax for regex conditionals in transform-into elements is:
(?x:a:b)
Which means: if the numbered capture group “x” has contents, insert “a”. Otherwise, insert “b”.
In the example, if the text “is a vegetable” does not fall after the name of the vegetable, then it will be inserted. If it does already exist, then “is fantastic and” will be inserted leaving the full completion reading something like “carrot is fantastic and is a vegetable”.
A real-life example is the CSS.sugar, which when auto-completing property names looks for a colon after the property, and if it isn’t there inserts it.
CodeSense Providers
Providers map contexts in a document to possible completions (reference based on the name of the sets you defined in CodeSenseLibraries). Providers should be defined in the CodeSense Providers subfolder of a sugar, as an XML file with the following structure:
<?xml version="1.0" encoding="UTF-8"?>
<codesense>
<provider>
<!-- The selector element specifies in what document contexts the provider
is active. The :capture pseudo-class will save the contents of the zone in a
variable. For the example below, the property-name is saved in the "name"
variable. -->
<selector>css > property-name:capture(name) + property-value</selector>
<!-- The completions element points to the CodeSense Library key that has to
be used. It can contain placeholders for captured pieces of text from
the selector. -->
<completions>com.macrabbit.css.property.${name}</completions>
<!-- The complete-match element defines a regular expression for the piece of
text that is completed by this provider. It is not necessary to append
an end-of-line symbol: this happens automatically. Note that even if this
provider is active in the current context, and the expression is matched,
it may still be ignored for a completion if other providers match a
different piece of the same text. -->
<complete-match>[a-zA-Z0-9-]*</complete-match>
<!-- (Optional) The require-suffix element defines a regular expression which
the text *after* the completion has to match. It is not necessary to
prepend a begin-of-line symbol: this happens automatically. -->
<require-suffix>[^:]|</require-suffix>
</provider>
<!-- Many providers can be defined in the same XML -->
<provider>
...
</provider>
</codesense>
The example above should be largely self-explanatory thanks to its comments, but one thing to keep in mind is that thanks to the :capture(VARIABLE) pseudo-class you do not have to directly refer to all sets in your CodeSenseLibraries directly. In the example above, both of these sets will be used depending on which CSS property the user is currently editing:
<set name="com.maccrabbit.css.property.display"> ... </set>
<set name="com.macrabbit.css.property.float"> ... </set>