Skip to content

Commit 8ef350e

Browse files
authored
[ZEPPELIN-6279] Add Search/Highlight keyword feature for Code Editor
### What is this PR for? This PR is a re-implementation of the Search and Replace UI, a feature that was available in the Classic UI. The core search and highlighting logic has been fully rewritten to work with the Monaco Editor, which replaces the legacy Ace Editor library. ### What type of PR is it? Feature ### Todos * [ ] - Replace and Replace All features are not implemented yet. * [ ] - The feature to display the total counts and the current index is not implemented yet. ### What is the Jira issue? * [ZEPPELIN-6279](https://issues.apache.org/jira/browse/ZEPPELIN-6279) ### How should this be tested? **Manual Testing** * Check out the PR branch and check if search and highlight feature works. ### Screenshots (if appropriate) <img width="972" height="280" alt="image" src="https://github.com/user-attachments/assets/5e9f0ff1-c796-4e59-8e91-40f3fe1a2ce8" /> ### Questions: * Does the license files need to update? No * Is there breaking changes for older versions? No * Does this needs documentation? No Closes #5079 from grcenneat/feat/ZEPPELIN-6279. Signed-off-by: ChanHo Lee <[email protected]>
1 parent 3ad1a2c commit 8ef350e

File tree

8 files changed

+162
-3
lines changed

8 files changed

+162
-3
lines changed

zeppelin-web-angular/src/app/pages/workspace/notebook/action-bar/action-bar.component.html

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,41 @@
179179
</nz-dropdown-menu>
180180
</nz-button-group>
181181
<nz-button-group nzSize="small">
182-
<button nz-button nz-tooltip nzTooltipTitle="Search code" (click)="searchCode()">
182+
<button
183+
nz-button
184+
nz-dropdown
185+
[nzDropdownMenu]="searchMenu"
186+
nzTrigger="click"
187+
nz-tooltip
188+
nzTooltipTitle="Search code"
189+
nzTooltipTrigger="hover"
190+
(nzVisibleChange)="onSearchMenuOpenChange($event)"
191+
>
183192
<i nz-icon nzType="search" nzTheme="outline"></i>
184193
</button>
194+
<!-- Search Code Dropdown UI -->
195+
<nz-dropdown-menu #searchMenu="nzDropdownMenu">
196+
<div class="dropdown-menu search-code">
197+
<div class="search-code-row">
198+
<nz-input-group nzAddOnBefore="Find">
199+
<input type="text" nz-input #searchInput [(ngModel)]="searchText" (ngModelChange)="searchCode()" />
200+
</nz-input-group>
201+
<nz-button-group class="search-row-btn-group">
202+
<button nz-button (click)="onFindPrevClick(searchText)"><i nz-icon nzType="left"></i></button>
203+
<button nz-button (click)="onFindNextClick(searchText)"><i nz-icon nzType="right"></i></button>
204+
</nz-button-group>
205+
</div>
206+
<div class="search-code-row">
207+
<nz-input-group nzAddOnBefore="Replace">
208+
<input type="text" nz-input [(ngModel)]="replaceText" />
209+
</nz-input-group>
210+
<nz-button-group class="search-row-btn-group">
211+
<button nz-button (click)="onReplaceClick(searchText, replaceText)">Replace</button>
212+
<button nz-button (click)="onReplaceAllClick(searchText, replaceText)">All</button>
213+
</nz-button-group>
214+
</div>
215+
</div>
216+
</nz-dropdown-menu>
185217
</nz-button-group>
186218
<nz-button-group nzSize="small" *ngIf="!viewOnly">
187219
<button

