@@ -22,6 +22,10 @@ class AutoCompleteCoordinator: TextViewCoordinator {
2222 private var suggestionController : SuggestionController ?
2323 /// The current TreeSitter node that the main cursor is at
2424 private var currentNode : SwiftTreeSitter . Node ?
25+ /// The current filter text based on partial token input
26+ private var currentFilterText : String = " "
27+ /// Stores the unfiltered completion items
28+ private var completionItems : [ CompletionItem ] = [ ]
2529
2630 init ( _ file: CEWorkspaceFile ) {
2731 self . file = file
@@ -56,42 +60,49 @@ class AutoCompleteCoordinator: TextViewCoordinator {
5660 return
5761 }
5862
63+ var tokenSubstringCount = 0
64+ currentFilterText = " "
5965 do {
6066 if let token = try textViewController? . treeSitterClient? . nodesAt ( range: cursorPos. range) . first {
6167 if tokenIsActionable ( token. node) {
6268 currentNode = token. node
63- }
6469
65- // Get the string from the start of the token to the location of the cursor
66- if cursorPos. range. location > token. node. range. location {
67- let selectedRange = NSRange (
68- location: token. node. range. location,
69- length: cursorPos. range. location - token. node. range. location
70- )
71- let tokenSubstring = textView. textStorage? . substring ( from: selectedRange)
72- // print("Token word: \(String(describing: tokenSubstring))")
70+ // Get the string from the start of the token to the location of the cursor
71+ if cursorPos. range. location > token. node. range. location {
72+ let selectedRange = NSRange (
73+ location: token. node. range. location,
74+ length: cursorPos. range. location - token. node. range. location
75+ )
76+ if let tokenSubstring = textView. textStorage? . substring ( from: selectedRange) {
77+ currentFilterText = tokenSubstring
78+ tokenSubstringCount = tokenSubstring. count
79+ }
80+ }
7381 }
7482 }
7583 } catch {
7684 print ( " Error getting TreeSitter node: \( error) " )
7785 }
7886
7987 Task {
80- let textPosition = Position ( line: cursorPos. line - 1 , character: cursorPos. column - 1 )
88+ var textPosition = Position ( line: cursorPos. line - 1 , character: cursorPos. column - 1 )
8189 // If we are asking for completions in the middle of a token, then
8290 // query the language server for completion items at the start of the token
83- // if let currentNode = currentNode, tokenIsActionable(currentNode) {
84- // if let newPos = textView.lspRangeFrom(nsRange: currentNode.range) {
85- // _currentNode
86- // }
87- // }
88- print ( " Getting completion items at token position: \( textPosition) " )
89-
90- let completionItems = await fetchCompletions ( position: textPosition)
91- suggestionController. items = completionItems
91+ if currentNode != nil {
92+ textPosition = Position (
93+ line: cursorPos. line - 1 ,
94+ character: cursorPos. column - tokenSubstringCount - 1
95+ )
96+ }
97+ completionItems = await fetchCompletions ( position: textPosition)
98+ suggestionController. items = filterCompletionItems ( completionItems)
9299
93100 let cursorRect = textView. firstRect ( forCharacterRange: cursorPos. range, actualRange: nil )
94- suggestionController. constrainWindowToScreenEdges ( cursorRect: cursorRect)
101+ suggestionController. constrainWindowToScreenEdges (
102+ cursorRect: cursorRect,
103+ // TODO: CALCULATE PADDING BASED ON FONT SIZE, THIS IS JUST TEMP
104+ horizontalOffset: 13 + 16.5 + CGFloat( tokenSubstringCount) * 7.4
105+ )
95106 suggestionController. showWindow ( attachedTo: window)
96107 }
97108 }
@@ -129,6 +140,26 @@ class AutoCompleteCoordinator: TextViewCoordinator {
129140 }
130141 }
131142
143+ /// Filters completion items based on the current partial token input
144+ private func filterCompletionItems( _ items: [ CompletionItem ] ) -> [ CompletionItem ] {
145+ guard !currentFilterText. isEmpty else {
146+ return items
147+ }
148+
149+ return items. filter { item in
150+ let insertText = LSPCompletionItemsUtil . getInsertText ( from: item)
151+ let label = item. label. lowercased ( )
152+ let filterText = currentFilterText. lowercased ( )
153+ if insertText. lowercased ( ) . hasPrefix ( filterText) {
154+ return true
155+ }
156+ if label. hasPrefix ( filterText) {
157+ return true
158+ }
159+ return false
160+ }
161+ }
162+
132163 /// Determines if a TreeSitter node is a type where we can build featues off of. This helps filter out
133164 /// nodes that represent blank spaces or other information that is not useful.
134165 private func tokenIsActionable( _ node: SwiftTreeSitter . Node ) -> Bool {
@@ -162,20 +193,8 @@ extension AutoCompleteCoordinator: SuggestionControllerDelegate {
162193 return
163194 }
164195
165- // Get the token the cursor is currently on. Here we will check if we want to
166- // replace the current token we are on or just add text onto it.
167- var replacementRange = cursorPos. range
168- do {
169- if let token = try textViewController? . treeSitterClient? . nodesAt ( range: cursorPos. range) . first {
170- if tokenIsActionable ( token. node) {
171- replacementRange = token. node. range
172- }
173- }
174- } catch {
175- print ( " Error getting TreeSitter node: \( error) " )
176- }
177-
178196 // Make the updates
197+ let replacementRange = currentNode? . range ?? cursorPos. range
179198 let insertText = LSPCompletionItemsUtil . getInsertText ( from: item)
180199 textView. undoManager? . beginUndoGrouping ( )
181200 textView. replaceString ( in: replacementRange, with: insertText)
@@ -188,9 +207,7 @@ extension AutoCompleteCoordinator: SuggestionControllerDelegate {
188207 self . onCompletion ( )
189208 }
190209
191- func onCompletion( ) {
192-
193- }
210+ func onCompletion( ) { }
194211
195212 func onCursorMove( ) {
196213 guard let cursorPos = textViewController? . cursorPositions. first,
@@ -206,37 +223,40 @@ extension AutoCompleteCoordinator: SuggestionControllerDelegate {
206223 return
207224 }
208225
209- do {
210- if let token = try textViewController? . treeSitterClient? . nodesAt ( range: cursorPos. range) . first {
211- // Moving to a new token requires a new call to the language server
212- // We extend the range so that the `contains` can include the end value of
213- // the token, since its check is exclusive.
214- let adjustedRange = currentNode. range. shifted ( endBy: 1 )
215- if let adjustedRange = adjustedRange,
216- !adjustedRange. contains ( cursorPos. range. location) {
217- suggestionController. close ( )
218- return
219- }
226+ // Moving to a new token requires a new call to the language server
227+ // We extend the range so that the `contains` can include the end value of
228+ // the token, since its check is exclusive.
229+ let adjustedRange = currentNode. range. shifted ( endBy: 1 )
230+ if let adjustedRange = adjustedRange,
231+ !adjustedRange. contains ( cursorPos. range. location) {
232+ suggestionController. close ( )
233+ return
234+ }
220235
221- // 1. Print cursor position and token range
222- print ( " Current node: \( String ( describing: currentNode) ) " )
223- print ( " Cursor pos: \( cursorPos. range. location) : Line: \( cursorPos. line) Col: \( cursorPos. column) " )
236+ // Check if cursor is at the start of the token
237+ if cursorPos. range. location == currentNode. range. location {
238+ currentFilterText = " "
239+ suggestionController. items = completionItems
240+ return
241+ }
224242
225- // Get the token string from the start of the token to the location of the cursor
226- // print("Token contains cursor position: \(String(describing: currentNode.range.contains(cursorPos.range.location)))")
227- // print("Token info: \(String(describing: tokenSubstring)) Range: \(String(describing: adjustedRange))")
228- // print("Current cursor position: \(cursorPos.range)")
243+ // Filter through the completion items based on how far the cursor is in the token
244+ if cursorPos. range. location > currentNode. range. location {
245+ let selectedRange = NSRange (
246+ location: currentNode. range. location,
247+ length: cursorPos. range. location - currentNode. range. location
248+ )
249+ if let tokenSubstring = textView. textStorage? . substring ( from: selectedRange) {
250+ currentFilterText = tokenSubstring
251+ suggestionController. items = filterCompletionItems ( completionItems)
229252 }
230- } catch {
231- print ( " Error getting TreeSitter node: \( error) " )
232253 }
233254 }
234255
235- func onItemSelect( item: LanguageServerProtocol . CompletionItem ) {
236-
237- }
256+ func onItemSelect( item: LanguageServerProtocol . CompletionItem ) { }
238257
239258 func onClose( ) {
240259 currentNode = nil
260+ currentFilterText = " "
241261 }
242262}
0 commit comments