Skip to content

Commit 68062ef

Browse files
committed
Implement word-extension selection, this implements #39
1 parent bf47695 commit 68062ef

1 file changed

Lines changed: 92 additions & 7 deletions

File tree

Sources/SwiftTerm/SelectionService.swift

Lines changed: 92 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ class SelectionService: CustomDebugStringConvertible {
9191
public func startSelection (row: Int, col: Int)
9292
{
9393
setSoftStart(row: row, col: col)
94+
selectionMode = .character
9495
setActiveAndNotify()
9596
}
9697

@@ -118,6 +119,7 @@ class SelectionService: CustomDebugStringConvertible {
118119
{
119120
end = start
120121
selectingRows = false
122+
selectionMode = .character
121123
setActiveAndNotify()
122124
}
123125

@@ -178,23 +180,31 @@ class SelectionService: CustomDebugStringConvertible {
178180
* The bufferPosition is buffer-relative
179181
*/
180182
public func shiftExtend (bufferPosition newEnd: Position) {
183+
var adjustedNewEnd = newEnd
184+
185+
// If we're in word selection mode, extend to word boundaries
186+
if selectionMode == .word {
187+
let direction = Position.compare(newEnd, start) == .before ? -1 : 1
188+
adjustedNewEnd = extendToWordBoundary(position: newEnd, in: terminal.buffer, direction: direction)
189+
}
190+
181191
var shouldSwapStart = false
182192
if Position.compare (start, end) == .before {
183193
// start is before end, is the new end before Start
184-
if Position.compare (newEnd, start) == .before {
194+
if Position.compare (adjustedNewEnd, start) == .before {
185195
// yes, swap Start and End
186196
shouldSwapStart = true
187197
}
188198
} else if Position.compare (start, end) == .after {
189-
if Position.compare (newEnd, start) == .after {
199+
if Position.compare (adjustedNewEnd, start) == .after {
190200
// yes, swap Start and End
191201
shouldSwapStart = true
192202
}
193203
}
194204
if (shouldSwapStart) {
195205
start = end
196206
}
197-
end = newEnd
207+
end = adjustedNewEnd
198208

199209
setActiveAndNotify()
200210
}
@@ -221,12 +231,21 @@ class SelectionService: CustomDebugStringConvertible {
221231
guard let pivot = pivot else {
222232
return
223233
}
224-
switch Position.compare (bufferPosition, pivot) {
234+
235+
var adjustedPosition = bufferPosition
236+
237+
// If we're in word selection mode, extend to word boundaries
238+
if selectionMode == .word {
239+
let direction = Position.compare(bufferPosition, pivot) == .before ? -1 : 1
240+
adjustedPosition = extendToWordBoundary(position: bufferPosition, in: terminal.buffer, direction: direction)
241+
}
242+
243+
switch Position.compare (adjustedPosition, pivot) {
225244
case .after:
226245
start = pivot
227-
end = bufferPosition
246+
end = adjustedPosition
228247
case .before:
229-
start = bufferPosition
248+
start = adjustedPosition
230249
end = pivot
231250
case .equal:
232251
start = pivot
@@ -250,7 +269,15 @@ class SelectionService: CustomDebugStringConvertible {
250269
* The position is in buffer coordinates
251270
*/
252271
public func dragExtend (bufferPosition: Position) {
253-
end = bufferPosition
272+
var adjustedEnd = bufferPosition
273+
274+
// If we're in word selection mode, extend to word boundaries
275+
if selectionMode == .word {
276+
let direction = Position.compare(bufferPosition, start) == .before ? -1 : 1
277+
adjustedEnd = extendToWordBoundary(position: bufferPosition, in: terminal.buffer, direction: direction)
278+
}
279+
280+
end = adjustedEnd
254281
setActiveAndNotify()
255282
}
256283

@@ -266,6 +293,15 @@ class SelectionService: CustomDebugStringConvertible {
266293

267294
public var selectingRows: Bool = false
268295

296+
/// Tracks the current selection mode to maintain consistency during extension
297+
public enum SelectionMode {
298+
case character
299+
case word
300+
case row
301+
}
302+
303+
public var selectionMode: SelectionMode = .character
304+
269305
/**
270306
* Selectss the specified row and triggers the selection
271307
*/
@@ -274,6 +310,7 @@ class SelectionService: CustomDebugStringConvertible {
274310
start = Position(col: 0, row: row)
275311
end = Position(col: terminal.cols-1, row: row)
276312
selectingRows = true
313+
selectionMode = .row
277314
setActiveAndNotify()
278315
}
279316

@@ -392,6 +429,52 @@ class SelectionService: CustomDebugStringConvertible {
392429
}
393430

394431
let nullChar = Character(UnicodeScalar(0))
432+
433+
/**
434+
* Extends a position to the nearest word boundary based on the character at that position
435+
*/
436+
func extendToWordBoundary(position: Position, in buffer: Buffer, direction: Int) -> Position {
437+
let ch = buffer.getChar(atBufferRelative: position).getCharacter()
438+
var includeFunc: (Character) -> Bool
439+
440+
switch ch {
441+
case Character(UnicodeScalar(0)):
442+
includeFunc = { ch in ch == Character(UnicodeScalar(0)) }
443+
case " ":
444+
includeFunc = { ch in ch == " " }
445+
case let ch where ch.isLetter || ch.isNumber:
446+
includeFunc = { ch in ch.isLetter || ch.isNumber || ch == "." || ch == "_" || ch == "-" }
447+
default:
448+
return position
449+
}
450+
451+
var result = position
452+
if direction < 0 {
453+
// Extend backward
454+
var col = position.col
455+
while col >= 0 {
456+
let testCh = buffer.getChar(atBufferRelative: Position(col: col, row: position.row)).getCharacter()
457+
if !includeFunc(testCh) {
458+
break
459+
}
460+
result.col = col
461+
col -= 1
462+
}
463+
} else {
464+
// Extend forward
465+
var col = position.col
466+
while col < terminal.cols {
467+
let testCh = buffer.getChar(atBufferRelative: Position(col: col, row: position.row)).getCharacter()
468+
if !includeFunc(testCh) {
469+
break
470+
}
471+
col += 1
472+
result.col = col
473+
}
474+
}
475+
476+
return result
477+
}
395478
/**
396479
* Implements the behavior to select the word at the specified position or an expression
397480
* which is a balanced set parenthesis, braces or brackets
@@ -428,6 +511,7 @@ class SelectionService: CustomDebugStringConvertible {
428511
start = position
429512
end = position
430513
}
514+
selectionMode = .word
431515
setActiveAndNotify()
432516
}
433517

@@ -438,6 +522,7 @@ class SelectionService: CustomDebugStringConvertible {
438522
{
439523
if active {
440524
active = false
525+
selectionMode = .character
441526
}
442527
}
443528

0 commit comments

Comments
 (0)