From e64a18b18ba4e4685e6f97b13bd05dfc1b2e2445 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Thu, 3 Jul 2025 22:46:19 -0500 Subject: [PATCH] Wiring of search-replace popup; moved some services up one level --- public/imgs/only-in-selection.png | Bin 0 -> 6270 bytes public/imgs/whole-word.png | Bin 0 -> 6651 bytes src/app/app.component.html | 1 + src/app/app.component.ts | 2 + .../search-replace/search-replace.service.ts | 27 ++ .../services/{editor => }/files.service.ts | 6 +- src/app/editor/code-view/view.base.ts | 32 ++- src/app/editor/code-view/view.component.ts | 9 +- src/app/editor/editors.component.ts | 2 +- .../search-replace.component.css | 34 +++ .../search-replace.component.html | 84 ++++++ .../search-replace.component.ts | 268 ++++++++++++++++++ src/assets/css/styles.css | 10 + 13 files changed, 457 insertions(+), 18 deletions(-) create mode 100644 public/imgs/only-in-selection.png create mode 100644 public/imgs/whole-word.png create mode 100644 src/app/common/services/editor/search-replace/search-replace.service.ts rename src/app/common/services/{editor => }/files.service.ts (94%) create mode 100644 src/app/editor/search-replace/search-replace.component.css create mode 100644 src/app/editor/search-replace/search-replace.component.html create mode 100644 src/app/editor/search-replace/search-replace.component.ts diff --git a/public/imgs/only-in-selection.png b/public/imgs/only-in-selection.png new file mode 100644 index 0000000000000000000000000000000000000000..3c35fc3f6dbf8fdc5b59df53656d530d5ac1168c GIT binary patch literal 6270 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 KO_6or=6?Yu9o_N( literal 0 HcmV?d00001 diff --git a/public/imgs/whole-word.png b/public/imgs/whole-word.png new file mode 100644 index 0000000000000000000000000000000000000000..d147682c5511c630f529540bda883593998d8262 GIT binary patch literal 6651 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 + \ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 0a1cf7c..7d8610b 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -3,6 +3,7 @@ import { Component } from '@angular/core'; import { InfoBarComponent } from './editor/info-bar/info-bar.component'; import { TabsComponent } from './editor/tabs/tabs.component'; import { EditorsComponent } from './editor/editors.component'; +import { SearchReplaceComponent } from "./editor/search-replace/search-replace.component"; import { FilesModalComponent } from "./common/components/modals/files/files-modal.component"; @@ -13,6 +14,7 @@ import { FilesModalComponent } from "./common/components/modals/files/files-moda InfoBarComponent, TabsComponent, EditorsComponent, + SearchReplaceComponent, FilesModalComponent ], templateUrl: './app.component.html', diff --git a/src/app/common/services/editor/search-replace/search-replace.service.ts b/src/app/common/services/editor/search-replace/search-replace.service.ts new file mode 100644 index 0000000..1e74c10 --- /dev/null +++ b/src/app/common/services/editor/search-replace/search-replace.service.ts @@ -0,0 +1,27 @@ +import { Injectable, inject } from '@angular/core'; +import { ReplaySubject, Observable } from 'rxjs'; + +import { ServiceMessage } from '../../../types/service-message.type'; + + + +@Injectable({ + providedIn: 'root' +}) +export class SearchReplaceService { + private messageSubject: ReplaySubject = new ReplaySubject(1); + + + constructor() { + } + + + public sendMessage(data: ServiceMessage): void { + this.messageSubject.next(data); + } + + public getMessage$(): Observable { + return this.messageSubject.asObservable(); + } + +} \ No newline at end of file diff --git a/src/app/common/services/editor/files.service.ts b/src/app/common/services/files.service.ts similarity index 94% rename from src/app/common/services/editor/files.service.ts rename to src/app/common/services/files.service.ts index fe84b7b..ea2997e 100644 --- a/src/app/common/services/editor/files.service.ts +++ b/src/app/common/services/files.service.ts @@ -4,10 +4,10 @@ import { ReplaySubject, Observable } from 'rxjs'; import { EditSession, UndoManager } from 'ace-builds'; import { getModeForPath } from 'ace-builds/src-noconflict/ext-modelist'; -import { TabsService } from './tabs/tabs.service'; +import { TabsService } from './editor/tabs/tabs.service'; -import { NewtonFile } from '../../types/file.type'; -import { ServiceMessage } from '../../types/service-message.type'; +import { NewtonFile } from '../types/file.type'; +import { ServiceMessage } from '../types/service-message.type'; diff --git a/src/app/editor/code-view/view.base.ts b/src/app/editor/code-view/view.base.ts index 7a4265f..fa7ced1 100644 --- a/src/app/editor/code-view/view.base.ts +++ b/src/app/editor/code-view/view.base.ts @@ -5,7 +5,8 @@ import { InfoBarService } from '../../common/services/editor/info-bar/info-bar.s import { FilesModalService } from '../../common/services/editor/modals/files-modal.service'; import { TabsService } from '../../common/services/editor/tabs/tabs.service'; import { EditorsService } from '../../common/services/editor/editors.service'; -import { FilesService } from '../../common/services/editor/files.service'; +import { FilesService } from '../../common/services/files.service'; +import { SearchReplaceService } from '../../common/services/editor/search-replace/search-replace.service'; import { EditorSettings } from "../../common/configs/editor.config"; import { NewtonFile } from '../../common/types/file.type'; @@ -16,17 +17,18 @@ import { ServiceMessage } from '../../common/types/service-message.type'; @Directive() export class CodeViewBase { - public uuid: string = uuid.v4(); - @Input() public isDefault: boolean = false; - @Input() public isMiniMap: boolean = false; + public uuid: string = uuid.v4(); + @Input() public isDefault: boolean = false; + @Input() public isMiniMap: boolean = false; public leftSiblingUUID!: string; public rightSiblingUUID!: string; - protected infoBarService: InfoBarService = inject(InfoBarService); - protected filesModalService: FilesModalService = inject(FilesModalService); - protected tabsService: TabsService = inject(TabsService); - protected editorsService: EditorsService = inject(EditorsService); - protected filesService: FilesService = inject(FilesService); + protected infoBarService: InfoBarService = inject(InfoBarService); + protected filesModalService: FilesModalService = inject(FilesModalService); + protected tabsService: TabsService = inject(TabsService); + protected editorsService: EditorsService = inject(EditorsService); + protected filesService: FilesService = inject(FilesService); + protected searchReplaceService: SearchReplaceService = inject(SearchReplaceService); @ViewChild('editor') editorElm!: ElementRef; @Input() editorSettings!: typeof EditorSettings; @@ -94,11 +96,19 @@ export class CodeViewBase { } public searchPopup() { - this.editor.execCommand("find"); + let message = new ServiceMessage(); + message.action = "toggle-search-replace"; + this.searchReplaceService.sendMessage(message); + + // this.editor.execCommand("find"); } public replacePopup() { - this.editor.execCommand("replace"); + let message = new ServiceMessage(); + message.action = "toggle-search-replace"; + this.searchReplaceService.sendMessage(message); + + // this.editor.execCommand("replace"); } public showFilesList() { diff --git a/src/app/editor/code-view/view.component.ts b/src/app/editor/code-view/view.component.ts index c10d5b1..44ed6d2 100644 --- a/src/app/editor/code-view/view.component.ts +++ b/src/app/editor/code-view/view.component.ts @@ -7,10 +7,10 @@ import "ace-builds/src-noconflict/ext-keybinding_menu"; import "ace-builds/src-noconflict/ext-command_bar"; import "ace-builds/src-noconflict/ext-prompt"; import "ace-builds/src-noconflict/ext-code_lens"; -import "ace-builds/src-noconflict/ext-searchbox"; +// import "ace-builds/src-noconflict/ext-searchbox"; import "ace-builds/src-noconflict/ext-language_tools"; -//import "ace-builds/src-noconflict/theme-one_dark"; -//import "ace-builds/src-noconflict/theme-penguins_in_space"; +// import "ace-builds/src-noconflict/theme-one_dark"; +// import "ace-builds/src-noconflict/theme-penguins_in_space"; import "ace-builds/src-noconflict/theme-gruvbox"; import { CodeViewBase } from './view.base'; @@ -51,6 +51,7 @@ export class CodeViewComponent extends CodeViewBase { if (this.isDefault) { this.editorsService.setActiveEditor(this.uuid); this.addActiveStyling(); + this.editor.focus(); } if (this.isMiniMap) { @@ -111,8 +112,10 @@ export class CodeViewComponent extends CodeViewBase { let message = new ServiceMessage(); message.action = "set-active-editor"; message.editorUUID = this.uuid; + message.rawData = this.editor; this.editorsService.sendMessage(message); + this.searchReplaceService.sendMessage(message); this.updateInfoBar(); }); diff --git a/src/app/editor/editors.component.ts b/src/app/editor/editors.component.ts index 926cfde..f6b0b5e 100644 --- a/src/app/editor/editors.component.ts +++ b/src/app/editor/editors.component.ts @@ -3,7 +3,7 @@ import { Subject, takeUntil } from 'rxjs'; import { EditorsService } from '../common/services/editor/editors.service'; import { TabsService } from '../common/services/editor/tabs/tabs.service'; -import { FilesService } from '../common/services/editor/files.service'; +import { FilesService } from '../common/services/files.service'; import { CodeViewComponent } from "./code-view/view.component"; diff --git a/src/app/editor/search-replace/search-replace.component.css b/src/app/editor/search-replace/search-replace.component.css new file mode 100644 index 0000000..a98271c --- /dev/null +++ b/src/app/editor/search-replace/search-replace.component.css @@ -0,0 +1,34 @@ +.width-8em { + width: 8em; +} + +.margin-tb-1em { + margin-top: 1em; + margin-bottom: 1em; +} + +.selected { + background-color: rgba(125, 125, 125, 1); + color: rgba(0, 0, 0, 1); +} + +.searching, +.search-success, +.search-fail { + border-style: solid; + color: rgba(125, 125, 125, 1) !important; +} + +.searching { + border-color: rgba(0, 225, 225, 0.64) !important; +} + +.search-success { + background: rgba(136, 204, 39, 0.12) !important; + border-color: rgba(136, 204, 39, 1) !important; +} + +.search-fail { + background: rgba(170, 18, 18, 0.12) !important; + border-color: rgba(200, 18, 18, 1) !important; +} \ No newline at end of file diff --git a/src/app/editor/search-replace/search-replace.component.html b/src/app/editor/search-replace/search-replace.component.html new file mode 100644 index 0000000..45acd09 --- /dev/null +++ b/src/app/editor/search-replace/search-replace.component.html @@ -0,0 +1,84 @@ +
+
+
+ +
+ +
+ +
+ +
+ + + + + +
+
+ +
+ +
+
+
+
+
+ +
+
+
+ + +
+
+
+
+ +
+
+
+
+
+ +
+
+
+ + +
+
+
+
+
\ No newline at end of file diff --git a/src/app/editor/search-replace/search-replace.component.ts b/src/app/editor/search-replace/search-replace.component.ts new file mode 100644 index 0000000..4568c8f --- /dev/null +++ b/src/app/editor/search-replace/search-replace.component.ts @@ -0,0 +1,268 @@ +import { Component, ElementRef, HostBinding, Input, ViewChild, inject } from '@angular/core'; +import { Subject, takeUntil } from 'rxjs'; + +import { SearchReplaceService } from '../../common/services/editor/search-replace/search-replace.service'; + +import { ServiceMessage } from '../../common/types/service-message.type'; + + + +@Component({ + selector: 'search-replace', + standalone: true, + imports: [ + ], + templateUrl: './search-replace.component.html', + styleUrl: './search-replace.component.css', + host: { + 'class': 'row search-replace', + "(keyup)": "globalSearchReplaceKeyHandler($event)" + } +}) +export class SearchReplaceComponent { + private unsubscribe: Subject = new Subject(); + + private searchReplaceService: SearchReplaceService = inject(SearchReplaceService); + + @HostBinding("class.hidden") isHidden: boolean = true; + @ViewChild('findEntryElm') findEntryElm!: ElementRef; + @ViewChild('replaceEntryElm') replaceEntryElm!: ElementRef; + + private editor!: any; + + @Input() findOptions: string = ""; + private useWholeWordSearch: boolean = false; + private searchOnlyInSelection: boolean = false; + private useCaseSensitive: boolean = false; + private useRegex: boolean = false; + private selection: string = ""; + private query: string = ""; + private toStr: string = ""; + private isBackwards: boolean = false; + private isWrap: boolean = true; + private searchTimeoutId: number = -1; + private searchTimeout: number = 400; + + + constructor() { + } + + + private ngAfterViewInit(): void { + this.loadSubscribers(); + } + + private ngOnDestroy() { + this.unsubscribe.next(); + this.unsubscribe.complete(); + } + + private loadSubscribers() { + this.searchReplaceService.getMessage$().pipe( + takeUntil(this.unsubscribe) + ).subscribe((message: ServiceMessage) => { + if (message.action === "toggle-search-replace") { + this.toggleSearchReplace(message); + } else if (message.action === "set-active-editor") { + this.setActiveEditor(message); + } + }); + } + + private toggleSearchReplace(message: ServiceMessage) { + this.selection = this.editor.getSelectedText(); + this.findEntryElm.nativeElement.value = this.selection; + + if (this.selection && !this.isHidden) { + this.findEntryElm.nativeElement.focus(); + return; + } + + this.isHidden = !this.isHidden; + + if (this.isHidden) { + this.editor.focus(); + return; + } + + setTimeout(() => { + this.findEntryElm.nativeElement.focus(); + }, 200); + } + + private setActiveEditor(message: ServiceMessage) { + if (!this.isHidden && this.editor == message.rawData) return; + + this.editor = message.rawData; + + if (this.isHidden) return; + + + this.searchForString(); + } + + public hideSearchReplace() { + if (this.selection) { + this.selection = ""; + return; + } + + this.isHidden = true; + this.editor.focus(); + } + + public globalSearchReplaceKeyHandler(event: any) { + if (event.ctrlKey && event.key === "f") { + this.hideSearchReplace(); + } else if (event.ctrlKey && event.key === "l") { + this.findEntryElm.nativeElement.focus(); + } else if (event.ctrlKey && event.key === "r") { + this.replaceEntryElm.nativeElement.focus(); + } + } + + public toggleWholeWordSearch(event: any) { + let target = event.target; + if (target.nodeName === "IMG") + target = target.parentElement; + + this.useWholeWordSearch = !this.useWholeWordSearch; + target.classList.toggle("selected"); + this.setFindOptionsLbl(); + this.findAllEntries(); + } + + public toggleSelectionOnlyScan(event: any) { + let target = event.target; + if (target.nodeName === "IMG") + target = target.parentElement; + + this.searchOnlyInSelection = !this.searchOnlyInSelection; + target.classList.toggle("selected"); + this.setFindOptionsLbl(); + this.findAllEntries(); + } + + public toggleCaseSensitive(event: any) { + this.useCaseSensitive = !this.useCaseSensitive; + event.target.classList.toggle("selected"); + this.setFindOptionsLbl(); + this.findAllEntries(); + } + + public toggleRegex(event: any) { + this.useRegex = !this.useRegex; + event.target.classList.toggle("selected"); + this.setFindOptionsLbl(); + this.findAllEntries(); + } + + private setFindOptionsLbl() { + let findOptionsStr = ""; + + if (this.useRegex) + findOptionsStr += "Regex" + + findOptionsStr += (findOptionsStr) ? ", " : ""; + findOptionsStr += (this.useCaseSensitive) ? "Case Sensitive" : "Case InSensitive"; + + if (this.searchOnlyInSelection) + findOptionsStr += ", Within Current Selection" + + if (this.useWholeWordSearch) + findOptionsStr += ", Whole Word" + + this.findOptions = findOptionsStr; + } + + + public findNextEntry() { + this.editor.findNext(); + } + + public findAllEntries() { + this.query = this.findEntryElm.nativeElement.value; + + if (!this.query) return; + + let totalCount = this.editor.findAll(this.query, { + backwards: this.isBackwards, + wrap: this.isWrap, + caseSensitive: this.useCaseSensitive, + wholeWord: this.useWholeWordSearch, + regExp: this.useRegex, + range: this.searchOnlyInSelection + }); + } + + public findPreviousEntry() { + this.editor.findPrevious(); + } + + public replaceEntry(event: any) { + if (event instanceof KeyboardEvent) { + if (event.key !== "Enter") { + return; + } + } + + let fromStr = this.findEntryElm.nativeElement.value; + let toStr = this.replaceEntryElm.nativeElement.value; + + if (!fromStr || !toStr) return; + + let totalCount = this.editor.replace(toStr, fromStr, { + backwards: this.isBackwards, + wrap: this.isWrap, + caseSensitive: this.useCaseSensitive, + wholeWord: this.useWholeWordSearch, + regExp: this.useRegex, + range: this.searchOnlyInSelection + }); + + this.editor.clearSelection(); + this.editor.findNext(); + } + + public replaceAll() { + let fromStr = this.findEntryElm.nativeElement.value; + let toStr = this.replaceEntryElm.nativeElement.value; + + if (!fromStr || !toStr) return; + + let totalCount = this.editor.replaceAll(toStr, fromStr, { + backwards: this.isBackwards, + wrap: this.isWrap, + caseSensitive: this.useCaseSensitive, + wholeWord: this.useWholeWordSearch, + regExp: this.useRegex, + range: this.searchOnlyInSelection + }); + } + + public searchForString() { + if (event instanceof KeyboardEvent) { + if (event.key !== "Enter") { + return; + } + } + + this.query = this.findEntryElm.nativeElement.value; + + if (!this.query) return; + + if (this.searchTimeoutId) { clearTimeout(this.searchTimeoutId); } + + this.searchTimeoutId = setTimeout(() => { + let totalCount = this.editor.find(this.query, { + backwards: this.isBackwards, + wrap: this.isWrap, + caseSensitive: this.useCaseSensitive, + wholeWord: this.useWholeWordSearch, + regExp: this.useRegex, + range: this.searchOnlyInSelection + }); + }, this.searchTimeout); + } + +} \ No newline at end of file diff --git a/src/assets/css/styles.css b/src/assets/css/styles.css index e3db070..5b18a1b 100644 --- a/src/assets/css/styles.css +++ b/src/assets/css/styles.css @@ -26,6 +26,16 @@ body { text-align: center; } +.search-replace { + bottom: 2em; + z-index: 999; + display: inline-block; + position: fixed; + width: 100%; + background-color: rgba(64, 64, 64, 0.24); +} + + .tabs { display: flex; overflow: auto;