Skip to content

Commit 1fd698a

Browse files
catrielmullerCopilotChris Hassonhassoncs
authored
Inline Assist - Parse chunks individually (#2012)
* feat: wip improve inline assistant performance * feat: improve search * Update src/services/ghost/GhostStrategy.ts Co-authored-by: Copilot <[email protected]> * Update src/services/ghost/GhostStrategy.ts Co-authored-by: Copilot <[email protected]> * Update src/services/ghost/GhostProvider.ts Co-authored-by: Copilot <[email protected]> * refactor: add changeset * refactor: improve prompt * refactor: improve prompt for non llama models * refactor: improve cursor position * refactor: select the closest cursor suggestion * refactor: divide groups * refactor: inline assist sequential suggestion * refactor: fix tests * refactor: fix display special characters * refactor: fix display special characters * refactor: improve visual of the popups * Update .changeset/beige-sheep-post.md Co-authored-by: Copilot <[email protected]> * refactor: wip stream parser * refactor: update refactored tests * refactor: update test cases responses to xml format * refactor: improve prompt to focus on the cursor and optimize the response for small chunks * feat: Default codestar model for Inline Assitant * feat: Improve Inline assist prompt * feat: Improve malformed xml response * feat: Improve changes out of the document * feat: Improve search with cursor * feat: Improve suggestion order on the response * refactor: add missing translations * refactor: update constructor to accept options object * refactor: adjust ghost decoration content join The `GhostDecorations` class now joins content with a space to ensure the first character doesn't get stripped when the CSS content is processed * refactor: adjust provider sorting logic based on PR comment * fix(ghost): Sanitize malformed CDATA sections in streaming parser Addresses a critical bug identified from user logs where malformed CDATA sections in the Ghost streaming parser caused parsing failures. The fix specifically targets and replaces `</![CDATA[` with `]]>` to correctly close CDATA blocks, ensuring proper XML sanitization and successful processing of streaming data. This change includes a new test case that precisely reproduces the reported malformed CDATA issue, verifying the fix. * refactor(ghost): Rename cost tracking variables for clarity Renamed `totalCost`, `totalInputTokens`, `totalOutputTokens`, `totalCacheWriteTokens`, and `totalCacheReadTokens` to `cost`, `inputTokens`, `outputTokens`, `cacheWriteTokens`, and `cacheReadTokens` respectively. This improves readability and conciseness of the variable names. * refactor(ghost): Extract CURSOR_MARKER to ghostConstants Moves the CURSOR_MARKER constant from GhostStreamingParser and BasePromptStrategy into a new dedicated file, `ghostConstants.ts`. This centralizes the definition of the marker used for autocomplete positions, improving maintainability and reducing redundancy across Ghost-related services and strategies. The change also updates all references to use the newly exported constant. * test(chat): Improve focus grabbing test for follow-up questions Fix flakey test * Update changeset --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: Chris Hasson <[email protected]> Co-authored-by: Chris Hasson <[email protected]>
1 parent b8c2b99 commit 1fd698a

File tree

69 files changed

+6015
-1400
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+6015
-1400
lines changed

.changeset/beige-sheep-post.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"kilo-code": minor
3+
---
4+
5+
Improve Inline Assist model compatibility and performance

packages/types/src/kilocode.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const ghostServiceSettingsSchema = z
66
autoTriggerDelay: z.number().min(1).max(30).default(3).optional(),
77
enableQuickInlineTaskKeybinding: z.boolean().optional(),
88
enableSmartInlineTaskKeybinding: z.boolean().optional(),
9+
enableCustomProvider: z.boolean().optional(),
910
apiConfigId: z.string().optional(),
1011
})
1112
.optional()

pnpm-lock.yaml

