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
|
import fetch from "node-fetch";
import * as path from "path";
import * as vscode from "vscode";
import {
Configuration,
DebugApi,
RangeInFile,
SerializedDebugContext,
UnittestApi,
} from "./client";
import { convertSingleToDoubleQuoteJSON } from "./util/util";
import { getExtensionUri } from "./util/vscode";
const axios = require("axios").default;
const util = require("util");
const exec = util.promisify(require("child_process").exec);
const configuration = new Configuration({
basePath: get_api_url(),
fetchApi: fetch as any,
middleware: [
{
pre: async (context) => {
// If there is a SerializedDebugContext in the body, add the files for the filesystem
context.init.body;
// Add the VS Code Machine Code Header
context.init.headers = {
...context.init.headers,
"x-vsc-machine-id": vscode.env.machineId,
};
},
},
],
});
export const debugApi = new DebugApi(configuration);
export const unittestApi = new UnittestApi(configuration);
function get_python_path() {
return path.join(getExtensionUri().fsPath, "..");
}
export function get_api_url() {
let extensionUri = getExtensionUri();
let configFile = path.join(extensionUri.fsPath, "config/config.json");
let config = require(configFile);
if (config.API_URL) {
return config.API_URL;
}
return "http://localhost:8000";
}
const API_URL = get_api_url();
export function getContinueServerUrl() {
return (
vscode.workspace.getConfiguration("continue").get<string>("serverUrl") ||
"http://localhost:8000"
);
}
function build_python_command(cmd: string): string {
return `cd ${get_python_path()} && source env/bin/activate && ${cmd}`;
}
function listToCmdLineArgs(list: string[]): string {
return list.map((el) => `"$(echo "${el}")"`).join(" ");
}
export async function runPythonScript(
scriptName: string,
args: string[]
): Promise<any> {
// TODO: Need to make sure that the path to poetry is in the PATH and that it is installed in the first place. Realistically also need to install npm in some cases.
const command = `export PATH="$PATH:/opt/homebrew/bin" && cd ${path.join(
getExtensionUri().fsPath,
"scripts"
)} && source env/bin/activate && python3 ${scriptName} ${listToCmdLineArgs(
args
)}`;
const { stdout, stderr } = await exec(command);
try {
let jsonString = stdout.substring(
stdout.indexOf("{"),
stdout.lastIndexOf("}") + 1
);
jsonString = convertSingleToDoubleQuoteJSON(jsonString);
return JSON.parse(jsonString);
} catch (e) {
if (stderr) {
throw new Error(stderr);
} else {
throw new Error("Failed to parse JSON: " + e);
}
}
}
function parseStdout(
stdout: string,
key: string,
until_end: boolean = false
): string {
const prompt = `${key}=`;
let lines = stdout.split("\n");
let value: string = "";
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith(prompt)) {
if (until_end) {
return lines.slice(i).join("\n").substring(prompt.length);
} else {
return lines[i].substring(prompt.length);
}
}
}
return "";
}
export async function askQuestion(
question: string,
workspacePath: string
): Promise<{ answer: string; range: vscode.Range; filename: string }> {
const command = build_python_command(
`python3 ${path.join(
get_python_path(),
"ask.py"
)} ask ${workspacePath} "${question}"`
);
const { stdout, stderr } = await exec(command);
if (stderr) {
throw new Error(stderr);
}
// Use the output
const answer = parseStdout(stdout, "Answer");
const filename = parseStdout(stdout, "Filename");
const startLineno = parseInt(parseStdout(stdout, "Start lineno"));
const endLineno = parseInt(parseStdout(stdout, "End lineno"));
const range = new vscode.Range(
new vscode.Position(startLineno, 0),
new vscode.Position(endLineno, 0)
);
if (answer && filename && startLineno && endLineno) {
return { answer, filename, range };
} else {
throw new Error("Error: No answer found");
}
}
export async function apiRequest(
endpoint: string,
options: {
method?: string;
query?: { [key: string]: any };
body?: { [key: string]: any };
}
): Promise<any> {
let defaults = {
method: "GET",
query: {},
body: {},
};
options = Object.assign(defaults, options); // Second takes over first
if (endpoint.startsWith("/")) endpoint = endpoint.substring(1);
console.log("API request: ", options.body);
let resp;
try {
resp = await axios({
method: options.method,
url: `${API_URL}/${endpoint}`,
data: options.body,
params: options.query,
headers: {
"x-vsc-machine-id": vscode.env.machineId,
},
});
} catch (err) {
console.log("Error: ", err);
throw err;
}
return resp.data;
}
// Write a docstring for the most specific function or class at the current line in the given file
export async function writeDocstringForFunction(
filename: string,
position: vscode.Position
): Promise<{ lineno: number; docstring: string }> {
let resp = await apiRequest("docstring/forline", {
query: {
filecontents: (
await vscode.workspace.fs.readFile(vscode.Uri.file(filename))
).toString(),
lineno: position.line.toString(),
},
});
const lineno = resp.lineno;
const docstring = resp.completion;
if (lineno && docstring) {
return { lineno, docstring };
} else {
throw new Error("Error: No docstring returned");
}
}
export async function findSuspiciousCode(
ctx: SerializedDebugContext
): Promise<RangeInFile[]> {
if (!ctx.traceback) return [];
let files = await getFileContents(
getFilenamesFromPythonStacktrace(ctx.traceback)
);
let resp = await debugApi.findSusCodeEndpointDebugFindPost({
findBody: {
traceback: ctx.traceback,
description: ctx.description,
filesystem: files,
},
});
let ranges = resp.response;
if (
ranges.length <= 1 &&
ctx.traceback &&
ctx.traceback.includes("AssertionError")
) {
let parsed_traceback =
await debugApi.parseTracebackEndpointDebugParseTracebackGet({
traceback: ctx.traceback,
});
let last_frame = parsed_traceback.frames[0];
if (!last_frame) return [];
ranges = (
await runPythonScript("build_call_graph.py", [
last_frame.filepath,
last_frame.lineno.toString(),
last_frame._function,
])
).value;
}
return ranges;
}
export async function writeUnitTestForFunction(
filename: string,
position: vscode.Position
): Promise<string> {
let resp = await apiRequest("unittest/forline", {
method: "POST",
body: {
filecontents: (
await vscode.workspace.fs.readFile(vscode.Uri.file(filename))
).toString(),
lineno: position.line,
userid: vscode.env.machineId,
},
});
return resp.completion;
}
async function getFileContents(
files: string[]
): Promise<{ [key: string]: string }> {
let contents = await Promise.all(
files.map(async (file: string) => {
return (
await vscode.workspace.fs.readFile(vscode.Uri.file(file))
).toString();
})
);
let fileContents: { [key: string]: string } = {};
for (let i = 0; i < files.length; i++) {
fileContents[files[i]] = contents[i];
}
return fileContents;
}
function getFilenamesFromPythonStacktrace(traceback: string): string[] {
let filenames: string[] = [];
for (let line of traceback.split("\n")) {
let match = line.match(/File "(.*)", line/);
if (match) {
filenames.push(match[1]);
}
}
return filenames;
}
|