فهرست منبع

remove old multiplace diff svg style autocomplete

Mark IJbema 3 ماه پیش
والد
کامیت
b146bcde23
31فایلهای تغییر یافته به همراه67 افزوده شده و 3346 حذف شده
  1. 0 111
      apps/storybook/src/components/CodeHighlighterExample.tsx
  2. 0 8
      apps/storybook/src/mocks/jsdom.ts
  3. 0 87
      apps/storybook/stories/SvgCodeHighlighter.stories.tsx
  4. 0 24
      src/services/ghost/EditorConfiguration.ts
  5. 0 51
      src/services/ghost/GhostCursor.ts
  6. 0 188
      src/services/ghost/GhostDecorations.ts
  7. 0 3
      src/services/ghost/GhostProvider.ts
  8. 2 66
      src/services/ghost/GhostStreamingParser.ts
  9. 3 291
      src/services/ghost/GhostSuggestions.ts
  10. 0 283
      src/services/ghost/GhostWorkspaceEdit.ts
  11. 0 76
      src/services/ghost/__tests__/EditorConfiguration.test.ts
  12. 0 230
      src/services/ghost/__tests__/GhostDecorations.test.ts
  13. 15 16
      src/services/ghost/__tests__/GhostProvider.spec.ts
  14. 3 4
      src/services/ghost/__tests__/GhostStreamingParser.test.ts
  15. 0 582
      src/services/ghost/__tests__/GhostSuggestions.test.ts
  16. 0 150
      src/services/ghost/__tests__/createSVGDecorationType.spec.ts
  17. 0 16
      src/services/ghost/types.ts
  18. 0 189
      src/services/ghost/utils/CharacterDiff.ts
  19. 0 189
      src/services/ghost/utils/CodeHighlighter.ts
  20. 0 121
      src/services/ghost/utils/SvgRenderer.ts
  21. 0 133
      src/services/ghost/utils/ThemeMapper.ts
  22. 0 32
      src/services/ghost/utils/__tests__/CharacterDiff.spec.ts
  23. 0 58
      src/services/ghost/utils/__tests__/CodeHighlighter.spec.ts
  24. 0 66
      src/services/ghost/utils/__tests__/SvgRenderer.spec.ts
  25. 0 56
      src/services/ghost/utils/__tests__/ThemeMapper.spec.ts
  26. 0 43
      src/services/ghost/utils/__tests__/textMeasurement.spec.ts
  27. 0 130
      src/services/ghost/utils/createSVGDecorationType.ts
  28. 0 15
      src/services/ghost/utils/htmlParser.ts
  29. 0 33
      src/services/ghost/utils/textMeasurement.ts
  30. 29 29
      src/test-llm-autocompletion/__tests__/strategy-tester.test.ts
  31. 15 66
      src/test-llm-autocompletion/strategy-tester.ts

+ 0 - 111
apps/storybook/src/components/CodeHighlighterExample.tsx

@@ -1,111 +0,0 @@
-import React, { useState, useEffect } from "react"
-import { calculateDiff } from "../../../../src/services/ghost/utils/CharacterDiff"
-import { generateHighlightedHtmlWithRanges } from "../../../../src/services/ghost/utils/CodeHighlighter"
-import { SvgRenderer } from "../../../../src/services/ghost/utils/SvgRenderer"
-export interface CharacterHighlightingProps {
-	originalText: string
-	newText: string
-	language: string
-	fontSize: number
-	width: number
-}
-
-export const SvgCodeHighlighterExample: React.FC<CharacterHighlightingProps> = ({
-	originalText: initialOriginalText,
-	newText: initialNewText,
-	language,
-	fontSize,
-	width,
-}) => {
-	const [originalText, setOriginalText] = useState(initialOriginalText)
-	const [newText, setNewText] = useState(initialNewText)
-	const [svg, setSvg] = useState<string>("")
-	const [loading, setLoading] = useState(true)
-
-	useEffect(() => {
-		let canceled = false
-		const generateSvg = async () => {
-			try {
-				setLoading(true)
-
-				// Calculate character differences
-				const ranges = calculateDiff(originalText, newText)
-
-				// Generate highlighted HTML
-				const { html, themeColors } = await generateHighlightedHtmlWithRanges(newText, language, ranges)
-				if (canceled) return
-
-				// Create SVG renderer
-				const renderer = new SvgRenderer(html, {
-					width,
-					height: fontSize * 2,
-					fontSize,
-					fontFamily: "Menlo, Monaco, 'Courier New', monospace",
-					letterSpacing: 0,
-					lineHeight: fontSize * 1.5,
-					themeColors,
-				})
-
-				// Render the SVG
-				const svgResult = renderer.render()
-				setSvg(svgResult)
-			} catch (error) {
-				const errorMessage = error instanceof Error ? error.message : String(error)
-				setSvg(
-					`<svg width="400" height="50"><text x="10" y="25" fill="red">Error: ${errorMessage}</text></svg>`,
-				)
-			} finally {
-				setLoading(false)
-			}
-		}
-
-		generateSvg()
-		return () => {
-			canceled = true
-		}
-	}, [originalText, newText, language, fontSize, width])
-
-	// Update internal state when props change
-	useEffect(() => {
-		setOriginalText(initialOriginalText)
-	}, [initialOriginalText])
-
-	useEffect(() => {
-		setNewText(initialNewText)
-	}, [initialNewText])
-
-	return (
-		<div className="p-4 text-vscode-foreground">
-			<h3 className="text-xl font-semibold mb-4">SVG Code Highlighter Demo</h3>
-
-			<div className="flex gap-5 mb-5">
-				<div className="flex-1">
-					<label className="block mb-1 font-bold text-sm py-1">Original Text</label>
-					<textarea
-						value={originalText}
-						onChange={(e) => setOriginalText(e.target.value)}
-						className="w-full h-30 bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border rounded p-2 font-mono text-sm resize-y focus:outline-none focus:ring-2 focus:ring-vscode-focusBorder"
-					/>
-				</div>
-				<div className="flex-1">
-					<label className="block mb-1 font-bold text-sm py-1">Updated Text</label>
-					<textarea
-						value={newText}
-						onChange={(e) => setNewText(e.target.value)}
-						className="w-full h-30 bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border rounded p-2 font-mono text-sm resize-y focus:ring-2 focus:ring-vscode-focusBorder"
-					/>
-				</div>
-			</div>
-
-			<div className="flex flex-col">
-				<strong className="text-sm py-1">SVG Preview</strong>
-				<div className="border border-vscode-input-border p-2.5 rounded">
-					{loading && (
-						<div className="text-vscode-descriptionForeground italic text-sm mt-1">Generating...</div>
-					)}
-					<div className="mt-2.5 min-h-[50px]" dangerouslySetInnerHTML={{ __html: svg }} />
-				</div>
-			</div>
-		</div>
-	)
-}

+ 0 - 8
apps/storybook/src/mocks/jsdom.ts

@@ -1,8 +0,0 @@
-// JSDOM is used in the extension for SVG code highlighting.
-// We render sample SVGs in Storybook for testing/ debugging purposes,
-// but use actual browser APIs instead of JSDOM there.
-// See ../../../../src/services/ghost/utils/htmlParser.ts
-
-export const JSDOM = null
-
-export default null

+ 0 - 87
apps/storybook/stories/SvgCodeHighlighter.stories.tsx

@@ -1,87 +0,0 @@
-import type { Meta, StoryObj } from "@storybook/react-vite"
-import { SvgCodeHighlighterExample } from "../src/components/CodeHighlighterExample"
-
-const meta = {
-	title: "Autocomplete/SvgCodeHighlighter",
-	component: SvgCodeHighlighterExample,
-	argTypes: {
-		originalText: {
-			control: "text",
-			description: "Original line of code",
-		},
-		newText: {
-			control: "text",
-			description: "New line of code with changes",
-		},
-		language: {
-			control: { type: "select" },
-			options: ["typescript", "javascript", "python", "html", "css"],
-			description: "Programming language for syntax highlighting",
-		},
-		fontSize: {
-			control: { type: "range", min: 10, max: 24, step: 1 },
-			description: "Font size for the rendered text",
-		},
-		width: {
-			control: { type: "range", min: 200, max: 800, step: 50 },
-			description: "SVG width",
-		},
-	},
-} satisfies Meta<typeof SvgCodeHighlighterExample>
-
-export default meta
-type Story = StoryObj<typeof meta>
-
-export const BasicExample: Story = {
-	args: {
-		originalText: 'const hello = "hi"',
-		newText: 'const hello = "hi";',
-		language: "typescript",
-		fontSize: 14,
-		width: 400,
-	},
-}
-
-export const MultilineChange: Story = {
-	args: {
-		originalText: `function sum(a, b) {
-	return a + b;
-}`,
-		newText: `function multiply(x, y) {
-	return x * y;
-}`,
-		language: "typescript",
-		fontSize: 14,
-		width: 400,
-	},
-}
-
-export const WordChange: Story = {
-	args: {
-		originalText: 'const hello = "hi";',
-		newText: 'const greeting = "hi";',
-		language: "typescript",
-		fontSize: 14,
-		width: 300,
-	},
-}
-
-export const SuffixAddition: Story = {
-	args: {
-		originalText: "ABC",
-		newText: "ABCDEF",
-		language: "typescript",
-		fontSize: 14,
-		width: 200,
-	},
-}
-
-export const MiddleCharacterChange: Story = {
-	args: {
-		originalText: "abc",
-		newText: "acc",
-		language: "typescript",
-		fontSize: 14,
-		width: 200,
-	},
-}

+ 0 - 24
src/services/ghost/EditorConfiguration.ts

@@ -1,24 +0,0 @@
-import * as vscode from "vscode"
-
-export interface EditorConfig {
-	fontSize: number
-	fontFamily: string
-	lineHeight: number
-}
-
-/**
- * Get all editor configuration values in one call
- */
-export function getEditorConfiguration(): EditorConfig {
-	const config = vscode.workspace.getConfiguration("editor")
-	const fontSize = config.get<number>("fontSize") || 14
-	const fontFamily = config.get<string>("fontFamily") || "Consolas, 'Courier New', monospace"
-	const rawLineHeight = config.get<number>("lineHeight") || 1.5
-
-	// VS Code lineHeight can be either:
-	// - A multiplier (like 1.2) if < 8
-	// - An absolute pixel value (like 18) if >= 8
-	const lineHeight = rawLineHeight < 8 ? rawLineHeight : rawLineHeight / fontSize
-
-	return { fontSize, fontFamily, lineHeight }
-}

+ 0 - 51
src/services/ghost/GhostCursor.ts

@@ -1,51 +0,0 @@
-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,
-			)
-		}
-	}
-}

+ 0 - 188
src/services/ghost/GhostDecorations.ts

@@ -1,188 +0,0 @@
-import * as vscode from "vscode"
-import { GhostSuggestionsState } from "./GhostSuggestions"
-import { GhostSuggestionEditOperation } from "./types"
-import { calculateDiff, type BackgroundRange } from "./utils/CharacterDiff"
-import { createSVGDecorationType, type SVGDecorationContent } from "./utils/createSVGDecorationType"
-
-export const DELETION_DECORATION_OPTIONS: vscode.DecorationRenderOptions = {
-	isWholeLine: false,
-	border: "1px solid",
-	borderColor: new vscode.ThemeColor("editorGutter.deletedBackground"),
-	overviewRulerColor: new vscode.ThemeColor("editorGutter.deletedBackground"),
-	overviewRulerLane: vscode.OverviewRulerLane.Right,
-}
-
-/**
- * Hybrid ghost decorations: SVG highlighting for edits/additions, simple styling for deletions
- * Acts as an orchestrator using createSVGDecorationType utility
- */
-export class GhostDecorations {
-	private deletionDecorationType: vscode.TextEditorDecorationType
-	private codeEditDecorationTypes: vscode.TextEditorDecorationType[] = []
-
-	constructor() {
-		this.deletionDecorationType = vscode.window.createTextEditorDecorationType(DELETION_DECORATION_OPTIONS)
-	}
-
-	/**
-	 * Display edit operations using SVG decorations
-	 */
-	private async displayEditOperationGroup(
-		editor: vscode.TextEditor,
-		group: GhostSuggestionEditOperation[],
-	): Promise<void> {
-		const line = Math.min(...group.map((x) => x.oldLine))
-		const range = this.calculateRangeForOperations(editor, line)
-
-		const newContent = group.find((x) => x.type === "+")?.content || ""
-		if (!newContent.trim()) {
-			return
-		}
-
-		const originalContent = line < editor.document.lineCount ? editor.document.lineAt(line).text : ""
-		const backgroundRanges = calculateDiff(originalContent, newContent)
-
-		const svgContent: SVGDecorationContent = {
-			text: newContent,
-			backgroundRanges: backgroundRanges,
-		}
-
-		await this.createSvgDecoration(editor, range, svgContent)
-	}
-
-	/**
-	 * Display deletion operations using simple border styling
-	 */
-	private displayDeleteOperationGroup(editor: vscode.TextEditor, group: GhostSuggestionEditOperation[]): void {
-		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.deletionDecorationType, [{ range }])
-	}
-
-	/**
-	 * Display suggestions using hybrid approach: SVG for edits/additions, simple styling for deletions
-	 */
-	public async displaySuggestions(suggestions: GhostSuggestionsState): Promise<void> {
-		const editor = vscode.window.activeTextEditor
-		if (!editor) {
-			return
-		}
-
-		const documentUri = editor.document.uri
-		const suggestionsFile = suggestions.getFile(documentUri)
-		if (!suggestionsFile) {
-			this.clearAll()
-			return
-		}
-		const fileOperations = suggestions.getFile(documentUri)?.getAllOperations() || []
-		if (fileOperations.length === 0) {
-			this.clearAll()
-			return
-		}
-
-		const groups = suggestionsFile.getGroupsOperations()
-		if (groups.length === 0) {
-			this.clearAll()
-			return
-		}
-
-		const selectedGroupIndex = suggestionsFile.getSelectedGroup()
-		if (selectedGroupIndex === null) {
-			this.clearAll()
-			return
-		}
-		const selectedGroup = groups[selectedGroupIndex]
-		const groupType = suggestionsFile.getGroupType(selectedGroup)
-
-		// Clear previous decorations
-		this.clearAll()
-
-		// Route to appropriate display method
-		if (groupType === "/") {
-			await this.displayEditOperationGroup(editor, selectedGroup)
-		} else if (groupType === "-") {
-			this.displayDeleteOperationGroup(editor, selectedGroup)
-		} else if (groupType === "+") {
-			await this.displayAdditionsOperationGroup(editor, selectedGroup)
-		}
-	}
-
-	/**
-	 * Display addition operations using SVG decorations
-	 */
-	private async displayAdditionsOperationGroup(
-		editor: vscode.TextEditor,
-		group: GhostSuggestionEditOperation[],
-	): Promise<void> {
-		const line = Math.min(...group.map((x) => x.oldLine))
-		const range = this.calculateRangeForOperations(editor, line)
-
-		const content = group
-			.sort((a, b) => a.line - b.line)
-			.map((x) => x.content)
-			.join("\n")
-		if (!content.trim()) {
-			return
-		}
-
-		// For additions, all content is new/modified (highlight entire content)
-		const backgroundRanges: BackgroundRange[] = [{ start: 0, end: content.length, type: "modified" }]
-		const svgContent: SVGDecorationContent = {
-			text: content,
-			backgroundRanges: backgroundRanges,
-		}
-
-		await this.createSvgDecoration(editor, range, svgContent)
-	}
-
-	/**
-	 * Calculate range for operations, handling end-of-document gracefully
-	 */
-	private calculateRangeForOperations(editor: vscode.TextEditor, line: number): vscode.Range {
-		if (line >= editor.document.lineCount) {
-			// If the line is beyond the document, use the last line of the document
-			const lastLineIndex = Math.max(0, editor.document.lineCount - 1)
-			const lastLineInfo = editor.document.lineAt(lastLineIndex)
-			return new vscode.Range(lastLineInfo.range.end, lastLineInfo.range.end)
-		} else {
-			const nextLineInfo = editor.document.lineAt(line)
-			return nextLineInfo.range
-		}
-	}
-
-	/**
-	 * Create SVG decoration using the createSVGDecorationType utility
-	 */
-	private async createSvgDecoration(
-		editor: vscode.TextEditor,
-		range: vscode.Range,
-		content: SVGDecorationContent,
-	): Promise<void> {
-		const decorationType = await createSVGDecorationType(content, editor.document)
-		this.codeEditDecorationTypes.push(decorationType)
-		editor.setDecorations(decorationType, [{ range }])
-	}
-
-	/**
-	 * Clears all ghost decorations from the active editor.
-	 */
-	public clearAll(): void {
-		const editor = vscode.window.activeTextEditor
-		if (!editor) {
-			return
-		}
-
-		editor.setDecorations(this.deletionDecorationType, [])
-
-		for (const decorationType of this.codeEditDecorationTypes) {
-			decorationType.dispose()
-		}
-		this.codeEditDecorationTypes = []
-	}
-}

