|
|
The Braces Matching SPI allows modules providing editor support for documents
to create their own BracesMatcher
s that are tailored for the type of documents they
support. The module itself provides an infrastructure for
highlighting matching areas identified by a matcher and navigating between them.
The Editor Braces Matching module provides the BracesMatchingSPI for registering services capable of finding matching areas of text in a document. These areas can be pretty much anything and the rules for finding them are solely up to the implementors. A typical usecase and the main motivation for this SPI is to be able to implement language specific matchers for finding pairs of braces, highlighting them and allowing users to move a caret (navigate) from one to the other. The matching areas can also be for example XML, HTML or JSP tags.
The Braces Matching SPI is a replacement for the old SPI in
org.netbeans.editor.ExtSyntaxSupport
.
The following paragraphs give an overview of the infrastructure used for braces matching and explain the essential terminology and ideas behind it.
The whole task of finding matching areas can be split in two parts. First we need to determine if the caret is positioned over a character or an area that could possibly have another matching areas. And if so, then we need to find those areas. For the purpose of this SPI we call the area at the caret the original area and the other areas are simply the matching areas.
for(int i = 0; i < 10; i++) { System.out.println(i); }| | - the caret - original area - matching area
There is always only one original area, if there is any at all. On the other hand the matchers are free to report as many matching areas as they like. In most cases, however, the matchers will only report none or one matching are as well.
Normally when typing or moving a caret in the editor one of the characters is always considered to lay 'under' a caret. The decision if it is the character on the left hand side or right hand side of a caret depends on the shape of the caret. Although the visual appearance of a caret can vary, there are generaly only two shapes to consider. The I-beam caret and the block caret.
The I-beam caret normally looks like a vertical line placed on a position between characters in text. However, when typing it is generally accepted that the left hand side character is the one under the I-beam caret. The I-beam caret is used in Netbeans editor when editing text in the normal mode.
The block caret on the other hand may look like a little square or vertical line placed beneath or under a character. This type of caret makes it visually very clear what character is 'under' the caret. It is the right hand side character. In Netbeans editor the block caret is used for the overwrite mode.
When searching for the original area the search has always to start at the character 'under' a caret - the important character. In order not to tie the braces matching infrastructure to a praticular implementation of a caret we use the parameter caret bias to describe on which side of a caret the important character lies. Obviously the caret bias can only have two values - backward or forward - designating positions on the left or right hand side of the caret.
Because the search for the original area always starts with the important character the result of this search is directly influenced by the caret bias. The search could result in two different original areas depending on the value of the caret bias, even if the actual position of the caret was not changed.
The caret bias is not only used for searching for the original area, but it is also used when navigating (jumping) between matching areas. The navigation is always done in a way to make the matching area where we are navigating to to appear on the same side of the caret as is the side of the caret bias. This makes sure that the matching area will be recognized as the next original area. (Strictly speaking this also depends on the particular matcher implementation that was used for finding the areas.)
In general when looking for the original area we not only want to look right next to a caret, but we also want to search a little bit further. Looking a little bit further makes it easier for users to see the matching areas and navigate between them. They don't have to position the caret right at a brace in order to see the matching one or navigate to it. The scope of this search should, however, never leave the line with the caret and in some situations it might be restricted even more.
Although there are only two directions to look in - backward or forward - in a real situation looking in both directions gives more practical results. That is why the infrastructure supports the following two search directions.
The maximum distance, measured in characters from the position of a caret, that the search should be attempted in is called maximum lookahead. The infrastructure uses independent values for both search directions and calls them maximum backward lookahead and maximum forward lookahead. The lookahead is normally limited by the beginning and end of a line with the caret, but can be further limited by those two parameters. The absolute lookahead maximum in each direction enforced by the infrastructure is 256 characters
There is an important relationship between maximum lookaheads and the caret bias. The maximum lookaheads can never obscure the important character. In other words the maximum lookahead in the direction of the caret bias will not prevent the infrastructure from searching through the important character (ie. the character right next to the caret).
There is no API for controlling caret bias, search direction and maximum lookaheads
on the global level. It is expected that the module implementing the braces matching
infrastructure will expose its own GUI for user to control those parameters. This
GUI is yet to be designed. By default the infrastructure will set the parameters
to mimic the behavior of the existing (old) implementation of braces matching
done using org.netbeans.editor.ExtSyntaxSupport
.
On the other hand the parameters can be controlled on per-component basis by setting appropriate client properties. This is to allow interested modules such as the jVi plugin to overwrite global settings and implement their own searching policies. The following properties are supported.
nbeditor-bracesMatching-caretBias
- The caret bias to determine
the important character, available
values are backward
and forward
.
nbeditor-bracesMatching-searchDirection
- The direction to
search in for the original area. Available values are backward-preferred
and forward-preferred
.
nbeditor-bracesMatching-maxBackwardLookahead
- The maximum
distance to search in when searching backward. The allowed values are 0
- 256
.
nbeditor-bracesMatching-maxForwardLookahead
- The maximum
distance to search in when searching forward. The allowed values are 0
- 256
.
Modules implementing the SPI are shielded from most of the complexity of the braces matching infrastructure as it was described earlier. Thier job is as simple as being able to search in one direction through the requested number of characters. The SPI provides all the neccessary information to perform such search as well as mechanisms for registering implemented matchers.
The main part of the SPI is the
BracesMatcher
interface, which
needs to be implemented by anybody who wishes to provide a text matching
services for a particular type of documents. Implementations of the
BracesMatcher
interface can be plugged in to the system by
registering
BracesMatcherFactory
in the MIME lookup.
The braces matching
infrastructure will use registered factories to create matchers for highlighting
matching areas in documents. Each type of a document (ie. mime type or more
precisely each mime path) can have at most one BracesMatcher
registered.
If there is more different implementations registered for the same mime path
the infrastructure will only use the first one.
When asking a BracesMatcherFactory
to create an instance of
BracesMatcher
the infrastructure passes the factory an instance of
MatcherContext
,
which provides information about a document
and a position in that document, where the search should start.
The registration of BracesMatcher
s has to be done through an
instance of the BracesMatcherFactory
class. The factory should
be registered in MimeLookup
under the mime-type of documents, which
the BracesMatcher
should be used for. For example, if a module
wants to provide BracesMatcher
for text/x-something
documents
it should implement its own BracesMatcherFactory
(e.g.
org.some.module.BMFactory
class) and register it in MimeLookup
using its XML layer as it is shown on the example below.
<folder name="Editors"> <folder name="text"> <folder name="x-something"> <folder name="BracesMatchers"> <file name="org-some-module-BMFactory.instance" /> </folder> </folder> </folder> </folder>
The BMFactory
class will simply return a new instance of
the module's implementation of the BracesMatcher
interface from its
createMatcher
method.
The registration mechanism described earlier and the general concept of MIME lookup
allows to provide special BracesMatcher
s for embedded languages.
The braces matching infrastructure will always try to find the inner-most language at
a position in a document, where the search should start. It will then create
the BracesMatcher
according to the mime path of that inner-most
language.
As per the general inheritance rules defined by MIME lookup this may result
in using a BracesMatcher
that is registered for a top level
language (ie. mime type) different from the main language of the scanned
document. Here is an example of a java snippet in a JSP page, the caret's
position is indicated by the '|' pipe character. Assuming that there are only
two BracesMatcher
implementations registered - one for text/x-jsp
and the other one for text/x-java
, the infrastructure's attempt to find a matcher
for the text/x-jsp/text/x-java
mime path is going to result in
finding the matcher registered for text/x-java
.
<% for(int i = 0; i < 10; i++) { %> <th><%=i%>. column</th> <% }| %>;
Since the support for languages embedding comes with the new Lexer API, the
above example is naturally only going to work for document types that provide
a Lexer
implementation. This is the case for the most document types
supported in Netbeans 6. In case that a document does not have a Lexer
the infrastructure will fall back to using the document's main mime type ignoring
any other languages that may potentially be embedded in the document.
While finding the original brace is relatively simle task, because it's always done in a limited and quite small area around a caret, finding the matching brace can take much more time and in generall it can involve searching through a whole document. The search is normally done in response to a moving caret, which means that there can be a lot of search requests generated when a user moves the caret quickly over some text. It is not very important to show all the results as the caret moves, but it is important to show the last result when the caret stops moving.
In order not to impact responsiveness of the editor and its caret the braces matching
infrastructure performs all searches in a background thread outside of the Swing's
event thread. If needed a running task is cancelled and rescheduled with new
parameters reflecting the latest position of the caret. The BracesMatcher
s
implemented by modules are required to react accordingly when a search task
is cancelled. This is described in detail in the javadoc for BracesMatcher
.
The current design does not cover searching for areas in right-to-left documents. This usecase has not been even analyzed and may prove itself unsolvable withough major changes in the infrastructure and the SPI.
Question (arch-usecases): Describe the main use cases of the new API. Who will use it under what circumstances? What kind of code would typically need to be written to use the module? Answer:Although the SPI can generally by used for highlighting areas in a document that have something in common, the usecases below are demonstrated on a simple braces matching example. This example is used for its simplicity and clarity, but it could be substituted by more complex examples.
The usecases listed here were heavily inspired by comments in issues 95126 and 66037.
Probably the main reason why this SPI exists is to allow Netbeans editor to
highlight matching braces in a document. The highlighting itself is done by the
infrastructure and is not of a concern for BracesMatcher
implementors.
It should be possible to highlight independently
(ie. in a different color) both the original brace and the matching brace. It should also
be possible to highlight the original brace in a special color when its matching
brace can't be found. The colors obviously have to be customizable by users.
If the original brace is detected and its matching brace is found Netbeans editor needs to allow an easy navigation between those two positions (ie. jumping from the original brace to the matching one and back).
In general if there is more than one matching area users should be allowed to cycle
through all of them. Since there is only one editor action (shortcut) for navigating
between matching areas the cycling is only done in one direction (backward). In
order for cycling to work properly the BracesMatcher
implementation has to report
matching areas in a consistent way. That is the matching areas should always be
sorted by their position in a document, starting with the one at the lowest offset.
The users are likely to have different preferences for the way how braces matching works therefore its behavior should be customizable. We have listed below several possible scenarios that can be used simply by setting different values for the search parameters described before. They all differ in the way how the original area is detected.
The shortcuts for the parameters have the following meaning - MBL
... max backward lookahead, MFL
... max forward lookahead,
SD
... search direction, CB
... caret bias. The values
are B
... backward or backward preferred and
F
... forward or forward preferred. The question mark ?
means that the value of this parameter has no effect for the search.
MBL = 0, MFL = 0, SD = ?, CB = F
: Check only the right hand side
character of the caret. This is the default for Netbeans overwrite mode (ie. the
block shaped caret).
MBL = 1, MFL = 1, SD = ?, CB = B
: Check characters right next to
the caret on both its sides. This is the default for Netbeans normal mode (ie.
the I-beam shaped caret).
MBL = 0, MFL = 256, SD = F, CB = B
: Check the character on the
left hand side of the caret, otherwise search forward. This is the default
for jVi insert mode.
MBL = 0, MFL = 256, SD = F, CB = F
: Search forward from the caret.
This is the default for jVi command mode.
MBL = 256, MFL = 256, SD = F, CB = B
: Check the left hand side
character, otherwise search forward first and then backward. Suitable for
I-beam carets that detect the original area anywhere on the line with the
caret giving preferrence to the area positioned forward from the caret. The
preferrence for the backward positioned area can simply be achieved by changing
the search direction (ie. SD = B
).
MBL = 256, MFL = 256, SD = F, CB = F
: The same as
option E, but for the block carets.
The editor in Netbeans can operate in two modes - normal and overwrite. They both use different shape of a caret and thus need a different caret bias. The braces matching infrastructure should detect what mode the editor is in and change the parameters used for searching for the original area accordingly. The caret bias is the most important parameter, but other parameters may need to be changed too.
This feature should of course be only active for text components where the parameters have not been overwritten by some other module.
By default the two search scenarios used for the normal and overwrite modes in Netbeans editor are the scenario B for the normal mode and scenario A for the overwrite mode. In general scenarios from both editor modes should be customizable by users.
Question (arch-time): What are the time estimates of the work? Answer:The SPI will be part of the standard Netbeans build in Netbeans 6.0 timeframe.
Question (arch-quality): How will the quality of your code be tested and how are future regressions going to be prevented? Answer:There are unit tests covering the basic funtionality of the infrastructure needed for the SPI plugins to work. New tests will be continuously added as the module develops.
Question (arch-where): Where one can find sources for your module? Answer:
The sources for the module are in the Apache Git repositories or in the GitHub repositories.
None.
Question (dep-platform): On which platforms does your module run? Does it run in the same way on each? Answer:No platform dependencies.
Question (dep-jre): Which version of JRE do you need (1.2, 1.3, 1.4, etc.)? Answer:Any JRE suitable for Netbeans platform itself.
Question (dep-jrejdk): Do you require the JDK or is the JRE enough? Answer:No.
No additional files.
Question (deploy-nbm): Can you deploy an NBM via the Update Center? Answer:Yes.
Question (deploy-shared): Do you need to be installed in the shared location only, or in the user directory only, or can your module be installed anywhere? Answer:Anywhere.
Question (deploy-packages): Are packages of your module made inaccessible by not declaring them public? Answer:
Yes. Only the org.netbeans.spi.editor.bracesmatching
package is public.
Just normal module dependency.
Yes.
Question (compat-standards): Does the module implement or define any standards? Is the implementation exact or does it deviate somehow? Answer:No standards implemented.
Question (compat-version): Can your module coexist with earlier and future versions of itself? Can you correctly read all old settings? Will future versions be able to read your current settings? Can you read or politely ignore settings stored by a future version? Answer:Yes. At the moment there are no previous versions of this module.
Question (compat-deprecation): How the introduction of your project influences functionality provided by previous version of the product? Answer:
The Braces Matching SPI is a replacement for ExtSyntaxSupport.findMatchingBlocks
SPI. The module provides a legacy matcher that will use ExtSyntaxSupport
in case that a new matcher is not available for a given document type.
java.io.File
directly?
Answer:
No.
Question (resources-layer): Does your module provide own layer? Does it create any files or folders in it? What it is trying to communicate by that and with which components? Answer:
Yes. The module layer contains MimeLookup
registrations neccessary
for plugging in its highlighting layer.
No.
Question (resources-mask): Does your module mask/hide/override any resources provided by other modules in their layers? Answer:No.
Question (resources-preferences): Does your module uses preferences via Preferences API? Does your module use NbPreferences or or regular JDK Preferences ? Does it read, write or both ? Does it share preferences with other modules ? If so, then why ? Answer:No.
org.openide.util.Lookup
or any similar technology to find any components to communicate with? Which ones?
Answer:
Yes. The module uses MimeLookup
to find instances of BracesMatcherFactory
registered for particular mime types. The instances are then used for creating
BracesMatcher
s, which are used for finding the matching areas of
a document.
Yes. An implementation of HighlightsLayerFactory
is registered in
the root of MimeLookup
to plug in a special highlighting layer
that will show seach results.
No.
System.getProperty
) property?
On a similar note, is there something interesting that you
pass to java.util.logging.Logger
? Or do you observe
what others log?
Answer:
No.
Question (exec-component): Is execution of your code influenced by any (string) property of any of your components? Answer:
Yes. The infrastructure recognizes the following properties that can be set
on a JTextComponent
. The properties influence the way how a matcher
is used for finding the original area (ie. the area where the matching starts,
for example the first brace).
No.
Question (exec-classloader): Does your code create its own class loader(s)? Answer:No.
Question (exec-reflection): Does your code use Java Reflection to execute other code? Answer:No.
Question (exec-privateaccess): Are you aware of any other parts of the system calling some of your methods by reflection? Answer:No.
Question (exec-process): Do you execute an external process from your module? How do you ensure that the result is the same on different platforms? Do you parse output? Do you depend on result code? Answer:No.
Question (exec-introspection): Does your module use any kind of runtime type information (instanceof
,
work with java.lang.Class
, etc.)?
Answer:
No.
Question (exec-threading): What threading models, if any, does your module adhere to? How the project behaves with respect to threading? Answer:
The infrastructure uses a dedicated RequestProcessor
for running
tasks that search for the original and matching areas. The search is initiated
by moving a caret in a text component, which usually happens in Swing's event
thread. In order not to block this thread the infrastructure posts the task to
the RequestProcessor
. Any previously running task is cancelled,
because its results are not needed anymore. When a task finishes it highlights
its results (if there are any) in the text component using the Highlighting SPI.
Please read more in the org.netbeans.spi.editor.bracesmatching
package's javadoc.
No.
Question (security-grant): Does your code grant additional rights to some other code? Answer:No.
None.
Question (format-dnd): Which protocols (if any) does your code understand during Drag & Drop? Answer:None.
Question (format-clipboard): Which data flavors (if any) does your code read from or insert to the clipboard (by access to clipboard on means calling methods onjava.awt.datatransfer.Transferable
?
Answer:
None.
No.
Question (perf-exit): Does your module run any code on exit? Answer:No.
Question (perf-scale): Which external criteria influence the performance of your program (size of file in editor, number of files in menu, in source directory, etc.) and how well your code scales? Answer:
No scaling problems in the infrastructure itself. The performance of registered
BracesMatcher
s is likely to be affected by the size of a document
they search in.
No limits on the numbers of registered BracesMatcherFactory
s. The
performance of Netbeans editor can be affected by poorly designed implementations
of BracesMatcher
.
Roughly 100 bytes per an edited document.
Question (perf-wakeup): Does any piece of your code wake up periodically and do something even when the system is otherwise idle (no user interaction)? Answer:No.
Question (perf-progress): Does your module execute any long-running tasks? Answer:No.
Question (perf-huge_dialogs): Does your module contain any dialogs or wizards with a large number of GUI controls such as combo boxes, lists, trees, or text areas? Answer:No.
Question (perf-menus): Does your module use dynamically updated context menus, or context-sensitive actions with complicated and slow enablement logic? Answer:No.
Question (perf-spi): How the performance of the plugged in code will be enforced? Answer:
Long running old searches for matching areas are cancelled by new requests. No
other enforcement is in place. A poorly written plugin BraceMatcher
can negatively influence the overall performance of Netbeans editor.