Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CodeMapper support #55406

Merged
merged 15 commits into from
May 22, 2024
2 changes: 2 additions & 0 deletions src/harness/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,8 @@ export class SessionClient implements LanguageService {
});
}

mapCode = notImplemented;

private createFileLocationOrRangeRequestArgs(positionOrRange: number | TextRange, fileName: string): protocol.FileLocationOrRangeRequestArgs {
return typeof positionOrRange === "number"
? this.createFileLocationRequestArgs(fileName, positionOrRange)
Expand Down
51 changes: 51 additions & 0 deletions src/harness/fourslashImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4510,6 +4510,57 @@ export class TestState {

this.verifyCurrentFileContent(newFileContent);
}

public baselineMapCode(
ranges: Range[][],
changes: string[] = [],
): void {
const fileName = this.activeFile.fileName;
const focusLocations = ranges.map(r =>
r.map(({ pos, end }) => {
return { start: pos, length: end - pos };
})
);
let before = this.getFileContent(fileName);
const edits = this.languageService.mapCode(
fileName,
// We trim the leading whitespace stuff just so our test cases can be more readable.
changes,
focusLocations,
this.formatCodeSettings,
{},
);
this.applyChanges(edits);
focusLocations.forEach(r => {
r.sort((a, b) => a.start - b.start);
});
focusLocations.sort((a, b) => a[0].start - b[0].start);
zkat marked this conversation as resolved.
Show resolved Hide resolved
for (const subLoc of focusLocations) {
for (const { start, length } of subLoc) {
let offset = 0;
for (const sl2 of focusLocations) {
for (const { start: s2, length: l2 } of sl2) {
if (s2 < start) {
offset += 4;
if ((s2 + l2) > start) {
offset -= 2;
}
}
}
}
before = before.slice(0, start + offset) + "[|" + before.slice(start + offset, start + offset + length) + "|]" + before.slice(start + offset + length);
}
}
const after = this.getFileContent(fileName);
const baseline = `
// === ORIGINAL ===
${before}
// === INCOMING CHANGES ===
${changes.join("\n// ---\n")}
// === MAPPED ===
${after}`;
this.baseline("mapCode", baseline, ".mapCode.ts");
}
}

function updateTextRangeForTextChanges({ pos, end }: ts.TextRange, textChanges: readonly ts.TextChange[]): ts.TextRange {
Expand Down
4 changes: 4 additions & 0 deletions src/harness/fourslashInterfaceImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,10 @@ export class VerifyNegatable {
public uncommentSelection(newFileContent: string) {
this.state.uncommentSelection(newFileContent);
}

public baselineMapCode(ranges: FourSlash.Range[][], changes: string[] = []): void {
this.state.baselineMapCode(ranges, changes);
}
}

export class Verify extends VerifyNegatable {
Expand Down
33 changes: 33 additions & 0 deletions src/server/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ export const enum CommandTypes {
ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls",
ProvideInlayHints = "provideInlayHints",
WatchChange = "watchChange",
MapCode = "mapCode",
zkat marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down Expand Up @@ -2342,6 +2343,38 @@ export interface InlayHintsResponse extends Response {
body?: InlayHintItem[];
}

export interface MapCodeRequestArgs extends FileRequestArgs {
/**
* The files and changes to try and apply/map.
*/
mapping: MapCodeRequestDocumentMapping;
}

export interface MapCodeRequestDocumentMapping {
/**
* The specific code to map/insert/replace in the file.
*/
contents: string[];
zkat marked this conversation as resolved.
Show resolved Hide resolved

/**
* Areas of "focus" to inform the code mapper with. For example, cursor
sandersn marked this conversation as resolved.
Show resolved Hide resolved
* location, current selection, viewport, etc. Nested arrays denote
* priority: toplevel arrays are more important than inner arrays, and
* inner array priorities are based on items within that array. Items
* earlier in the arrays have higher priority.
*/
focusLocations?: TextSpan[][];
}

export interface MapCodeRequest extends FileRequest {
command: CommandTypes.MapCode;
arguments: MapCodeRequestArgs;
}

export interface MapCodeResponse extends Response {
body: readonly FileCodeEdits[];
}

/**
* Synchronous request for semantic diagnostics of one file.
*/
Expand Down
23 changes: 23 additions & 0 deletions src/server/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1906,6 +1906,26 @@ export class Session<TMessage = string> implements EventSender {
});
}

private mapCode(args: protocol.MapCodeRequestArgs): protocol.FileCodeEdits[] {
const formatOptions = this.getHostFormatOptions();
const preferences = this.getHostPreferences();
MariaSolOs marked this conversation as resolved.
Show resolved Hide resolved
const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args);
const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!;
const focusLocations = args.mapping.focusLocations?.map(spans => {
return spans.map(loc => {
const start = scriptInfo.lineOffsetToPosition(loc.start.line, loc.start.offset);
const end = scriptInfo.lineOffsetToPosition(loc.end.line, loc.end.offset);
return {
start,
length: end - start,
};
});
});

const changes = languageService.mapCode(file, args.mapping.contents, focusLocations, formatOptions, preferences);
return this.mapTextChangesToCodeEdits(changes);
}

private setCompilerOptionsForInferredProjects(args: protocol.SetCompilerOptionsForInferredProjectsArgs): void {
this.projectService.setCompilerOptionsForInferredProjects(args.options, args.projectRootPath);
}
Expand Down Expand Up @@ -3610,6 +3630,9 @@ export class Session<TMessage = string> implements EventSender {
[protocol.CommandTypes.ProvideInlayHints]: (request: protocol.InlayHintsRequest) => {
return this.requiredResponse(this.provideInlayHints(request.arguments));
},
[protocol.CommandTypes.MapCode]: (request: protocol.MapCodeRequest) => {
return this.requiredResponse(this.mapCode(request.arguments));
},
}));

public addProtocolHandler(command: string, handler: (request: protocol.Request) => HandlerResponse) {
Expand Down
3 changes: 3 additions & 0 deletions src/services/_namespaces/ts.MapCode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/* Generated file to emulate the ts.MapCode namespace. */

export * from "../mapCode.js";
2 changes: 2 additions & 0 deletions src/services/_namespaces/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export { GoToDefinition };
import * as InlayHints from "./ts.InlayHints.js";
export { InlayHints };
import * as JsDoc from "./ts.JsDoc.js";
import * as MapCode from "./ts.MapCode.js";
export { MapCode };
export { JsDoc };
import * as NavigateTo from "./ts.NavigateTo.js";
export { NavigateTo };
Expand Down
Loading