Skip to content

Commit 3a53356

Browse files
potetojbrown215
andauthored
[forgive] Polish decorations (#33002)
Polishes up decorations. Co-authored-by: Jordan Brown <[email protected]> --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33002). * #33004 * #33003 * __->__ #33002 Co-authored-by: Jordan Brown <[email protected]>
1 parent b75af04 commit 3a53356

File tree

6 files changed

+170
-71
lines changed

6 files changed

+170
-71
lines changed

compiler/packages/babel-plugin-react-compiler/src/Inference/InferEffectDependencies.ts

+1
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ export function inferEffectDependencies(fn: HIRFunction): void {
261261
} else if (
262262
value.args.length >= 2 &&
263263
value.args.length - 1 === autodepFnLoads.get(callee.identifier.id) &&
264+
value.args[0] != null &&
264265
value.args[0].kind === 'Identifier'
265266
) {
266267
const penultimateArg = value.args[value.args.length - 2];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import * as vscode from 'vscode';
2+
import {
3+
LanguageClient,
4+
RequestType,
5+
type Position,
6+
} from 'vscode-languageclient/node';
7+
import {positionLiteralToVSCodePosition, positionsToRange} from './mapping';
8+
9+
export type AutoDepsDecorationsLSPEvent = {
10+
useEffectCallExpr: [Position, Position];
11+
decorations: Array<[Position, Position]>;
12+
};
13+
14+
export interface AutoDepsDecorationsParams {
15+
position: Position;
16+
}
17+
18+
export namespace AutoDepsDecorationsRequest {
19+
export const type = new RequestType<
20+
AutoDepsDecorationsParams,
21+
AutoDepsDecorationsLSPEvent | null,
22+
void
23+
>('react/autodeps_decorations');
24+
}
25+
26+
const inferredEffectDepDecoration =
27+
vscode.window.createTextEditorDecorationType({
28+
// TODO: make configurable?
29+
borderColor: new vscode.ThemeColor('diffEditor.move.border'),
30+
borderStyle: 'solid',
31+
borderWidth: '0 0 4px 0',
32+
});
33+
34+
let currentlyDecoratedAutoDepFnLoc: vscode.Range | null = null;
35+
export function getCurrentlyDecoratedAutoDepFnLoc(): vscode.Range | null {
36+
return currentlyDecoratedAutoDepFnLoc;
37+
}
38+
export function setCurrentlyDecoratedAutoDepFnLoc(range: vscode.Range): void {
39+
currentlyDecoratedAutoDepFnLoc = range;
40+
}
41+
export function clearCurrentlyDecoratedAutoDepFnLoc(): void {
42+
currentlyDecoratedAutoDepFnLoc = null;
43+
}
44+
45+
let decorationRequestId = 0;
46+
export type AutoDepsDecorationsOptions = {
47+
shouldUpdateCurrent: boolean;
48+
};
49+
export function requestAutoDepsDecorations(
50+
client: LanguageClient,
51+
position: vscode.Position,
52+
options: AutoDepsDecorationsOptions,
53+
) {
54+
const id = ++decorationRequestId;
55+
client
56+
.sendRequest(AutoDepsDecorationsRequest.type, {position})
57+
.then(response => {
58+
if (response !== null) {
59+
const {
60+
decorations,
61+
useEffectCallExpr: [start, end],
62+
} = response;
63+
// Maintain ordering
64+
if (decorationRequestId === id) {
65+
if (options.shouldUpdateCurrent) {
66+
setCurrentlyDecoratedAutoDepFnLoc(positionsToRange(start, end));
67+
}
68+
drawInferredEffectDepDecorations(decorations);
69+
}
70+
} else {
71+
clearCurrentlyDecoratedAutoDepFnLoc();
72+
clearDecorations(inferredEffectDepDecoration);
73+
}
74+
});
75+
}
76+
77+
export function drawInferredEffectDepDecorations(
78+
decorations: Array<[Position, Position]>,
79+
): void {
80+
const decorationOptions = decorations.map(([start, end]) => {
81+
return {
82+
range: new vscode.Range(
83+
positionLiteralToVSCodePosition(start),
84+
positionLiteralToVSCodePosition(end),
85+
),
86+
hoverMessage: 'Inferred as an effect dependency',
87+
};
88+
});
89+
vscode.window.activeTextEditor?.setDecorations(
90+
inferredEffectDepDecoration,
91+
decorationOptions,
92+
);
93+
}
94+
95+
export function clearDecorations(
96+
decorationType: vscode.TextEditorDecorationType,
97+
) {
98+
vscode.window.activeTextEditor?.setDecorations(decorationType, []);
99+
}

compiler/packages/react-forgive/client/src/extension.ts

+26-45
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,16 @@ import {
55
LanguageClient,
66
LanguageClientOptions,
77
type Position,
8-
RequestType,
98
ServerOptions,
109
TransportKind,
1110
} from 'vscode-languageclient/node';
12-
import {WHITE} from './colors';
11+
import {positionLiteralToVSCodePosition} from './mapping';
12+
import {
13+
getCurrentlyDecoratedAutoDepFnLoc,
14+
requestAutoDepsDecorations,
15+
} from './autodeps';
1316

1417
let client: LanguageClient;
15-
const inferredEffectDepDecoration =
16-
vscode.window.createTextEditorDecorationType({
17-
backgroundColor: WHITE.toAlphaString(0.3),
18-
});
19-
20-
type Range = [Position, Position];
21-
interface AutoDepsDecorationsParams {
22-
position: Position;
23-
}
24-
namespace AutoDepsDecorationsRequest {
25-
export const type = new RequestType<
26-
AutoDepsDecorationsParams,
27-
Array<Range> | null,
28-
void
29-
>('react/autodeps_decorations');
30-
}
3118

3219
export function activate(context: vscode.ExtensionContext) {
3320
const serverModule = context.asAbsolutePath(path.join('dist', 'server.js'));
@@ -71,31 +58,31 @@ export function activate(context: vscode.ExtensionContext) {
7158

7259
vscode.languages.registerHoverProvider(documentSelector, {
7360
provideHover(_document, position, _token) {
74-
client
75-
.sendRequest(AutoDepsDecorationsRequest.type, {position})
76-
.then(decorations => {
77-
if (Array.isArray(decorations)) {
78-
const decorationOptions = decorations.map(([start, end]) => {
79-
return {
80-
range: new vscode.Range(
81-
new vscode.Position(start.line, start.character),
82-
new vscode.Position(end.line, end.character),
83-
),
84-
hoverMessage: 'Inferred as an effect dependency',
85-
};
86-
});
87-
vscode.window.activeTextEditor?.setDecorations(
88-
inferredEffectDepDecoration,
89-
decorationOptions,
90-
);
91-
} else {
92-
clearDecorations(inferredEffectDepDecoration);
93-
}
94-
});
61+
requestAutoDepsDecorations(client, position, {shouldUpdateCurrent: true});
9562
return null;
9663
},
9764
});
9865

66+
vscode.workspace.onDidChangeTextDocument(async _e => {
67+
const currentlyDecoratedAutoDepFnLoc = getCurrentlyDecoratedAutoDepFnLoc();
68+
if (currentlyDecoratedAutoDepFnLoc !== null) {
69+
requestAutoDepsDecorations(client, currentlyDecoratedAutoDepFnLoc.start, {
70+
shouldUpdateCurrent: false,
71+
});
72+
}
73+
});
74+
75+
vscode.commands.registerCommand(
76+
'react.requestAutoDepsDecorations',
77+
(position: Position) => {
78+
requestAutoDepsDecorations(
79+
client,
80+
positionLiteralToVSCodePosition(position),
81+
{shouldUpdateCurrent: true},
82+
);
83+
},
84+
);
85+
9986
client.registerProposedFeatures();
10087
client.start();
10188
}
@@ -106,9 +93,3 @@ export function deactivate(): Thenable<void> | undefined {
10693
}
10794
return;
10895
}
109-
110-
export function clearDecorations(
111-
decorationType: vscode.TextEditorDecorationType,
112-
) {
113-
vscode.window.activeTextEditor?.setDecorations(decorationType, []);
114-
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import * as vscode from 'vscode';
2+
import {Position} from 'vscode-languageclient/node';
3+
4+
export function positionLiteralToVSCodePosition(
5+
position: Position,
6+
): vscode.Position {
7+
return new vscode.Position(position.line, position.character);
8+
}
9+
10+
export function positionsToRange(start: Position, end: Position): vscode.Range {
11+
return new vscode.Range(
12+
positionLiteralToVSCodePosition(start),
13+
positionLiteralToVSCodePosition(end),
14+
);
15+
}

compiler/packages/react-forgive/server/src/index.ts

+28-25
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
CodeAction,
1111
CodeActionKind,
1212
CodeLens,
13+
Command,
1314
createConnection,
1415
type InitializeParams,
1516
type InitializeResult,
@@ -96,6 +97,7 @@ connection.onInitialize((_params: InitializeParams) => {
9697
logger: {
9798
logEvent(_filename: string | null, event: LoggerEvent) {
9899
connection.console.info(`Received event: ${event.kind}`);
100+
connection.console.debug(JSON.stringify(event, null, 2));
99101
if (event.kind === 'CompileSuccess') {
100102
compiledFns.add(event);
101103
}
@@ -191,7 +193,6 @@ connection.onCodeLensResolve(lens => {
191193

192194
connection.onCodeAction(params => {
193195
connection.console.log('onCodeAction');
194-
connection.console.log(JSON.stringify(params, null, 2));
195196
const codeActions: Array<CodeAction> = [];
196197
for (const codeActionEvent of codeActionEvents) {
197198
if (
@@ -200,39 +201,41 @@ connection.onCodeAction(params => {
200201
codeActionEvent.anchorRange,
201202
)
202203
) {
203-
codeActions.push(
204-
CodeAction.create(
205-
codeActionEvent.title,
206-
{
207-
changes: {
208-
[params.textDocument.uri]: [
209-
{
210-
newText: codeActionEvent.newText,
211-
range: codeActionEvent.editRange,
212-
},
213-
],
214-
},
204+
const codeAction = CodeAction.create(
205+
codeActionEvent.title,
206+
{
207+
changes: {
208+
[params.textDocument.uri]: [
209+
{
210+
newText: codeActionEvent.newText,
211+
range: codeActionEvent.editRange,
212+
},
213+
],
215214
},
216-
codeActionEvent.kind,
217-
),
215+
},
216+
codeActionEvent.kind,
218217
);
218+
// After executing a codeaction, we want to draw autodep decorations again
219+
codeAction.command = Command.create(
220+
'Request autodeps decorations',
221+
'react.requestAutoDepsDecorations',
222+
codeActionEvent.anchorRange[0],
223+
);
224+
codeActions.push(codeAction);
219225
}
220226
}
221227
return codeActions;
222228
});
223229

224-
connection.onCodeActionResolve(codeAction => {
225-
connection.console.log('onCodeActionResolve');
226-
connection.console.log(JSON.stringify(codeAction, null, 2));
227-
return codeAction;
228-
});
229-
230+
/**
231+
* The client can request the server to compute autodeps decorations based on a currently selected
232+
* position if the selected position is within an autodep eligible function call.
233+
*/
230234
connection.onRequest(AutoDepsDecorationsRequest.type, async params => {
231235
const position = params.position;
232-
connection.console.debug('Client hovering on: ' + JSON.stringify(position));
233-
for (const dec of autoDepsDecorations) {
234-
if (isPositionWithinRange(position, dec.useEffectCallExpr)) {
235-
return dec.decorations;
236+
for (const decoration of autoDepsDecorations) {
237+
if (isPositionWithinRange(position, decoration.useEffectCallExpr)) {
238+
return decoration;
236239
}
237240
}
238241
return null;

compiler/packages/react-forgive/server/src/requests/autodepsdecorations.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export interface AutoDepsDecorationsParams {
1313
export namespace AutoDepsDecorationsRequest {
1414
export const type = new RequestType<
1515
AutoDepsDecorationsParams,
16-
Array<Range> | null,
16+
AutoDepsDecorationsLSPEvent,
1717
void
1818
>('react/autodeps_decorations');
1919
}

0 commit comments

Comments
 (0)