chrome-devtools-frontend 1.0.1516909 → 1.0.1518653
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.
- package/docs/checklist/README.md +2 -2
- package/docs/checklist/javascript.md +1 -1
- package/docs/contributing/README.md +1 -1
- package/docs/contributing/settings-experiments-features.md +9 -8
- package/docs/cookbook/devtools_on_devtools.md +2 -2
- package/docs/cookbook/localization.md +10 -10
- package/docs/devtools-protocol.md +9 -8
- package/docs/ecosystem/automatic_workspace_folders.md +3 -3
- package/docs/get_the_code.md +0 -2
- package/docs/styleguide/ux/components.md +166 -85
- package/docs/styleguide/ux/numbers.md +3 -4
- package/front_end/core/common/README.md +13 -12
- package/front_end/core/host/GdpClient.ts +16 -1
- package/front_end/core/host/UserMetrics.ts +4 -2
- package/front_end/core/root/Runtime.ts +13 -0
- package/front_end/core/sdk/CSSMatchedStyles.ts +5 -1
- package/front_end/entrypoints/main/MainImpl.ts +6 -3
- package/front_end/generated/InspectorBackendCommands.js +10 -7
- package/front_end/generated/SupportedCSSProperties.js +21 -7
- package/front_end/generated/protocol-mapping.d.ts +16 -1
- package/front_end/generated/protocol-proxy-api.d.ts +13 -1
- package/front_end/generated/protocol.ts +95 -0
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +166 -49
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +14 -181
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +13 -315
- package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +224 -50
- package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +310 -11
- package/front_end/models/ai_assistance/performance/AIContext.ts +15 -2
- package/front_end/models/ai_code_completion/AiCodeCompletion.ts +17 -11
- package/front_end/models/badges/Badge.ts +8 -3
- package/front_end/models/badges/CodeWhispererBadge.ts +2 -4
- package/front_end/models/badges/StarterBadge.ts +2 -2
- package/front_end/models/badges/UserBadges.ts +21 -3
- package/front_end/models/javascript_metadata/NativeFunctions.js +1 -1
- package/front_end/models/trace/README.md +28 -1
- package/front_end/models/trace/handlers/UserTimingsHandler.ts +1 -1
- package/front_end/models/trace/helpers/Trace.ts +99 -43
- package/front_end/models/trace/types/TraceEvents.ts +9 -0
- package/front_end/panels/accessibility/ARIAAttributesView.ts +113 -191
- package/front_end/panels/accessibility/AccessibilityNodeView.ts +9 -9
- package/front_end/panels/accessibility/AccessibilitySubPane.ts +6 -4
- package/front_end/panels/accessibility/accessibilityProperties.css +2 -0
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +16 -2
- package/front_end/panels/ai_assistance/components/ChatView.ts +9 -10
- package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +42 -0
- package/front_end/panels/common/AiCodeCompletionDisclaimer.ts +32 -9
- package/front_end/panels/common/AiCodeCompletionSummaryToolbar.ts +7 -1
- package/front_end/panels/common/BadgeNotification.ts +21 -5
- package/front_end/panels/common/GdpSignUpDialog.ts +18 -9
- package/front_end/panels/console/ConsolePrompt.ts +1 -1
- package/front_end/panels/console/ConsoleView.ts +6 -2
- package/front_end/panels/elements/ElementsPanel.ts +4 -0
- package/front_end/panels/elements/ElementsTreeElement.ts +18 -0
- package/front_end/panels/elements/ElementsTreeOutline.ts +13 -0
- package/front_end/panels/elements/StylePropertyTreeElement.ts +21 -6
- package/front_end/panels/media/TickingFlameChart.ts +1 -1
- package/front_end/panels/profiler/HeapSnapshotView.ts +34 -19
- package/front_end/panels/search/SearchResultsPane.ts +124 -128
- package/front_end/panels/search/SearchView.ts +24 -17
- package/front_end/panels/settings/components/SyncSection.ts +16 -8
- package/front_end/panels/sources/AiCodeCompletionPlugin.ts +6 -1
- package/front_end/panels/sources/SourcesPanel.ts +3 -0
- package/front_end/panels/timeline/AppenderUtils.ts +2 -2
- package/front_end/panels/timeline/ExtensionTrackAppender.ts +13 -4
- package/front_end/panels/timeline/GPUTrackAppender.ts +2 -1
- package/front_end/panels/timeline/InteractionsTrackAppender.ts +5 -1
- package/front_end/panels/timeline/LayoutShiftsTrackAppender.ts +2 -1
- package/front_end/panels/timeline/ThreadAppender.ts +12 -3
- package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +9 -4
- package/front_end/panels/timeline/TimelinePanel.ts +3 -2
- package/front_end/panels/timeline/TimelineUIUtils.ts +5 -4
- package/front_end/panels/timeline/TimingsTrackAppender.ts +6 -1
- package/front_end/panels/timeline/components/CPUThrottlingSelector.ts +95 -82
- package/front_end/panels/timeline/components/LiveMetricsView.ts +2 -2
- package/front_end/panels/timeline/components/cpuThrottlingSelector.css +17 -15
- package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +3 -0
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/third_party/codemirror.next/chunk/codemirror.js +1 -1
- package/front_end/third_party/codemirror.next/chunk/codemirror.js.map +1 -1
- package/front_end/third_party/codemirror.next/codemirror.next.d.ts +6 -9
- package/front_end/third_party/codemirror.next/package.json +2 -1
- package/front_end/third_party/diff/README.chromium +1 -0
- package/front_end/ui/components/text_editor/config.ts +6 -7
- package/front_end/ui/components/tooltips/Tooltip.ts +70 -31
- package/front_end/ui/legacy/README.md +33 -24
- package/front_end/ui/legacy/SearchableView.ts +19 -26
- package/front_end/ui/legacy/TextPrompt.ts +166 -1
- package/front_end/ui/legacy/Treeoutline.ts +16 -2
- package/front_end/ui/legacy/UIUtils.ts +15 -2
- package/front_end/ui/legacy/XElement.ts +0 -43
- package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +20 -4
- package/front_end/ui/visual_logging/KnownContextValues.ts +19 -6
- package/front_end/ui/visual_logging/README.md +43 -27
- package/package.json +1 -1
@@ -52,7 +52,7 @@ import {
|
|
52
52
|
StylesSidebarPane,
|
53
53
|
} from './StylesSidebarPane.js';
|
54
54
|
|
55
|
-
const {html, nothing, render, Directives: {classMap
|
55
|
+
const {html, nothing, render, Directives: {classMap}} = Lit;
|
56
56
|
const ASTUtils = SDK.CSSPropertyParser.ASTUtils;
|
57
57
|
const FlexboxEditor = ElementsComponents.StylePropertyEditor.FlexboxEditor;
|
58
58
|
const GridEditor = ElementsComponents.StylePropertyEditor.GridEditor;
|
@@ -446,19 +446,25 @@ export class AttributeRenderer extends rendererBase(SDK.CSSPropertyParserMatcher
|
|
446
446
|
const attrCall =
|
447
447
|
this.#treeElement?.getTracingTooltip('attr', match.node, this.#matchedStyles, this.#computedStyles, context);
|
448
448
|
const tooltipId = attributeMissing ? undefined : this.#treeElement?.getTooltipId('custom-attribute');
|
449
|
+
const tooltip = tooltipId ? {tooltipId} : undefined;
|
449
450
|
// clang-format off
|
450
451
|
render(html`
|
451
452
|
<span data-title=${computedValue || ''}
|
452
453
|
jslog=${VisualLogging.link('css-variable').track({click: true, hover: true})}
|
453
|
-
>${attrCall ?? 'attr'}(<
|
454
|
-
|
455
|
-
|
456
|
-
|
454
|
+
>${attrCall ?? 'attr'}(<devtools-link-swatch class=${attributeClass} .data=${{
|
455
|
+
tooltip,
|
456
|
+
text: match.name,
|
457
|
+
isDefined: true,
|
458
|
+
onLinkActivate: () => this.#handleAttributeActivate(this.#matchedStyles.originatingNodeForStyle(match.style), match.name),
|
459
|
+
}}></devtools-link-swatch>${tooltipId ? html`
|
457
460
|
<devtools-tooltip
|
458
461
|
id=${tooltipId}
|
459
462
|
variant=rich
|
460
463
|
jslogContext=elements.css-var
|
461
|
-
>${JSON.stringify(rawValue)}</devtools-tooltip>` :
|
464
|
+
>${JSON.stringify(rawValue)}</devtools-tooltip>` : nothing}${
|
465
|
+
match.type ? html` <span class=${typeClass}>${match.type}</span>` : nothing
|
466
|
+
}${renderedFallback ? html`, <span class=${fallbackClass}>${renderedFallback.nodes}</span>` : nothing
|
467
|
+
})</span>`, varSwatch);
|
462
468
|
// clang-format on
|
463
469
|
|
464
470
|
const color = computedValue && Common.Color.parse(computedValue);
|
@@ -478,6 +484,15 @@ export class AttributeRenderer extends rendererBase(SDK.CSSPropertyParserMatcher
|
|
478
484
|
|
479
485
|
return [colorSwatch, varSwatch];
|
480
486
|
}
|
487
|
+
|
488
|
+
#handleAttributeActivate(node: SDK.DOMModel.DOMNode|null, attribute: string): void {
|
489
|
+
if (!node) {
|
490
|
+
return;
|
491
|
+
}
|
492
|
+
Host.userMetrics.actionTaken(Host.UserMetrics.Action.AttributeLinkClicked);
|
493
|
+
Host.userMetrics.swatchActivated(Host.UserMetrics.SwatchType.ATTR_LINK);
|
494
|
+
ElementsPanel.instance().highlightNodeAttribute(node, attribute);
|
495
|
+
}
|
481
496
|
}
|
482
497
|
|
483
498
|
// clang-format off
|
@@ -19,7 +19,7 @@ function getGroupDefaultTextColor(): string {
|
|
19
19
|
const DefaultStyle: () => PerfUI.FlameChart.GroupStyle = () => ({
|
20
20
|
height: 20,
|
21
21
|
padding: 2,
|
22
|
-
collapsible:
|
22
|
+
collapsible: PerfUI.FlameChart.GroupCollapsibleState.NEVER,
|
23
23
|
font: defaultFont,
|
24
24
|
color: getGroupDefaultTextColor(),
|
25
25
|
backgroundColor: 'rgba(100 0 0 / 10%)',
|
@@ -303,7 +303,17 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
|
|
303
303
|
baseProfile!: HeapProfileHeader|null;
|
304
304
|
trackingOverviewGrid?: HeapTimelineOverview;
|
305
305
|
currentSearchResultIndex = -1;
|
306
|
-
|
306
|
+
currentSearch?: HeapSnapshotModel.HeapSnapshotModel.SearchConfig;
|
307
|
+
|
308
|
+
get currentQuery(): string|undefined {
|
309
|
+
return this.currentSearch?.query;
|
310
|
+
}
|
311
|
+
set currentQuery(value: string) {
|
312
|
+
if (this.currentSearch) {
|
313
|
+
this.currentSearch.query = value;
|
314
|
+
}
|
315
|
+
}
|
316
|
+
|
307
317
|
constructor(dataDisplayDelegate: DataDisplayDelegate, profile: HeapProfileHeader) {
|
308
318
|
super({
|
309
319
|
title: i18nString(UIStrings.heapSnapshot),
|
@@ -643,8 +653,13 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
|
|
643
653
|
|
644
654
|
performSearch(searchConfig: UI.SearchableView.SearchConfig, shouldJump: boolean, jumpBackwards?: boolean): void {
|
645
655
|
const nextQuery = new HeapSnapshotModel.HeapSnapshotModel.SearchConfig(
|
646
|
-
searchConfig.query.trim(),
|
647
|
-
|
656
|
+
searchConfig.query.trim(),
|
657
|
+
searchConfig.caseSensitive,
|
658
|
+
searchConfig.wholeWord,
|
659
|
+
searchConfig.isRegex,
|
660
|
+
shouldJump,
|
661
|
+
jumpBackwards || false,
|
662
|
+
);
|
648
663
|
|
649
664
|
void this.searchThrottler.schedule(this.performSearchInternal.bind(this, nextQuery));
|
650
665
|
}
|
@@ -657,7 +672,7 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
|
|
657
672
|
return;
|
658
673
|
}
|
659
674
|
|
660
|
-
this.
|
675
|
+
this.currentSearch = nextQuery;
|
661
676
|
const query = nextQuery.query.trim();
|
662
677
|
|
663
678
|
if (!query) {
|
@@ -682,7 +697,7 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
|
|
682
697
|
}
|
683
698
|
|
684
699
|
const filter = this.dataGrid.nodeFilter();
|
685
|
-
this.searchResults = filter ? await this.profile.snapshotProxy.search(this.
|
700
|
+
this.searchResults = filter ? await this.profile.snapshotProxy.search(this.currentSearch, filter) : [];
|
686
701
|
|
687
702
|
this.searchableViewInternal.updateSearchMatchesCount(this.searchResults.length);
|
688
703
|
if (this.searchResults.length) {
|
@@ -724,10 +739,10 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
|
|
724
739
|
if (!this.dataGrid) {
|
725
740
|
return;
|
726
741
|
}
|
727
|
-
let child:
|
742
|
+
let child: DataGrid.DataGrid.DataGridNode<HeapSnapshotGridNode>|null = this.dataGrid.rootNode().children[0];
|
728
743
|
while (child) {
|
729
744
|
child.refresh();
|
730
|
-
child =
|
745
|
+
child = child.traverseNextNode(false, null, true);
|
731
746
|
}
|
732
747
|
}
|
733
748
|
|
@@ -735,28 +750,28 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
|
|
735
750
|
if (this.baseProfile === this.profiles()[this.baseSelect.selectedIndex()]) {
|
736
751
|
return;
|
737
752
|
}
|
738
|
-
this.baseProfile =
|
753
|
+
this.baseProfile = this.profiles()[this.baseSelect.selectedIndex()];
|
739
754
|
const dataGrid = (this.dataGrid as HeapSnapshotDiffDataGrid);
|
740
755
|
// Change set base data source only if main data source is already set.
|
741
756
|
if (dataGrid.snapshot) {
|
742
757
|
void this.baseProfile.loadPromise.then(dataGrid.setBaseDataSource.bind(dataGrid));
|
743
758
|
}
|
744
759
|
|
745
|
-
if (!this.
|
760
|
+
if (!this.currentSearch || !this.searchResults) {
|
746
761
|
return;
|
747
762
|
}
|
748
763
|
|
749
764
|
// The current search needs to be performed again. First negate out previous match
|
750
765
|
// count by calling the search finished callback with a negative number of matches.
|
751
766
|
// Then perform the search again with the same query and callback.
|
752
|
-
this.performSearch(this.
|
767
|
+
this.performSearch(this.currentSearch, false);
|
753
768
|
}
|
754
769
|
|
755
|
-
static readonly ALWAYS_AVAILABLE_FILTERS = [
|
770
|
+
static readonly ALWAYS_AVAILABLE_FILTERS: ReadonlyArray<{uiName: string, filterName: string}> = [
|
756
771
|
{uiName: i18nString(UIStrings.duplicatedStrings), filterName: 'duplicatedStrings'},
|
757
772
|
{uiName: i18nString(UIStrings.objectsRetainedByDetachedDomNodes), filterName: 'objectsRetainedByDetachedDomNodes'},
|
758
773
|
{uiName: i18nString(UIStrings.objectsRetainedByConsole), filterName: 'objectsRetainedByConsole'},
|
759
|
-
]
|
774
|
+
];
|
760
775
|
|
761
776
|
changeFilter(): void {
|
762
777
|
let selectedIndex = this.filterSelect.selectedIndex();
|
@@ -773,19 +788,19 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
|
|
773
788
|
return;
|
774
789
|
}
|
775
790
|
(this.dataGrid as HeapSnapshotConstructorsDataGrid)
|
776
|
-
.filterSelectIndexChanged(
|
791
|
+
.filterSelectIndexChanged(this.profiles(), profileIndex, filterName);
|
777
792
|
|
778
|
-
if (!this.
|
793
|
+
if (!this.currentSearch || !this.searchResults) {
|
779
794
|
return;
|
780
795
|
}
|
781
796
|
|
782
797
|
// The current search needs to be performed again. First negate out previous match
|
783
798
|
// count by calling the search finished callback with a negative number of matches.
|
784
799
|
// Then perform the search again with the same query and callback.
|
785
|
-
this.performSearch(this.
|
800
|
+
this.performSearch(this.currentSearch, false);
|
786
801
|
}
|
787
802
|
|
788
|
-
profiles():
|
803
|
+
profiles(): HeapProfileHeader[] {
|
789
804
|
return this.profile.profileType().getProfiles();
|
790
805
|
}
|
791
806
|
|
@@ -866,7 +881,7 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
|
|
866
881
|
return;
|
867
882
|
}
|
868
883
|
if (!this.baseProfile) {
|
869
|
-
this.baseProfile =
|
884
|
+
this.baseProfile = this.profiles()[this.baseSelect.selectedIndex()];
|
870
885
|
}
|
871
886
|
|
872
887
|
const baseSnapshotProxy = await this.baseProfile.loadPromise;
|
@@ -900,14 +915,14 @@ export class HeapSnapshotView extends UI.View.SimpleView implements DataDisplayD
|
|
900
915
|
|
901
916
|
void this.updateDataSourceAndView();
|
902
917
|
|
903
|
-
if (!this.
|
918
|
+
if (!this.currentSearch || !this.searchResults) {
|
904
919
|
return;
|
905
920
|
}
|
906
921
|
|
907
922
|
// The current search needs to be performed again. First negate out previous match
|
908
923
|
// count by calling the search finished callback with a negative number of matches.
|
909
924
|
// Then perform the search again the with same query and callback.
|
910
|
-
this.performSearch(this.
|
925
|
+
this.performSearch(this.currentSearch, false);
|
911
926
|
}
|
912
927
|
|
913
928
|
async selectLiveObject(perspectiveName: string, snapshotObjectId: string): Promise<void> {
|
@@ -35,22 +35,95 @@ const UIStrings = {
|
|
35
35
|
const str_ = i18n.i18n.registerUIStrings('panels/search/SearchResultsPane.ts', UIStrings);
|
36
36
|
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
37
37
|
|
38
|
+
interface SearchMatch {
|
39
|
+
lineContent: string;
|
40
|
+
matchRanges: TextUtils.TextRange.SourceRange[];
|
41
|
+
resultLabel: string|number;
|
42
|
+
}
|
43
|
+
|
38
44
|
export class SearchResultsPane extends UI.Widget.VBox {
|
39
45
|
private readonly searchConfig: Workspace.SearchConfig.SearchConfig;
|
40
|
-
private readonly searchResults
|
41
|
-
private
|
46
|
+
private readonly searchResults = new Map<SearchResult, UI.TreeOutline.TreeElement>();
|
47
|
+
private expandedResults = new WeakSet<SearchResult>();
|
48
|
+
private readonly searchMatches = new WeakMap<SearchResult, SearchMatch[]>();
|
42
49
|
private treeOutline: UI.TreeOutline.TreeOutlineInShadow;
|
43
|
-
private matchesExpandedCount
|
50
|
+
private matchesExpandedCount = 0;
|
51
|
+
|
52
|
+
private addTreeElement(searchResult: SearchResult): void {
|
53
|
+
const treeElement = new UI.TreeOutline.TreeElement('', true);
|
54
|
+
treeElement.toggleOnClick = true;
|
55
|
+
this.searchResults.set(searchResult, treeElement);
|
56
|
+
this.treeOutline.appendChild(treeElement);
|
57
|
+
if (!this.treeOutline.selectedTreeElement) {
|
58
|
+
treeElement.select(/* omitFocus */ true, /* selectedByUser */ true);
|
59
|
+
}
|
60
|
+
treeElement.listItemElement.classList.add('search-result');
|
61
|
+
// clang-format off
|
62
|
+
render(html`
|
63
|
+
<span class="search-result-file-name">${searchResult.label()}
|
64
|
+
<span class="search-result-dash">${'\u2014'}</span>
|
65
|
+
<span class="search-result-qualifier">${searchResult.description()}</span>
|
66
|
+
</span>
|
67
|
+
<span class="search-result-matches-count"
|
68
|
+
aria-label=${i18nString(UIStrings.matchesCountS, {PH1: searchResult.matchesCount()})}>
|
69
|
+
${searchResult.matchesCount()}
|
70
|
+
</span>`,
|
71
|
+
treeElement.listItemElement);
|
72
|
+
// clang-format on
|
73
|
+
treeElement.tooltip = searchResult.description();
|
74
|
+
}
|
75
|
+
|
76
|
+
private appendSearchMatchElement(searchResult: SearchResult, match: SearchMatch, i: number): void {
|
77
|
+
const {lineContent, matchRanges, resultLabel} = match;
|
78
|
+
const element = this.searchResults.get(searchResult);
|
79
|
+
if (!element) {
|
80
|
+
return;
|
81
|
+
}
|
82
|
+
const searchMatchElement = new UI.TreeOutline.TreeElement();
|
83
|
+
element.appendChild(searchMatchElement);
|
84
|
+
// clang-format off
|
85
|
+
render(html`
|
86
|
+
<button class="devtools-link text-button link-style search-match-link"
|
87
|
+
jslog="Link; context: search-match; track: click" role="link" tabindex="0"
|
88
|
+
@click=${() => void Common.Revealer.reveal(searchResult.matchRevealable(i))}>
|
89
|
+
<span class="search-match-line-number"
|
90
|
+
aria-label=${typeof resultLabel === 'number' && !isNaN(resultLabel)
|
91
|
+
? i18nString(UIStrings.lineS, {PH1: resultLabel}) : resultLabel}>
|
92
|
+
${resultLabel}
|
93
|
+
</span>
|
94
|
+
<span class="search-match-content" aria-label="${lineContent} line">
|
95
|
+
${lineContent}
|
96
|
+
</span>
|
97
|
+
</button>`,
|
98
|
+
searchMatchElement.listItemElement);
|
99
|
+
// clang-format on
|
100
|
+
const contentSpan = searchMatchElement.listItemElement.querySelector('.search-match-content') as HTMLElement;
|
101
|
+
UI.UIUtils.highlightRangesWithStyleClass(contentSpan, matchRanges, 'highlighted-search-result');
|
102
|
+
searchMatchElement.listItemElement.className = 'search-match';
|
103
|
+
searchMatchElement.listItemElement.addEventListener('keydown', event => {
|
104
|
+
if (event.key === 'Enter') {
|
105
|
+
event.consume(true);
|
106
|
+
void Common.Revealer.reveal(searchResult.matchRevealable(i));
|
107
|
+
}
|
108
|
+
});
|
109
|
+
searchMatchElement.tooltip = lineContent;
|
110
|
+
}
|
44
111
|
|
45
112
|
constructor(searchConfig: Workspace.SearchConfig.SearchConfig) {
|
46
113
|
super({useShadowDom: true});
|
47
114
|
this.searchConfig = searchConfig;
|
48
|
-
|
49
|
-
this.searchResults = [];
|
50
|
-
this.treeElements = [];
|
51
115
|
this.treeOutline = new UI.TreeOutline.TreeOutlineInShadow();
|
52
116
|
this.treeOutline.registerRequiredCSS(searchResultsPaneStyles);
|
53
117
|
this.treeOutline.setHideOverflow(true);
|
118
|
+
this.treeOutline.addEventListener(
|
119
|
+
UI.TreeOutline.Events.ElementExpanded,
|
120
|
+
(event: Common.EventTarget.EventTargetEvent<UI.TreeOutline.TreeElement>) => {
|
121
|
+
const searchResult = this.searchResults.entries().find(entry => entry[1] === event.data)?.[0];
|
122
|
+
if (!searchResult) {
|
123
|
+
return;
|
124
|
+
}
|
125
|
+
this.expandSearchResult(searchResult);
|
126
|
+
});
|
54
127
|
|
55
128
|
this.contentElement.appendChild(this.treeOutline.element);
|
56
129
|
|
@@ -58,108 +131,43 @@ export class SearchResultsPane extends UI.Widget.VBox {
|
|
58
131
|
}
|
59
132
|
|
60
133
|
addSearchResult(searchResult: SearchResult): void {
|
61
|
-
this.searchResults.push(searchResult);
|
62
134
|
this.addTreeElement(searchResult);
|
63
|
-
}
|
64
|
-
|
65
|
-
showAllMatches(): void {
|
66
|
-
this.treeElements.forEach(treeElement => {
|
67
|
-
treeElement.expand();
|
68
|
-
treeElement.showAllMatches();
|
69
|
-
});
|
70
|
-
}
|
71
|
-
|
72
|
-
collapseAllResults(): void {
|
73
|
-
this.treeElements.forEach(treeElement => {
|
74
|
-
treeElement.collapse();
|
75
|
-
});
|
76
|
-
}
|
77
|
-
|
78
|
-
private addTreeElement(searchResult: SearchResult): void {
|
79
|
-
const treeElement = new SearchResultsTreeElement(this.searchConfig, searchResult);
|
80
|
-
this.treeOutline.appendChild(treeElement);
|
81
|
-
if (!this.treeOutline.selectedTreeElement) {
|
82
|
-
treeElement.select(/* omitFocus */ true, /* selectedByUser */ true);
|
83
|
-
}
|
84
135
|
// Expand until at least a certain number of matches is expanded.
|
85
136
|
if (this.matchesExpandedCount < matchesExpandedByDefault) {
|
86
|
-
|
137
|
+
this.searchResults.get(searchResult)?.expand();
|
87
138
|
}
|
88
|
-
this.matchesExpandedCount += searchResult.matchesCount();
|
89
|
-
this.treeElements.push(treeElement);
|
90
|
-
}
|
91
|
-
}
|
92
|
-
|
93
|
-
export const matchesExpandedByDefault = 200;
|
94
|
-
export const matchesShownAtOnce = 20;
|
95
|
-
|
96
|
-
export class SearchResultsTreeElement extends UI.TreeOutline.TreeElement {
|
97
|
-
private searchConfig: Workspace.SearchConfig.SearchConfig;
|
98
|
-
private searchResult: SearchResult;
|
99
|
-
private initialized: boolean;
|
100
|
-
override toggleOnClick: boolean;
|
101
|
-
|
102
|
-
constructor(searchConfig: Workspace.SearchConfig.SearchConfig, searchResult: SearchResult) {
|
103
|
-
super('', true);
|
104
|
-
this.searchConfig = searchConfig;
|
105
|
-
this.searchResult = searchResult;
|
106
|
-
this.initialized = false;
|
107
|
-
this.toggleOnClick = true;
|
108
139
|
}
|
109
140
|
|
110
|
-
|
111
|
-
|
112
|
-
|
141
|
+
showAllMatches(): void {
|
142
|
+
for (const searchResult of this.searchResults.keys()) {
|
143
|
+
const startMatchIndex = this.searchMatches.get(searchResult)?.length ?? 0;
|
144
|
+
this.appendSearchMatches(searchResult, startMatchIndex, searchResult.matchesCount());
|
113
145
|
}
|
114
|
-
|
115
|
-
this.updateMatchesUI();
|
116
|
-
this.initialized = true;
|
117
146
|
}
|
118
147
|
|
119
|
-
|
120
|
-
this.
|
121
|
-
|
148
|
+
collapseAllResults(): void {
|
149
|
+
for (const treeElement of this.searchResults.values()) {
|
150
|
+
treeElement.collapse();
|
151
|
+
}
|
122
152
|
}
|
123
153
|
|
124
|
-
private
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
this.
|
129
|
-
this.appendShowMoreMatchesElement(toIndex - 1);
|
154
|
+
private expandSearchResult(searchResult: SearchResult): void {
|
155
|
+
const toIndex = Math.min(searchResult.matchesCount(), matchesShownAtOnce);
|
156
|
+
if (toIndex < searchResult.matchesCount()) {
|
157
|
+
this.appendSearchMatches(searchResult, 0, toIndex - 1);
|
158
|
+
this.appendShowMoreMatchesElement(searchResult, toIndex - 1);
|
130
159
|
} else {
|
131
|
-
this.appendSearchMatches(0, toIndex);
|
160
|
+
this.appendSearchMatches(searchResult, 0, toIndex);
|
132
161
|
}
|
162
|
+
this.matchesExpandedCount += toIndex;
|
163
|
+
this.requestUpdate();
|
133
164
|
}
|
134
165
|
|
135
|
-
|
136
|
-
this.
|
166
|
+
private collapseSearchResult(searchResult: SearchResult): void {
|
167
|
+
this.searchResults.get(searchResult)?.collapse();
|
137
168
|
}
|
138
169
|
|
139
|
-
private
|
140
|
-
this.listItemElement.classList.add('search-result');
|
141
|
-
// clang-format off
|
142
|
-
render(html`
|
143
|
-
<span class="search-result-file-name">${this.searchResult.label()}
|
144
|
-
<span class="search-result-dash">${'\u2014'}</span>
|
145
|
-
<span class="search-result-qualifier">${this.searchResult.description()}</span>
|
146
|
-
</span>
|
147
|
-
<span class="search-result-matches-count"
|
148
|
-
aria-label=${i18nString(UIStrings.matchesCountS, {PH1: this.searchResult.matchesCount()})}>
|
149
|
-
${this.searchResult.matchesCount()}
|
150
|
-
</span>`,
|
151
|
-
this.listItemElement);
|
152
|
-
// clang-format on
|
153
|
-
|
154
|
-
this.tooltip = this.searchResult.description();
|
155
|
-
if (this.expanded) {
|
156
|
-
this.updateMatchesUI();
|
157
|
-
}
|
158
|
-
}
|
159
|
-
|
160
|
-
private appendSearchMatches(fromIndex: number, toIndex: number): void {
|
161
|
-
const searchResult = this.searchResult;
|
162
|
-
|
170
|
+
private appendSearchMatches(searchResult: SearchResult, fromIndex: number, toIndex: number): void {
|
163
171
|
const queries = this.searchConfig.queries();
|
164
172
|
const regexes = [];
|
165
173
|
for (let i = 0; i < queries.length; ++i) {
|
@@ -167,6 +175,12 @@ export class SearchResultsTreeElement extends UI.TreeOutline.TreeElement {
|
|
167
175
|
queries[i], !this.searchConfig.ignoreCase(), this.searchConfig.isRegex()));
|
168
176
|
}
|
169
177
|
|
178
|
+
const searchMatches = this.searchMatches.get(searchResult) ?? [];
|
179
|
+
this.searchMatches.set(searchResult, searchMatches);
|
180
|
+
if (searchMatches.length > toIndex) {
|
181
|
+
return;
|
182
|
+
}
|
183
|
+
|
170
184
|
for (let i = fromIndex; i < toIndex; ++i) {
|
171
185
|
let lineContent = searchResult.matchLineContent(i);
|
172
186
|
let matchRanges: TextUtils.TextRange.SourceRange[] = [];
|
@@ -189,46 +203,23 @@ export class SearchResultsTreeElement extends UI.TreeOutline.TreeElement {
|
|
189
203
|
}
|
190
204
|
|
191
205
|
const resultLabel = searchResult.matchLabel(i);
|
192
|
-
|
193
|
-
|
194
|
-
this.appendChild(searchMatchElement);
|
195
|
-
// clang-format off
|
196
|
-
render(html`
|
197
|
-
<button class="devtools-link text-button link-style search-match-link"
|
198
|
-
jslog="Link; context: search-match; track: click" role="link" tabindex="0"
|
199
|
-
@click=${() => void Common.Revealer.reveal(searchResult.matchRevealable(i))}>
|
200
|
-
<span class="search-match-line-number"
|
201
|
-
aria-label=${typeof resultLabel === 'number' && !isNaN(resultLabel)
|
202
|
-
? i18nString(UIStrings.lineS, {PH1: resultLabel}) : resultLabel}>
|
203
|
-
${resultLabel}
|
204
|
-
</span>
|
205
|
-
<span class="search-match-content" aria-label="${lineContent} line">
|
206
|
-
${lineContent}
|
207
|
-
</span>
|
208
|
-
</button>`,
|
209
|
-
searchMatchElement.listItemElement);
|
210
|
-
// clang-format on
|
211
|
-
const contentSpan = searchMatchElement.listItemElement.querySelector('.search-match-content') as HTMLElement;
|
212
|
-
UI.UIUtils.highlightRangesWithStyleClass(contentSpan, matchRanges, 'highlighted-search-result');
|
213
|
-
searchMatchElement.listItemElement.className = 'search-match';
|
214
|
-
searchMatchElement.listItemElement.addEventListener('keydown', event => {
|
215
|
-
if (event.key === 'Enter') {
|
216
|
-
event.consume(true);
|
217
|
-
void Common.Revealer.reveal(searchResult.matchRevealable(i));
|
218
|
-
}
|
219
|
-
});
|
220
|
-
searchMatchElement.tooltip = lineContent;
|
206
|
+
searchMatches.push({lineContent, matchRanges, resultLabel});
|
207
|
+
this.appendSearchMatchElement(searchResult, searchMatches[i], i);
|
221
208
|
}
|
222
209
|
}
|
223
210
|
|
224
|
-
private appendShowMoreMatchesElement(startMatchIndex: number): void {
|
225
|
-
const
|
211
|
+
private appendShowMoreMatchesElement(searchResult: SearchResult, startMatchIndex: number): void {
|
212
|
+
const element = this.searchResults.get(searchResult);
|
213
|
+
if (!element) {
|
214
|
+
return;
|
215
|
+
}
|
216
|
+
const matchesLeftCount = searchResult.matchesCount() - startMatchIndex;
|
226
217
|
const showMoreMatchesText = i18nString(UIStrings.showDMore, {PH1: matchesLeftCount});
|
227
218
|
const showMoreMatchesTreeElement = new UI.TreeOutline.TreeElement(showMoreMatchesText);
|
228
|
-
|
219
|
+
element.appendChild(showMoreMatchesTreeElement);
|
229
220
|
showMoreMatchesTreeElement.listItemElement.classList.add('show-more-matches');
|
230
221
|
showMoreMatchesTreeElement.onselect =
|
231
|
-
this.showMoreMatchesElementSelected.bind(this, showMoreMatchesTreeElement, startMatchIndex);
|
222
|
+
this.showMoreMatchesElementSelected.bind(this, searchResult, showMoreMatchesTreeElement, startMatchIndex);
|
232
223
|
}
|
233
224
|
|
234
225
|
private regexMatchRanges(lineContent: string, regex: RegExp): TextUtils.TextRange.SourceRange[] {
|
@@ -243,13 +234,18 @@ export class SearchResultsTreeElement extends UI.TreeOutline.TreeElement {
|
|
243
234
|
}
|
244
235
|
|
245
236
|
private showMoreMatchesElementSelected(
|
246
|
-
showMoreMatchesTreeElement: UI.TreeOutline.TreeElement,
|
247
|
-
|
248
|
-
|
249
|
-
|
237
|
+
searchResult: SearchResult, showMoreMatchesTreeElement: UI.TreeOutline.TreeElement,
|
238
|
+
startMatchIndex: number): boolean {
|
239
|
+
const parentElement = showMoreMatchesTreeElement.parent;
|
240
|
+
parentElement?.removeChild(showMoreMatchesTreeElement);
|
241
|
+
this.appendSearchMatches(searchResult, startMatchIndex, searchResult.matchesCount());
|
242
|
+
return true;
|
250
243
|
}
|
251
244
|
}
|
252
245
|
|
246
|
+
export const matchesExpandedByDefault = 200;
|
247
|
+
export const matchesShownAtOnce = 20;
|
248
|
+
|
253
249
|
const DEFAULT_OPTS = {
|
254
250
|
prefixLength: 25,
|
255
251
|
maxLength: 1000,
|
@@ -106,12 +106,11 @@ const UIStrings = {
|
|
106
106
|
} as const;
|
107
107
|
const str_ = i18n.i18n.registerUIStrings('panels/search/SearchView.ts', UIStrings);
|
108
108
|
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
109
|
-
const {ref} = Directives;
|
109
|
+
const {ref, live} = Directives;
|
110
110
|
const {widgetConfig} = UI.Widget;
|
111
111
|
|
112
112
|
interface SearchViewInput {
|
113
113
|
query: string;
|
114
|
-
focusSearchInput: boolean;
|
115
114
|
matchCase: boolean;
|
116
115
|
isRegex: boolean;
|
117
116
|
searchMessage: string;
|
@@ -128,12 +127,15 @@ interface SearchViewInput {
|
|
128
127
|
onClearSearch: () => void;
|
129
128
|
}
|
130
129
|
|
131
|
-
|
130
|
+
interface SearchViewOutput {
|
131
|
+
focusSearchInput: () => void;
|
132
|
+
}
|
133
|
+
|
134
|
+
type View = (input: SearchViewInput, output: SearchViewOutput, target: HTMLElement) => void;
|
132
135
|
|
133
136
|
export const DEFAULT_VIEW: View = (input, output, target) => {
|
134
137
|
const {
|
135
138
|
query,
|
136
|
-
focusSearchInput,
|
137
139
|
matchCase,
|
138
140
|
isRegex,
|
139
141
|
searchMessage,
|
@@ -175,14 +177,16 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
|
|
175
177
|
change: true, keydown: 'ArrowUp|ArrowDown|Enter'})}
|
176
178
|
aria-label=${i18nString(UIStrings.find)}
|
177
179
|
size="100" results="0"
|
178
|
-
.value=${query}
|
180
|
+
.value=${live(query)}
|
179
181
|
@keydown=${onQueryKeyDown}
|
180
182
|
@input=${(e: Event) => onQueryChange((e.target as HTMLInputElement).value)}
|
181
183
|
${ref(e => {
|
182
|
-
|
183
|
-
e
|
184
|
-
|
185
|
-
|
184
|
+
output.focusSearchInput = () => {
|
185
|
+
if (e instanceof HTMLInputElement) {
|
186
|
+
e.focus();
|
187
|
+
e.select();
|
188
|
+
}
|
189
|
+
};
|
186
190
|
})}>
|
187
191
|
<devtools-button class="clear-button" tabindex="-1"
|
188
192
|
@click=${onClearSearchInput}
|
@@ -258,7 +262,7 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
|
|
258
262
|
|
259
263
|
export class SearchView extends UI.Widget.VBox {
|
260
264
|
readonly #view: View;
|
261
|
-
#focusSearchInput:
|
265
|
+
#focusSearchInput = (): void => {};
|
262
266
|
#isIndexing: boolean;
|
263
267
|
#searchId: number;
|
264
268
|
#searchMatchesCount: number;
|
@@ -294,7 +298,6 @@ export class SearchView extends UI.Widget.VBox {
|
|
294
298
|
this.#view = view;
|
295
299
|
this.setMinimumSize(0, 40);
|
296
300
|
|
297
|
-
this.#focusSearchInput = false;
|
298
301
|
this.#isIndexing = false;
|
299
302
|
this.#searchId = 1;
|
300
303
|
this.#query = '';
|
@@ -320,7 +323,6 @@ export class SearchView extends UI.Widget.VBox {
|
|
320
323
|
override performUpdate(): void {
|
321
324
|
const input: SearchViewInput = {
|
322
325
|
query: this.#query,
|
323
|
-
focusSearchInput: this.#focusSearchInput,
|
324
326
|
matchCase: this.#matchCase,
|
325
327
|
isRegex: this.#isRegex,
|
326
328
|
searchMessage: this.#searchMessage,
|
@@ -338,9 +340,13 @@ export class SearchView extends UI.Widget.VBox {
|
|
338
340
|
onRefresh: this.#onRefresh.bind(this),
|
339
341
|
onClearSearch: this.#onClearSearch.bind(this),
|
340
342
|
};
|
341
|
-
const
|
343
|
+
const that = this;
|
344
|
+
const output: SearchViewOutput = {
|
345
|
+
set focusSearchInput(value: () => void) {
|
346
|
+
that.#focusSearchInput = value;
|
347
|
+
}
|
348
|
+
};
|
342
349
|
this.#view(input, output, this.contentElement);
|
343
|
-
this.#focusSearchInput = false;
|
344
350
|
}
|
345
351
|
|
346
352
|
#onToggleRegex(): void {
|
@@ -360,7 +366,9 @@ export class SearchView extends UI.Widget.VBox {
|
|
360
366
|
toggle(queryCandidate: string, searchImmediately?: boolean): void {
|
361
367
|
this.#query = queryCandidate;
|
362
368
|
this.requestUpdate();
|
363
|
-
this.
|
369
|
+
void this.updateComplete.then(() => {
|
370
|
+
this.focus();
|
371
|
+
});
|
364
372
|
|
365
373
|
this.#initScope();
|
366
374
|
if (searchImmediately) {
|
@@ -537,8 +545,7 @@ export class SearchView extends UI.Widget.VBox {
|
|
537
545
|
}
|
538
546
|
|
539
547
|
override focus(): void {
|
540
|
-
this.#focusSearchInput
|
541
|
-
this.requestUpdate();
|
548
|
+
this.#focusSearchInput();
|
542
549
|
}
|
543
550
|
|
544
551
|
override willHide(): void {
|