Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/beige-sheep-post.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kilo-code": minor
---

Inline Assist - Model Compatibility and Performance improvements.
51 changes: 51 additions & 0 deletions src/services/ghost/GhostCursor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import * as vscode from "vscode"
import { GhostSuggestionsState } from "./GhostSuggestions"

export class GhostCursor {
public moveToAppliedGroup(suggestions: GhostSuggestionsState) {
const editor = vscode.window.activeTextEditor
if (!editor) {
console.log("No active editor found, returning")
return
}

const documentUri = editor.document.uri
const suggestionsFile = suggestions.getFile(documentUri)
if (!suggestionsFile) {
console.log(`No suggestions found for document: ${documentUri.toString()}`)
return
}
const groups = suggestionsFile.getGroupsOperations()
if (groups.length === 0) {
console.log("No groups to display, returning")
return
}
const selectedGroupIndex = suggestionsFile.getSelectedGroup()
if (selectedGroupIndex === null) {
console.log("No group selected, returning")
return
}
const group = groups[selectedGroupIndex]
const groupType = suggestionsFile.getGroupType(group)

if (groupType === "/" || groupType === "-") {
const line = Math.min(...group.map((x) => x.oldLine))
const lineText = editor.document.lineAt(line).text
const lineLength = lineText.length
editor.selection = new vscode.Selection(line, lineLength, line, lineLength)
editor.revealRange(
new vscode.Range(line, lineLength, line, lineLength),
vscode.TextEditorRevealType.InCenter,
)
} else if (groupType === "+") {
const line = Math.min(...group.map((x) => x.oldLine)) + group.length
const lineText = editor.document.lineAt(line).text
const lineLength = lineText.length
editor.selection = new vscode.Selection(line, lineLength, line, lineLength)
editor.revealRange(
new vscode.Range(line, lineLength, line, lineLength),
vscode.TextEditorRevealType.InCenter,
)
}
}
}
261 changes: 152 additions & 109 deletions src/services/ghost/GhostDecorations.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,161 @@
import * as vscode from "vscode"
import { GhostSuggestionsState } from "./GhostSuggestions"
import { GhostSuggestionEditOperation } from "./types"

const ADDITION_DECORATION_OPTIONS: vscode.DecorationRenderOptions = {
after: {
margin: "0 0 0 0.1em",
color: new vscode.ThemeColor("editor.background"),
backgroundColor: new vscode.ThemeColor("editorGutter.addedBackground"),
color: new vscode.ThemeColor("editor.foreground"),
backgroundColor: new vscode.ThemeColor("editor.background"),
border: "1px solid",
borderColor: new vscode.ThemeColor("editorGutter.addedBackground"),
},
opacity: "0.8",
isWholeLine: false,
borderColor: new vscode.ThemeColor("editorGutter.addedBackground"),
overviewRulerColor: new vscode.ThemeColor("editorGutter.addedBackground"),
overviewRulerLane: vscode.OverviewRulerLane.Right,
}

const ADDITION_ACTIVE_DECORATION_OPTIONS: vscode.DecorationRenderOptions = {
...ADDITION_DECORATION_OPTIONS,
after: {
...ADDITION_DECORATION_OPTIONS.after,
borderColor: new vscode.ThemeColor("editorGutter.addedSecondaryBackground"),
border: "1px solid",
fontWeight: "bold",
},
}

const DELETION_DECORATION_OPTIONS: vscode.DecorationRenderOptions = {
isWholeLine: false,
color: new vscode.ThemeColor("editor.background"),
backgroundColor: new vscode.ThemeColor("editorGutter.deletedBackground"),
opacity: "0.8",
overviewRulerColor: new vscode.ThemeColor("editorGutter.deletedBackground"),
overviewRulerLane: vscode.OverviewRulerLane.Right,
}

