Skip to content

Commit d79b47a

Browse files
Refining textual context concept
1 parent 73e360f commit d79b47a

4 files changed

Lines changed: 33 additions & 58 deletions

File tree

Sources/TextFormation/PatternMatcher.swift

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,18 @@
11
import Rearrange
22

33
public struct TextualContext<TextRange: Bounded> {
4-
public struct Line {
5-
public let range: TextRange
6-
public let nonwhitespaceContent: String
7-
8-
public init(range: TextRange, nonwhitespaceContent: String) {
9-
self.range = range
10-
self.nonwhitespaceContent = nonwhitespaceContent
11-
}
12-
}
13-
14-
public let current: Line
15-
public let preceding: Line
4+
public let current: String
5+
public let preceding: String
6+
public let precedingLeadingWhitespaceRange: TextRange
167

178
public init(
18-
current: Line,
19-
preceding: Line
9+
current: String,
10+
preceding: String,
11+
precedingLeadingWhitespaceRange: TextRange
2012
) {
2113
self.current = current
2214
self.preceding = preceding
15+
self.precedingLeadingWhitespaceRange = precedingLeadingWhitespaceRange
2316
}
2417
}
2518

@@ -39,11 +32,11 @@ public struct PreceedingLineSuffixIndenter<TextRange: Bounded> {
3932

4033
extension PreceedingLineSuffixIndenter: PatternMatcher {
4134
public func action(for context: TextualContext<TextRange>) -> Indentation<TextRange>? {
42-
if context.preceding.nonwhitespaceContent.hasSuffix(suffix) == false {
35+
if context.preceding.hasSuffix(suffix) == false {
4336
return nil
4437
}
4538

46-
return .relativeIncrease(context.preceding.range)
39+
return .relativeIncrease(context.precedingLeadingWhitespaceRange)
4740
}
4841
}
4942

@@ -57,11 +50,11 @@ public struct PreceedingLinePrefixIndenter<TextRange: Bounded> {
5750

5851
extension PreceedingLinePrefixIndenter: PatternMatcher {
5952
public func action(for context: TextualContext<TextRange>) -> Indentation<TextRange>? {
60-
if context.preceding.nonwhitespaceContent.hasPrefix(prefix) == false {
53+
if context.preceding.hasPrefix(prefix) == false {
6154
return nil
6255
}
6356

64-
return .relativeIncrease(context.preceding.range)
57+
return .relativeIncrease(context.precedingLeadingWhitespaceRange)
6558
}
6659
}
6760

@@ -75,11 +68,11 @@ public struct CurrentLinePrefixOutdenter<TextRange: Bounded> {
7568

7669
extension CurrentLinePrefixOutdenter: PatternMatcher {
7770
public func action(for context: TextualContext<TextRange>) -> Indentation<TextRange>? {
78-
if context.current.nonwhitespaceContent.hasPrefix(prefix) == false {
71+
if context.current.hasPrefix(prefix) == false {
7972
return nil
8073
}
8174

82-
return .relativeDecrease(context.preceding.range)
75+
return .relativeDecrease(context.precedingLeadingWhitespaceRange)
8376
}
8477
}
8578

@@ -99,7 +92,7 @@ extension PreceedingLineConditionalMatcher: PatternMatcher {
9992
return nil
10093
}
10194

102-
if context.preceding.nonwhitespaceContent.hasPrefix(previousPrefix) {
95+
if context.preceding.hasPrefix(previousPrefix) {
10396
return nil
10497
}
10598

Sources/TextFormation/TextSystemInterface.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ extension TextSystemInterface {
2727

2828
return try substring(in: range)
2929
}
30+
31+
func insert(at position: Position, string: String) throws -> Output? {
32+
guard let range = textRange(from: position, to: position) else {
33+
return nil
34+
}
35+
36+
return try applyMutation(range, string: string)
37+
}
3038
}
3139

3240
#if canImport(Foundation)

Sources/TextFormation/TextualIndenter.swift

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,19 @@ import Rearrange
33
#if compiler(>=6.1)
44
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
55
public struct TextualIndenter<TextRange: Bounded> where TextRange: Hashable {
6-
public typealias ContextProvider = (Position) throws -> TextualContext<TextRange>
76
public typealias IndentationResult = Result<Indentation<TextRange>, IndentationError>
87
public typealias Matcher = any PatternMatcher<TextRange>
98
public typealias Position = TextRange.Bound
109

1110
public let patterns: [Matcher]
12-
public let provider: ContextProvider
1311

1412
public init(
15-
patterns: [Matcher],
16-
provider: @escaping ContextProvider
13+
patterns: [Matcher] = Self.basicPatterns
1714
) {
1815
self.patterns = patterns
19-
self.provider = provider
2016
}
2117

22-
public func computeIndentation(at position: Position) throws -> Indentation<TextRange> {
23-
let context = try provider(position)
24-
18+
public func computeIndentation(at position: Position, context: TextualContext<TextRange>) throws -> Indentation<TextRange> {
2519
// unique them, just in case two matches produce the same identical action
2620
let potentialIndents = Set(patterns.compactMap({ $0.action(for: context) }))
2721

@@ -31,7 +25,7 @@ public struct TextualIndenter<TextRange: Bounded> where TextRange: Hashable {
3125

3226
// we have no matches, or conflicting matches
3327

34-
return .equal(context.preceding.range)
28+
return .equal(context.precedingLeadingWhitespaceRange)
3529
}
3630

3731
// func computeTextualContent(at position: Position) throws -> TextualContext {

Tests/TextFormationTests/TextualIndenterTests.swift

Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,22 @@ import Testing
33

44
import TextFormation
55

6-
#if compiler(>=6.1) && (os(macOS) || os(iOS) || os(tvOS) || os(visionOS))
7-
extension TextualContext where TextRange == NSRange {
8-
init<R: RangeExpression>(
9-
preceding: R,
10-
_ precedingContent: String,
11-
current: R,
12-
_ currentContent: String
13-
) where R.Bound == TextRange.Bound {
14-
self.init(
15-
current: Self.Line(range: NSRange(current), nonwhitespaceContent: currentContent),
16-
preceding: Self.Line(range: NSRange(preceding), nonwhitespaceContent: currentContent)
17-
)
18-
}
19-
}
20-
6+
#if compiler(>=6.1)
217
struct TextualIndenterTests {
228
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
239
@Test func emptyString() throws {
24-
let indenter = TextualIndenter<NSRange>(patterns: TextualIndenter.basicPatterns, provider: { pos in
25-
try #require(pos == 0)
10+
let indenter = TextualIndenter<NSRange>(patterns: TextualIndenter.basicPatterns)
11+
let context = TextualContext(current: "", preceding: "", precedingLeadingWhitespaceRange: NSRange.zero)
2612

27-
throw IndentationError.unableToComputeReferenceRange
28-
})
29-
30-
#expect(throws: (any Error).self) { try indenter.computeIndentation(at: 0) }
13+
#expect(try indenter.computeIndentation(at: 0, context: context) == .equal(NSRange(0..<0)))
3114
}
3215

3316
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
3417
@Test func propagatesPreviousLineIndentation() throws {
35-
let indenter = TextualIndenter<NSRange>(patterns: TextualIndenter.basicPatterns, provider: { pos in
36-
try #require(pos == 0)
37-
38-
return TextualContext(preceding: 0..<1, "\t", current: 2..<2, "")
39-
})
40-
41-
#expect(try indenter.computeIndentation(at: 0) == .equal(NSRange(0..<1)))
18+
let indenter = TextualIndenter<NSRange>(patterns: TextualIndenter.basicPatterns)
19+
let context = TextualContext(current: "", preceding: "\t", precedingLeadingWhitespaceRange: NSRange(0..<1))
20+
21+
#expect(try indenter.computeIndentation(at: 0, context: context) == .equal(NSRange(0..<1)))
4222
}
4323
}
4424

0 commit comments

Comments
 (0)