chrome-devtools-frontend 1.0.1562885 → 1.0.1563377

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,7 @@
5
5
  import * as Common from '../../../core/common/common.js';
6
6
  import * as Host from '../../../core/host/host.js';
7
7
  import * as i18n from '../../../core/i18n/i18n.js';
8
+ import type * as AiCodeCompletion from '../../../models/ai_code_completion/ai_code_completion.js';
8
9
  import * as AiCodeGeneration from '../../../models/ai_code_generation/ai_code_generation.js';
9
10
  import * as PanelCommon from '../../../panels/common/common.js';
10
11
  import * as CodeMirror from '../../../third_party/codemirror.next/codemirror.next.js';
@@ -12,6 +13,7 @@ import * as UI from '../../../ui/legacy/legacy.js';
12
13
  import * as VisualLogging from '../../visual_logging/visual_logging.js';
13
14
 
14
15
  import {AiCodeCompletionTeaserPlaceholder} from './AiCodeCompletionTeaserPlaceholder.js';
16
+ import {AiCodeGenerationParser} from './AiCodeGenerationParser.js';
15
17
  import {
16
18
  acceptAiAutoCompleteSuggestion,
17
19
  aiAutoCompleteSuggestion,
@@ -43,6 +45,7 @@ export interface AiCodeGenerationConfig {
43
45
  onRequestTriggered: () => void;
44
46
  // TODO(b/445394511): Move exposing citations to onSuggestionAccepted
45
47
  onResponseReceived: (citations: Host.AidaClient.Citation[]) => void;
48
+ panel: AiCodeCompletion.AiCodeCompletion.ContextFlavor;
46
49
  }
47
50
 
48
51
  export class AiCodeGenerationProvider {
@@ -64,6 +67,9 @@ export class AiCodeGenerationProvider {
64
67
  throw new Error('AI code generation feature is not enabled.');
65
68
  }
66
69
  this.#generationTeaser = new PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaser();
70
+ this.#generationTeaser.disclaimerTooltipId =
71
+ aiCodeGenerationConfig.panel + '-ai-code-generation-disclaimer-tooltip';
72
+ this.#generationTeaser.panel = aiCodeGenerationConfig.panel;
67
73
  this.#aiCodeGenerationConfig = aiCodeGenerationConfig;
68
74
  }
69
75
 
@@ -74,11 +80,11 @@ export class AiCodeGenerationProvider {
74
80
  extension(): CodeMirror.Extension[] {
75
81
  return [
76
82
  CodeMirror.EditorView.updateListener.of(update => this.#activateTeaser(update)),
77
- CodeMirror.EditorView.updateListener.of(update => this.#abortGenerationDuringUpdate(update)),
83
+ CodeMirror.EditorView.updateListener.of(update => this.#abortOrDismissGenerationDuringUpdate(update)),
78
84
  aiAutoCompleteSuggestion,
79
85
  aiAutoCompleteSuggestionState,
80
86
  aiCodeGenerationTeaserModeState,
81
- this.#generationTeaserCompartment.of([]),
87
+ CodeMirror.Prec.highest(this.#generationTeaserCompartment.of([])),
82
88
  CodeMirror.Prec.highest(CodeMirror.keymap.of(this.#editorKeymap())),
83
89
  ];
84
90
  }
@@ -137,9 +143,7 @@ export class AiCodeGenerationProvider {
137
143
  return false;
138
144
  }
139
145
  if (hasActiveAiSuggestion(this.#editor.state)) {
140
- this.#editor.dispatch({
141
- effects: setAiAutoCompleteSuggestion.of(null),
142
- });
146
+ this.#dismissTeaserAndSuggestion();
143
147
  return true;
144
148
  }
145
149
  const generationTeaserIsLoading = this.#generationTeaser.displayState ===
@@ -147,7 +151,7 @@ export class AiCodeGenerationProvider {
147
151
  if (this.#generationTeaser.isShowing() && generationTeaserIsLoading) {
148
152
  this.#controller.abort();
149
153
  this.#controller = new AbortController();
150
- this.#dismissTeaser();
154
+ this.#dismissTeaserAndSuggestion();
151
155
  return true;
152
156
  }
153
157
  return false;
@@ -189,9 +193,14 @@ export class AiCodeGenerationProvider {
189
193
  ];
190
194
  }
191
195
 
192
- #dismissTeaser(): void {
196
+ #dismissTeaserAndSuggestion(): void {
193
197
  this.#generationTeaser.displayState = PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.TRIGGER;
194
- this.#editor?.dispatch({effects: setAiCodeGenerationTeaserMode.of(AiCodeGenerationTeaserMode.DISMISSED)});
198
+ this.#editor?.dispatch({
199
+ effects: [
200
+ setAiCodeGenerationTeaserMode.of(AiCodeGenerationTeaserMode.DISMISSED),
201
+ setAiAutoCompleteSuggestion.of(null),
202
+ ]
203
+ });
195
204
  }
196
205
 
197
206
  #activateTeaser(update: CodeMirror.ViewUpdate): void {
@@ -206,26 +215,35 @@ export class AiCodeGenerationProvider {
206
215
  }
207
216
 
208
217
  /**
209
- * Monitors editor changes to cancel an ongoing AI generation.
210
- * We abort the request and dismiss the teaser if the user modifies the
211
- * document or moves their cursor/selection. These actions indicate the user
212
- * is no longer focused on the current generation point or has manually
213
- * resumed editing, making the pending suggestion irrelevant.
218
+ * Monitors editor changes to cancel an ongoing AI generation or dismiss one
219
+ * if it already exists.
220
+ * We abort the request (or dismiss suggestion) and dismiss the teaser if the
221
+ * user modifies the document or moves their cursor/selection. These actions
222
+ * indicate the user is no longer focused on the current generation point or
223
+ * has manually resumed editing, making the suggestion irrelevant.
214
224
  */
215
- #abortGenerationDuringUpdate(update: CodeMirror.ViewUpdate): void {
225
+ #abortOrDismissGenerationDuringUpdate(update: CodeMirror.ViewUpdate): void {
216
226
  if (!update.docChanged && update.state.selection.main.head === update.startState.selection.main.head) {
217
227
  return;
218
228
  }
219
229
  const currentTeaserMode = update.state.field(aiCodeGenerationTeaserModeState);
220
- const generationTeaserIsLoading = this.#generationTeaser.displayState ===
221
- PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.LOADING;
222
- // Generation should be in progress
223
- if (currentTeaserMode === AiCodeGenerationTeaserMode.DISMISSED || !generationTeaserIsLoading) {
230
+ if (currentTeaserMode === AiCodeGenerationTeaserMode.DISMISSED) {
231
+ return;
232
+ }
233
+ if (this.#generationTeaser.displayState ===
234
+ PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.LOADING) {
235
+ this.#controller.abort();
236
+ this.#controller = new AbortController();
237
+ this.#dismissTeaserAndSuggestion();
238
+ return;
239
+ }
240
+ if (this.#generationTeaser.displayState ===
241
+ PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.GENERATED) {
242
+ update.view.dispatch({effects: setAiAutoCompleteSuggestion.of(null)});
243
+ this.#generationTeaser.displayState =
244
+ PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.TRIGGER;
224
245
  return;
225
246
  }
226
- this.#controller.abort();
227
- this.#controller = new AbortController();
228
- this.#dismissTeaser();
229
247
  }
230
248
 
231
249
  async #triggerAiCodeGeneration(options?: {signal?: AbortSignal}): Promise<void> {
@@ -235,9 +253,8 @@ export class AiCodeGenerationProvider {
235
253
 
236
254
  this.#generationTeaser.displayState = PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.LOADING;
237
255
  const cursor = this.#editor.state.selection.main.head;
238
- // TODO(b/445899453): Detect all types of comments
239
- const query = this.#editor.state.doc.lineAt(cursor).text;
240
- if (query.trim().length === 0) {
256
+ const query = AiCodeGenerationParser.extractCommentText(this.#editor.state, cursor);
257
+ if (!query || query.trim().length === 0) {
241
258
  return;
242
259
  }
243
260
 
@@ -251,7 +268,7 @@ export class AiCodeGenerationProvider {
251
268
  this.#aiCodeGenerationConfig?.generationContext.inferenceLanguage, options);
252
269
 
253
270
  if (this.#generationTeaser) {
254
- this.#dismissTeaser();
271
+ this.#dismissTeaserAndSuggestion();
255
272
  }
256
273
 
257
274
  if (!generationResponse || generationResponse.samples.length === 0) {
@@ -265,20 +282,30 @@ export class AiCodeGenerationProvider {
265
282
  return;
266
283
  }
267
284
 
285
+ const backtickRegex = /^```(?:\w+)?\n([\s\S]*?)\n```$/;
286
+ const matchArray = topSample.generationString.match(backtickRegex);
287
+ const suggestionText = matchArray ? matchArray[1].trim() : topSample.generationString;
288
+
268
289
  this.#editor.dispatch({
269
- effects: setAiAutoCompleteSuggestion.of({
270
- text: '\n' + topSample.generationString,
271
- from: cursor,
272
- rpcGlobalId: generationResponse.metadata.rpcGlobalId,
273
- sampleId: topSample.sampleId,
274
- startTime,
275
- onImpression: this.#aiCodeGeneration?.registerUserImpression.bind(this.#aiCodeGeneration),
276
- })
290
+ effects: [
291
+ setAiAutoCompleteSuggestion.of({
292
+ text: '\n' + suggestionText,
293
+ from: cursor,
294
+ rpcGlobalId: generationResponse.metadata.rpcGlobalId,
295
+ sampleId: topSample.sampleId,
296
+ startTime,
297
+ onImpression: this.#aiCodeGeneration?.registerUserImpression.bind(this.#aiCodeGeneration),
298
+ }),
299
+ setAiCodeGenerationTeaserMode.of(AiCodeGenerationTeaserMode.ACTIVE)
300
+ ]
277
301
  });
302
+ this.#generationTeaser.displayState =
303
+ PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.GENERATED;
278
304
 
279
- AiCodeGeneration.debugLog('Suggestion dispatched to the editor', topSample.generationString);
305
+ AiCodeGeneration.debugLog('Suggestion dispatched to the editor', suggestionText);
280
306
  const citations = topSample.attributionMetadata?.citations ?? [];
281
307
  this.#aiCodeGenerationConfig?.onResponseReceived(citations);
308
+ return;
282
309
  } catch (e) {
283
310
  AiCodeGeneration.debugLog('Error while fetching code generation suggestions from AIDA', e);
284
311
  this.#aiCodeGenerationConfig?.onResponseReceived([]);
@@ -286,7 +313,7 @@ export class AiCodeGenerationProvider {
286
313
  }
287
314
 
288
315
  if (this.#generationTeaser) {
289
- this.#dismissTeaser();
316
+ this.#dismissTeaserAndSuggestion();
290
317
  }
291
318
  }
292
319
  }
@@ -318,8 +345,7 @@ function aiCodeGenerationTeaserExtension(teaser: PanelCommon.AiCodeGenerationTea
318
345
  const line = this.#view.state.doc.lineAt(cursorPosition);
319
346
 
320
347
  const isEmptyLine = line.length === 0;
321
- // TODO(b/445899453): Detect all types of comments
322
- const isComment = line.text.startsWith('//');
348
+ const isComment = Boolean(AiCodeGenerationParser.extractCommentText(this.#view.state, cursorPosition));
323
349
  const isCursorAtEndOfLine = cursorPosition >= line.to;
324
350
 
325
351
  if ((isEmptyLine) || (isComment && isCursorAtEndOfLine)) {
@@ -332,9 +358,10 @@ function aiCodeGenerationTeaserExtension(teaser: PanelCommon.AiCodeGenerationTea
332
358
  }
333
359
 
334
360
  #updateTeaserState(state: CodeMirror.EditorState): void {
335
- // Only handle non loading states, as updates during generation are handled by
336
- // #abortGenerationDuringUpdate in AiCodeGenerationProvider
337
- if (teaser.displayState === PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.LOADING) {
361
+ // Only handle non loading and non generated states, as updates during and after generation are handled by
362
+ // #abortOrDismissGenerationDuringUpdate in AiCodeGenerationProvider
363
+ if (teaser.displayState === PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.LOADING ||
364
+ teaser.displayState === PanelCommon.AiCodeGenerationTeaser.AiCodeGenerationTeaserDisplayState.GENERATED) {
338
365
  return;
339
366
  }
340
367
  const cursorPosition = state.selection.main.head;
@@ -348,5 +375,15 @@ function aiCodeGenerationTeaserExtension(teaser: PanelCommon.AiCodeGenerationTea
348
375
  }
349
376
  }, {
350
377
  decorations: v => v.decorations,
378
+ eventHandlers: {
379
+ mousemove(event: MouseEvent): boolean {
380
+ // Required for mouse hover to propagate to the info button in teaser.
381
+ return (event.target instanceof Node && teaser.contentElement.contains(event.target));
382
+ },
383
+ mousedown(event: MouseEvent): boolean {
384
+ // Required for mouse click to propagate to the info tooltip in teaser.
385
+ return (event.target instanceof Node && teaser.contentElement.contains(event.target));
386
+ },
387
+ },
351
388
  });
352
389
  }
@@ -4,6 +4,7 @@
4
4
 
5
5
  export * as AiCodeCompletionProvider from './AiCodeCompletionProvider.js';
6
6
  export * as AiCodeCompletionTeaserPlaceholder from './AiCodeCompletionTeaserPlaceholder.js';
7
+ export * as AiCodeGenerationParser from './AiCodeGenerationParser.js';
7
8
  export * as AiCodeGenerationProvider from './AiCodeGenerationProvider.js';
8
9
  export * as AutocompleteHistory from './AutocompleteHistory.js';
9
10
  export * as Config from './config.js';
@@ -320,13 +320,14 @@ export class Linkifier extends Common.ObjectWrapper.ObjectWrapper<EventTypes> im
320
320
  }
321
321
 
322
322
  linkifyRawLocation(
323
- rawLocation: SDK.DebuggerModel.Location, fallbackUrl: Platform.DevToolsPath.UrlString,
324
- className?: string): HTMLElement {
323
+ rawLocation: SDK.DebuggerModel.Location, fallbackUrl: Platform.DevToolsPath.UrlString, className?: string,
324
+ options?: LinkifyOptions): HTMLElement {
325
325
  return this.linkifyScriptLocation(
326
326
  rawLocation.debuggerModel.target(), rawLocation.scriptId, fallbackUrl, rawLocation.lineNumber, {
327
327
  columnNumber: rawLocation.columnNumber,
328
328
  className,
329
329
  inlineFrameIndex: rawLocation.inlineFrameIndex,
330
+ tabStop: options?.tabStop,
330
331
  });
331
332
  }
332
333
 
@@ -352,6 +352,7 @@ export const knownContextValues = new Set([
352
352
  'ai-code-completion-teaser-dismissed',
353
353
  'ai-code-completion-teaser.dismiss',
354
354
  'ai-code-completion-teaser.fre',
355
+ 'ai-code-generation-teaser.info-button',
355
356
  'ai-explorer',
356
357
  'ai_assistance',
357
358
  'align-content',
package/package.json CHANGED
@@ -105,5 +105,5 @@
105
105
  "flat-cache": "6.1.12"
106
106
  }
107
107
  },
108
- "version": "1.0.1562885"
108
+ "version": "1.0.1563377"
109
109
  }