const DELETION_ACTIVE_DECORATION_OPTIONS: vscode.DecorationRenderOptions = {
...DELETION_DECORATION_OPTIONS,
borderColor: new vscode.ThemeColor("editorGutter.deletedSecondaryBackground"),
borderStyle: "solid",
borderWidth: "1px",
fontWeight: "bold",
const EDIT_DECORATION_OPTIONS: vscode.DecorationRenderOptions = {
after: {
// CSS INJECT
textDecoration:
"none; display: block; position: absolute; top: 100%; left: 20px; width: max-content; z-index: 100; box-shadow: 0 1px 3px rgba(255, 255, 255, 0.12), 0 1px 2px rgba(255, 255, 255, 0.24); padding: 0.2em;",
color: new vscode.ThemeColor("editor.foreground"),
backgroundColor: new vscode.ThemeColor("editor.background"),
border: "1px solid",
borderColor: new vscode.ThemeColor("editorGutter.addedBackground"),
},
textDecoration: "none; position: relative;",
isWholeLine: false,
border: "1px solid",
borderColor: new vscode.ThemeColor("editorGutter.deletedBackground"),
overviewRulerColor: new vscode.ThemeColor("editorGutter.deletedBackground"),
overviewRulerLane: vscode.OverviewRulerLane.Right,
}

export class GhostDecorations {
private additionDecorationType: vscode.TextEditorDecorationType
private deletionDecorationType: vscode.TextEditorDecorationType
private deletionActiveDecorationType: vscode.TextEditorDecorationType
private editionDecorationType: vscode.TextEditorDecorationType

constructor() {
this.additionDecorationType = vscode.window.createTextEditorDecorationType(ADDITION_DECORATION_OPTIONS)
this.deletionDecorationType = vscode.window.createTextEditorDecorationType(DELETION_DECORATION_OPTIONS)
this.deletionActiveDecorationType = vscode.window.createTextEditorDecorationType(
DELETION_ACTIVE_DECORATION_OPTIONS,
)
this.editionDecorationType = vscode.window.createTextEditorDecorationType(EDIT_DECORATION_OPTIONS)
}

/**
* Clears all ghost decorations from the active editor.
*/
public clearAll(): void {
const editor = vscode.window.activeTextEditor
if (!editor) {
return
}

editor.setDecorations(this.additionDecorationType, [])
editor.setDecorations(this.deletionDecorationType, [])
editor.setDecorations(this.editionDecorationType, [])
}

// TODO: Split the differences between the contents and show each range individually
private displayEditOpertionGroup = (editor: vscode.TextEditor, group: GhostSuggestionEditOperation[]) => {
const line = Math.min(...group.map((x) => x.oldLine))

const nextLineInfo = editor.document.lineAt(line)
const range = nextLineInfo.range

const newContent = group.find((x) => x.type === "+")?.content || ""

const leadingWhitespace = newContent.match(/^\s*/)?.[0] ?? ""
const preservedWhitespace = leadingWhitespace.replace(/ /g, "\u00A0")
const trimmedContent = newContent.trimStart()

const contentText = preservedWhitespace + trimmedContent

const renderOptions: vscode.DecorationRenderOptions = { ...EDIT_DECORATION_OPTIONS }
renderOptions.after = {
...renderOptions.after,
contentText: `${contentText}`,
}

// Apply the decorations directly
editor.setDecorations(this.additionDecorationType, [])
editor.setDecorations(this.deletionDecorationType, [])
editor.setDecorations(this.editionDecorationType, [
{
range,
renderOptions,
},
])
}

private displayDeleteOperationGroup = (editor: vscode.TextEditor, group: GhostSuggestionEditOperation[]) => {
const lines = group.map((x) => x.oldLine)
const from = Math.min(...lines)
const to = Math.max(...lines)

const start = editor.document.lineAt(from).range.start
const end = editor.document.lineAt(to).range.end
const range = new vscode.Range(start, end)

editor.setDecorations(this.additionDecorationType, [])
editor.setDecorations(this.editionDecorationType, [])
editor.setDecorations(this.deletionDecorationType, [
{
range,
},
])
}

private getCssInjectionForEdit = (content: string) => {
let filteredContent = content
if (filteredContent === "") {
filteredContent = "[↵]"
}
filteredContent = filteredContent.replaceAll(`"`, `\\"`)
filteredContent = filteredContent.replaceAll(`'`, `\\'`)
filteredContent = filteredContent.replaceAll(`;`, `\\;`)

console.log(content)

return `none; display: block; position: absolute; top: 0px; left: 0px; width: max-content; z-index: 100; white-space: pre-wrap; content: "${filteredContent}"; box-shadow: 0 1px 3px rgba(255, 255, 255, 0.12), 0 1px 2px rgba(255, 255, 255, 0.24); padding: 0.2em;`
}

private displayAdditionsOperationGroup = (editor: vscode.TextEditor, group: GhostSuggestionEditOperation[]) => {
const line = Math.min(...group.map((x) => x.oldLine))

const nextLineInfo = editor.document.lineAt(line)
const range = nextLineInfo.range

let content = group
.sort((a, b) => a.line - b.line)
.map((x) => x.content)
.join(`\\A`)

const renderOptions: vscode.DecorationRenderOptions = { ...ADDITION_DECORATION_OPTIONS }
renderOptions.after = {
...renderOptions.after,
textDecoration: this.getCssInjectionForEdit(content),
}

// Apply the decorations directly
editor.setDecorations(this.deletionDecorationType, [])
editor.setDecorations(this.editionDecorationType, [])
editor.setDecorations(this.additionDecorationType, [
{
range,
renderOptions,
},
])
}

/**
Expand All @@ -64,109 +169,47 @@ export class GhostDecorations {
return
}

const additionDecorations: vscode.DecorationOptions[] = []
const deletionDecorations: vscode.DecorationOptions[] = []
const deletionActiveDecorations: vscode.DecorationOptions[] = []

const documentUri = editor.document.uri
const suggestionsFile = suggestions.getFile(documentUri)
if (!suggestionsFile) {
console.log(`No suggestions found for document: ${documentUri.toString()}`)
this.clearAll()
return
}
const fileOperations = suggestions.getFile(documentUri)?.getAllOperations() || []
if (fileOperations.length === 0) {
console.log("No operations to display, returning")
this.clearAll()
return
}
let linesAdded = 0
let linesRemoved = 0

const groups = suggestionsFile.getGroupsOperations()
if (groups.length === 0) {
console.log("No groups to display, returning")
this.clearAll()
return
}

for (let groupIndex = 0; groupIndex < groups.length; groupIndex++) {
const operations = groups[groupIndex]
const selected = groupIndex === suggestionsFile.getSelectedGroup()
for (const op of operations) {
if (op.type === "+") {
const anchorLine = op.line + linesRemoved
if (anchorLine < 0 || anchorLine >= editor.document.lineCount) {
continue
}

const nextLineInfo = editor.document.lineAt(anchorLine)
const position = nextLineInfo.range.start
const range = new vscode.Range(position, position)

// Whitespace in `contentText` collapses. To preserve indentation,
// replace leading spaces with non-breaking space characters.
const leadingWhitespace = op.content.match(/^\s*/)?.[0] ?? ""
const preservedWhitespace = leadingWhitespace.replace(/ /g, "\u00A0")
const trimmedContent = op.content.trimStart()

// Make the ghost text more visible with a clear prefix and formatting
// Split the content by newlines to handle multi-line additions properly
const contentText = preservedWhitespace + trimmedContent

const renderOptions: vscode.DecorationRenderOptions = selected
? { ...ADDITION_ACTIVE_DECORATION_OPTIONS }
: { ...ADDITION_DECORATION_OPTIONS }

renderOptions.after = {
...renderOptions.after,
contentText: `${contentText}`,
}

additionDecorations.push({
range,
renderOptions,
})
linesAdded++
}

