TextFormation is simple rule system that can be used to implement typing completions and whitespace control. Think matching “}” with “{“ and indenting.
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 TextMutationbefore it has been applied. A filter can have three possible result actions.
none indicates that the mutation should be passed to the next filter in the list
stop means no further filtering should be applied
discard is just like stop, but also means the TextMutation shouldn’t be applied
Filters do not necessarily change the text. You must respect the filter action, ensuring that the mutation is actually applied in the cases of none and stop. The design of the filters tries hard to allow mutations to occur to help maintain the expected selection and undo behaviors of a standard text view.
Usage
Careful use of filter nesting, possibly CompositeFilter, and these actions 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:
// simple indentation algorithm that uses minimal text context
let indenter = TextualIndenter()
// delete any trailing whitespace, and use our indenter to compute
// any needed leading whitespace using a four-space unit
let providers = WhitespaceProviders(leadingWhitespace: indenter.substitionProvider(indentationUnit: " ", width: 4),
trailingWhitespace: { _, _ in return "" })
// skip over closings
let skip = SkipFilter(matching: "}")
// apply whitespace to our close
let closeWhitespace = LineLeadingWhitespaceFilter(string: "}", leadingWhitespaceProvider: providers.leadingWhitespace)
// handle newlines inserted in between opening and closing
let newlinePair = NewlineWithinPairFilter(open: "{", close: "}", whitespaceProviders: providers)
// auto-insert closings after an opening, with special-handling for newlines
let closePair = ClosePairFilter(open: "{", close: "}", whitespaceProviders: providers)
// surround selection-replacements with the pair
let openPairReplacement = OpenPairReplacementFilter(open: "{", close: "}")
// delete a matching close when adjacent and the opening is deleted
let deleteClose = DeleteCloseFilter(open: "{", close: "}")
let filters: [Filter] = [skip, closeWhitespace, openPairReplacement, newlinePair, closePair, deleteClose]
// treat a "stop" as only applying to our local chain
let filter = CompositeFilter(filters: filters, handler: { (_, action) in
switch action {
case .stop, .none:
return .none
case .discard:
return .discard
}
})
// use filter
This kind of usage is probably going to be common, so all this behavior is wrapped up in a pre-made filter: StandardOpenPairFilter.
let indenter = TextualIndenter()
let providers = WhitespaceProviders(leadingWhitespace: indenter.substitionProvider(indentationUnit: " ", width: 4),
trailingWhitespace: { _, _ in return "" })
let filter = StandardOpenPairFilter(open: "{", close: "}", whitespaceProviders: providers)
There’s also a nice little type called TextViewFilterApplier that can make it easier to connect filters up to an NSTextView or UITextView. All you need to do use one of the stand-in delegate methods:
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 also includes pre-defined patterns for some languages:
TextFormation
TextFormation is simple rule system that can be used to implement typing completions and whitespace control. Think matching “}” with “{“ and indenting.
Integration
Swift Package Manager:
Concept
TextFormation’s core model is a
Filter
. Filters are typically set up once for a given language. From there, changes in the form of aTextMutation
are fed in. The filter examines aTextMutation
before it has been applied. A filter can have three possible result actions.none
indicates that the mutation should be passed to the next filter in the liststop
means no further filtering should be applieddiscard
is just like stop, but also means theTextMutation
shouldn’t be appliedFilters do not necessarily change the text. You must respect the filter action, ensuring that the mutation is actually applied in the cases of
none
andstop
. The design of the filters tries hard to allow mutations to occur to help maintain the expected selection and undo behaviors of a standard text view.Usage
Careful use of filter nesting, possibly
CompositeFilter
, and these actions 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:This kind of usage is probably going to be common, so all this behavior is wrapped up in a pre-made filter:
StandardOpenPairFilter
.There’s also a nice little type called
TextViewFilterApplier
that can make it easier to connect filters up to anNSTextView
orUITextView
. All you need to do use one of the stand-in delegate methods:Indenting
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 also includes pre-defined patterns for some languages:
Suggestions or Feedback
We’d love to hear from you! Get in touch via an issue or pull request.
Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.