TextFormation is simple rule system that can be used to implement typing completions and whitespace control.
Think matching typing "{", hitting return, and getting "}" with indenting.
- Text system agnostic
- Many pre-built filters for common language patterns
- Compatible with multiple cursors editing systems
- Flexible whitespace calculations
Warning
The main branch has undergone some major changes to support new capabilities. Not all indentation calculation features are available quite yet.
dependencies: [
.package(url: "https://github.com/ChimeHQ/TextFormation", branch: "main")
]
TextFormation's core model is a Filter
. Filters are typically set up once for a given language. From there, changes in the form of a TextMutation
are fed in. The filter examines a TextMutation
before it has been applied. Filters can be stateful, but if they return MutationOutput
, it means it has processed the mutation and no further action should be taken.
TextFormation is fully text system-agnostic and it models the text system using an abstraction based on types from Rearrange. This requires that you provide a TextSystemInterface
implementation. This type is responsible for supporting the querying and mutation capabilities filters need, along with an abstraction for how text positions and ranges are represented.
Careful filter ordering can produce some pretty powerful behaviors. Here's an example of a chain that produces typing completions that roughly matches what Xcode does for open/close curly braces:
// skip over closings
let skip = SkipFilter<MyTextSystem>(matching: "}")
// apply whitespace to the closing delimiter
let closeWhitespace = LineLeadingWhitespaceFilter<MyTextSystem>(string: "}")
// handle newlines inserted in between opening and closing
let newlinePair = NewlineWithinPairFilter<MyTextSystem>(open: "{", close: "}")
// auto-insert closings after an opening, with special-handling for newlines
let closePair = ClosePairFilter<MyTextSystem>(open: "{", close: "}")
// surround selection-replacements with the pair
let openPairReplacement = OpenPairReplacementFilter<MyTextSystem>(open: "{", close: "}")
// delete a matching close when adjacent and the opening is deleted
let deleteClose = DeleteCloseFilter<MyTextSystem>(open: "{", close: "}")
This kind of usage is probably going to be common, so all this behavior is wrapped up in a pre-made filter: StandardOpenPairFilter
.
let filter = StandardOpenPairFilter<MyTextSystem>(open: "{", close: "}")
Correctly indenting in the general case may require parsing. It also typically needs some understanding of the user's preferences. The included TextualIndenter
type has a pattern-based system that can perform sufficiently in many situations.
It includes basicPatterns
that work well for many languages. There are also some pre-defined patterns:
TextualIndenter.rubyPatterns
TextualIndenter.pythonPatterns
I would love to hear from you! Issues or pull requests work great. Both a [Matrix space][matrix] and [Discord][discord] are available for live help, but I have a strong bias towards answering in the form of documentation. You can also find me on the web.
I prefer collaboration, and would love to find ways to work together if you have a similar project.
I prefer indentation with tabs for improved accessibility. But, I'd rather you use the system you want and make a PR than hesitate because of whitespace.
By participating in this project you agree to abide by the Contributor Code of Conduct.