if (op.type === "-") {
const anchorLine = op.line + linesAdded
if (anchorLine < 0 || anchorLine >= editor.document.lineCount) {
continue
}
const range = editor.document.lineAt(anchorLine).range

if (selected) {
deletionActiveDecorations.push({
range,
})
} else {
deletionDecorations.push({
range,
})
}

linesRemoved++
}
}
}

// Apply the decorations directly
editor.setDecorations(this.additionDecorationType, additionDecorations)
editor.setDecorations(this.deletionDecorationType, deletionDecorations)
editor.setDecorations(this.deletionActiveDecorationType, deletionActiveDecorations)
}

/**
* Clears all ghost decorations from the active editor.
*/
public clearAll(): void {
const editor = vscode.window.activeTextEditor
if (!editor) {
const selectedGroupIndex = suggestionsFile.getSelectedGroup()
if (selectedGroupIndex === null) {
console.log("No group selected, returning")
this.clearAll()
return
}

editor.setDecorations(this.additionDecorationType, [])
editor.setDecorations(this.deletionDecorationType, [])
editor.setDecorations(this.deletionActiveDecorationType, [])
const selectedGroup = groups[selectedGroupIndex]
const groupType = suggestionsFile.getGroupType(selectedGroup)

console.log("selectedGroup", selectedGroup)
console.log("groupType", groupType)

if (groupType === "/") {
this.displayEditOpertionGroup(editor, selectedGroup)
} else if (groupType === "-") {
this.displayDeleteOperationGroup(editor, selectedGroup)
} else if (groupType === "+") {
this.displayAdditionsOperationGroup(editor, selectedGroup)
} else {
this.clearAll()
}
}
}
6 changes: 6 additions & 0 deletions src/services/ghost/GhostModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export class GhostModel {
}
}

public getApiConfigId() {
return this.apiConfigId
}

public async reload(settings: GhostServiceSettings, providerSettingsManager: ProviderSettingsManager) {
this.apiConfigId = settings?.apiConfigId || null
const defaultApiConfigId = ContextProxy.instance?.getValues?.()?.currentApiConfigName || ""
Expand All @@ -42,6 +46,8 @@ export class GhostModel {
throw new Error("API handler is not initialized. Please check your configuration.")
}

console.log("USED MODEL", this.apiHandler.getModel())

const stream = this.apiHandler.createMessage(systemPrompt, [
{ role: "user", content: [{ type: "text", text: userPrompt }] },
])
Expand Down
Loading
Loading