diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.scss b/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.scss
index 1fa6dfe80f0..a8b3bfb686a 100644
--- a/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.scss
+++ b/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.scss
@@ -9,7 +9,7 @@
line-height: 1.5;
display: block;
- .viewport {
+ #viewport {
overflow: auto;
height: calc(100vh - 132px);
will-change: scroll-position, contents;
@@ -219,6 +219,18 @@
text-decoration: line-through;
}
+ .codeline-search-match-highlight {
+ background-color: var(--primary-color);
+ color: var(--primary-btn-color);
+ padding: 0px;
+ margin: 0px;
+
+ &.active {
+ background-color: red;
+ }
+ }
+
+
.java {
.javadoc {
diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.spec.ts b/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.spec.ts
index 77f6f293b27..e7983174a2a 100644
--- a/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.spec.ts
+++ b/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.spec.ts
@@ -115,5 +115,47 @@ describe('CodePanelComponent', () => {
expect(clipboardSpy).toHaveBeenCalledWith('\ttoken1\ntoken2');
});
});
+
+ describe('searchCodePanelRowData', () => {
+ it('should be defined', () => {
+ expect(component.searchCodePanelRowData).toBeDefined();
+ });
+
+ it('should handle no matches gracefully', () => {
+ const token1 = new StructuredToken();
+ token1.value = 'token1';
+ const token2 = new StructuredToken();
+ token2.value = 'token2';
+ const codePanelRowData1 = new CodePanelRowData();
+ codePanelRowData1.rowOfTokens = [token1, token2];
+ component.codePanelRowData = [codePanelRowData1];
+ component.searchCodePanelRowData('nonexistent');
+ expect(component.codeLineSearchMatchInfo?.length).toBeUndefined();
+ });
+
+ it('should handle an empty search term', () => {
+ const token1 = new StructuredToken();
+ token1.value = 'token1';
+ const token2 = new StructuredToken();
+ token2.value = 'token2';
+ const codePanelRowData1 = new CodePanelRowData();
+ codePanelRowData1.rowOfTokens = [token1, token2];
+ component.codePanelRowData = [codePanelRowData1];
+ component.searchCodePanelRowData('');
+ expect(component.codeLineSearchMatchInfo?.length).toBeUndefined();
+ });
+ });
+
+ describe('highlightSearchMatches', () => {
+ it('should be defined', () => {
+ expect(component.highlightSearchMatches).toBeDefined();
+ });
+
+ it('should clear previous highlights', () => {
+ spyOn(component, 'clearSearchMatchHighlights');
+ component.highlightSearchMatches();
+ expect(component.clearSearchMatchHighlights).toHaveBeenCalled();
+ });
+ });
});
diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.ts b/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.ts
index 56f9b1f40a9..86d6bdf09c6 100644
--- a/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.ts
+++ b/src/dotnet/APIView/ClientSPA/src/app/_components/code-panel/code-panel.component.ts
@@ -1,5 +1,5 @@
-import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, QueryList, SimpleChanges, ViewChildren } from '@angular/core';
-import { take, takeUntil } from 'rxjs/operators';
+import { ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnChanges, Output, QueryList, SimpleChanges, ViewChildren } from '@angular/core';
+import { max, take, takeUntil } from 'rxjs/operators';
import { Datasource, IDatasource, SizeStrategy } from 'ngx-ui-scroll';
import { CommentsService } from 'src/app/_services/comments/comments.service';
import { getQueryParams } from 'src/app/_helpers/router-helpers';
@@ -16,6 +16,8 @@ import { SignalRService } from 'src/app/_services/signal-r/signal-r.service';
import { Subject } from 'rxjs';
import { CommentThreadUpdateAction, CommentUpdatesDto } from 'src/app/_dtos/commentThreadUpdateDto';
import { Menu } from 'primeng/menu';
+import { CodeLineSearchInfo, CodeLineSearchMatch } from 'src/app/_models/codeLineSearchInfo';
+import { DoublyLinkedList, DoublyLinkedListNode } from 'src/app/_helpers/doubly-linkedlist';
@Component({
selector: 'app-code-panel',
@@ -35,8 +37,12 @@ export class CodePanelComponent implements OnChanges{
@Input() userProfile : UserProfile | undefined;
@Input() showLineNumbers: boolean = true;
@Input() loadFailed : boolean = false;
+ @Input() codeLineSearchText: string | undefined;
+ @Input() codeLineNavigationDirection: number | undefined;
@Output() hasActiveConversationEmitter : EventEmitter
= new EventEmitter();
+ @Output() codeLineSearchInfoEmitter : EventEmitter = new EventEmitter();
+
@ViewChildren(Menu) menus!: QueryList
diff --git a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.ts b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.ts
index 363a5150870..37ae132a06e 100644
--- a/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.ts
+++ b/src/dotnet/APIView/ClientSPA/src/app/_components/review-page/review-page.component.ts
@@ -20,6 +20,7 @@ import { CommentItemModel, CommentType } from 'src/app/_models/commentItemModel'
import { SignalRService } from 'src/app/_services/signal-r/signal-r.service';
import { SamplesRevisionService } from 'src/app/_services/samples/samples.service';
import { SamplesRevision } from 'src/app/_models/samples';
+import { CodeLineSearchInfo } from 'src/app/_models/codeLineSearchInfo';
@Component({
selector: 'app-review-page',
@@ -52,6 +53,7 @@ export class ReviewPageComponent implements OnInit {
preferredApprovers : string[] = [];
hasFatalDiagnostics : boolean = false;
hasActiveConversation : boolean = false;
+ codeLineSearchInfo : CodeLineSearchInfo | undefined = new CodeLineSearchInfo();
numberOfActiveConversation : number = 0;
hasHiddenAPIs : boolean = false;
hasHiddenAPIThatIsDiff : boolean = false;
@@ -69,6 +71,9 @@ export class ReviewPageComponent implements OnInit {
apiRevisionPageSize = 50;
lastNodeIdUnhashedDiscarded = '';
+ codeLineSearchText: string | undefined = undefined;
+ codeLineNavigationDirection: number | undefined = undefined;
+
private destroy$ = new Subject