Skip to content

Commit 85a589b

Browse files
committed
Initial progress on automatic application and diagnostics
1 parent 41ad592 commit 85a589b

File tree

5 files changed

+111
-35
lines changed

5 files changed

+111
-35
lines changed

accelerant/chat.py

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,17 @@
1010
ChatCompletionSystemMessageParam,
1111
ParsedChatCompletion,
1212
)
13-
from pydantic import BaseModel
1413
from rich import print as rprint
1514

15+
from accelerant.chat_interface import (
16+
ProjectAnalysis,
17+
OptimizationSuite,
18+
)
19+
from accelerant.lsp import (
20+
request_document_diagnostics,
21+
syncexec,
22+
)
23+
from accelerant.patch import apply_simultaneous_suggestions
1624
from accelerant.perf import PerfData
1725
from accelerant.project import Project
1826
from accelerant.tools import (
@@ -24,28 +32,6 @@
2432
from perfparser import LineLoc
2533

2634

27-
class RegionAnalysis(BaseModel):
28-
filename: str
29-
line: int
30-
performanceAnalysis: str
31-
32-
33-
class ProjectAnalysis(BaseModel):
34-
regions: List[RegionAnalysis]
35-
36-
37-
class OptimizationSuggestion(BaseModel):
38-
filename: str
39-
startLine: int
40-
endLine: int
41-
newCode: str
42-
43-
44-
class OptimizationSuite(BaseModel):
45-
highLevelSummary: str
46-
suggestions: List[OptimizationSuggestion]
47-
48-
4935
def optimize_locations(
5036
project: Project, locs: List[LineLoc], perf_data: Optional[PerfData], model_id: str
5137
) -> str:
@@ -117,9 +103,22 @@ def optimize_locations(
117103
),
118104
{"role": "user", "content": sugg_req_msg},
119105
]
120-
sugg_str, _ = run_chat(
106+
sugg_str, opt_suite = run_chat(
121107
messages, client, model_id, tool_runner, response_format=OptimizationSuite
122108
)
109+
assert opt_suite
110+
apply_simultaneous_suggestions(opt_suite.suggestions, project)
111+
# TODO: collect diagnostics from all files, then feed them back to LLM
112+
# FIXME: how to get diagnostics from files that depend on these files
113+
# but weren't themselves changed?
114+
print(
115+
syncexec(
116+
project.lsp(),
117+
request_document_diagnostics(
118+
project.lsp().language_server, opt_suite.suggestions[0].filename
119+
),
120+
)
121+
)
123122
return sugg_str
124123

125124

accelerant/chat_interface.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from typing import List
2+
from pydantic import BaseModel
3+
4+
5+
class RegionAnalysis(BaseModel):
6+
filename: str
7+
line: int
8+
performanceAnalysis: str
9+
10+
11+
class ProjectAnalysis(BaseModel):
12+
regions: List[RegionAnalysis]
13+
14+
15+
class OptimizationSuggestion(BaseModel):
16+
filename: str
17+
startLine: int
18+
endLine: int
19+
newCode: str
20+
21+
22+
class OptimizationSuite(BaseModel):
23+
highLevelSummary: str
24+
suggestions: List[OptimizationSuggestion]

accelerant/lsp.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,15 @@
55
from multilspy import LanguageServer, SyncLanguageServer
66
from multilspy.multilspy_utils import PathUtils
77

8-
from accelerant.project import Project
9-
108

119
async def request_definition_full(
1210
lsp: LanguageServer, relative_file_path: str, line: int, column: int
1311
):
1412
with lsp.open_file(relative_file_path):
13+
uri = relpath_to_uri(relative_file_path, lsp.repository_root_path)
1514
response = await lsp.server.send.definition(
1615
{
17-
"textDocument": {
18-
"uri": Path(
19-
str(PurePath(lsp.repository_root_path, relative_file_path))
20-
).as_uri()
21-
},
16+
"textDocument": {"uri": uri},
2217
"position": {
2318
"line": line,
2419
"character": column,
@@ -28,9 +23,22 @@ async def request_definition_full(
2823
return response
2924

3025

31-
def extract_relative_path(uri: str, project: Project) -> str:
26+
async def request_document_diagnostics(lsp: LanguageServer, relpath: str):
27+
with lsp.open_file(relpath):
28+
uri = relpath_to_uri(relpath, lsp.repository_root_path)
29+
lsp.server.notify.did_save_text_document({"textDocument": {"uri": uri}})
30+
return await lsp.server.send.text_document_diagnostic(
31+
{"textDocument": {"uri": uri}}
32+
)
33+
34+
35+
def relpath_to_uri(relpath: str, root: str) -> str:
36+
return Path(str(PurePath(root, relpath))).as_uri()
37+
38+
39+
def uri_to_relpath(uri: str, root: str) -> str:
3240
absolutePath = PathUtils.uri_to_path(uri)
33-
relativePath = str(PurePath(os.path.relpath(absolutePath, project._root)))
41+
relativePath = str(PurePath(os.path.relpath(absolutePath, root)))
3442
return relativePath
3543

3644

accelerant/patch.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from pathlib import PurePath
2+
from typing import List
3+
4+
from accelerant.chat_interface import OptimizationSuggestion
5+
from accelerant.project import Project
6+
7+
8+
def apply_simultaneous_suggestions(
9+
suggestions: List[OptimizationSuggestion],
10+
project: Project,
11+
):
12+
"""
13+
Apply suggestions that were created to be applied simultaneously.
14+
In other words, they don't account for changes in line numbers caused by
15+
other suggestions.
16+
"""
17+
by_file: dict[str, List[OptimizationSuggestion]] = {}
18+
for sugg in suggestions:
19+
if sugg.filename not in by_file:
20+
by_file[sugg.filename] = []
21+
by_file[sugg.filename].append(sugg)
22+
23+
for relpath, suggs in by_file.items():
24+
abspath = PurePath(project._root, relpath)
25+
# FIXME: handle conflicting suggestions (i.e. that overlap)
26+
suggs = list(sorted(suggs, key=lambda s: s.startLine))
27+
with open(abspath, "r") as f:
28+
old_lines = enumerate(f.readlines(), start=1)
29+
30+
print(abspath, suggs)
31+
with open(abspath, "w") as f:
32+
skip_until_after = 0
33+
for old_num, old_line in old_lines:
34+
if old_num <= skip_until_after:
35+
continue
36+
if suggs and suggs[0].startLine == old_num:
37+
print("apply")
38+
sugg = suggs[0]
39+
suggs = suggs[1:]
40+
f.write(sugg.newCode)
41+
if not sugg.newCode.endswith("\n"):
42+
f.write("\n")
43+
skip_until_after = sugg.endLine
44+
else:
45+
f.write(old_line)

accelerant/tools.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import openai
66
from openai.types.chat import ChatCompletionToolParam
77

8-
from accelerant.lsp import request_definition_full, syncexec, extract_relative_path
8+
from accelerant.lsp import request_definition_full, syncexec, uri_to_relpath
99
from accelerant.util import find_symbol, truncate_for_llm
1010
from accelerant.project import Project
1111

@@ -109,7 +109,7 @@ def convert_lsp_loc(r: dict, p: Project) -> dict:
109109
if "relativePath" in r:
110110
filename = r["relativePath"]
111111
else:
112-
filename = extract_relative_path(r["targetUri"], p)
112+
filename = uri_to_relpath(r["targetUri"], str(p._root))
113113
if "targetRange" in r:
114114
range = r["targetRange"]
115115
else:

0 commit comments

Comments
 (0)