zeppelin-web-angular/src/app/pages/workspace/notebook/action-bar/action-bar.component.less

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,51 @@
113113
}
114114
}
115115
});
116+
117+
::ng-deep html.dark .search-code,
118+
::ng-deep .search-code {
119+
padding: 0;
120+
&-row {
121+
display: flex;
122+
.ant-input-group,
123+
.ant-input-group-addon,
124+
.ant-input {
125+
height: 32px;
126+
border-radius: 0;
127+
}
128+
.search-row-btn-group {
129+
display: flex;
130+
align-items: center;
131+
flex-wrap: nowrap;
132+
}
133+
.ant-btn-group > .ant-btn:first-child,
134+
.ant-btn-group > .ant-btn:last-child {
135+
border-radius: 0;
136+
}
137+
&:first-child {
138+
.ant-input-group-addon {
139+
border-top-left-radius: 4px;
140+
}
141+
.ant-btn:last-child {
142+
border-top-right-radius: 4px;
143+
}
144+
}
145+
&:last-child {
146+
.ant-input-group-addon {
147+
border-bottom-left-radius: 4px;
148+
}
149+
.ant-btn:last-child {
150+
border-bottom-right-radius: 4px;
151+
}
152+
}
153+
154+
nz-input-group + nz-button-group {
155+
margin-left: -1px;
156+
}
157+
158+
& ~ & {
159+
margin-top: -1px;
160+
}
161+
}
162+
}
163+

zeppelin-web-angular/src/app/pages/workspace/notebook/action-bar/action-bar.component.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ import {
1414
ChangeDetectionStrategy,
1515
ChangeDetectorRef,
1616
Component,
17+
ElementRef,
1718
EventEmitter,
1819
Inject,
1920
Input,
2021
OnInit,
21-
Output
22+
Output,
23+
ViewChild
2224
} from '@angular/core';
2325
import { ActivatedRoute, Router } from '@angular/router';
2426
import { NzMessageService } from 'ng-zorro-antd/message';
@@ -51,13 +53,17 @@ export class NotebookActionBarComponent extends MessageListenersManager implemen
5153
>();
5254
@Output() readonly editorHideChange = new EventEmitter<boolean>();
5355
@Output() readonly tableHideChange = new EventEmitter<boolean>();
56+
@Output() readonly handleSearch = new EventEmitter<string>();
57+
@ViewChild('searchInput', { static: false }) searchInputRef?: ElementRef<HTMLInputElement>;
5458
lfOption: Array<'report' | 'default' | 'simple'> = ['default', 'simple', 'report'];
5559
isRevisionSupported: boolean = false;
5660
isNoteParagraphRunning = false;
5761
principal = this.ticketService.ticket.principal;
5862
editorHide = false;
5963
commitVisible = false;
6064
tableHide = false;
65+
searchText = '';
66+
replaceText = '';
6167
cronOption = [
6268
{ name: 'None', value: undefined },
6369
{ name: '1m', value: '0 0/1 * * * ?' },
@@ -219,7 +225,32 @@ export class NotebookActionBarComponent extends MessageListenersManager implemen
219225
}
220226

221227
searchCode() {
222-
// TODO(hsuanxyz)
228+
this.handleSearch.emit(this.searchText);
229+
}
230+
231+
onSearchMenuOpenChange(open: boolean) {
232+
if (open) {
233+
setTimeout(() => {
234+
this.searchInputRef?.nativeElement?.focus();
235+
}, 0);
236+
} else {
237+
this.searchText = '';
238+
this.searchCode();
239+
}
240+
}
241+
242+
// TODO: Implement logic to find the previous search match in the notebook editor
243+
onFindPrevClick(searchText: string) {}
244+
245+
// TODO: Implement logic to find the next search match in the notebook editor
246+
onFindNextClick(searchText: string) {}
247+
248+
// TODO: Implement logic to replace the current search match with the replacement text
249+
onReplaceClick(searchText: string, replaceText: string) {}
250+
251+
// TODO: Implement logic to replace all search matches with the replacement text
252+
onReplaceAllClick(searchText: string, replaceText: string) {
253+
this.handleSearch.emit(searchText);
223254
}
224255

