Skip to content

Commit f90c823

Browse files
committed
experimental recent copy retriever
1 parent c9e483d commit f90c823

File tree

3 files changed

+116
-1
lines changed

3 files changed

+116
-1
lines changed

vscode/src/completions/completion-provider-config.ts

+2
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ class CompletionProviderConfig {
7373
return 'recent-edits-5m'
7474
case 'recent-edits-mixed':
7575
return 'recent-edits-mixed'
76+
case 'recent-copy':
77+
return 'recent-copy'
7678
default:
7779
return this.experimentBasedContextStrategy()
7880
}

vscode/src/completions/context/context-strategy.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { JaccardSimilarityRetriever } from './retrievers/jaccard-similarity/jacc
55
import { LspLightRetriever } from './retrievers/lsp-light/lsp-light-retriever'
66
import { RecentEditsRetriever } from './retrievers/recent-edits/recent-edits-retriever'
77
import { loadTscRetriever } from './retrievers/tsc/load-tsc-retriever'
8+
import {RecentCopyRetriever} from './retrievers/recent-copy/recent-copy';
89

910
export type ContextStrategy =
1011
| 'lsp-light'
@@ -19,6 +20,7 @@ export type ContextStrategy =
1920
| 'recent-edits-1m'
2021
| 'recent-edits-5m'
2122
| 'recent-edits-mixed'
23+
| 'recent-copy'
2224

2325
export interface ContextStrategyFactory extends vscode.Disposable {
2426
getStrategy(document: vscode.TextDocument): { name: ContextStrategy; retrievers: ContextRetriever[] }
@@ -81,6 +83,10 @@ export class DefaultContextStrategyFactory implements ContextStrategyFactory {
8183
this.graphRetriever = new LspLightRetriever()
8284
this.disposables.push(this.localRetriever, this.graphRetriever)
8385
break
86+
case 'recent-copy':
87+
this.localRetriever = new RecentCopyRetriever(60*1000, 100)
88+
this.disposables.push(this.localRetriever)
89+
break
8490
case 'jaccard-similarity':
8591
this.localRetriever = new JaccardSimilarityRetriever()
8692
this.disposables.push(this.localRetriever)
@@ -134,7 +140,8 @@ export class DefaultContextStrategyFactory implements ContextStrategyFactory {
134140
case 'jaccard-similarity':
135141
case 'recent-edits':
136142
case 'recent-edits-1m':
137-
case 'recent-edits-5m': {
143+
case 'recent-edits-5m':
144+
case 'recent-copy': {
138145
if (this.localRetriever) {
139146
retrievers.push(this.localRetriever)
140147
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import type { AutocompleteContextSnippet } from '@sourcegraph/cody-shared'
2+
import * as vscode from 'vscode'
3+
import type { ContextRetriever } from '../../../types'
4+
5+
interface TrackedSelections {
6+
timestamp: number
7+
content: string
8+
languageId: string
9+
uri: vscode.Uri
10+
startLine: number
11+
endLine: number
12+
}
13+
14+
export class RecentCopyRetriever implements vscode.Disposable, ContextRetriever {
15+
// We use a map from the document URI to the set of tracked completions inside that document to
16+
// improve performance of the `onDidChangeTextDocument` event handler.
17+
private trackedSelections: TrackedSelections[] = new Array<TrackedSelections>()
18+
public identifier = 'recent-copy'
19+
private disposables: vscode.Disposable[] = []
20+
21+
constructor(
22+
private readonly maxAgeMs: number,
23+
private readonly maxSelections: number
24+
) {
25+
this.disposables.push(
26+
vscode.window.onDidChangeTextEditorSelection(this.onDidChangeTextEditorSelection.bind(this))
27+
)
28+
}
29+
30+
public async retrieve(): Promise<AutocompleteContextSnippet[]> {
31+
const clipboardContent = await vscode.env.clipboard.readText()
32+
const selectionItem = this.getSelectionItemIfExist(clipboardContent)
33+
if (selectionItem) {
34+
const autocompleteItem = {
35+
uri: selectionItem.uri,
36+
startLine: selectionItem.startLine,
37+
endLine: selectionItem.endLine,
38+
content: selectionItem.content,
39+
}
40+
return [autocompleteItem]
41+
}
42+
return []
43+
}
44+
45+
public isSupportedForLanguageId(): boolean {
46+
return true
47+
}
48+
49+
private getSelectionItemIfExist(text: string): TrackedSelections | undefined {
50+
for (const ts of this.trackedSelections) {
51+
if (ts.content === text) {
52+
return ts
53+
}
54+
}
55+
return undefined
56+
}
57+
58+
private async onDidChangeTextEditorSelection(
59+
event: vscode.TextEditorSelectionChangeEvent
60+
): Promise<void> {
61+
const editor = event.textEditor
62+
this.addSelectionForTracking(editor.document, editor.selection)
63+
}
64+
65+
private addSelectionForTracking(document: vscode.TextDocument, selection: vscode.Selection): void {
66+
if (selection.isEmpty) {
67+
return
68+
}
69+
const selectedText = document.getText(selection)
70+
const existingSelectionIndex = this.trackedSelections.findIndex(
71+
ts => ts.content === selectedText
72+
)
73+
if (existingSelectionIndex !== -1) {
74+
// Remove the selection from the tracked selections
75+
this.trackedSelections = this.trackedSelections.splice(existingSelectionIndex, 1)
76+
}
77+
this.trackedSelections.push({
78+
timestamp: Date.now(),
79+
content: selectedText,
80+
languageId: document.languageId,
81+
uri: document.uri,
82+
startLine: selection.start.line,
83+
endLine: selection.end.line,
84+
})
85+
this.reconcileOutdatedChanges()
86+
}
87+
88+
private reconcileOutdatedChanges(): void {
89+
const now = Date.now()
90+
const firstOutdatedChange = this.trackedSelections.findIndex(selection => {
91+
return now - selection.timestamp < this.maxAgeMs
92+
})
93+
this.trackedSelections = this.trackedSelections.slice(firstOutdatedChange)
94+
// Limit the array to last maxSelection changes
95+
if (this.trackedSelections.length > this.maxSelections) {
96+
this.trackedSelections = this.trackedSelections.slice(-this.maxSelections)
97+
}
98+
}
99+
100+
public dispose(): void {
101+
this.trackedSelections = []
102+
for (const disposable of this.disposables) {
103+
disposable.dispose()
104+
}
105+
}
106+
}

0 commit comments

Comments
 (0)