Skip to content

Commit ddf43cd

Browse files
authored
CodeMapper support (#55406)
1 parent 8537bb7 commit ddf43cd

File tree

64 files changed

+1931
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+1931
-0
lines changed

src/harness/client.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,8 @@ export class SessionClient implements LanguageService {
789789
});
790790
}
791791

792+
mapCode = notImplemented;
793+
792794
private createFileLocationOrRangeRequestArgs(positionOrRange: number | TextRange, fileName: string): protocol.FileLocationOrRangeRequestArgs {
793795
return typeof positionOrRange === "number"
794796
? this.createFileLocationRequestArgs(fileName, positionOrRange)

src/harness/fourslashImpl.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4510,6 +4510,57 @@ export class TestState {
45104510

45114511
this.verifyCurrentFileContent(newFileContent);
45124512
}
4513+
4514+
public baselineMapCode(
4515+
ranges: Range[][],
4516+
changes: string[] = [],
4517+
): void {
4518+
const fileName = this.activeFile.fileName;
4519+
const focusLocations = ranges.map(r =>
4520+
r.map(({ pos, end }) => {
4521+
return { start: pos, length: end - pos };
4522+
})
4523+
);
4524+
let before = this.getFileContent(fileName);
4525+
const edits = this.languageService.mapCode(
4526+
fileName,
4527+
// We trim the leading whitespace stuff just so our test cases can be more readable.
4528+
changes,
4529+
focusLocations,
4530+
this.formatCodeSettings,
4531+
{},
4532+
);
4533+
this.applyChanges(edits);
4534+
focusLocations.forEach(r => {
4535+
r.sort((a, b) => a.start - b.start);
4536+
});
4537+
focusLocations.sort((a, b) => a[0].start - b[0].start);
4538+
for (const subLoc of focusLocations) {
4539+
for (const { start, length } of subLoc) {
4540+
let offset = 0;
4541+
for (const sl2 of focusLocations) {
4542+
for (const { start: s2, length: l2 } of sl2) {
4543+
if (s2 < start) {
4544+
offset += 4;
4545+
if ((s2 + l2) > start) {
4546+
offset -= 2;
4547+
}
4548+
}
4549+
}
4550+
}
4551+
before = before.slice(0, start + offset) + "[|" + before.slice(start + offset, start + offset + length) + "|]" + before.slice(start + offset + length);
4552+
}
4553+
}
4554+
const after = this.getFileContent(fileName);
4555+
const baseline = `
4556+
// === ORIGINAL ===
4557+
${before}
4558+
// === INCOMING CHANGES ===
4559+
${changes.join("\n// ---\n")}
4560+
// === MAPPED ===
4561+
${after}`;
4562+
this.baseline("mapCode", baseline, ".mapCode.ts");
4563+
}
45134564
}
45144565

45154566
function updateTextRangeForTextChanges({ pos, end }: ts.TextRange, textChanges: readonly ts.TextChange[]): ts.TextRange {

src/harness/fourslashInterfaceImpl.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,10 @@ export class VerifyNegatable {
239239
public uncommentSelection(newFileContent: string) {
240240
this.state.uncommentSelection(newFileContent);
241241
}
242+
243+
public baselineMapCode(ranges: FourSlash.Range[][], changes: string[] = []): void {
244+
this.state.baselineMapCode(ranges, changes);
245+
}
242246
}
243247

244248
export class Verify extends VerifyNegatable {

src/server/protocol.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ export const enum CommandTypes {
200200
ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls",
201201
ProvideInlayHints = "provideInlayHints",
202202
WatchChange = "watchChange",
203+
MapCode = "mapCode",
203204
}
204205

205206
/**
@@ -2342,6 +2343,38 @@ export interface InlayHintsResponse extends Response {
23422343
body?: InlayHintItem[];
23432344
}
23442345

2346+
export interface MapCodeRequestArgs extends FileRequestArgs {
2347+
/**
2348+
* The files and changes to try and apply/map.
2349+
*/
2350+
mapping: MapCodeRequestDocumentMapping;
2351+
}
2352+
2353+
export interface MapCodeRequestDocumentMapping {
2354+
/**
2355+
* The specific code to map/insert/replace in the file.
2356+
*/
2357+
contents: string[];
2358+
2359+
/**
2360+
* Areas of "focus" to inform the code mapper with. For example, cursor
2361+
* location, current selection, viewport, etc. Nested arrays denote
2362+
* priority: toplevel arrays are more important than inner arrays, and
2363+
* inner array priorities are based on items within that array. Items
2364+
* earlier in the arrays have higher priority.
2365+
*/
2366+
focusLocations?: TextSpan[][];
2367+
}
2368+
2369+
export interface MapCodeRequest extends FileRequest {
2370+
command: CommandTypes.MapCode;
2371+
arguments: MapCodeRequestArgs;
2372+
}
2373+
2374+
export interface MapCodeResponse extends Response {
2375+
body: readonly FileCodeEdits[];
2376+
}
2377+
23452378
/**
23462379
* Synchronous request for semantic diagnostics of one file.
23472380
*/

src/server/session.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1906,6 +1906,26 @@ export class Session<TMessage = string> implements EventSender {
19061906
});
19071907
}
19081908

1909+
private mapCode(args: protocol.MapCodeRequestArgs): protocol.FileCodeEdits[] {
1910+
const formatOptions = this.getHostFormatOptions();
1911+
const preferences = this.getHostPreferences();
1912+
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
1913+
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
1914+
const focusLocations = args.mapping.focusLocations?.map(spans => {
1915+
return spans.map(loc => {
1916+
const start = scriptInfo.lineOffsetToPosition(loc.start.line, loc.start.offset);
1917+
const end = scriptInfo.lineOffsetToPosition(loc.end.line, loc.end.offset);
1918+
return {
1919+
start,
1920+
length: end - start,
1921+
};
1922+
});
1923+
});
1924+
1925+
const changes = languageService.mapCode(file, args.mapping.contents, focusLocations, formatOptions, preferences);
1926+
return this.mapTextChangesToCodeEdits(changes);
1927+
}
1928+
19091929
private setCompilerOptionsForInferredProjects(args: protocol.SetCompilerOptionsForInferredProjectsArgs): void {
19101930
this.projectService.setCompilerOptionsForInferredProjects(args.options, args.projectRootPath);
19111931
}
@@ -3610,6 +3630,9 @@ export class Session<TMessage = string> implements EventSender {
36103630
[protocol.CommandTypes.ProvideInlayHints]: (request: protocol.InlayHintsRequest) => {
36113631
return this.requiredResponse(this.provideInlayHints(request.arguments));
36123632
},
3633+
[protocol.CommandTypes.MapCode]: (request: protocol.MapCodeRequest) => {
3634+
return this.requiredResponse(this.mapCode(request.arguments));
3635+
},
36133636
}));
36143637

36153638
public addProtocolHandler(command: string, handler: (request: protocol.Request) => HandlerResponse) {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/* Generated file to emulate the ts.MapCode namespace. */
2+
3+
export * from "../mapCode.js";

src/services/_namespaces/ts.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ export { GoToDefinition };
3333
import * as InlayHints from "./ts.InlayHints.js";
3434
export { InlayHints };
3535
import * as JsDoc from "./ts.JsDoc.js";
36+
import * as MapCode from "./ts.MapCode.js";
37+
export { MapCode };
3638
export { JsDoc };
3739
import * as NavigateTo from "./ts.NavigateTo.js";
3840
export { NavigateTo };

0 commit comments

Comments
 (0)