225256
deleteNote() {

zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
[currentRevision]="currentRevision"
2424
(tableHideChange)="setAllParagraphTableHide($event)"
2525
(editorHideChange)="setAllParagraphEditorHide($event)"
26+
(handleSearch)="onParagraphSearch($event)"
2627
#actionBar
2728
></zeppelin-notebook-action-bar>
2829
<div class="flex-container">

zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,10 @@ export class NotebookComponent extends MessageListenersManager implements OnInit
258258
this.cdr.markForCheck();
259259
}
260260

261+
onParagraphSearch(term: string) {
262+
this.listOfNotebookParagraphComponent.forEach(comp => comp.highlightMatches(term || ''));
263+
}
264+
261265
saveParagraph(id: string) {
262266
const paragraphFound = this.listOfNotebookParagraphComponent.toArray().find(p => p.paragraph.id === id);
263267
if (!paragraphFound) {

zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/code-editor/code-editor.component.less

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,9 @@
3333
}
3434

3535
});
36+
37+
::ng-deep .editor-search-highlight {
38+
background: yellow;
39+
color: black;
40+
border-radius: 2px;
41+
}

zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/code-editor/code-editor.component.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { NotebookParagraphControlComponent } from '../control/control.component'
3434

3535
type IStandaloneCodeEditor = MonacoEditor.IStandaloneCodeEditor;
3636
type IEditor = MonacoEditor.IEditor;
37+
type DecorationIdentifier = ReturnType<monaco.editor.ICodeEditor['deltaDecorations']>[number];
3738

3839
@Component({
3940
selector: 'zeppelin-notebook-paragraph-code-editor',
@@ -62,6 +63,7 @@ export class NotebookParagraphCodeEditorComponent
6263
@Output() readonly initKeyBindings = new EventEmitter<IStandaloneCodeEditor>();
6364
private editor?: IStandaloneCodeEditor;
6465
private monacoDisposables: IDisposable[] = [];
66+
private highlightDecorations: DecorationIdentifier[] = [];
6567
height = 18;
6668
interpreterName?: string;
6769

@@ -346,6 +348,37 @@ export class NotebookParagraphCodeEditorComponent
346348
}
347349
}
348350

351+
highlightMatches(term: string) {
352+
if (!this.editor || !term) {
353+
// Remove previous highlights if term is empty
354+
this.highlightDecorations = this.editor?.deltaDecorations(this.highlightDecorations, []) || [];
355+
return;
356+
}
357+
const model = this.editor.getModel();
358+
if (!model) {
359+
return;
360+
}
361+
const text = model.getValue();
362+
const newDecorations = [];
363+
let startIndex = 0;
364+
while (term && text) {
365+
const idx = text.indexOf(term, startIndex);
366+
if (idx === -1) {
367+
break;
368+
}
369+
const startPos = model.getPositionAt(idx);
370+
const endPos = model.getPositionAt(idx + term.length);
371+
newDecorations.push({
372+
range: new monaco.Range(startPos.lineNumber, startPos.column, endPos.lineNumber, endPos.column),
373+
options: {
374+
inlineClassName: 'editor-search-highlight'
375+
}
376+
});
377+
startIndex = idx + term.length;
378+
}
379+
this.highlightDecorations = this.editor.deltaDecorations(this.highlightDecorations, newDecorations);
380+
}
381+
349382
constructor(
350383
private cdr: ChangeDetectorRef,
351384
private ngZone: NgZone,

zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/paragraph.component.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ export class NotebookParagraphComponent extends ParagraphBase
125125
}
126126
}
127127

128+
highlightMatches(searchText: string) {
129+
this.notebookParagraphCodeEditorComponent?.highlightMatches(searchText);
130+
}
131+
128132
textChanged(text: string) {
129133
this.dirtyText = text;
130134
this.paragraph.text = text;

0 commit comments

Comments
 (0)