This repository was archived by the owner on Feb 25, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 295
/
Copy pathextension.ts
297 lines (270 loc) · 10.8 KB
/
extension.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as os from 'os';
import { commands, DiagnosticSeverity, env, ExtensionContext, languages, modelSelection, window, workspace, } from 'vscode';
import { createInlineCompletionItemProvider } from './completions/create-inline-completion-item-provider';
import { CSChatAgentProvider } from './completions/providers/chatprovider';
import { AideProbeProvider } from './completions/providers/probeProvider';
import { getGitCurrentHash, getGitRepoName } from './git/helper';
import { aideCommands } from './inlineCompletion/commands';
import { startupStatusBar } from './inlineCompletion/statusBar';
import logger from './logger';
import postHogClient from './posthog/client';
import { AideQuickFix } from './quickActions/fix';
import { RepoRef, RepoRefBackend, SideCarClient } from './sidecar/client';
import { loadOrSaveToStorage } from './storage/types';
import { copySettings } from './utilities/copySettings';
import { getRelevantFiles, shouldTrackFile } from './utilities/openTabs';
import { checkReadonlyFSMode } from './utilities/readonlyFS';
import { reportIndexingPercentage } from './utilities/reportIndexingUpdate';
import { startSidecarBinary } from './utilities/setupSidecarBinary';
import { readCustomSystemInstruction } from './utilities/systemInstruction';
import { CodeSymbolInformationEmbeddings } from './utilities/types';
import { getUniqueId } from './utilities/uniqueId';
import { ProjectContext } from './utilities/workspaceContext';
import { CSEventHandler } from './csEvents/csEventHandler';
import { RecentEditsRetriever } from './server/editedFiles';
export let SIDECAR_CLIENT: SideCarClient | null = null;
export async function activate(context: ExtensionContext) {
// Project root here
const uniqueUserId = getUniqueId();
logger.info(`[CodeStory]: ${uniqueUserId} Activating extension with storage: ${context.globalStorageUri}`);
postHogClient?.capture({
distinctId: getUniqueId(),
event: 'extension_activated',
properties: {
platform: os.platform(),
},
});
// Gets access to all the events the editor is throwing our way
const csEventHandler = new CSEventHandler(context);
context.subscriptions.push(csEventHandler);
const registerPreCopyCommand = commands.registerCommand(
'webview.preCopySettings',
async () => {
await copySettings(env.appRoot, logger);
}
);
context.subscriptions.push(registerPreCopyCommand);
const startRecording = commands.registerCommand(
'codestory.startRecordingContext',
async () => {
await csEventHandler.startRecording();
console.log('start recording context');
}
);
context.subscriptions.push(startRecording);
const stopRecording = commands.registerCommand(
'codestory.stopRecordingContext',
async () => {
const response = await csEventHandler.stopRecording();
await sidecarClient.sendContextRecording(response);
console.log(JSON.stringify(response));
console.log('stop recording context');
}
);
context.subscriptions.push(stopRecording);
let rootPath = workspace.rootPath;
if (!rootPath) {
rootPath = '';
}
// Create the copy settings from vscode command for the extension
const registerCopySettingsCommand = commands.registerCommand(
'webview.copySettings',
async () => {
await copySettings(rootPath ?? '', logger);
}
);
context.subscriptions.push(registerCopySettingsCommand);
const readonlyFS = checkReadonlyFSMode();
if (readonlyFS) {
window.showErrorMessage('Move Aide to the Applications folder using Finder. More instructions here: [link](https://docs.codestory.ai/troubleshooting#macos-readonlyfs-warning)');
return;
}
const agentSystemInstruction = readCustomSystemInstruction();
if (agentSystemInstruction === null) {
console.log(
'Aide can help you better if you give it custom instructions by going to your settings and setting it in aide.systemInstruction (search for this string in User Settings) and reload vscode for this to take effect by doing Cmd+Shift+P: Developer: Reload Window'
);
}
// Now we get all the required information and log it
const repoName = await getGitRepoName(
rootPath,
);
const repoHash = await getGitCurrentHash(
rootPath,
);
// We also get some context about the workspace we are in and what we are
// upto
const projectContext = new ProjectContext();
await projectContext.collectContext();
postHogClient?.capture({
distinctId: await getUniqueId(),
event: 'activated_lsp',
properties: {
repoName,
repoHash,
}
});
// Get model selection configuration
const modelConfiguration = await modelSelection.getConfiguration();
// Setup the sidecar client here
const sidecarUrl = await startSidecarBinary(context.globalStorageUri.fsPath, env.appRoot);
// allow-any-unicode-next-line
// window.showInformationMessage(`Sidecar binary 🦀 started at ${sidecarUrl}`);
const sidecarClient = new SideCarClient(sidecarUrl, modelConfiguration);
SIDECAR_CLIENT = sidecarClient;
// we want to send the open tabs here to the sidecar
const openTextDocuments = await getRelevantFiles();
openTextDocuments.forEach((openTextDocument) => {
// not awaiting here so we can keep loading the extension in the background
if (shouldTrackFile(openTextDocument.uri)) {
sidecarClient.documentOpen(openTextDocument.uri.fsPath, openTextDocument.contents, openTextDocument.language);
}
});
// Setup the current repo representation here
const currentRepo = new RepoRef(
// We assume the root-path is the one we are interested in
rootPath,
RepoRefBackend.local,
);
// setup the callback for the model configuration
modelSelection.onDidChangeConfiguration((config) => {
sidecarClient.updateModelConfiguration(config);
// console.log('Model configuration updated:' + JSON.stringify(config));
});
await sidecarClient.indexRepositoryIfNotInvoked(currentRepo);
// Show the indexing percentage on startup
await reportIndexingPercentage(sidecarClient, currentRepo);
// register the inline code completion provider
await createInlineCompletionItemProvider(
{
triggerNotice: notice => {
console.log(notice);
},
sidecarClient,
}
);
// register the commands here for inline completion
aideCommands();
// set the status bar as well
startupStatusBar();
// Get the storage object here
const codeStoryStorage = await loadOrSaveToStorage(context.globalStorageUri.fsPath, rootPath);
logger.info(codeStoryStorage);
logger.info(rootPath);
// Register the semantic search command here
commands.registerCommand('codestory.semanticSearch', async (prompt: string): Promise<CodeSymbolInformationEmbeddings[]> => {
logger.info('[semanticSearch][extension] We are executing semantic search :' + prompt);
postHogClient?.capture({
distinctId: await getUniqueId(),
event: 'search',
properties: {
prompt,
repoName,
repoHash,
},
});
// We should be using the searchIndexCollection instead here, but for now
// embedding search is fine
// Here we will ping the semantic client instead so we can get the results
const results = await sidecarClient.getSemanticSearchResult(
prompt,
currentRepo,
);
return results;
});
// Register the quick action providers
const aideQuickFix = new AideQuickFix();
languages.registerCodeActionsProvider('*', aideQuickFix);
const chatAgentProvider = new CSChatAgentProvider(
rootPath, repoName, repoHash,
uniqueUserId,
sidecarClient, currentRepo, projectContext
);
context.subscriptions.push(chatAgentProvider);
// add the recent edits retriver to the subscriptions
// so we can grab the recent edits very quickly
const recentEditsRetriever = new RecentEditsRetriever(300 * 1000, workspace);
context.subscriptions.push(recentEditsRetriever);
const probeProvider = new AideProbeProvider(sidecarClient, rootPath, recentEditsRetriever);
context.subscriptions.push(probeProvider);
// Register feedback commands
context.subscriptions.push(
commands.registerCommand('codestory.feedback', async () => {
// Redirect to Discord server link
await commands.executeCommand('vscode.open', 'https://discord.gg/FdKXRDGVuz');
})
);
// records when we change to a new text document
workspace.onDidChangeTextDocument(async (event) => {
console.log('onDidChangeTextDocument');
const fileName = event.document.fileName;
await csEventHandler.onDidChangeTextDocument(fileName);
});
window.onDidChangeActiveTextEditor(async (editor) => {
if (editor) {
const activeDocument = editor.document;
if (activeDocument) {
const activeDocumentUri = activeDocument.uri;
if (shouldTrackFile(activeDocumentUri)) {
// track that changed document over here
await csEventHandler.onDidChangeTextDocument(activeDocumentUri.fsPath);
await sidecarClient.documentOpen(
activeDocumentUri.fsPath,
activeDocument.getText(),
activeDocument.languageId
);
}
}
}
});
// When the selection changes in the editor we should trigger an event
window.onDidChangeTextEditorSelection(async (event) => {
const textEditor = event.textEditor;
if (shouldTrackFile(textEditor.document.uri)) {
console.log('onDidChangeTextEditorSelection');
console.log(event.selections);
// track the changed selection over here
const selections = event.selections;
if (selections.length !== 0) {
await csEventHandler.onDidChangeTextDocumentSelection(textEditor.document.uri.fsPath, selections);
}
}
});
// Listen to all the files which are changing, so we can keep our tree sitter cache hot
workspace.onDidChangeTextDocument(async (event) => {
const documentUri = event.document.uri;
// if its a schema type, then skip tracking it
if (documentUri.scheme === 'vscode') {
return;
}
// TODO(skcd): we want to send the file change event to the sidecar over here
if (shouldTrackFile(documentUri)) {
await sidecarClient.documentContentChange(
documentUri.fsPath,
event.contentChanges,
event.document.getText(),
event.document.languageId,
);
}
});
const diagnosticsListener = languages.onDidChangeDiagnostics(async (event) => {
for (const uri of event.uris) {
// filter out diagnostics which are ONLY errors and warnings
const diagnostics = languages.getDiagnostics(uri).filter((diagnostic) => {
return (diagnostic.severity === DiagnosticSeverity.Error || diagnostic.severity === DiagnosticSeverity.Warning);
});
// Send diagnostics to sidecar
try {
await sidecarClient.sendDiagnostics(uri.toString(), diagnostics);
} catch (error) {
// console.error(`Failed to send diagnostics for ${uri.toString()}:`, error);
}
}
});
// shouldn't all listeners have this?
context.subscriptions.push(diagnosticsListener);
}