Lines changed: 22 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
import * as vscode from "vscode"
2+
import { GhostSuggestionContext } from "./types"
3+
import { ContextAnalysis, UseCaseType } from "./types/PromptStrategy"
4+
5+
/**
6+
* Analyzes GhostSuggestionContext to determine the appropriate use case and context properties
7+
*/
8+
export class ContextAnalyzer {
9+
/**
10+
* Analyzes the given context to determine use case and properties
11+
* @param context The suggestion context to analyze
12+
* @returns Analysis result with use case and context properties
13+
*/
14+
analyze(context: GhostSuggestionContext): ContextAnalysis {
15+
const analysis: ContextAnalysis = {
16+
useCase: UseCaseType.AUTO_TRIGGER,
17+
hasUserInput: !!context.userInput,
18+
hasErrors: this.hasErrors(context),
19+
hasWarnings: this.hasWarnings(context),
20+
hasSelection: this.hasSelection(context),
21+
isInComment: this.isInComment(context),
22+
isNewLine: this.isNewLine(context),
23+
isInlineEdit: this.isInlineEdit(context),
24+
cursorLine: this.getCursorLine(context),
25+
cursorPosition: context.range?.start.character || 0,
26+
astNodeType: context.rangeASTNode?.type,
27+
}
28+
29+
// Determine primary use case based on priority
30+
analysis.useCase = this.determineUseCase(analysis)
31+
32+
return analysis
33+
}
34+
35+
/**
36+
* Determines the primary use case based on context analysis
37+
* Priority order is important here
38+
*/
39+
private determineUseCase(analysis: ContextAnalysis): UseCaseType {
40+
// Priority 1: User explicit request
41+
if (analysis.hasUserInput) {
42+
return UseCaseType.USER_REQUEST
43+
}
44+
45+
// Priority 2: Error fixing
46+
if (analysis.hasErrors) {
47+
return UseCaseType.ERROR_FIX
48+
}
49+
50+
// Priority 3: Selection refactoring
51+
if (analysis.hasSelection) {
52+
return UseCaseType.SELECTION_REFACTOR
53+
}
54+
55+
// Priority 4: Comment-driven development
56+
if (analysis.isInComment) {
57+
return UseCaseType.COMMENT_DRIVEN
58+
}
59+
60+
// Priority 5: New line completion
61+
if (analysis.isNewLine) {
62+
return UseCaseType.NEW_LINE
63+
}
64+
65+
// Priority 6: Inline completion
66+
if (analysis.isInlineEdit) {
67+
return UseCaseType.INLINE_COMPLETION
68+
}
69+
70+
// Priority 7: AST-aware completion
71+
if (analysis.astNodeType) {
72+
return UseCaseType.AST_AWARE
73+
}
74+
75+
// Priority 8: Default auto-trigger
76+
return UseCaseType.AUTO_TRIGGER
77+
}
78+
79+
/**
80+
* Checks if the context has compilation errors
81+
*/
82+
private hasErrors(context: GhostSuggestionContext): boolean {
83+
return context.diagnostics?.some((d) => d.severity === vscode.DiagnosticSeverity.Error) || false
84+
}
85+
86+
/**
87+
* Checks if the context has warnings
88+
*/
89+
private hasWarnings(context: GhostSuggestionContext): boolean {
90+
return context.diagnostics?.some((d) => d.severity === vscode.DiagnosticSeverity.Warning) || false
91+
}
92+
93+
/**
94+
* Checks if text is selected (non-empty range)
95+
*/
96+
private hasSelection(context: GhostSuggestionContext): boolean {
97+
return context.range ? !context.range.isEmpty : false
98+
}
99+
100+
/**
101+
* Checks if the cursor is within a comment
102+
*/
103+
private isInComment(context: GhostSuggestionContext): boolean {
104+
if (!context.document || !context.range) return false
105+
106+
const line = context.document.lineAt(context.range.start.line).text
107+
const beforeCursor = line.substring(0, context.range.start.character)
108+
109+
// Check for common comment patterns
110+
const commentPatterns = [
111+
/\/\//, // JavaScript/TypeScript single-line comment
112+
/\/\*/, // Multi-line comment start
113+
/#/, // Python/Shell comment
114+
/<!--/, // HTML comment
115+
]
116+
117+
const hasCommentPattern = commentPatterns.some((pattern) => pattern.test(beforeCursor))
118+
119+
// Also check AST node type if available
120+
const isCommentNode = context.rangeASTNode?.type === "comment"
121+
122+
return hasCommentPattern || isCommentNode
123+
}
124+
125+
/**
126+
* Checks if the cursor is on a new/empty line
127+
*/
128+
private isNewLine(context: GhostSuggestionContext): boolean {
129+
if (!context.document || !context.range) return false
130+
131+
const line = context.document.lineAt(context.range.start.line).text
132+
return line.trim() === ""
133+
}
134+
135+
/**
136+
* Checks if the cursor is in the middle of a line (inline editing)
137+
*/
138+
private isInlineEdit(context: GhostSuggestionContext): boolean {
139+
if (!context.document || !context.range) return false
140+
141+
const line = context.document.lineAt(context.range.start.line).text
142+
const hasContent = line.trim() !== ""
143+
const notInComment = !this.isInComment(context)
144+
145+
return hasContent && notInComment
146+
}
147+
148+
/**
149+
* Gets the text of the current line where the cursor is
150+
*/
151+
private getCursorLine(context: GhostSuggestionContext): string {
152+
if (!context.document || !context.range) return ""
153+
154+
return context.document.lineAt(context.range.start.line).text
155+
}
156+
157+
/**
158+
* Checks if the cursor is at the end of a line
159+
*/
160+
isAtEndOfLine(context: GhostSuggestionContext): boolean {
161+
if (!context.document || !context.range) return false
162+
163+
const line = context.document.lineAt(context.range.start.line).text
164+
return context.range.start.character >= line.length
165+
}
166+
167+
/**
168+
* Checks if the cursor is at the beginning of a line
169+
*/
170+
isAtBeginningOfLine(context: GhostSuggestionContext): boolean {
171+
if (!context.range) return false
172+
return context.range.start.character === 0
173+
}
174+
175+
/**
176+
* Gets the number of lines in the selection
177+
*/
178+
getSelectionLineCount(context: GhostSuggestionContext): number {
179+
if (!context.range || context.range.isEmpty) return 0
180+
return context.range.end.line - context.range.start.line + 1
181+
}
182+
183+
/**
184+
* Checks if the cursor is inside a function/method
185+
*/
186+
isInsideFunction(context: GhostSuggestionContext): boolean {
187+
if (!context.rangeASTNode) return false
188+
189+
const functionTypes = [
190+
"function",
191+
"method",
192+
"function_declaration",
193+
"function_expression",
194+
"arrow_function",
195+
"method_definition",
196+
]
197+
198+
let node = context.rangeASTNode
199+
while (node) {
200+
if (functionTypes.includes(node.type)) {
201+
return true
202+
}
203+
node = node.parent as any
204+
}
205+
206+
return false
207+
}
208+
209+
/**
210+
* Checks if the cursor is inside a class
211+
*/
212+
isInsideClass(context: GhostSuggestionContext): boolean {
213+
if (!context.rangeASTNode) return false
214+
215+
const classTypes = ["class", "class_declaration", "class_expression"]
216+
217+
let node = context.rangeASTNode
218+
while (node) {
219+
if (classTypes.includes(node.type)) {
220+
return true
221+
}
222+
node = node.parent as any
223+
}
224+
225+
return false
226+
}
227+
}

0 commit comments

Comments
 (0)