+ 0 - 3
src/services/ghost/GhostProvider.ts

@@ -313,9 +313,6 @@ export class GhostProvider {
 			await this.load()
 		}
 
-		console.log("system", systemPrompt)
-		console.log("userprompt", userPrompt)
-
 		// Initialize the streaming parser
 		this.streamingParser.initialize(context)
 

+ 2 - 66
src/services/ghost/GhostStreamingParser.ts

@@ -1,6 +1,5 @@
 import * as vscode from "vscode"
-import { ParsedDiff, structuredPatch } from "diff"
-import { GhostSuggestionContext, GhostSuggestionEditOperationType } from "./types"
+import { GhostSuggestionContext } from "./types"
 import { GhostSuggestionsState } from "./GhostSuggestions"
 import { CURSOR_MARKER } from "./ghostConstants"
 
@@ -244,20 +243,11 @@ export class GhostStreamingParser {
 		const range = this.context!.range
 
 		const modifiedContent = this.generateModifiedContent(newChanges, document, range)
-		const relativePath = vscode.workspace.asRelativePath(document.uri, false)
-		const patch = structuredPatch(
-			relativePath,
-			relativePath,
-			document.getText(),
-			modifiedContent ?? document.getText(),
-			"",
-			"",
-		)
 
 		const modifiedContent_has_prefix_and_suffix =
 			modifiedContent?.startsWith(prefix) && modifiedContent.endsWith(suffix)
 
-		const suggestions = this.convertToSuggestions(patch, document)
+		const suggestions = new GhostSuggestionsState()
 
 		if (modifiedContent_has_prefix_and_suffix && modifiedContent) {
 			// Mark as FIM option
@@ -416,58 +406,4 @@ export class GhostStreamingParser {
 
 		return modifiedContent
 	}
-
-	private convertToSuggestions(patch: ParsedDiff, document: vscode.TextDocument): GhostSuggestionsState {
-		const suggestions = new GhostSuggestionsState()
-
-		const suggestionFile = suggestions.addFile(document.uri)
-
-		for (const hunk of patch.hunks) {
-			let currentOldLineNumber = hunk.oldStart
-			let currentNewLineNumber = hunk.newStart
-
-			for (const line of hunk.lines) {
-				const operationType = line.charAt(0)
-				const content = line.substring(1)
-
-				switch (operationType) {
-					// Case 1: The line is an addition
-					case "+":
-						suggestionFile.addOperation({
-							type: "+",
-							line: currentNewLineNumber - 1,
-							oldLine: currentOldLineNumber - 1,
-							newLine: currentNewLineNumber - 1,
-							content: content,
-						})
-						// Only increment the new line counter for additions and context lines
-						currentNewLineNumber++
-						break
-
-					// Case 2: The line is a deletion
-					case "-":
-						suggestionFile.addOperation({
-							type: "-",
-							line: currentOldLineNumber - 1,
-							oldLine: currentOldLineNumber - 1,
-							newLine: currentNewLineNumber - 1,
-							content: content,
-						})
-						// Only increment the old line counter for deletions and context lines
-						currentOldLineNumber++
-						break
-
-					// Case 3: The line is unchanged (context)
-					default:
-						// For context lines, we increment both counters
-						currentOldLineNumber++
-						currentNewLineNumber++
-						break
-				}
-			}
-		}
-
-		suggestions.sortGroups()
-		return suggestions
-	}
 }

+ 3 - 291
src/services/ghost/GhostSuggestions.ts

@@ -1,263 +1,3 @@
-import * as vscode from "vscode"
-import { GhostSuggestionEditOperation, GhostSuggestionEditOperationsOffset } from "./types"
-
-class GhostSuggestionFile {
-	public fileUri: vscode.Uri
-	private selectedGroup: number | null = null
-	private groups: Array<GhostSuggestionEditOperation[]> = []
-
-	constructor(public uri: vscode.Uri) {
-		this.fileUri = uri
-	}
-
-	public addOperation(operation: GhostSuggestionEditOperation) {
-		// Priority 1: Try to create or join a modification group (delete on line N, add on line N+1)
-		const modificationGroupIndex = this.findOrCreateModificationGroup(operation)
-		if (modificationGroupIndex !== -1) {
-			return
-		}
-
-		// Priority 2: Try to join an existing group of same type on subsequent lines
-		const sameTypeGroupIndex = this.findSameTypeGroup(operation)
-		if (sameTypeGroupIndex !== -1) {
-			this.groups[sameTypeGroupIndex].push(operation)
-			return
-		}
-
-		// Priority 3: Create a new group
-		this.groups.push([operation])
-	}
-
-	private findOrCreateModificationGroup(operation: GhostSuggestionEditOperation): number {
-		// Look for existing operations that can form a modification group
-		// Modification group: delete on line N, add on line N+1
-		for (let i = 0; i < this.groups.length; i++) {
-			const group = this.groups[i]
-
-			for (const existingOp of group) {
-				// Check if we can form a modification group
-				const canFormModificationGroup =
-					(operation.type === "+" && existingOp.type === "-" && existingOp.newLine === operation.newLine) ||
-					(operation.type === "-" && existingOp.type === "+" && operation.newLine === existingOp.newLine)
-
-				if (canFormModificationGroup) {
-					// Remove the existing operation from its current group
-					this.removeOperationFromGroup(i, existingOp)
-
-					// Create new modification group with delete first, then add
-					const deleteOp = operation.type === "-" ? operation : existingOp
-					const addOp = operation.type === "+" ? operation : existingOp
-					this.groups.push([deleteOp, addOp])
-					return this.groups.length - 1
-				}
-			}
-		}
-		return -1
-	}
-
-	private findSameTypeGroup(operation: GhostSuggestionEditOperation): number {
-		for (let i = 0; i < this.groups.length; i++) {
-			const group = this.groups[i]
-
-			// Skip modification groups (groups with both + and -)
-			const hasDelete = group.some((op) => op.type === "-")
-			const hasAdd = group.some((op) => op.type === "+")
-			if (hasDelete && hasAdd) {
-				continue
-			}
-
-			// Check if group has same type operations
-			if (group.length > 0 && group[0].type === operation.type) {
-				// Check if the operation is on a subsequent line
-				const maxLine = Math.max(...group.map((op) => op.line))
-				const minLine = Math.min(...group.map((op) => op.line))
-
-				if (operation.line === maxLine + 1 || operation.line === minLine - 1) {
-					return i
-				}
-			}
-		}
-		return -1
-	}
-
-	private removeOperationFromGroup(groupIndex: number, operation: GhostSuggestionEditOperation) {
-		const group = this.groups[groupIndex]
-		const opIndex = group.findIndex(
-			(op) => op.line === operation.line && op.type === operation.type && op.content === operation.content,
-		)
-
-		if (opIndex !== -1) {
-			group.splice(opIndex, 1)
-
-			// Remove empty groups
-			if (group.length === 0) {
-				this.groups.splice(groupIndex, 1)
-			}
-		}
-	}
-
-	public isEmpty(): boolean {
-		return this.groups.length === 0
-	}
-
-	public getSelectedGroup(): number | null {
-		return this.selectedGroup
-	}
-
-	public getGroupType = (group: GhostSuggestionEditOperation[]) => {
-		const types = group.flatMap((x) => x.type)
-		if (types.length == 2) {
-			return "/"
-		}
-		return types[0]
-	}
-
-	public getSelectedGroupPreviousOperations(): GhostSuggestionEditOperation[] {
-		if (this.selectedGroup === null || this.selectedGroup <= 0) {
-			return []
-		}
-		const previousGroups = this.groups.slice(0, this.selectedGroup)
-		return previousGroups.flat()
-	}
-
-	public getSelectedGroupOperations(): GhostSuggestionEditOperation[] {
-		if (this.selectedGroup === null || this.selectedGroup >= this.groups.length) {
-			return []
-		}
-		return this.groups[this.selectedGroup]
-	}
-
-	public getPlaceholderOffsetSelectedGroupOperations(): GhostSuggestionEditOperationsOffset {
-		const operations = this.getSelectedGroupPreviousOperations()
-		const { added, removed } = operations.reduce(
-			(acc, op) => {
-				if (op.type === "+") {
-					return { added: acc.added + 1, removed: acc.removed }
-				} else if (op.type === "-") {
-					return { added: acc.added, removed: acc.removed + 1 }
-				}
-				return acc
-			},
-			{ added: 0, removed: 0 },
-		)
-		return { added, removed, offset: added - removed }
-	}
-
-	public getGroupsOperations(): GhostSuggestionEditOperation[][] {
-		return this.groups
-	}
-
-	public getAllOperations(): GhostSuggestionEditOperation[] {
-		return this.groups.flat().sort((a, b) => a.line - b.line)
-	}
-
-	public sortGroups() {
-		this.groups
-			.sort((a, b) => {
-				const aLine = a[0].line
-				const bLine = b[0].line
-				return aLine - bLine
-			})
-			.forEach((group) => {
-				group.sort((a, b) => a.line - b.line)
-			})
-		this.selectedGroup = this.groups.length > 0 ? 0 : null
-	}
-
-	private computeOperationsOffset(group: GhostSuggestionEditOperation[]): GhostSuggestionEditOperationsOffset {
-		const { added, removed } = group.reduce(
-			(acc, op) => {
-				if (op.type === "+") {
-					return { added: acc.added + 1, removed: acc.removed }
-				} else if (op.type === "-") {
-					return { added: acc.added, removed: acc.removed + 1 }
-				}
-				return acc
-			},
-			{ added: 0, removed: 0 },
-		)
-		return { added, removed, offset: added - removed }
-	}
-
-	public deleteSelectedGroup() {
-		if (this.selectedGroup !== null && this.selectedGroup < this.groups.length) {
-			const deletedGroup = this.groups.splice(this.selectedGroup, 1)
-			const { offset } = this.computeOperationsOffset(deletedGroup[0])
-			// update deleted operations in the next groups
-			for (let i = this.selectedGroup; i < this.groups.length; i++) {
-				for (let j = 0; j < this.groups[i].length; j++) {
-					const op = this.groups[i][j]
-					if (op.type === "-") {
-						op.line = op.line + offset
-					}
-					op.oldLine = op.oldLine + offset
-				}
-			}
-			// reset selected group
-			this.selectedGroup = null
-		}
-	}
-
-	public selectNextGroup() {
-		if (this.selectedGroup === null) {
-			this.selectedGroup = 0
-		} else {
-			this.selectedGroup = (this.selectedGroup + 1) % this.groups.length
-		}
-	}
-
-	public selectPreviousGroup() {
-		if (this.selectedGroup === null) {
-			this.selectedGroup = this.groups.length - 1
-		} else {
-			this.selectedGroup = (this.selectedGroup - 1 + this.groups.length) % this.groups.length
-		}
-	}
-
-	public selectClosestGroup(selection: vscode.Selection) {
-		if (this.groups.length === 0) {
-			this.selectedGroup = null
-			return
-		}
-
-		console.log("GROUPS", this.groups)
-
-		let bestGroup: { groupIndex: number; distance: number } | null = null
-		const selectionStartLine = selection.start.line
-		const selectionEndLine = selection.end.line
-
-		// Find the group with minimum distance to the selection
-		for (let groupIndex = 0; groupIndex < this.groups.length; groupIndex++) {
-			const group = this.groups[groupIndex]
-			const groupLine = Math.min(...group.map((x) => x.oldLine))
-
-			// Calculate minimum distance from selection to any operation in this group
-			let distance = Infinity
-			if (groupLine < selectionStartLine) {
-				distance = selectionStartLine - groupLine
-			} else if (groupLine > selectionEndLine) {
-				distance = groupLine - selectionEndLine
-			} else {
-				distance = 0
-			}
-
-			// Check if this group is better than current best
-			if (bestGroup === null || distance < bestGroup.distance) {
-				bestGroup = { groupIndex, distance }
-			}
-			if (distance === 0) {
-				break
-			}
-		}
-
-		// Set the closest group as selected
-		if (bestGroup !== null) {
-			this.selectedGroup = bestGroup.groupIndex
-			console.log("BEST GROUP", this.groups[bestGroup.groupIndex])
-		}
-	}
-}
-
 export interface FillInAtCursorSuggestion {
 	text: string
 	prefix: string
@@ -265,7 +5,6 @@ export interface FillInAtCursorSuggestion {
 }
 
 export class GhostSuggestionsState {
-	private files = new Map<string, GhostSuggestionFile>()
 	private fillinAtCursorSuggestion: FillInAtCursorSuggestion | undefined = undefined
 
 	constructor() {}
@@ -278,38 +17,11 @@ export class GhostSuggestionsState {
 		return this.fillinAtCursorSuggestion
 	}
 
-	public addFile(fileUri: vscode.Uri) {
-		const key = fileUri.toString()
-		if (!this.files.has(key)) {
-			this.files.set(key, new GhostSuggestionFile(fileUri))
-		}
-		return this.files.get(key)!
-	}
-
-	public getFile(fileUri: vscode.Uri): GhostSuggestionFile | undefined {
-		return this.files.get(fileUri.toString())
-	}
-
-	public clear() {
-		this.files.clear()
-	}
-
 	public hasSuggestions(): boolean {
-		return this.files.size > 0
-	}
-
-	public validateFiles() {
-		for (const file of this.files.values()) {
-			if (file.isEmpty()) {
-				this.files.delete(file.fileUri.toString())
-			}
-		}
+		return !!this.fillinAtCursorSuggestion
 	}
 
-	public sortGroups() {
-		this.validateFiles()
-		for (const file of this.files.values()) {
-			file.sortGroups()
-		}
+	public clear(): void {
+		this.fillinAtCursorSuggestion = undefined
 	}
 }

+ 0 - 283
src/services/ghost/GhostWorkspaceEdit.ts

@@ -1,283 +0,0 @@
-import * as vscode from "vscode"
-import { GhostSuggestionEditOperation, GhostSuggestionEditOperationsOffset } from "./types"
-import { GhostSuggestionsState } from "./GhostSuggestions"
-
-export class GhostWorkspaceEdit {
-	private locked: boolean = false
-
-	private groupOperationsIntoBlocks = <T>(ops: T[], lineKey: keyof T): T[][] => {
-		if (ops.length === 0) {
-			return []
-		}
-		const blocks: T[][] = [[ops[0]]]
-		for (let i = 1; i < ops.length; i++) {
-			const op = ops[i]
-			const lastBlock = blocks[blocks.length - 1]
-			const lastOp = lastBlock[lastBlock.length - 1]
-			if (Number(op[lineKey]) === Number(lastOp[lineKey]) + 1) {
-				lastBlock.push(op)
-			} else if (Number(op[lineKey]) === Number(lastOp[lineKey])) {
-				lastBlock.push(op)
-			} else {
-				blocks.push([op])
-			}
-		}
-		return blocks
-	}
-
-	private async applyOperations(
-		documentUri: vscode.Uri,
-		operations: GhostSuggestionEditOperation[],
-		previousOperations: GhostSuggestionEditOperation[],
-	) {
-		const workspaceEdit = new vscode.WorkspaceEdit()
-		if (operations.length === 0) {
-			return // No operations to apply
-		}
-
-		const document = await vscode.workspace.openTextDocument(documentUri)
-		if (!document) {
-			console.log(`Could not open document: ${documentUri.toString()}`)
-			return
-		}
-		// --- 1. Calculate Initial State from Previous Operations ---
-		let originalLineCursor = 0
-		let finalLineCursor = 0
-
-		if (previousOperations.length > 0) {
-			const prevDeletes = previousOperations.filter((op) => op.type === "-").sort((a, b) => a.line - b.line)
-			const prevInserts = previousOperations.filter((op) => op.type === "+").sort((a, b) => a.line - b.line)
-			let prevDelPtr = 0
-			let prevInsPtr = 0
-
-			// "Dry run" the simulation on previous operations to set the cursors accurately.
-			while (prevDelPtr < prevDeletes.length || prevInsPtr < prevInserts.length) {
-				const nextDelLine = prevDeletes[prevDelPtr]?.line ?? Infinity
-				const nextInsLine = prevInserts[prevInsPtr]?.line ?? Infinity
-
-				if (nextDelLine <= originalLineCursor && nextDelLine !== Infinity) {
-					originalLineCursor++
-					prevDelPtr++
-				} else if (nextInsLine <= finalLineCursor && nextInsLine !== Infinity) {
-					finalLineCursor++
-					prevInsPtr++
-				} else if (nextDelLine === Infinity && nextInsLine === Infinity) {
-					break
-				} else {
-					originalLineCursor++
-					finalLineCursor++
-				}
-			}
-		}
-
-		// --- 2. Translate and Prepare Current Operations ---
-		const currentDeletes = operations.filter((op) => op.type === "-").sort((a, b) => a.line - b.line)
-		const currentInserts = operations.filter((op) => op.type === "+").sort((a, b) => a.line - b.line)
-		const translatedInsertOps: { originalLine: number; content: string }[] = []
-		let currDelPtr = 0
-		let currInsPtr = 0
-
-		// Run the simulation for the new operations, starting from the state calculated above.
-		while (currDelPtr < currentDeletes.length || currInsPtr < currentInserts.length) {
-			const nextDelLine = currentDeletes[currDelPtr]?.line ?? Infinity
-			const nextInsLine = currentInserts[currInsPtr]?.line ?? Infinity
-
-			if (nextDelLine <= originalLineCursor && nextDelLine !== Infinity) {
-				originalLineCursor++
-				currDelPtr++
-			} else if (nextInsLine <= finalLineCursor && nextInsLine !== Infinity) {
-				translatedInsertOps.push({
-					originalLine: originalLineCursor,
-					content: currentInserts[currInsPtr].content || "",
-				})
-				finalLineCursor++
-				currInsPtr++
-			} else if (nextDelLine === Infinity && nextInsLine === Infinity) {
-				break
-			} else {
-				originalLineCursor++
-				finalLineCursor++
-			}
-		}
-
-		// --- 3. Group and Apply Deletions ---
-		const deleteBlocks = this.groupOperationsIntoBlocks(currentDeletes, "line")
-		for (const block of deleteBlocks) {
-			const firstDeleteLine = block[0].line
-			const lastDeleteLine = block[block.length - 1].line
-			const startPosition = new vscode.Position(firstDeleteLine, 0)
-			let endPosition
-
-			if (lastDeleteLine >= document.lineCount - 1) {
-				endPosition = document.lineAt(lastDeleteLine).rangeIncludingLineBreak.end
-			} else {
-				endPosition = new vscode.Position(lastDeleteLine + 1, 0)
-			}
-			workspaceEdit.delete(documentUri, new vscode.Range(startPosition, endPosition))
-		}
-
-		// --- 4. Group and Apply Translated Insertions ---
-		const insertionBlocks = this.groupOperationsIntoBlocks(translatedInsertOps, "originalLine")
-		for (const block of insertionBlocks) {
-			const anchorLine = block[0].originalLine
-			const textToInsert = block.map((op) => op.content).join("\n") + "\n"
-			workspaceEdit.insert(documentUri, new vscode.Position(anchorLine, 0), textToInsert)
-		}
-
-		await vscode.workspace.applyEdit(workspaceEdit)
-	}
-
-	private async revertOperationsPlaceholder(documentUri: vscode.Uri, operations: GhostSuggestionEditOperation[]) {
-		let workspaceEdit = new vscode.WorkspaceEdit()
-		let deletedLines: number = 0
-		for (const op of operations) {
-			if (op.type === "-") {
-				deletedLines++
-			}
-			if (op.type === "+") {
-				const startPosition = new vscode.Position(op.line + deletedLines, 0)
-				const endPosition = new vscode.Position(op.line + deletedLines + 1, 0)
-				const range = new vscode.Range(startPosition, endPosition)
-				workspaceEdit.delete(documentUri, range)
-			}
-		}
-		await vscode.workspace.applyEdit(workspaceEdit)
-	}
-
-	private async applyOperationsPlaceholders(documentUri: vscode.Uri, operations: GhostSuggestionEditOperation[]) {
-		const workspaceEdit = new vscode.WorkspaceEdit()
-		const document = await vscode.workspace.openTextDocument(documentUri)
-		if (!document) {
-			console.log(`Could not open document: ${documentUri.toString()}`)
-			return
-		}
-
-		let lineOffset = 0
-		for (const op of operations) {
-			// Calculate the equivalent line in the *original* document.
-			const originalLine = op.line - lineOffset
-
-			// A quick guard against invalid operations.
-			if (originalLine < 0) {
-				continue
-			}
-
-			if (op.type === "+") {
-				const position = new vscode.Position(originalLine, 0)
-				const textToInsert = "\n"
-				workspaceEdit.insert(documentUri, position, textToInsert)
-				lineOffset++
-			}
-
-			if (op.type === "-") {
-				// Guard against deleting a line that doesn't exist.
-				if (originalLine >= document.lineCount) {
-					continue
-				}
-				lineOffset--
-			}
-		}
-
-		await vscode.workspace.applyEdit(workspaceEdit)
-	}
-
-	private getActiveFileOperations(suggestions: GhostSuggestionsState) {
-		const editor = vscode.window.activeTextEditor
-		if (!editor) {
-			return {
-				documentUri: null,
-				operations: [],
-			}
-		}
-		const documentUri = editor.document.uri
-		const operations = suggestions.getFile(documentUri)?.getAllOperations() || []
-		return {
-			documentUri,
-			operations,
-		}
-	}
-
-	private async getActiveFileSelectedOperations(suggestions: GhostSuggestionsState) {
-		const empty = {
-			documentUri: null,
-			operations: [],
-			previousOperations: [],
-		}
-		const editor = vscode.window.activeTextEditor
-		if (!editor) {
-			return empty
-		}
-		const documentUri = editor.document.uri
-		const suggestionsFile = suggestions.getFile(documentUri)
-		if (!suggestionsFile) {
-			return empty
-		}
-		const operations = suggestionsFile.getSelectedGroupOperations()
-		const previousOperations = suggestionsFile.getSelectedGroupPreviousOperations()
-		return {
-			documentUri,
-			operations,
-			previousOperations,
-		}
-	}
-
-	public isLocked(): boolean {
-		return this.locked
-	}
-
-	public async applySuggestions(suggestions: GhostSuggestionsState) {
-		if (this.locked) {
-			return
-		}
-		this.locked = true
-		const { documentUri, operations } = this.getActiveFileOperations(suggestions)
-		if (!documentUri || operations.length === 0) {
-			this.locked = false
-			return
-		}
-		await this.applyOperations(documentUri, operations, [])
-		this.locked = false
-	}
-
-	public async applySelectedSuggestions(suggestions: GhostSuggestionsState) {
-		if (this.locked) {
-			return
-		}
-		this.locked = true
-		const { documentUri, operations, previousOperations } = await this.getActiveFileSelectedOperations(suggestions)
-		if (!documentUri || operations.length === 0) {
-			this.locked = false
-			return
-		}
-		await this.applyOperations(documentUri, operations, previousOperations)
-		this.locked = false
-	}
-
-	public async revertSuggestionsPlaceholder(suggestions: GhostSuggestionsState): Promise<void> {
-		if (this.locked) {
-			return
-		}
-		this.locked = true
-		const { documentUri, operations } = this.getActiveFileOperations(suggestions)
-		if (!documentUri || operations.length === 0) {
-			this.locked = false
-			return
-		}
-		await this.revertOperationsPlaceholder(documentUri, operations)
-		this.locked = false
-	}
-
-	public async applySuggestionsPlaceholders(suggestions: GhostSuggestionsState) {
-		if (this.locked) {
-			return
-		}
-		this.locked = true
-		const { documentUri, operations } = await this.getActiveFileOperations(suggestions)
-		if (!documentUri || operations.length === 0) {
-			this.locked = false
-			return
-		}
-		await this.applyOperationsPlaceholders(documentUri, operations)
-		this.locked = false
-	}
-}

+ 0 - 76
src/services/ghost/__tests__/EditorConfiguration.test.ts

@@ -1,76 +0,0 @@
-import { describe, it, expect, beforeEach, vi } from "vitest"
-import * as vscode from "vscode"
-import { getEditorConfiguration } from "../EditorConfiguration"
-
-// Mock vscode module
-vi.mock("vscode", () => ({
-	workspace: {
-		getConfiguration: vi.fn(),
-	},
-}))
-
-describe("getEditorConfiguration", () => {
-	beforeEach(() => {
-		vi.clearAllMocks()
-	})
-	it("should convert absolute pixel lineHeight to multiplier when >= 8", () => {
-		const mockConfig = {
-			get: vi.fn((key: string) => {
-				switch (key) {
-					case "fontSize":
-						return 16
-					case "lineHeight":
-						return 24 // Absolute pixels
-					case "fontFamily":
-						return "Monaco, monospace"
-					default:
-						return undefined
-				}
-			}),
-		}
-		;(vscode.workspace.getConfiguration as any).mockReturnValue(mockConfig)
-
-		const config = getEditorConfiguration()
-		expect(config.lineHeight).toBe(1.5) // 24 / 16 = 1.5
-	})
-
-	it("should preserve multiplier lineHeight when < 8", () => {
-		const mockConfig = {
-			get: vi.fn((key: string) => {
-				switch (key) {
-					case "fontSize":
-						return 14
-					case "lineHeight":
-						return 1.5 // Already a multiplier
-					case "fontFamily":
-						return "Consolas, monospace"
-					default:
-						return undefined
-				}
-			}),
-		}
-		;(vscode.workspace.getConfiguration as any).mockReturnValue(mockConfig)
-
-		const config = getEditorConfiguration()
-		expect(config.lineHeight).toBe(1.5) // Should remain unchanged
-	})
-
-	it("should handle edge case of exactly 8 as absolute pixels", () => {
-		const mockConfig = {
-			get: vi.fn((key: string) => {
-				switch (key) {
-					case "fontSize":
-						return 16
-					case "lineHeight":
-						return 8 // Edge case: exactly 8
-					default:
-						return undefined
-				}
-			}),
-		}
-		;(vscode.workspace.getConfiguration as any).mockReturnValue(mockConfig)
-
-		const config = getEditorConfiguration()
-		expect(config.lineHeight).toBe(0.5) // 8 / 16 = 0.5
-	})
-})

+ 0 - 230
src/services/ghost/__tests__/GhostDecorations.test.ts

@@ -1,230 +0,0 @@
-import { describe, it, expect, beforeEach, beforeAll, vi } from "vitest"
-import * as vscode from "vscode"
-import { GhostDecorations } from "../GhostDecorations"
-import { GhostSuggestionsState } from "../GhostSuggestions"
-import { GhostSuggestionEditOperation } from "../types"
-import { initializeHighlighter } from "../utils/CodeHighlighter"
-
-// Mock the SVG decoration utilities
-vi.mock("../utils/createSVGDecorationType", () => ({
-	createSVGDecorationType: vi.fn().mockResolvedValue({
-		dispose: vi.fn(),
-	}),
-}))
-
-// Mock EditorConfiguration
-vi.mock("../EditorConfiguration", () => ({
-	getEditorConfiguration: vi.fn().mockReturnValue({
-		fontSize: 14,
-		fontFamily: "Consolas, 'Courier New', monospace",
-		lineHeight: 1.2,
-	}),
-}))
-
-// Mock text measurement utilities
-vi.mock("../utils/textMeasurement", () => ({
-	calculateContainerWidth: vi.fn().mockReturnValue(200),
-	calculateCharacterWidth: vi.fn().mockReturnValue(8),
-}))
-
-// Mock ThemeMapper
-vi.mock("../utils/ThemeMapper", () => ({
-	getThemeColors: vi.fn().mockReturnValue({
-		background: "#1e1e1e",
-		foreground: "#cccccc",
-	}),
-}))
-
-// Mock SvgRenderer
-vi.mock("../utils/SvgRenderer", () => ({
-	SvgRenderer: vi.fn().mockImplementation(() => ({
-		render: vi.fn().mockReturnValue("<svg>test</svg>"),
-	})),
-}))
-
-// Mock the utilities
-vi.mock("../utils/CodeHighlighter", () => ({
-	initializeHighlighter: vi.fn().mockResolvedValue(undefined),
-	getLanguageForDocument: vi.fn().mockReturnValue("typescript"),
-	generateHighlightedHtmlWithRanges: vi.fn().mockResolvedValue({
-		html: "<span>highlighted code</span>",
-	}),
-}))
-
-// Mock vscode module
-vi.mock("vscode", () => ({
-	window: {
-		activeTextEditor: null,
-		activeColorTheme: {
-			kind: 2, // Dark theme
-		},
-		createTextEditorDecorationType: vi.fn(() => ({
-			dispose: vi.fn(),
-		})),
-	},
-	workspace: {
-		getConfiguration: vi.fn(() => ({
-			get: vi.fn((key: string) => {
-				switch (key) {
-					case "fontSize":
-						return 14
-					case "fontFamily":
-						return "Consolas, 'Courier New', monospace"
-					case "lineHeight":
-						return 1.2
-					default:
-						return undefined
-				}
-			}),
-		})),
-	},
-	ColorThemeKind: {
-		Light: 1,
-		Dark: 2,
-		HighContrast: 3,
-		HighContrastLight: 4,
-	},
-	ThemeColor: vi.fn((color: any) => ({ id: color })),
-	OverviewRulerLane: {
-		Right: 7,
-	},
-	Range: vi.fn((start: any, end: any) => ({ start, end })),
-	Position: vi.fn((line: any, character: any) => ({ line, character })),
-	Selection: vi.fn((startLine: any, startChar: any, endLine: any, endChar: any) => ({
-		start: { line: startLine, character: startChar },
-		end: { line: endLine, character: endChar },
-	})),
-	Uri: {
-		file: vi.fn((path: string) => ({ fsPath: path, toString: () => path })),
-		parse: vi.fn((uri: string) => ({ toString: () => uri })),
-	},
-	DecorationRangeBehavior: {
-		ClosedClosed: 1,
-	},
-}))
-
-describe("GhostDecorations", () => {
-	let ghostDecorations: GhostDecorations
-	let mockEditor: any
-	let mockDocument: any
-	let ghostSuggestions: GhostSuggestionsState
-	let mockUri: vscode.Uri
-
-	beforeAll(async () => {
-		await initializeHighlighter()
-	})
-
-	beforeEach(() => {
-		ghostDecorations = new GhostDecorations()
-		ghostSuggestions = new GhostSuggestionsState()
-		mockUri = vscode.Uri.file("/test/file.ts")
-
-		// Create mock document with 5 lines
-		mockDocument = {
-			uri: mockUri,
-			lineCount: 5,
-			lineAt: vi.fn((lineNumber: number) => {
-				if (lineNumber < 0 || lineNumber >= 5) {
-					throw new Error(`Line ${lineNumber} does not exist`)
-				}
-				return {
-					range: {
-						start: { line: lineNumber, character: 0 },
-						end: { line: lineNumber, character: 10 },
-					},
-					text: `line ${lineNumber}`,
-				}
-			}),
-		}
-
-		mockEditor = {
-			document: mockDocument,
-			setDecorations: vi.fn(),
-		}
-
-		// Mock activeTextEditor
-		;(vscode.window as any).activeTextEditor = mockEditor
-	})
-
-	afterEach(() => {
-		vi.clearAllMocks()
-	})
-
-	describe("displayAdditionsOperationGroup", () => {
-		it("should handle additions at the end of document without throwing error", async () => {
-			const file = ghostSuggestions.addFile(mockUri)
-
-			// Add operation that tries to add content after the last line (line 5, but document only has lines 0-4)
-			const addOp: GhostSuggestionEditOperation = {
-				line: 6,
-				oldLine: 5, // This line exists (0-indexed, so line 5 is the 6th line, but document only has 5 lines 0-4)
-				newLine: 6,
-				type: "+",
-				content: "new line at end",
-			}
-
-			file.addOperation(addOp)
-			file.selectClosestGroup(new vscode.Selection(5, 0, 5, 0))
-
-			// This should not throw an error
-			await expect(async () => {
-				await ghostDecorations.displaySuggestions(ghostSuggestions)
-			}).not.toThrow()
-
-			// Should have called setDecorations
-			expect(mockEditor.setDecorations).toHaveBeenCalled()
-		})
-
-		it("should handle additions beyond document end gracefully", () => {
-			const file = ghostSuggestions.addFile(mockUri)
-
-			// Add operation that tries to add content way beyond the document end
-			const addOp: GhostSuggestionEditOperation = {
-				line: 10,
-				oldLine: 10, // Way beyond document end
-				newLine: 10,
-				type: "+",
-				content: "new line way beyond end",
-			}
-
-			file.addOperation(addOp)
-			file.selectClosestGroup(new vscode.Selection(5, 0, 5, 0))
-
-			// This should not throw an error
-			expect(() => {
-				ghostDecorations.displaySuggestions(ghostSuggestions)
-			}).not.toThrow()
-		})
-
-		it("should work correctly for additions within document bounds", async () => {
-			const file = ghostSuggestions.addFile(mockUri)
-
-			// Add operation within document bounds
-			const addOp: GhostSuggestionEditOperation = {
-				line: 3,
-				oldLine: 2, // This line exists
-				newLine: 3,
-				type: "+",
-				content: "new line in middle",
-			}
-
-			file.addOperation(addOp)
-			file.selectClosestGroup(new vscode.Selection(2, 0, 2, 0))
-
-			// This should work fine
-			await expect(async () => {
-				await ghostDecorations.displaySuggestions(ghostSuggestions)
-			}).not.toThrow()
-
-			// Should have called setDecorations with SVG decorations for additions
-			expect(mockEditor.setDecorations).toHaveBeenCalledWith(
-				expect.anything(), // SVG decoration type
-				expect.arrayContaining([
-					expect.objectContaining({
-						range: expect.anything(),
-					}),
-				]),
-			)
-		})
-	})
-})

+ 15 - 16
src/services/ghost/__tests__/GhostProvider.spec.ts

@@ -4,7 +4,6 @@ import * as path from "node:path"
 import { MockWorkspace } from "./MockWorkspace"
 import * as vscode from "vscode"
 import { GhostStreamingParser } from "../GhostStreamingParser"
-import { GhostWorkspaceEdit } from "../GhostWorkspaceEdit"
 import { GhostSuggestionContext } from "../types"
 
 vi.mock("vscode", () => ({
@@ -69,13 +68,11 @@ vi.mock("vscode", () => ({
 describe("GhostProvider", () => {
 	let mockWorkspace: MockWorkspace
 	let streamingParser: GhostStreamingParser
-	let workspaceEdit: GhostWorkspaceEdit
 
 	beforeEach(() => {
 		vi.clearAllMocks()
 		streamingParser = new GhostStreamingParser()
 		mockWorkspace = new MockWorkspace()
-		workspaceEdit = new GhostWorkspaceEdit()
 
 		vi.mocked(vscode.workspace.openTextDocument).mockImplementation(async (uri: any) => {
 			const uriObj = typeof uri === "string" ? vscode.Uri.parse(uri) : uri
@@ -126,17 +123,6 @@ describe("GhostProvider", () => {
 		return { testUri, context, mockDocument }
 	}
 
-	async function parseAndApplySuggestions(response: string, context: GhostSuggestionContext) {
-		// Initialize streaming parser
-		streamingParser.initialize(context)
-
-		// Process the complete response
-		const result = streamingParser.parseResponse(response, "", "")
-
-		// Apply the suggestions
-		await workspaceEdit.applySuggestions(result.suggestions)
-	}
-
 	// Test cases directory for file-based tests
 	const TEST_CASES_DIR = path.join(__dirname, "__test_cases__")
 
@@ -152,8 +138,21 @@ describe("GhostProvider", () => {
 		const response = fs.readFileSync(diffFilePath, "utf8")
 		const expectedContent = fs.readFileSync(expectedFilePath, "utf8")
 
-		const { testUri, context } = await setupTestDocument(`${testCaseName}/input.js`, initialContent)
-		await parseAndApplySuggestions(response, context)
+		const { testUri, context, mockDocument } = await setupTestDocument(`${testCaseName}/input.js`, initialContent)
+		
+		// Parse and apply suggestions
+		streamingParser.initialize(context)
+		const parseResult = streamingParser.parseResponse(response, "", "")
+		
+		// Apply the changes if we have suggestions
+		if (parseResult.hasNewSuggestions) {
+			const fillInSuggestion = parseResult.suggestions.getFillInAtCursor()
+			if (fillInSuggestion) {
+				// For FIM (Fill-In-Middle) suggestions, reconstruct the full content
+				const newContent = fillInSuggestion.prefix + fillInSuggestion.text + fillInSuggestion.suffix
+				;(mockDocument as any).updateContent(newContent)
+			}
+		}
 
 		const finalContent = mockWorkspace.getDocumentContent(testUri)
 		// Compare the normalized content

+ 3 - 4
src/services/ghost/__tests__/GhostStreamingParser.test.ts

@@ -623,7 +623,7 @@ function fibonacci(n: number): number {
 
 			const result = parser.parseResponse(change, prefix, suffix)
 
-			expect(result.suggestions.hasSuggestions()).toBe(true)
+			expect(result.suggestions.hasSuggestions()).toBe(false)
 			// Check that FIM was NOT set
 			const fimContent = result.suggestions.getFillInAtCursor()
 			expect(fimContent).toBeUndefined()
@@ -649,7 +649,7 @@ function fibonacci(n: number): number {
 
 			const result = parser.parseResponse(change, prefix, suffix)
 
-			expect(result.suggestions.hasSuggestions()).toBe(true)
+			expect(result.suggestions.hasSuggestions()).toBe(false)
 			// Check that FIM was NOT set
 			const fimContent = result.suggestions.getFillInAtCursor()
 			expect(fimContent).toBeUndefined()
@@ -675,7 +675,7 @@ function fibonacci(n: number): number {
 
 			const result = parser.parseResponse(change, prefix, suffix)
 
-			expect(result.suggestions.hasSuggestions()).toBe(true)
+			expect(result.suggestions.hasSuggestions()).toBe(false)
 			// Check that FIM was NOT set
 			const fimContent = result.suggestions.getFillInAtCursor()
 			expect(fimContent).toBeUndefined()
@@ -761,7 +761,6 @@ function fibonacci(n: number): number {
 
 			const result = parser.parseResponse(change, prefix, suffix)
 
-			expect(result.suggestions.hasSuggestions()).toBe(false)
 			const fimContent = result.suggestions.getFillInAtCursor()
 			// When no changes are applied, FIM is set to empty string (the entire unchanged document matches prefix+suffix)
 			expect(fimContent).toEqual({

+ 0 - 582
src/services/ghost/__tests__/GhostSuggestions.test.ts

@@ -1,582 +0,0 @@
-import * as vscode from "vscode"
-import { GhostSuggestionsState } from "../GhostSuggestions"
-import { GhostSuggestionEditOperation } from "../types"
-
-describe("GhostSuggestions", () => {
-	let ghostSuggestions: GhostSuggestionsState
-	let mockUri: vscode.Uri
-
-	beforeEach(() => {
-		ghostSuggestions = new GhostSuggestionsState()
-		mockUri = vscode.Uri.file("/test/file.ts")
-	})
-
-	describe("selectClosestGroup", () => {
-		it("should select the closest group to a selection", () => {
-			const file = ghostSuggestions.addFile(mockUri)
-
-			// Add operations with large distances to ensure separate groups
-			const operation1: GhostSuggestionEditOperation = {
-				line: 1,
-				oldLine: 1,
-				newLine: 1,
-				type: "+",
-				content: "line 1",
-			}
-			const operation2: GhostSuggestionEditOperation = {
-				line: 50,
-				oldLine: 50,
-				newLine: 50,
-				type: "+",
-				content: "line 50",
-			}
-			const operation3: GhostSuggestionEditOperation = {
-				line: 100,
-				oldLine: 100,
-				newLine: 100,
-				type: "+",
-				content: "line 100",
-			}
-
-			file.addOperation(operation1)
-			file.addOperation(operation2)
-			file.addOperation(operation3)
-
-			file.sortGroups()
-
-			const groups = file.getGroupsOperations()
-
-			// Test the selectClosestGroup functionality regardless of how many groups exist
-			if (groups.length === 1) {
-				// All operations are in one group - test that it selects the group
-				const selection = new vscode.Selection(45, 0, 55, 0) // Closest to operation2 at line 50
-				file.selectClosestGroup(selection)
-				expect(file.getSelectedGroup()).toBe(0) // Only group
-			} else {
-				// Multiple groups exist - test that it selects the closest one
-				expect(groups.length).toBeGreaterThan(1)
-				const selection = new vscode.Selection(45, 0, 55, 0) // Closest to operation2 at line 50
-				file.selectClosestGroup(selection)
-				// Should select whichever group contains the operation closest to line 50
-				expect(file.getSelectedGroup()).not.toBeNull()
-			}
-		})
-
-		it("should select group when selection overlaps with operation", () => {
-			const file = ghostSuggestions.addFile(mockUri)
-
-			const operation1: GhostSuggestionEditOperation = {
-				line: 5,
-				oldLine: 5,
-				newLine: 5,
-				type: "+",
-				content: "line 5",
-			}
-			const operation2: GhostSuggestionEditOperation = {
-				line: 50,
-				oldLine: 50,
-				newLine: 50,
-				type: "+",
-				content: "line 50",
-			}
-
-			file.addOperation(operation1)
-			file.addOperation(operation2)
-
-			file.sortGroups()
-
-			// Create a selection that includes line 50
-			const selection = new vscode.Selection(49, 0, 51, 0)
-			file.selectClosestGroup(selection)
-
-			// Should select a group (distance is 0 since selection overlaps)
-			expect(file.getSelectedGroup()).not.toBeNull()
-		})
-
-		it("should select first group when selection is before all operations", () => {
-			const file = ghostSuggestions.addFile(mockUri)
-
-			const operation1: GhostSuggestionEditOperation = {
-				line: 10,
-				oldLine: 10,
-				newLine: 10,
-				type: "+",
-				content: "line 10",
-			}
-			const operation2: GhostSuggestionEditOperation = {
-				line: 20,
-				oldLine: 20,
-				newLine: 20,
-				type: "+",
-				content: "line 20",
-			}
-
-			file.addOperation(operation1)
-			file.addOperation(operation2)
-
-			file.sortGroups()
-
-			// Create a selection before all operations
-			const selection = new vscode.Selection(1, 0, 3, 0)
-			file.selectClosestGroup(selection)
-
-			expect(file.getSelectedGroup()).toBe(0) // First group (operation1)
-		})
-
-		it("should select group closest to selection when selection is after all operations", () => {
-			const file = ghostSuggestions.addFile(mockUri)
-
-			const operation1: GhostSuggestionEditOperation = {
-				line: 10,
-				oldLine: 10,
-				newLine: 10,
-				type: "+",
-				content: "line 10",
-			}
-			const operation2: GhostSuggestionEditOperation = {
-				line: 50,
-				oldLine: 50,
-				newLine: 50,
-				type: "+",
-				content: "line 50",
-			}
-
-			file.addOperation(operation1)
-			file.addOperation(operation2)
-
-			file.sortGroups()
-
-			// Create a selection after all operations (closer to operation2)
-			const selection = new vscode.Selection(60, 0, 65, 0)
-			file.selectClosestGroup(selection)
-
-			// Should select a group (the one with operation closest to the selection)
-			expect(file.getSelectedGroup()).not.toBeNull()
-		})
-
-		it("should handle empty groups", () => {
-			const file = ghostSuggestions.addFile(mockUri)
-
-			const selection = new vscode.Selection(10, 0, 15, 0)
-			file.selectClosestGroup(selection)
-
-			expect(file.getSelectedGroup()).toBeNull()
-		})
-
-		it("should select group with multiple operations closest to selection", () => {
-			const file = ghostSuggestions.addFile(mockUri)
-
-			// Create a group with multiple operations
-			const operation1: GhostSuggestionEditOperation = {
-				line: 5,
-				oldLine: 5,
-				newLine: 5,
-				type: "+",
-				content: "line 5",
-			}
-			const operation2: GhostSuggestionEditOperation = {
-				line: 6,
-				oldLine: 6,
-				newLine: 6,
-				type: "+",
-				content: "line 6",
-			}
-			const operation3: GhostSuggestionEditOperation = {
-				line: 20,
-				oldLine: 20,
-				newLine: 20,
-				type: "+",
-				content: "line 20",
-			}
-
-			file.addOperation(operation1)
-			file.addOperation(operation2) // Should be in same group as operation1
-			file.addOperation(operation3) // Should be in different group
-
-			file.sortGroups()
-
-			// Create a selection closer to the first group
-			const selection = new vscode.Selection(8, 0, 10, 0)
-			file.selectClosestGroup(selection)
-
-			expect(file.getSelectedGroup()).toBe(0) // First group
-		})
-	})
-
-	describe("addOperation grouping rules", () => {
-		it("should create modification group (delete on line N, add on line N+1) with highest priority", () => {
-			const file = ghostSuggestions.addFile(mockUri)
-
-			// Add a delete operation on line 5
-			const deleteOp: GhostSuggestionEditOperation = {
-				line: 5,
-				oldLine: 5,
-				newLine: 6,
-				type: "-",
-				content: "old content",
-			}
-			file.addOperation(deleteOp)
-
-			// Add an add operation on line 6 (next line) - should form modification group
-			const addOp: GhostSuggestionEditOperation = {
-				line: 6,
-				oldLine: 5,
-				newLine: 6,
-				type: "+",
-				content: "new content",
-			}
-			file.addOperation(addOp)
-
-			const groups = file.getGroupsOperations()
-			expect(groups.length).toBe(1)
-			expect(groups[0].length).toBe(2)
-			expect(groups[0]).toContainEqual(deleteOp)
-			expect(groups[0]).toContainEqual(addOp)
-		})
-
-		it("should move operation from existing group to create modification group", () => {
-			const file = ghostSuggestions.addFile(mockUri)
-
-			// Add consecutive delete operations (should be in same group)
-			const deleteOp1: GhostSuggestionEditOperation = {
-				line: 5,
-				oldLine: 5,
-				newLine: 6,
-				type: "-",
-				content: "line 5",
-			}
-			const deleteOp2: GhostSuggestionEditOperation = {
-				line: 6,
-				oldLine: 6,
-				newLine: 6,
-				type: "-",
-				content: "line 6",
-			}
-			file.addOperation(deleteOp1)
-			file.addOperation(deleteOp2)
-
-			// Add an add operation on line 6 (after deleteOp1) - should move deleteOp1 to new modification group
-			const addOp: GhostSuggestionEditOperation = {
-				line: 6,
-				oldLine: 5,
-				newLine: 6,
-				type: "+",
-				content: "new line 6",
-			}
-			file.addOperation(addOp)
-
-			const groups = file.getGroupsOperations()
-			expect(groups.length).toBe(2)
-
-			// Find the modification group
-			const modificationGroup = groups.find(
-				(group) => group.some((op) => op.type === "+") && group.some((op) => op.type === "-"),
-			)
-			expect(modificationGroup).toBeDefined()
-			expect(modificationGroup!.length).toBe(2)
-			expect(modificationGroup).toContainEqual(deleteOp1)
-			expect(modificationGroup).toContainEqual(addOp)
-
-			// Find the delete-only group
-			const deleteGroup = groups.find((group) => group.every((op) => op.type === "-") && group.length === 1)
-			expect(deleteGroup).toBeDefined()
-			expect(deleteGroup).toContainEqual(deleteOp2)
-		})
-
-		it("should group consecutive delete operations", () => {
-			const file = ghostSuggestions.addFile(mockUri)
-
-			const deleteOp1: GhostSuggestionEditOperation = {
-				line: 5,
-				oldLine: 5,
-				newLine: 5,
-				type: "-",
-				content: "line 5",
-			}
-			const deleteOp2: GhostSuggestionEditOperation = {
-				line: 6,
-				oldLine: 6,
-				newLine: 6,
-				type: "-",
-				content: "line 6",
-			}
-			const deleteOp3: GhostSuggestionEditOperation = {
-				line: 7,
-				oldLine: 7,
-				newLine: 7,
-				type: "-",
-				content: "line 7",
-			}
-
-			file.addOperation(deleteOp1)
-			file.addOperation(deleteOp2)
-			file.addOperation(deleteOp3)
-
-			const groups = file.getGroupsOperations()
-			expect(groups.length).toBe(1)
-			expect(groups[0].length).toBe(3)
-			expect(groups[0]).toContainEqual(deleteOp1)
-			expect(groups[0]).toContainEqual(deleteOp2)
-			expect(groups[0]).toContainEqual(deleteOp3)
-		})
-
-		it("should group consecutive add operations", () => {
-			const file = ghostSuggestions.addFile(mockUri)
-
-			const addOp1: GhostSuggestionEditOperation = {
-				line: 5,
-				oldLine: 5,
-				newLine: 5,
-				type: "+",
-				content: "line 5",
-			}
-			const addOp2: GhostSuggestionEditOperation = {
-				line: 6,
-				oldLine: 6,
-				newLine: 6,
-				type: "+",
-				content: "line 6",
-			}
-			const addOp3: GhostSuggestionEditOperation = {
-				line: 7,
-				oldLine: 7,
-				newLine: 7,
-				type: "+",
-				content: "line 7",
-			}
-
-			file.addOperation(addOp1)
-			file.addOperation(addOp2)
-			file.addOperation(addOp3)
-
-			const groups = file.getGroupsOperations()
-			expect(groups.length).toBe(1)
-			expect(groups[0].length).toBe(3)
-			expect(groups[0]).toContainEqual(addOp1)
-			expect(groups[0]).toContainEqual(addOp2)
-			expect(groups[0]).toContainEqual(addOp3)
-		})
-
-		it("should create separate groups for non-consecutive operations", () => {
-			const file = ghostSuggestions.addFile(mockUri)
-
-			const addOp1: GhostSuggestionEditOperation = {
-				line: 5,
-				oldLine: 5,
-				newLine: 5,
-				type: "+",
-				content: "line 5",
-			}
-			const addOp2: GhostSuggestionEditOperation = {
-				line: 10,
-				oldLine: 10,
-				newLine: 10,
-				type: "+",
-				content: "line 10",
-			} // Gap of 5 lines
-
-			file.addOperation(addOp1)
-			file.addOperation(addOp2)
-
-			const groups = file.getGroupsOperations()
-			expect(groups.length).toBe(2)
-			expect(groups[0]).toContainEqual(addOp1)
-			expect(groups[1]).toContainEqual(addOp2)
-		})
-
-		it("should create modification group when delete is followed by add on next line", () => {
-			const file = ghostSuggestions.addFile(mockUri)
-
-			const deleteOp: GhostSuggestionEditOperation = {
-				line: 5,
-				oldLine: 5,
-				newLine: 6,
-				type: "-",
-				content: "line 5",
-			}
-			const addOp: GhostSuggestionEditOperation = {
-				line: 6,
-				oldLine: 5,
-				newLine: 6,
-				type: "+",
-				content: "line 6",
-			} // Next line - should form modification group
-
-			file.addOperation(deleteOp)
-			file.addOperation(addOp)
-
-			const groups = file.getGroupsOperations()
-			expect(groups.length).toBe(1) // Should be in one modification group
-			expect(groups[0].length).toBe(2)
-			expect(groups[0]).toContainEqual(deleteOp)
-			expect(groups[0]).toContainEqual(addOp)
-		})
-
-		it("should not group different operation types when not consecutive", () => {
-			const file = ghostSuggestions.addFile(mockUri)
-
-			const deleteOp: GhostSuggestionEditOperation = {
-				line: 5,
-				oldLine: 5,
-				newLine: 5,
-				type: "-",
-				content: "line 5",
-			}
-			const addOp: GhostSuggestionEditOperation = {
-				line: 7,
-				oldLine: 7,
-				newLine: 7,
-				type: "+",
-				content: "line 7",
-			} // Gap of 1 line - should not form modification group
-
-			file.addOperation(deleteOp)
-			file.addOperation(addOp)
-
-			const groups = file.getGroupsOperations()
-			expect(groups.length).toBe(2)
-			expect(groups[0]).toContainEqual(deleteOp)
-			expect(groups[1]).toContainEqual(addOp)
-		})
-
-		it("should handle reverse consecutive operations (adding before existing)", () => {
-			const file = ghostSuggestions.addFile(mockUri)
-
-			const addOp1: GhostSuggestionEditOperation = {
-				line: 6,
-				oldLine: 6,
-				newLine: 6,
-				type: "+",
-				content: "line 6",
-			}
-			const addOp2: GhostSuggestionEditOperation = {
-				line: 5,
-				oldLine: 5,
-				newLine: 5,
-				type: "+",
-				content: "line 5",
-			} // Before existing
-
-			file.addOperation(addOp1)
-			file.addOperation(addOp2)
-
-			const groups = file.getGroupsOperations()
-			expect(groups.length).toBe(1)
-			expect(groups[0].length).toBe(2)
-			expect(groups[0]).toContainEqual(addOp1)
-			expect(groups[0]).toContainEqual(addOp2)
-		})
-
-		it("should prioritize modification groups over same-type groups", () => {
-			const file = ghostSuggestions.addFile(mockUri)
-
-			// Create a group of consecutive add operations
-			const addOp1: GhostSuggestionEditOperation = {
-				line: 6,
-				oldLine: 5,
-				newLine: 6,
-				type: "+",
-				content: "line 6",
-			}
-			const addOp2: GhostSuggestionEditOperation = {
-				line: 7,
-				oldLine: 7,
-				newLine: 7,
-				type: "+",
-				content: "line 7",
-			}
-			file.addOperation(addOp1)
-			file.addOperation(addOp2)
-
-			// Add a delete operation on line 5 - should move addOp1 to modification group (delete line 5, add line 6)
-			const deleteOp: GhostSuggestionEditOperation = {
-				line: 5,
-				oldLine: 5,
-				newLine: 6,
-				type: "-",
-				content: "old line 5",
-			}
-			file.addOperation(deleteOp)
-
-			const groups = file.getGroupsOperations()
-			expect(groups.length).toBe(2)
-
-			// Find modification group
-			const modificationGroup = groups.find(
-				(group) => group.some((op) => op.type === "+") && group.some((op) => op.type === "-"),
-			)
-			expect(modificationGroup).toBeDefined()
-			expect(modificationGroup!.length).toBe(2)
-			expect(modificationGroup).toContainEqual(addOp1)
-			expect(modificationGroup).toContainEqual(deleteOp)
-
-			// Find remaining add group
-			const addGroup = groups.find((group) => group.every((op) => op.type === "+") && group.length === 1)
-			expect(addGroup).toBeDefined()
-			expect(addGroup).toContainEqual(addOp2)
-		})
-
-		it("should handle complex mixed operations scenario", () => {
-			const file = ghostSuggestions.addFile(mockUri)
-
-			// Add operations in mixed order
-			const deleteOp1: GhostSuggestionEditOperation = {
-				line: 5,
-				oldLine: 5,
-				newLine: 6,
-				type: "-",
-				content: "line 5",
-			}
-			const deleteOp2: GhostSuggestionEditOperation = {
-				line: 7,
-				oldLine: 7,
-				newLine: 7,
-				type: "-",
-				content: "line 7",
-			}
-			const addOp1: GhostSuggestionEditOperation = {
-				line: 10,
-				oldLine: 10,
-				newLine: 10,
-				type: "+",
-				content: "line 10",
-			}
-			const addOp2: GhostSuggestionEditOperation = {
-				line: 11,
-				oldLine: 11,
-				newLine: 11,
-				type: "+",
-				content: "line 11",
-			}
-			const modifyAdd: GhostSuggestionEditOperation = {
-				line: 6,
-				oldLine: 5,
-				newLine: 6,
-				type: "+",
-				content: "new line 6",
-			}
-
-			file.addOperation(deleteOp1)
-			file.addOperation(deleteOp2)
-			file.addOperation(addOp1)
-			file.addOperation(addOp2)
-			file.addOperation(modifyAdd) // Should create modification group with deleteOp1 (delete line 5, add line 6)
-
-			const groups = file.getGroupsOperations()
-			expect(groups.length).toBe(3)
-
-			// Should have: modification group (delete line 5, add line 6), delete group (line 7), add group (lines 10-11)
-			const modificationGroup = groups.find(
-				(group) => group.some((op) => op.type === "+") && group.some((op) => op.type === "-"),
-			)
-			expect(modificationGroup).toBeDefined()
-			expect(modificationGroup!.length).toBe(2)
-
-			const deleteGroup = groups.find((group) => group.every((op) => op.type === "-") && group.length === 1)
-			expect(deleteGroup).toBeDefined()
-
-			const addGroup = groups.find((group) => group.every((op) => op.type === "+") && group.length === 2)
-			expect(addGroup).toBeDefined()
-		})
-	})
-})

+ 0 - 150
src/services/ghost/__tests__/createSVGDecorationType.spec.ts

@@ -1,150 +0,0 @@
-import { describe, it, expect, beforeEach, beforeAll, vi } from "vitest"
-import * as vscode from "vscode"
-import { createSVGDecorationType, type SVGDecorationContent } from "../utils/createSVGDecorationType"
-import { initializeHighlighter } from "../utils/CodeHighlighter"
-
-// Mock the utilities
-vi.mock("../utils/CodeHighlighter", () => ({
-	initializeHighlighter: vi.fn().mockResolvedValue(undefined),
-	getLanguageForDocument: vi.fn().mockReturnValue("typescript"),
-	generateHighlightedHtmlWithRanges: vi.fn().mockResolvedValue({
-		html: "<span>highlighted code</span>",
-	}),
-}))
-
-vi.mock("../utils/SvgRenderer", () => ({
-	SvgRenderer: vi.fn().mockImplementation(() => ({
-		render: vi.fn().mockReturnValue("<svg><text>Mock SVG Content</text></svg>"),
-	})),
-}))
-
-// Mock vscode module
-vi.mock("vscode", () => ({
-	window: {
-		createTextEditorDecorationType: vi.fn(() => ({
-			dispose: vi.fn(),
-		})),
-		activeColorTheme: {
-			kind: 1, // Dark theme
-		},
-	},
-	workspace: {
-		getConfiguration: vi.fn(() => ({
-			get: vi.fn((key: string) => {
-				switch (key) {
-					case "fontSize":
-						return 14
-					case "fontFamily":
-						return "Consolas, 'Courier New', monospace"
-					case "lineHeight":
-						return 1.2
-					default:
-						return undefined
-				}
-			}),
-		})),
-	},
-	Uri: {
-		parse: vi.fn((uri: string) => ({ toString: () => uri })),
-	},
-	DecorationRangeBehavior: {
-		ClosedClosed: 1,
-	},
-	ColorThemeKind: {
-		Dark: 1,
-		Light: 2,
-		HighContrast: 3,
-		HighContrastLight: 4,
-	},
-	ThemeColor: vi.fn((color: any) => ({ id: color })),
-	OverviewRulerLane: {
-		Right: 7,
-	},
-}))
-
-describe("createSVGDecorationType", () => {
-	let mockDocument: vscode.TextDocument
-
-	beforeAll(async () => {
-		await initializeHighlighter()
-	})
-
-	beforeEach(() => {
-		mockDocument = {
-			uri: { fsPath: "/test/file.ts" },
-			languageId: "typescript",
-		} as vscode.TextDocument
-		vi.clearAllMocks()
-	})
-	it("should create decoration type with simple text content", async () => {
-		const content: SVGDecorationContent = {
-			text: "console.log('hello')",
-			backgroundRanges: [{ start: 0, end: 5, type: "modified" }],
-		}
-		const decorationType = await createSVGDecorationType(content, mockDocument)
-
-		expect(vscode.window.createTextEditorDecorationType).toHaveBeenCalledWith(
-			expect.objectContaining({
-				after: expect.objectContaining({
-					contentIconPath: expect.objectContaining({ toString: expect.any(Function) }),
-					width: expect.stringMatching(/200px/),
-					height: expect.stringMatching(/17px/),
-				}),
-			}),
-		)
-		expect(decorationType).toBeDefined()
-	})
-
-	it("should create decoration type with multi-line content", async () => {
-		const content: SVGDecorationContent = {
-			text: "line1\nline2\nline3",
-			backgroundRanges: [{ start: 0, end: 5, type: "modified" }],
-		}
-
-		const decorationType = await createSVGDecorationType(content, mockDocument)
-		expect(decorationType).toBeDefined()
-	})
-
-	it("should handle empty background ranges", async () => {
-		const content: SVGDecorationContent = {
-			text: "some text",
-			backgroundRanges: [],
-		}
-
-		const decorationType = await createSVGDecorationType(content, mockDocument)
-		expect(decorationType).toBeDefined()
-	})
-
-	it("should apply custom options", async () => {
-		const content: SVGDecorationContent = {
-			text: "test content",
-			backgroundRanges: [{ start: 0, end: 4, type: "modified" }],
-		}
-		const options = { marginTop: 10, marginLeft: 20 }
-
-		const decorationType = await createSVGDecorationType(content, mockDocument, options)
-		expect(vscode.window.createTextEditorDecorationType).toHaveBeenCalledWith(
-			expect.objectContaining({
-				after: expect.objectContaining({
-					border: expect.stringContaining("margin-top: 10px"),
-				}),
-			}),
-		)
-		expect(decorationType).toBeDefined()
-	})
-
-	it("should handle code highlighting errors gracefully", async () => {
-		// Mock the code highlighter to throw an error
-		const { generateHighlightedHtmlWithRanges } = await import("../utils/CodeHighlighter")
-		;(generateHighlightedHtmlWithRanges as any).mockRejectedValueOnce(new Error("Highlighting failed"))
-
-		const content: SVGDecorationContent = {
-			text: "failing code",
-			backgroundRanges: [{ start: 0, end: 4, type: "modified" }],
-		}
-
-		// Should not throw, but fall back to plain text
-		const decorationType = await createSVGDecorationType(content, mockDocument)
-		expect(decorationType).toBeDefined()
-	})
-})

+ 0 - 16
src/services/ghost/types.ts

@@ -35,22 +35,6 @@ export interface GhostDocumentStoreItem {
 	recentActions?: UserAction[]
 }
 
-export type GhostSuggestionEditOperationType = "+" | "-"
-
-export interface GhostSuggestionEditOperation {
-	type: GhostSuggestionEditOperationType
-	line: number
-	oldLine: number
-	newLine: number
-	content: string
-}
-
-export interface GhostSuggestionEditOperationsOffset {
-	added: number
-	removed: number
-	offset: number
-}
-
 export interface GhostSuggestionContext {
 	document: vscode.TextDocument
 	editor?: vscode.TextEditor

+ 0 - 189
src/services/ghost/utils/CharacterDiff.ts

@@ -1,189 +0,0 @@
-import { diffChars, diffWords } from "diff"
-
-/**
- * Represents a range of text with highlighting information for diff visualization
- */
-export interface BackgroundRange {
-	/** Starting position (inclusive) */
-	start: number
-	/** Ending position (exclusive) */
-	end: number
-	/** Type of change for this range */
-	type: "unchanged" | "added" | "removed" | "modified"
-}
-
-/**
- * Calculate text diff with intelligent semantic grouping.
- *
- * Hybrid approach: uses word-level diffing with character-level refinement for small changes.
- * Handles prefix/suffix additions and maintains word boundaries for readability.
- */
-export function calculateDiff(originalText: string, newText: string): BackgroundRange[] {
-	if (!originalText && !newText) return []
-	if (!originalText) return [{ start: 0, end: newText.length, type: "added" }]
-	if (!newText) return []
-
-	const wordDiff = diffWords(originalText, newText)
-	const ranges: BackgroundRange[] = []
-	let currentPosition = 0
-
-	for (let i = 0; i < wordDiff.length; i++) {
-		const change = wordDiff[i]
-
-		if (change.added) {
-			ranges.push({
-				start: currentPosition,
-				end: currentPosition + change.value.length,
-				type: "added",
-			})
-			currentPosition += change.value.length
-		} else if (change.removed) {
-			const nextChange = wordDiff[i + 1]
-			if (nextChange?.added) {
-				const { ranges: processedRanges, newPosition } = processWordPair(
-					change.value,
-					nextChange.value,
-					currentPosition,
-				)
-				ranges.push(...processedRanges)
-				currentPosition = newPosition
-				i++ // Skip the next change since we processed it
-			}
-			// If removal without following addition, skip (removed content not shown)
-		} else {
-			ranges.push({
-				start: currentPosition,
-				end: currentPosition + change.value.length,
-				type: "unchanged",
-			})
-			currentPosition += change.value.length
-		}
-	}
-
-	return ranges
-}
-
-/**
- * Process removal/addition word pair to determine optimal diff strategy.
- */
-function processWordPair(
-	removedWord: string,
-	addedWord: string,
-	startPosition: number,
-): { ranges: BackgroundRange[]; newPosition: number } {
-	// Handle prefix additions: "abc" → "abcd"
-	if (addedWord.startsWith(removedWord)) {
-		const ranges: BackgroundRange[] = [
-			{ start: startPosition, end: startPosition + removedWord.length, type: "unchanged" },
-		]
-
-		if (addedWord.length > removedWord.length) {
-			ranges.push({
-				start: startPosition + removedWord.length,
-				end: startPosition + addedWord.length,
-				type: "added",
-			})
-		}
-
-		return { ranges, newPosition: startPosition + addedWord.length }
-	}
-
-	// Handle suffix additions: "bcd" → "abcd"
-	if (addedWord.endsWith(removedWord)) {
-		const prefixLength = addedWord.length - removedWord.length
-		const ranges: BackgroundRange[] = []
-
-		if (prefixLength > 0) {
-			ranges.push({
-				start: startPosition,
-				end: startPosition + prefixLength,
-				type: "added",
-			})
-		}
-
-		ranges.push({
-			start: startPosition + prefixLength,
-			end: startPosition + addedWord.length,
-			type: "unchanged",
-		})
-
-		return { ranges, newPosition: startPosition + addedWord.length }
-	}
-
-	// For small, similar-length words, use character-level refinement
-	if (shouldUseCharacterRefinement(removedWord, addedWord)) {
-		const ranges = refineWithCharacterDiff(removedWord, addedWord, startPosition)
-		return { ranges, newPosition: startPosition + addedWord.length }
-	}
-
-	// Large word change - treat as single modification
-	const ranges: BackgroundRange[] = [
-		{
-			start: startPosition,
-			end: startPosition + addedWord.length,
-			type: "modified",
-		},
-	]
-
-	return { ranges, newPosition: startPosition + addedWord.length }
-}
-
-/**
- * Determine if character-level refinement should be used for small, similar-length words.
- */
-function shouldUseCharacterRefinement(word1: string, word2: string): boolean {
-	return word1.length <= 10 && word2.length <= 10 && Math.abs(word1.length - word2.length) <= 2
-}
-
-/**
- * Refine small word changes using character-level diffing.
- * Shows character-by-character changes for similar words to improve readability.
- */
-function refineWithCharacterDiff(originalWord: string, newWord: string, startPosition: number): BackgroundRange[] {
-	// For small words with similar lengths, show character-by-character changes
-	if (Math.min(originalWord.length, newWord.length) <= 10 && Math.abs(originalWord.length - newWord.length) <= 3) {
-		const ranges: BackgroundRange[] = []
-		const maxLength = Math.max(originalWord.length, newWord.length)
-
-		for (let i = 0; i < maxLength; i++) {
-			const originalChar = originalWord[i] || ""
-			const newChar = newWord[i] || ""
-			const type = originalChar === newChar ? "unchanged" : "modified"
-
-			ranges.push({
-				start: startPosition + i,
-				end: startPosition + i + 1,
-				type,
-			})
-		}
-
-		return ranges
-	}
-
-	// For larger or very different words, use Myers diff
-	const charDiff = diffChars(originalWord, newWord)
-	const ranges: BackgroundRange[] = []
-	let currentPosition = startPosition
-
-	for (const change of charDiff) {
-		if (change.added) {
-			ranges.push({
-				start: currentPosition,
-				end: currentPosition + change.value.length,
-				type: "modified",
-			})
-			currentPosition += change.value.length
-		} else if (change.removed) {
-			// Skip removed content
-		} else {
-			ranges.push({
-				start: currentPosition,
-				end: currentPosition + change.value.length,
-				type: "unchanged",
-			})
-			currentPosition += change.value.length
-		}
-	}
-
-	return ranges
-}

+ 0 - 189
src/services/ghost/utils/CodeHighlighter.ts

@@ -1,189 +0,0 @@
-// kilocode_change - new file: SVG-based syntax highlighting for ghost decorations
-import * as vscode from "vscode"
-import { getSingletonHighlighter, type Highlighter, type ThemedToken, type BundledLanguage } from "shiki"
-import { VS_CODE_TO_SHIKI_LANGUAGE_MAP } from "./constants"
-import { getShikiTheme, getThemeColors, SUPPORTED_SHIKI_THEMES, type ThemeColors } from "./ThemeMapper"
-import { type BackgroundRange } from "./CharacterDiff"
-import { escapeHtml } from "../../../shared/utils/escapeHtml"
-
-export type { ThemeColors, BackgroundRange }
-
-export interface HighlightedResult {
-	html: string
-	themeColors: ThemeColors
-}
-
-let highlighter: Highlighter | null = null
-
-/**
- * Initialize the Shiki highlighter with VS Code themes
- */
-export async function initializeHighlighter(): Promise<Highlighter> {
-	if (highlighter) {
-		return highlighter
-	}
-
-	highlighter = await getSingletonHighlighter({
-		themes: SUPPORTED_SHIKI_THEMES,
-		langs: [
-			"typescript",
-			"javascript",
-			"python",
-			"java",
-			"cpp",
-			"c",
-			"csharp",
-			"go",
-			"rust",
-			"php",
-			"ruby",
-			"swift",
-			"kotlin",
-			"scala",
-			"html",
-			"css",
-			"json",
-			"yaml",
-			"xml",
-			"markdown",
-			"bash",
-			"shell",
-			"sql",
-			"dockerfile",
-			"plaintext",
-		],
-	})
-
-	return highlighter
-}
-
-export function getLanguageForDocument(document: vscode.TextDocument): string {
-	return VS_CODE_TO_SHIKI_LANGUAGE_MAP[document.languageId] || "plaintext"
-}
-
-export async function generateHighlightedHtmlWithRanges(
-	code: string,
-	language: string,
-	backgroundRanges: BackgroundRange[],
-): Promise<HighlightedResult> {
-	if (!highlighter) {
-		highlighter = await initializeHighlighter()
-	}
-
-	const theme = getShikiTheme()
-	const themeColors = getThemeColors()
-
-	const tokensResult = highlighter.codeToTokens(code, { lang: language as BundledLanguage, theme: theme })
-	const html = renderTokensToHtmlWithBackgrounds(tokensResult.tokens, backgroundRanges, themeColors)
-
-	return { html, themeColors }
-}
-
-function renderTokensToHtmlWithBackgrounds(
-	lines: ThemedToken[][],
-	backgroundRanges: BackgroundRange[],
-	themeColors: ThemeColors,
-): string {
-	let html = '<pre class="shiki"><code>'
-	let globalOffset = 0
-
-	for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
-		const line = lines[lineIndex]
-		html += '<span class="line">'
-
-		for (const token of line) {
-			const tokenStart = globalOffset
-			const tokenEnd = globalOffset + token.content.length
-
-			const overlappingRanges = backgroundRanges.filter(
-				(range) => range.start < tokenEnd && range.end > tokenStart,
-			)
-
-			if (overlappingRanges.length > 0) {
-				html += renderTokenWithCharacterRanges(
-					token.content,
-					token.color || themeColors.foreground,
-					tokenStart,
-					backgroundRanges,
-					themeColors,
-				)
-			} else {
-				html += `<span style="color:${token.color || themeColors.foreground}">${escapeHtml(token.content)}</span>`
-			}
-
-			globalOffset += token.content.length
-		}
-
-		html += "</span>"
-		if (lineIndex < lines.length - 1) {
-			html += "\n"
-			globalOffset += 1
-		}
-	}
-
-	html += "</code></pre>"
-
-	return html
-}
-
-function renderTokenWithCharacterRanges(
-	text: string,
-	tokenColor: string,
-	offset: number,
-	ranges: BackgroundRange[],
-	themeColors: ThemeColors,
-): string {
-	let html = ""
-	let currentIndex = 0
-
-	const segments: Array<{ text: string; hasBackground: boolean; rangeType?: string }> = []
-
-	while (currentIndex < text.length) {
-		const globalPos = offset + currentIndex
-		const affectingRange = ranges.find((range) => range.start <= globalPos && range.end > globalPos)
-
-		if (affectingRange) {
-			const rangeStart = Math.max(0, affectingRange.start - offset)
-			const rangeEnd = Math.min(text.length, affectingRange.end - offset)
-
-			if (currentIndex < rangeStart) {
-				segments.push({
-					text: text.slice(currentIndex, rangeStart),
-					hasBackground: false,
-				})
-			}
-
-			segments.push({
-				text: text.slice(Math.max(currentIndex, rangeStart), rangeEnd),
-				hasBackground: affectingRange.type !== "unchanged",
-				rangeType: affectingRange.type,
-			})
-
-			currentIndex = rangeEnd
-		} else {
-			const nextRangeStart = ranges
-				.filter((range) => range.start > globalPos && range.type === "modified")
-				.reduce((min, range) => Math.min(min, range.start - offset), text.length)
-
-			segments.push({
-				text: text.slice(currentIndex, Math.min(nextRangeStart, text.length)),
-				hasBackground: false,
-			})
-
-			currentIndex = Math.min(nextRangeStart, text.length)
-		}
-	}
-
-	for (const segment of segments) {
-		if (segment.hasBackground) {
-			const rangeType = segment.rangeType || "modified"
-			const cssClass = `diff-${rangeType}`
-
-			html += `<span class="${cssClass}" style="color:${tokenColor};background-color:${themeColors.modifiedBackground}">${escapeHtml(segment.text)}</span>`
-		} else {
-			html += `<span style="color:${tokenColor}">${escapeHtml(segment.text)}</span>`
-		}
-	}
-
-	return html
-}

+ 0 - 121
src/services/ghost/utils/SvgRenderer.ts

@@ -1,121 +0,0 @@
-// kilocode_change - new file: SvgRenderer class for Shiki HTML-to-SVG conversion with diff highlighting
-import { escapeHtml } from "../../../shared/utils/escapeHtml"
-import { parseHtmlDocument } from "./htmlParser"
-import { calculateTextWidth } from "./textMeasurement"
-import { type ThemeColors } from "./ThemeMapper"
-
-// DOM Node type constants for cross-environment compatibility
-const TEXT_NODE = 3
-
-interface SvgRenderOptions {
-	width: number
-	height: number
-	fontSize: number
-	fontFamily: string
-	fontWeight?: string
-	letterSpacing: number
-	lineHeight: number
-	themeColors: ThemeColors
-}
-
-export class SvgRenderer {
-	constructor(
-		private html: string,
-		private options: SvgRenderOptions,
-	) {}
-
-	public render(): string {
-		const { guts, lineBackgrounds, characterBackgrounds } = this.convertShikiHtmlToSvgContent()
-
-		const lines = this.html.split("\n")
-		const actualHeight = lines.length * this.options.lineHeight
-
-		return `<svg xmlns="http://www.w3.org/2000/svg" width="${this.options.width}" height="${actualHeight}" shape-rendering="crispEdges">
-			<g>
-				<rect x="0" y="0" rx="10" ry="10" width="${this.options.width}" height="${actualHeight}" fill="${this.options.themeColors.background}" shape-rendering="crispEdges" />
-				${lineBackgrounds}
-				${characterBackgrounds}
-				${guts}
-			</g>
-		</svg>`
-	}
-
-	private convertShikiHtmlToSvgContent(): {
-		guts: string
-		lineBackgrounds: string
-		characterBackgrounds: string
-	} {
-		const document = parseHtmlDocument(this.html)
-		const lines = Array.from(document.querySelectorAll(".line"))
-
-		const characterBackgrounds: string[] = []
-		let currentX = 0
-
-		const svgLines = lines.map((line: Element, index: number) => {
-			const lineY = index * this.options.lineHeight + this.options.lineHeight / 2
-			currentX = 0
-
-			const spans = Array.from(line.childNodes)
-				.map((node: ChildNode) => {
-					if (node.nodeType === TEXT_NODE) {
-						const textContent = node.textContent ?? ""
-						const estimatedWidth = calculateTextWidth(textContent, this.options.fontSize)
-						currentX += estimatedWidth
-						return `<tspan xml:space="preserve">${escapeHtml(textContent)}</tspan>`
-					}
-
-					const el = node as HTMLElement
-					const style = el.getAttribute("style") || ""
-					const colorMatch = style.match(/color:\s*(#[0-9a-fA-F]{6})/)
-					const backgroundColorMatch = style.match(/background-color:\s*(#[0-9a-fA-F]{6,8}|rgba?\([^)]+\))/)
-					const classes = el.getAttribute("class") || ""
-					let fill = colorMatch ? ` fill="${colorMatch[1]}"` : ""
-
-					const content = el.textContent || ""
-					const estimatedWidth = calculateTextWidth(content, this.options.fontSize)
-
-					if (classes.includes("diff-")) {
-						let bgColor = this.options.themeColors.modifiedBackground
-
-						if (classes.includes("diff-added")) {
-							bgColor = this.options.themeColors.addedBackground
-						} else if (classes.includes("diff-removed")) {
-							bgColor = this.options.themeColors.removedBackground
-						} else if (classes.includes("diff-modified")) {
-							bgColor = this.options.themeColors.modifiedBackground
-						}
-
-						characterBackgrounds.push(
-							`<rect x="${currentX}" y="${index * this.options.lineHeight}" width="${estimatedWidth}" height="${this.options.lineHeight}" fill="${bgColor}" shape-rendering="crispEdges" />`,
-						)
-					} else if (backgroundColorMatch) {
-						characterBackgrounds.push(
-							`<rect x="${currentX}" y="${index * this.options.lineHeight}" width="${estimatedWidth}" height="${this.options.lineHeight}" fill="${this.options.themeColors.highlightedBackground}" shape-rendering="crispEdges" />`,
-						)
-					}
-
-					currentX += estimatedWidth
-
-					return `<tspan xml:space="preserve"${fill}>${escapeHtml(content)}</tspan>`
-				})
-				.join("")
-
-			const textElement = `<text x="0" y="${lineY}" font-family="${this.options.fontFamily}" font-size="${this.options.fontSize.toString()}" xml:space="preserve" dominant-baseline="central" shape-rendering="crispEdges">${spans}</text>`
-
-			return textElement
-		})
-
-		const lineBackgrounds = lines
-			.map((line: Element, index: number) => {
-				const y = index * this.options.lineHeight
-				return `<rect x="0" y="${y}" width="100%" height="${this.options.lineHeight}" fill="${this.options.themeColors.background}" shape-rendering="crispEdges" />`
-			})
-			.join("\n")
-
-		return {
-			guts: svgLines.join("\n"),
-			lineBackgrounds,
-			characterBackgrounds: characterBackgrounds.join("\n"),
-		}
-	}
-}

+ 0 - 133
src/services/ghost/utils/ThemeMapper.ts

@@ -1,133 +0,0 @@
-// kilocode_change - new file: Theme mapping utility for VSCode to Shiki theme conversion
-import * as vscode from "vscode"
-
-export interface ThemeColors {
-	background: string
-	foreground: string
-	modifiedBackground: string
-	border: string
-	removedBackground: string
-	addedBackground: string
-	highlightedBackground: string
-}
-const VSCODE_TO_SHIKI_THEME_MAP: Record<string, string> = {
-	// Dark themes
-	"Dark+ (default dark)": "dark-plus",
-	"Dark Modern": "dark-plus",
-	"Dark (Visual Studio)": "dark-plus",
-	"Ayu Dark": "ayu-dark",
-	"One Dark Pro": "one-dark-pro",
-	"Material Theme Darker": "material-theme-darker",
-	"Solarized Dark": "solarized-dark",
-	"Gruvbox Dark Hard": "gruvbox-dark-hard",
-	"Gruvbox Dark Medium": "gruvbox-dark-medium",
-	"Gruvbox Dark Soft": "gruvbox-dark-soft",
-	"GitHub Dark": "github-dark",
-	"GitHub Dark Default": "github-dark-default",
-	"GitHub Dark Dimmed": "github-dark-dimmed",
-	"GitHub Dark High Contrast": "github-dark-high-contrast",
-	"Everforest Dark": "everforest-dark",
-	"Vitesse Dark": "vitesse-dark",
-	"Min Dark": "min-dark",
-	"Slack Dark": "slack-dark",
-
-	// Light themes
-	"Light+ (default light)": "light-plus",
-	"Light Modern": "light-plus",
-	"Light (Visual Studio)": "light-plus",
-	"GitHub Light": "github-light",
-	"GitHub Light Default": "github-light",
-	"GitHub Light High Contrast": "github-light-high-contrast",
-	"Solarized Light": "solarized-light",
-	"One Light": "one-light",
-	"Material Theme Lighter": "material-theme-lighter",
-	"Vitesse Light": "vitesse-light",
-	"Min Light": "min-light",
-}
-
-export const SUPPORTED_SHIKI_THEMES = [
-	// Core VSCode themes
-	"dark-plus",
-	"light-plus",
-	// GitHub themes
-	"github-dark",
-	"github-dark-default",
-	"github-dark-dimmed",
-	"github-dark-high-contrast",
-	"github-light",
-	"github-light-default",
-	"github-light-high-contrast",
-	// Popular dark themes
-	"ayu-dark",
-	"one-dark-pro",
-	"material-theme-darker",
-	"solarized-dark",
-	"gruvbox-dark-hard",
-	"gruvbox-dark-medium",
-	"gruvbox-dark-soft",
-	"everforest-dark",
-	"vitesse-dark",
-	"min-dark",
-	"slack-dark",
-	// Popular light themes
-	"one-light",
-	"material-theme-lighter",
-	"solarized-light",
-	"vitesse-light",
-	"min-light",
-]
-
-export function getVSCodeThemeName(): string {
-	const workbenchConfig = vscode.workspace.getConfiguration()
-	return workbenchConfig.get<string>("workbench.colorTheme") ?? "Default Dark Modern"
-}
-
-export function getShikiTheme(): string {
-	const currentTheme = vscode.window.activeColorTheme
-	const isDark = currentTheme.kind === vscode.ColorThemeKind.Dark
-	const themeName = getVSCodeThemeName()
-
-	const mappedTheme = VSCODE_TO_SHIKI_THEME_MAP[themeName]
-	if (mappedTheme) {
-		return mappedTheme
-	}
-
-	for (const [vscodeTheme, shikiTheme] of Object.entries(VSCODE_TO_SHIKI_THEME_MAP)) {
-		if (themeName.toLowerCase().includes(vscodeTheme.toLowerCase().split(" ")[0])) {
-			return shikiTheme
-		}
-	}
-
-	if (isDark) {
-		return "dark-plus"
-	} else {
-		return "light-plus"
-	}
-}
-
-export function getThemeColors(): ThemeColors {
-	const currentTheme = vscode.window.activeColorTheme
-	const isDark = currentTheme?.kind === vscode.ColorThemeKind.Dark
-
-	if (isDark) {
-		return {
-			background: "#1e1e1e",
-			foreground: "#d4d4d4",
-			modifiedBackground: "#33333333",
-			border: "#3c3c3c",
-			removedBackground: "rgba(248, 113, 133, 0.2)",
-			addedBackground: "rgba(107, 166, 205, 0.2)",
-			highlightedBackground: "#264f78",
-		}
-	} else {
-		return {
-			background: "#ffffff",
-			foreground: "#24292e",
-			modifiedBackground: "#dddddd30",
-			border: "#e1e4e8",
-			removedBackground: "rgba(248, 113, 133, 0.15)",
-			addedBackground: "rgba(107, 166, 205, 0.15)",
-			highlightedBackground: "#e7f3ff",
-		}
-	}
-}

+ 0 - 32
src/services/ghost/utils/__tests__/CharacterDiff.spec.ts

@@ -1,32 +0,0 @@
-import { describe, it, expect } from "vitest"
-import { calculateDiff } from "../CharacterDiff"
-
-describe("CharacterDiff", () => {
-	describe("calculateDiff", () => {
-		it("should handle simple addition", () => {
-			const result = calculateDiff("abc", "abcd")
-			expect(result).toEqual([
-				{ start: 0, end: 3, type: "unchanged" },
-				{ start: 3, end: 4, type: "added" },
-			])
-		})
-
-		it("should handle variable name changes", () => {
-			const result = calculateDiff("const userName = 'john'", "const fullName = 'john'")
-			// Should preserve common parts and highlight the word change
-			expect(result.some((r) => r.type === "unchanged")).toBe(true)
-			expect(result.some((r) => r.type === "modified")).toBe(true)
-		})
-
-		it("should handle empty strings", () => {
-			expect(calculateDiff("", "abc")).toEqual([{ start: 0, end: 3, type: "added" }])
-			expect(calculateDiff("abc", "")).toEqual([])
-			expect(calculateDiff("", "")).toEqual([])
-		})
-
-		it("should handle identical strings", () => {
-			const result = calculateDiff("hello", "hello")
-			expect(result).toEqual([{ start: 0, end: 5, type: "unchanged" }])
-		})
-	})
-})

+ 0 - 58
src/services/ghost/utils/__tests__/CodeHighlighter.spec.ts

@@ -1,58 +0,0 @@
-import { describe, it, expect, vi, beforeEach } from "vitest"
-
-// Mock shiki
-vi.mock("shiki", () => ({
-	getSingletonHighlighter: vi.fn().mockResolvedValue({
-		codeToTokens: vi.fn().mockReturnValue({
-			tokens: [
-				[
-					{ content: "function", color: "#569cd6" },
-					{ content: " ", color: "#d4d4d4" },
-					{ content: "test", color: "#dcdcaa" },
-					{ content: "()", color: "#d4d4d4" },
-				],
-			],
-		}),
-	}),
-}))
-
-// Mock vscode
-vi.mock("vscode", () => ({
-	window: {
-		activeColorTheme: { kind: 1 },
-	},
-	ColorThemeKind: { Dark: 1, Light: 2 },
-	workspace: {
-		getConfiguration: vi.fn().mockReturnValue({
-			get: vi.fn().mockReturnValue("Dark+ (default dark)"),
-		}),
-	},
-}))
-
-import { initializeHighlighter, getLanguageForDocument } from "../CodeHighlighter"
-
-describe("CodeHighlighter", () => {
-	beforeEach(() => {
-		vi.clearAllMocks()
-	})
-
-	describe("getLanguageForDocument", () => {
-		it("should map TypeScript correctly", () => {
-			const mockDocument = { languageId: "typescript" } as any
-			const result = getLanguageForDocument(mockDocument)
-			expect(result).toBe("typescript")
-		})
-
-		it("should map unknown language to plaintext", () => {
-			const mockDocument = { languageId: "unknown-language" } as any
-			const result = getLanguageForDocument(mockDocument)
-			expect(result).toBe("plaintext")
-		})
-	})
-
-	describe("initializeHighlighter", () => {
-		it("should initialize without throwing", async () => {
-			await expect(initializeHighlighter()).resolves.not.toThrow()
-		})
-	})
-})

+ 0 - 66
src/services/ghost/utils/__tests__/SvgRenderer.spec.ts

@@ -1,66 +0,0 @@
-import { describe, it, expect, beforeAll, vi } from "vitest"
-
-// Mock vscode module
-vi.mock("vscode", () => ({
-	window: {
-		activeColorTheme: {
-			kind: 1, // Dark theme
-		},
-	},
-	workspace: {
-		getConfiguration: vi.fn(() => ({
-			get: vi.fn((key: string) => {
-				if (key === "workbench.colorTheme") return "Dark+ (default dark)"
-				return undefined
-			}),
-		})),
-	},
-	ColorThemeKind: {
-		Dark: 1,
-		Light: 2,
-	},
-}))
-
-import { SvgRenderer } from "../SvgRenderer"
-import { initializeHighlighter, generateHighlightedHtmlWithRanges } from "../CodeHighlighter"
-import { getThemeColors } from "../ThemeMapper"
-import { type BackgroundRange } from "../CharacterDiff"
-
-describe("SvgRenderer", () => {
-	beforeAll(async () => {
-		await initializeHighlighter()
-	})
-
-	describe("Integration with CodeHighlighter", () => {
-		it("should work with highlighted HTML from CodeHighlighter", async () => {
-			const code = 'function test() { return "hello"; }'
-			const backgroundRanges: BackgroundRange[] = [
-				{ start: 9, end: 13, type: "added" }, // highlight 'test' as added
-			]
-			const themeColors = getThemeColors()
-
-			const { html } = await generateHighlightedHtmlWithRanges(code, "typescript", backgroundRanges)
-			const renderer = new SvgRenderer(html, {
-				width: 400,
-				height: 50,
-				fontSize: 14,
-				fontFamily: "monospace",
-				fontWeight: "normal",
-				letterSpacing: 0,
-				lineHeight: 20,
-				themeColors,
-			})
-
-			const result = renderer.render()
-			expect(result).toMatchInlineSnapshot(`
-					"<svg xmlns="http://www.w3.org/2000/svg" width="400" height="20" shape-rendering="crispEdges">
-			<g>
-				<rect x="0" y="0" rx="10" ry="10" width="400" height="20" fill="#1e1e1e" shape-rendering="crispEdges" />
-				<rect x="0" y="0" width="100%" height="20" fill="#1e1e1e" shape-rendering="crispEdges" />
-				<rect x="75.60000000000001" y="0" width="33.6" height="20" fill="rgba(107, 166, 205, 0.2)" shape-rendering="crispEdges" />
-				<text x="0" y="10" font-family="monospace" font-size="14" xml:space="preserve" dominant-baseline="central" shape-rendering="crispEdges"><tspan xml:space="preserve" fill="#569CD6">function</tspan><tspan xml:space="preserve" fill="#D4D4D4"> </tspan><tspan xml:space="preserve" fill="#DCDCAA">test</tspan><tspan xml:space="preserve" fill="#D4D4D4">() { </tspan><tspan xml:space="preserve" fill="#C586C0">return</tspan><tspan xml:space="preserve" fill="#D4D4D4"> </tspan><tspan xml:space="preserve" fill="#CE9178">&quot;hello&quot;</tspan><tspan xml:space="preserve" fill="#D4D4D4">; }</tspan></text>
-			</g>
-		</svg>"`)
-		})
-	})
-})

+ 0 - 56
src/services/ghost/utils/__tests__/ThemeMapper.spec.ts

@@ -1,56 +0,0 @@
-import { describe, it, expect, vi, beforeEach } from "vitest"
-
-// Mock vscode
-vi.mock("vscode", () => ({
-	window: {
-		activeColorTheme: {
-			kind: 1, // Dark theme
-		},
-	},
-	ColorThemeKind: {
-		Dark: 1,
-		Light: 2,
-		HighContrast: 3,
-	},
-	workspace: {
-		getConfiguration: vi.fn().mockReturnValue({
-			get: vi.fn().mockReturnValue("Dark+ (default dark)"),
-		}),
-	},
-}))
-
-import { getShikiTheme, getThemeColors } from "../ThemeMapper"
-import * as vscode from "vscode"
-
-describe("ThemeMapper", () => {
-	beforeEach(() => {
-		vi.clearAllMocks()
-	})
-
-	describe("getShikiTheme", () => {
-		it("should map known VS Code themes to Shiki themes", () => {
-			const mockConfig = { get: vi.fn().mockReturnValue("Dark+ (default dark)") }
-			vi.mocked(vscode.workspace.getConfiguration).mockReturnValue(mockConfig as any)
-
-			const result = getShikiTheme()
-			expect(result).toBe("dark-plus")
-		})
-
-		it("should fallback to appropriate default for unknown themes", () => {
-			const mockConfig = { get: vi.fn().mockReturnValue("Unknown Theme") }
-			vi.mocked(vscode.workspace.getConfiguration).mockReturnValue(mockConfig as any)
-
-			const result = getShikiTheme()
-			expect(result).toBe("dark-plus") // Should fallback to dark-plus for dark theme
-		})
-	})
-
-	describe("getThemeColors", () => {
-		it("should return appropriate colors based on theme type", () => {
-			const colors = getThemeColors()
-			expect(colors).toHaveProperty("background")
-			expect(colors).toHaveProperty("foreground")
-			expect(colors).toHaveProperty("modifiedBackground")
-		})
-	})
-})

+ 0 - 43
src/services/ghost/utils/__tests__/textMeasurement.spec.ts

@@ -1,43 +0,0 @@
-import { describe, it, expect } from "vitest"
-import { calculateContainerWidth, calculateTextWidth } from "../textMeasurement"
-
-describe("calculateContainerWidth", () => {
-	it("should provide adequate width for single line text", () => {
-		const text = "\t// Function implementation"
-		const fontSize = 14
-		const width = calculateContainerWidth(text, fontSize)
-
-		// Should have minimum width
-		expect(width).toBeGreaterThanOrEqual(32)
-	})
-
-	it("should handle multi-line text correctly", () => {
-		const text = "function example() {\n\t// Function implementation\n}"
-		const fontSize = 14
-		const width = calculateContainerWidth(text, fontSize)
-
-		// Should be based on longest line (the comment line)
-		const longestLine = "\t// Function implementation"
-		const expectedMinWidth = longestLine.length * fontSize * 0.6 + 32
-		expect(width).toBeGreaterThanOrEqual(Math.floor(expectedMinWidth))
-	})
-
-	it("should maintain minimum width for short text", () => {
-		const width = calculateContainerWidth("a", 14)
-		expect(width).toBeGreaterThanOrEqual(32)
-	})
-
-	it("regression test: should prevent 'implementation' cutoff", () => {
-		// This is the exact scenario from the bug report
-		const text = "\t// Function implementation"
-		const fontSize = 14
-		const containerWidth = calculateContainerWidth(text, fontSize)
-		const actualTextWidth = calculateTextWidth(text, fontSize)
-
-		// Container should be wider than the actual text content
-		expect(containerWidth).toBeGreaterThan(actualTextWidth)
-
-		// Should have at least 32px padding
-		expect(containerWidth - actualTextWidth).toBeGreaterThanOrEqual(32)
-	})
-})

+ 0 - 130
src/services/ghost/utils/createSVGDecorationType.ts

@@ -1,130 +0,0 @@
-import * as vscode from "vscode"
-import { SvgRenderer } from "./SvgRenderer"
-import { getLanguageForDocument, generateHighlightedHtmlWithRanges } from "./CodeHighlighter"
-import { type BackgroundRange } from "./CharacterDiff"
-import { getEditorConfiguration, type EditorConfig } from "../EditorConfiguration"
-import { calculateContainerWidth, calculateCharacterWidth } from "./textMeasurement"
-import { getThemeColors } from "./ThemeMapper"
-
-export interface SVGDecorationContent {
-	text: string
-	backgroundRanges: BackgroundRange[]
-}
-
-export interface SVGDecorationOptions {
-	marginTop?: number
-	marginLeft?: number
-}
-
-/**
- * Create an SVG decoration type that can be applied to an editor
- * @param content The content to render with background highlighting
- * @param document The document context for language detection
- * @param options Additional decoration options
- * @returns A VS Code TextEditorDecorationType ready to use
- */
-export async function createSVGDecorationType(
-	content: SVGDecorationContent,
-	document: vscode.TextDocument,
-	options: SVGDecorationOptions = {},
-): Promise<vscode.TextEditorDecorationType> {
-	const language = getLanguageForDocument(document)
-	const config = getEditorConfiguration()
-
-	const { width, height } = calculateSVGDimensions(content.text, config)
-	const processedText = language
-		? await highlightCodeWithRanges(content.text, language, content.backgroundRanges)
-		: content.text
-
-	const svgContent = renderSVGContent(processedText, width, height, config)
-	const svgDataUri = createSVGDataUri(svgContent)
-
-	return createDecorationTypeFromSVGDataUri(svgDataUri, width, height, config, options)
-}
-
-/**
- * Highlight code using Shiki with background ranges for character-level highlighting
- */
-async function highlightCodeWithRanges(
-	text: string,
-	language: string,
-	backgroundRanges: BackgroundRange[],
-): Promise<string> {
-	try {
-		const result = await generateHighlightedHtmlWithRanges(text, language, backgroundRanges)
-		return result.html
-	} catch (error) {
-		console.error("Failed to highlight code with Shiki:", error)
-		// Fallback to plain text
-		return text
-	}
-}
-
-/**
- * Calculate dimensions for the SVG based on text content
- */
-function calculateSVGDimensions(text: string, config: EditorConfig): { width: number; height: number } {
-	const lines = text.split("\n")
-	const width = calculateContainerWidth(text, config.fontSize)
-	const height = lines.length * config.fontSize * config.lineHeight
-
-	return {
-		width: Math.round(width),
-		height: Math.round(height),
-	}
-}
-
-/**
- * Render SVG content using the SvgRenderer
- */
-function renderSVGContent(processedText: string, width: number, height: number, config: EditorConfig): string {
-	const themeColors = getThemeColors()
-	const renderer = new SvgRenderer(processedText, {
-		width,
-		height,
-		fontSize: config.fontSize,
-		fontFamily: config.fontFamily,
-		fontWeight: "normal",
-		letterSpacing: 0,
-		lineHeight: config.fontSize * config.lineHeight,
-		themeColors,
-	})
-
-	return renderer.render()
-}
-
-/**
- * Create data URI from SVG content
- */
-function createSVGDataUri(svgContent: string): string {
-	const encodedSvg = encodeURIComponent(svgContent)
-	return `data:image/svg+xml,${encodedSvg}`
-}
-
-/**
- * Create VS Code decoration type from data URI
- */
-function createDecorationTypeFromSVGDataUri(
-	svgDataUri: string,
-	width: number,
-	height: number,
-	config: EditorConfig,
-	options: SVGDecorationOptions,
-): vscode.TextEditorDecorationType {
-	const marginTop = options.marginTop ?? Math.ceil(0)
-	const marginLeft = options.marginLeft ?? Math.ceil(calculateCharacterWidth(config.fontSize) + 6)
-	const shadowColor = `rgba(0, 0, 0, 0.2)`
-
-	return vscode.window.createTextEditorDecorationType({
-		after: {
-			contentIconPath: vscode.Uri.parse(svgDataUri),
-			border: `transparent; position: absolute; z-index: 2147483647;
-			filter: drop-shadow(-3px 3px 0px ${shadowColor}) drop-shadow(3px -3px 0px ${shadowColor}) drop-shadow(3px 3px 0px ${shadowColor}) drop-shadow(-3px -3px 0px ${shadowColor});
-			margin-top: ${marginTop}px;
-			margin-left: ${marginLeft}px;`,
-			width: `${width}px`,
-			height: `${height}px`,
-		},
-		rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed,
-	})
-}

+ 0 - 15
src/services/ghost/utils/htmlParser.ts

@@ -1,15 +0,0 @@
-// kilocode_change: DOM adapter for browser/Node.js compatibility
-import { JSDOM } from "jsdom"
-
-// JSDOM is used in the extension for SVG code highlighting.
-// We render sample SVGs in Storybook for testing/ debugging purposes,
-// but use actual browser APIs instead of JSDOM there.
-export function parseHtmlDocument(html: string): Document {
-	if (typeof window !== "undefined" && typeof DOMParser !== "undefined") {
-		// Browser environment - use native DOM
-		return new DOMParser().parseFromString(html, "text/html")
-	} else {
-		// Node.js environment - use JSDOM (stubbed in browser builds)
-		return new JSDOM(html).window.document
-	}
-}

+ 0 - 33
src/services/ghost/utils/textMeasurement.ts

@@ -1,33 +0,0 @@
-/**
- * Text measurement utilities for consistent SVG rendering and width calculations
- */
-
-/** Character width ratio for monospace fonts - consistent across all components */
-const MONOSPACE_CHARACTER_WIDTH_RATIO = 0.6
-
-/** Container padding for SVG decorations */
-const CONTAINER_PADDING = 32
-
-/**
- * Calculate character width for given font size
- */
-export function calculateCharacterWidth(fontSize: number): number {
-	return fontSize * MONOSPACE_CHARACTER_WIDTH_RATIO
-}
-
-/**
- * Calculate estimated width for text content
- */
-export function calculateTextWidth(text: string, fontSize: number): number {
-	return text.length * calculateCharacterWidth(fontSize)
-}
-
-/**
- * Calculate container width with proper padding for SVG decorations
- */
-export function calculateContainerWidth(text: string, fontSize: number): number {
-	const lines = text.split("\n")
-	const maxLineLength = Math.max(...lines.map((line) => line.length))
-	const baseWidth = maxLineLength * calculateCharacterWidth(fontSize)
-	return Math.max(CONTAINER_PADDING, Math.round(baseWidth + CONTAINER_PADDING))
-}

+ 29 - 29
src/test-llm-autocompletion/__tests__/strategy-tester.test.ts

@@ -48,7 +48,7 @@ describe("StrategyTester.parseCompletion", () => {
 	})
 
 	describe("basic functionality", () => {
-		it("should return null for empty XML response", () => {
+		it.skip("should return null for empty XML response", () => {
 			const originalContent = "const x = 1"
 			const xmlResponse = ""
 
@@ -57,7 +57,7 @@ describe("StrategyTester.parseCompletion", () => {
 			expect(result).toBeNull()
 		})
 
-		it("should return null for malformed XML", () => {
+		it.skip("should return null for malformed XML", () => {
 			const originalContent = "const x = 1"
 			const xmlResponse = "<change><search>incomplete"
 
@@ -66,7 +66,7 @@ describe("StrategyTester.parseCompletion", () => {
 			expect(result).toBeNull()
 		})
 
-		it("should return null when no suggestions are parsed", () => {
+		it.skip("should return null when no suggestions are parsed", () => {
 			const originalContent = "const x = 1"
 			const xmlResponse = "<change></change>"
 
@@ -75,7 +75,7 @@ describe("StrategyTester.parseCompletion", () => {
 			expect(result).toBeNull()
 		})
 
-		it("should return null for empty operations array", () => {
+		it.skip("should return null for empty operations array", () => {
 			const originalContent = "const x = 1"
 			// XML with search/replace that doesn't match anything in the document
 			const xmlResponse = `<change><search><![CDATA[nonexistent code that will never match]]></search><replace><![CDATA[replacement]]></replace></change>`
@@ -87,7 +87,7 @@ describe("StrategyTester.parseCompletion", () => {
 	})
 
 	describe("diff operations - additions", () => {
-		it("should handle simple addition (adding new lines)", () => {
+		it.skip("should handle simple addition (adding new lines)", () => {
 			const originalContent = "const x = 1"
 			// Search must match the original content exactly for the parser to find it
 			const xmlResponse = `<change><search><![CDATA[const x = 1]]></search><replace><![CDATA[const x = 1
@@ -98,7 +98,7 @@ const y = 2]]></replace></change>`
 			expect(result).toBe("const x = 1\nconst y = 2")
 		})
 
-		it("should handle multiple line additions", () => {
+		it.skip("should handle multiple line additions", () => {
 			const originalContent = "const x = 1"
 			const xmlResponse = `<change><search><![CDATA[const x = 1]]></search><replace><![CDATA[const x = 1
 const y = 2
@@ -109,7 +109,7 @@ const z = 3]]></replace></change>`
 			expect(result).toBe("const x = 1\nconst y = 2\nconst z = 3")
 		})
 
-		it("should handle addition in middle of file", () => {
+		it.skip("should handle addition in middle of file", () => {
 			const originalContent = "const x = 1\nconst z = 3"
 			const xmlResponse = `<change><search><![CDATA[const x = 1
 const z = 3]]></search><replace><![CDATA[const x = 1
@@ -123,7 +123,7 @@ const z = 3]]></replace></change>`
 	})
 
 	describe("diff operations - deletions", () => {
-		it("should handle simple deletion (removing lines)", () => {
+		it.skip("should handle simple deletion (removing lines)", () => {
 			const originalContent = "const x = 1\nconst y = 2"
 			const xmlResponse = `<change><search><![CDATA[const x = 1
 const y = 2]]></search><replace><![CDATA[const x = 1]]></replace></change>`
@@ -133,7 +133,7 @@ const y = 2]]></search><replace><![CDATA[const x = 1]]></replace></change>`
 			expect(result).toBe("const x = 1")
 		})
 
-		it("should handle deletion in middle of file", () => {
+		it.skip("should handle deletion in middle of file", () => {
 			const originalContent = "const x = 1\nconst y = 2\nconst z = 3"
 			const xmlResponse = `<change><search><![CDATA[const x = 1
 const y = 2
@@ -147,7 +147,7 @@ const z = 3]]></replace></change>`
 	})
 
 	describe("diff operations - modifications", () => {
-		it("should handle modification (delete + add on same line)", () => {
+		it.skip("should handle modification (delete + add on same line)", () => {
 			const originalContent = "var x = 1"
 			const xmlResponse = `<change><search><![CDATA[var x = 1]]></search><replace><![CDATA[const x = 1]]></replace></change>`
 
@@ -156,7 +156,7 @@ const z = 3]]></replace></change>`
 			expect(result).toBe("const x = 1")
 		})
 
-		it("should handle function modification", () => {
+		it.skip("should handle function modification", () => {
 			const originalContent = `function test() {
 	var x = 1
 	return x
@@ -177,7 +177,7 @@ const z = 3]]></replace></change>`
 }`)
 		})
 
-		it("should handle multiple modifications across different lines", () => {
+		it.skip("should handle multiple modifications across different lines", () => {
 			const originalContent = `var x = 1
 var y = 2
 var z = 3`
@@ -193,7 +193,7 @@ const z = 3`)
 	})
 
 	describe("diff operations - mixed operations", () => {
-		it("should handle mixed additions and deletions", () => {
+		it.skip("should handle mixed additions and deletions", () => {
 			const originalContent = `const x = 1
 const y = 2
 const z = 3`
@@ -210,7 +210,7 @@ const w = 1.5
 const z = 3`)
 		})
 
-		it("should handle complex multi-line changes", () => {
+		it.skip("should handle complex multi-line changes", () => {
 			const originalContent = `function calculate(a, b) {
 	var sum = a + b
 	return sum
@@ -237,7 +237,7 @@ const z = 3`)
 	})
 
 	describe("edge cases", () => {
-		it("should handle empty lines in operations", () => {
+		it.skip("should handle empty lines in operations", () => {
 			const originalContent = `const x = 1
 
 const y = 2`
@@ -252,7 +252,7 @@ const y = 2]]></replace></change>`
 const y = 2`)
 		})
 
-		it("should handle operations with special characters", () => {
+		it.skip("should handle operations with special characters", () => {
 			const originalContent = `const regex = /test/`
 			const xmlResponse = `<change><search><![CDATA[const regex = /test/]]></search><replace><![CDATA[const regex = /test$/]]></replace></change>`
 
@@ -261,7 +261,7 @@ const y = 2`)
 			expect(result).toBe(`const regex = /test$/`)
 		})
 
-		it("should preserve indentation in operations", () => {
+		it.skip("should preserve indentation in operations", () => {
 			const originalContent = `function test() {
 	if (true) {
 		return 1
@@ -284,7 +284,7 @@ const y = 2`)
 }`)
 		})
 
-		it("should handle CDATA sections with special XML characters", () => {
+		it.skip("should handle CDATA sections with special XML characters", () => {
 			const originalContent = `const html = "<div>test</div>"`
 			const xmlResponse = `<change><search><![CDATA[const html = "<div>test</div>"]]></search><replace><![CDATA[const html = "<div>test & more</div>"]]></replace></change>`
 
@@ -293,7 +293,7 @@ const y = 2`)
 			expect(result).toBe(`const html = "<div>test & more</div>"`)
 		})
 
-		it("should handle single line file", () => {
+		it.skip("should handle single line file", () => {
 			const originalContent = `const x = 1`
 			const xmlResponse = `<change><search><![CDATA[const x = 1]]></search><replace><![CDATA[const x = 2]]></replace></change>`
 
@@ -304,7 +304,7 @@ const y = 2`)
 	})
 
 	describe("error handling", () => {
-		it("should handle parser errors gracefully (return null)", () => {
+		it.skip("should handle parser errors gracefully (return null)", () => {
 			const originalContent = "const x = 1"
 			const xmlResponse = "<invalid>xml</that><breaks>parser"
 
@@ -313,7 +313,7 @@ const y = 2`)
 			expect(result).toBeNull()
 		})
 
-		it("should handle XML without CDATA sections", () => {
+		it.skip("should handle XML without CDATA sections", () => {
 			const originalContent = "const x = 1"
 			const xmlResponse = `<change><search>const x = 1</search><replace>const x = 2</replace></change>`
 
@@ -325,7 +325,7 @@ const y = 2`)
 	})
 
 	describe("integration with real XML responses", () => {
-		it("should handle single change block", () => {
+		it.skip("should handle single change block", () => {
 			const originalContent = `function test() {
 	return true
 }`
@@ -344,7 +344,7 @@ const y = 2`)
 }`)
 		})
 
-		it("should handle multiple change blocks", () => {
+		it.skip("should handle multiple change blocks", () => {
 			const originalContent = `function test() {
 	return true
 }
@@ -377,7 +377,7 @@ function other() {
 }`)
 		})
 
-		it("should handle complex multi-line changes with CDATA", () => {
+		it.skip("should handle complex multi-line changes with CDATA", () => {
 			const originalContent = `class Calculator {
 	add(a, b) {
 		return a + b
@@ -412,7 +412,7 @@ function other() {
 }`)
 		})
 
-		it("should handle changes with nested XML-like content", () => {
+		it.skip("should handle changes with nested XML-like content", () => {
 			const originalContent = `const template = "<div></div>"`
 			const xmlResponse = `<change><search><![CDATA[const template = "<div></div>"]]></search><replace><![CDATA[const template = "<div><span>Hello</span></div>"]]></replace></change>`
 
@@ -421,7 +421,7 @@ function other() {
 			expect(result).toBe(`const template = "<div><span>Hello</span></div>"`)
 		})
 
-		it("should handle sequential operations on adjacent lines", () => {
+		it.skip("should handle sequential operations on adjacent lines", () => {
 			const originalContent = `const a = 1
 const b = 2
 const c = 3`
@@ -443,7 +443,7 @@ const c = 30`)
 		/**
 		 * Test realistic refactoring: Converting callback to async/await
 		 */
-		it("should handle callback to async/await refactoring", () => {
+		it.skip("should handle callback to async/await refactoring", () => {
 			const originalContent = `function fetchData(callback) {
 	getData((err, data) => {
 		if (err) {
@@ -485,7 +485,7 @@ const c = 30`)
 		/**
 		 * Test realistic refactoring: Adding error handling
 		 */
-		it("should handle adding error handling to existing code", () => {
+		it.skip("should handle adding error handling to existing code", () => {
 			const originalContent = `function processData(data) {
 	const result = transform(data)
 	return result
@@ -519,7 +519,7 @@ const c = 30`)
 		/**
 		 * Test realistic refactoring: Adding TypeScript types
 		 */
-		it("should handle adding TypeScript type annotations", () => {
+		it.skip("should handle adding TypeScript type annotations", () => {
 			const originalContent = `function add(a, b) {
 	return a + b
 }`

+ 15 - 66
src/test-llm-autocompletion/strategy-tester.ts

@@ -1,6 +1,6 @@
 import { LLMClient } from "./llm-client.js"
 import { AutoTriggerStrategy } from "../services/ghost/strategies/AutoTriggerStrategy.js"
-import { GhostSuggestionContext, AutocompleteInput, GhostSuggestionEditOperation } from "../services/ghost/types.js"
+import { GhostSuggestionContext, AutocompleteInput } from "../services/ghost/types.js"
 import { MockTextDocument } from "../services/mocking/MockTextDocument.js"
 import { CURSOR_MARKER } from "../services/ghost/ghostConstants.js"
 import { GhostStreamingParser } from "../services/ghost/GhostStreamingParser.js"
@@ -104,79 +104,28 @@ export class StrategyTester {
 				return null
 			}
 
-			// Get the file operations
-			const file = result.suggestions.getFile(uri)
-			if (!file) {
-				return null
-			}
+			throw new Error("Code needs to be ported to FIM style completion")
 
-			// Get all operations and apply them
-			const operations = file.getAllOperations()
-			if (operations.length === 0) {
-				return null
-			}
+			// // Get the file operations
+			// const file = result.suggestions.getFile(uri)
+			// if (!file) {
+			// 	return null
+			// }
+
+			// // Get all operations and apply them
+			// const operations = file.getAllOperations()
+			// if (operations.length === 0) {
+			// 	return null
+			// }
 
-			// Apply operations to reconstruct the modified code
-			return this.applyOperations(originalContent, operations)
+			// // Apply operations to reconstruct the modified code
+			// return this.applyOperations(originalContent, operations)
 		} catch (error) {
 			console.warn("Failed to parse completion:", error)
 			return null
 		}
 	}
 
-	/**
-	 * Apply diff operations to reconstruct the modified code
-	 * Operations use 0-based line numbers from the parser
-	 */
-	private applyOperations(originalContent: string, operations: GhostSuggestionEditOperation[]): string {
-		const originalLines = originalContent.split("\n")
-
-		// Sort operations by oldLine to process them in order
-		const sortedOps = [...operations].sort((a, b) => {
-			if (a.oldLine !== b.oldLine) {
-				return a.oldLine - b.oldLine
-			}
-			// Deletions before additions on same line
-			if (a.type === "-" && b.type === "+") return -1
-			if (a.type === "+" && b.type === "-") return 1
-			return 0
-		})
-
-		const finalLines: string[] = []
-		let currentOriginalLine = 0
-
-		for (const op of sortedOps) {
-			// Add any unmodified lines before this operation
-			while (currentOriginalLine < op.oldLine) {
-				finalLines.push(originalLines[currentOriginalLine])
-				currentOriginalLine++
-			}
-
-			if (op.type === "+") {
-				// Addition: add the new content
-				// Strip leading newlines to prevent extra blank lines
-				const content = op.content.replace(/^\n+/, "")
-				finalLines.push(content)
-			} else if (op.type === "-") {
-				// Deletion: skip the original line
-				currentOriginalLine++
-			}
-		}
-
-		// Add remaining original lines
-		while (currentOriginalLine < originalLines.length) {
-			finalLines.push(originalLines[currentOriginalLine])
-			currentOriginalLine++
-		}
-
-		// Filter out undefined values that may occur from line number mismatches
-		const validLines = finalLines.filter((line) => line !== undefined)
-		const resultText = validLines.join("\n")
-
-		// Remove leading/trailing newlines that may come from diff generation
-		return resultText.replace(/^\n+/, "").replace(/\n+$/, "")
-	}
-
 	/**
 	 * Get the type of the strategy (always auto-trigger now)
 	 */