diff --git a/application/apps/indexer/processor/src/map.rs b/application/apps/indexer/processor/src/map.rs index 2d6083d5f..7d81a7b21 100644 --- a/application/apps/indexer/processor/src/map.rs +++ b/application/apps/indexer/processor/src/map.rs @@ -206,6 +206,16 @@ impl SearchMap { Ok(&self.matches[from as usize..]) } + pub fn indexes_to_rev(&self, to: u64) -> Result<&[stypes::FilterMatch], MapError> { + if to >= self.len() as u64 { + return Err(MapError::OutOfRange(format!( + "Search has: {} matches. Requested from: {to}", + self.len(), + ))); + } + Ok(&self.matches[..to as usize]) + } + pub fn get_match_index(&self, pos: u64) -> Option { self.matches.iter().enumerate().find_map(|(index, m)| { if m.index == pos { diff --git a/application/apps/indexer/session/src/session.rs b/application/apps/indexer/session/src/session.rs index 55f73bf8a..169e312dd 100644 --- a/application/apps/indexer/session/src/session.rs +++ b/application/apps/indexer/session/src/session.rs @@ -238,9 +238,10 @@ impl Session { &self, filter: SearchFilter, from: u64, + rev: bool, ) -> Result, stypes::ComputationError> { self.state - .search_nested_match(filter, from) + .search_nested_match(filter, from, rev) .await .map_err(stypes::ComputationError::NativeError) } diff --git a/application/apps/indexer/session/src/state/api.rs b/application/apps/indexer/session/src/state/api.rs index d0ab24414..0f09cce8d 100644 --- a/application/apps/indexer/session/src/state/api.rs +++ b/application/apps/indexer/session/src/state/api.rs @@ -124,10 +124,12 @@ pub enum Api { oneshot::Sender, stypes::NativeError>>, ), ), + #[allow(clippy::type_complexity)] SearchNestedMatch( ( SearchFilter, u64, + bool, oneshot::Sender, stypes::NativeError>>, ), ), @@ -376,9 +378,10 @@ impl SessionStateAPI { &self, filter: SearchFilter, from: u64, + rev: bool, ) -> Result, stypes::NativeError> { let (tx, rx) = oneshot::channel(); - self.exec_operation(Api::SearchNestedMatch((filter, from, tx)), rx) + self.exec_operation(Api::SearchNestedMatch((filter, from, rev, tx)), rx) .await? } diff --git a/application/apps/indexer/session/src/state/mod.rs b/application/apps/indexer/session/src/state/mod.rs index 04015cba5..26b2148d6 100644 --- a/application/apps/indexer/session/src/state/mod.rs +++ b/application/apps/indexer/session/src/state/mod.rs @@ -152,27 +152,40 @@ impl SessionState { &mut self, filter: SearchFilter, from: u64, + rev: bool, ) -> Result, stypes::NativeError> { - let indexes = self - .search_map - .indexes_from(from) - .map_err(|e| stypes::NativeError { - severity: stypes::Severity::ERROR, - kind: stypes::NativeErrorKind::Grabber, - message: Some(format!("{e}")), - })?; + let indexes = if !rev { + self.search_map.indexes_from(from) + } else { + self.search_map.indexes_to_rev(from) + } + .map_err(|e| stypes::NativeError { + severity: stypes::Severity::ERROR, + kind: stypes::NativeErrorKind::Grabber, + message: Some(format!("{e}")), + })?; let searcher = LineSearcher::new(&filter).map_err(|e| stypes::NativeError { severity: stypes::Severity::ERROR, kind: stypes::NativeErrorKind::OperationSearch, message: Some(e.to_string()), })?; - for range in self.transform_indexes(indexes).iter() { - if let Some(ln) = self - .session_file - .grab(&LineRange::from(range.clone()))? - .iter() - .find(|ln| searcher.is_match(&ln.content)) - { + let mut indexes: std::vec::IntoIter> = + self.transform_indexes(indexes).into_iter(); + while let Some(range) = if !rev { + indexes.next() + } else { + indexes.next_back() + } { + let grabbed = self.session_file.grab(&LineRange::from(range.clone()))?; + let found = if !rev { + grabbed.iter().find(|ln| searcher.is_match(&ln.content)) + } else { + grabbed + .iter() + .rev() + .find(|ln| searcher.is_match(&ln.content)) + }; + if let Some(ln) = found { let Some(srch_pos) = self.search_map.get_match_index(ln.pos as u64) else { return Err(stypes::NativeError { severity: stypes::Severity::ERROR, @@ -657,9 +670,9 @@ pub async fn run( stypes::NativeError::channel("Failed to respond to Api::GrabSearch") })?; } - Api::SearchNestedMatch((filter, from, tx_response)) => { + Api::SearchNestedMatch((filter, from, rev, tx_response)) => { tx_response - .send(state.handle_search_nested_match(filter, from)) + .send(state.handle_search_nested_match(filter, from, rev)) .map_err(|_| { stypes::NativeError::channel("Failed to respond to Api::SearchNestedMatch") })?; diff --git a/application/apps/rustcore/rs-bindings/src/js/session/mod.rs b/application/apps/rustcore/rs-bindings/src/js/session/mod.rs index b54c1228f..566c56c19 100644 --- a/application/apps/rustcore/rs-bindings/src/js/session/mod.rs +++ b/application/apps/rustcore/rs-bindings/src/js/session/mod.rs @@ -391,6 +391,7 @@ impl RustSession { &self, filter: WrappedSearchFilter, from: i64, + rev: bool, ) -> Result, stypes::ComputationError> { let res = self .session @@ -399,6 +400,7 @@ impl RustSession { .search_nested_match( filter.as_filter(), u64::try_from(from).map_err(|_| stypes::ComputationError::InvalidData)?, + rev, ) .await?; Ok(if let Some((pos, srch_pos)) = res { diff --git a/application/apps/rustcore/ts-bindings/src/api/session.search.ts b/application/apps/rustcore/ts-bindings/src/api/session.search.ts index 8f7c340f8..269b5e844 100644 --- a/application/apps/rustcore/ts-bindings/src/api/session.search.ts +++ b/application/apps/rustcore/ts-bindings/src/api/session.search.ts @@ -66,8 +66,12 @@ export class SessionSearch { return this.managers.search.run(filters); } - public searchNestedMatch(filter: IFilter, from: number): Promise<[number, number] | undefined> { - return this.session.searchNestedMatch(filter, from); + public searchNestedMatch( + filter: IFilter, + from: number, + rev: boolean, + ): Promise<[number, number] | undefined> { + return this.session.searchNestedMatch(filter, from, rev); } public values(filters: string[]): ICancelablePromise { diff --git a/application/apps/rustcore/ts-bindings/src/native/native.session.ts b/application/apps/rustcore/ts-bindings/src/native/native.session.ts index b92c6b18e..eeece0c67 100644 --- a/application/apps/rustcore/ts-bindings/src/native/native.session.ts +++ b/application/apps/rustcore/ts-bindings/src/native/native.session.ts @@ -151,6 +151,7 @@ export abstract class RustSession extends RustSessionRequiered { public abstract searchNestedMatch( filter: IFilter, from: number, + rev: boolean, ): Promise<[number, number] | undefined>; public abstract search(filters: IFilter[], operationUuid: string): Promise; @@ -295,6 +296,7 @@ export abstract class RustSessionNative { is_word: boolean; }, from: number, + rev: boolean, ): Promise<[number, number] | undefined>; public abstract applySearchValuesFilters( @@ -841,7 +843,11 @@ export class RustSessionWrapper extends RustSession { }); } - public searchNestedMatch(filter: IFilter, from: number): Promise<[number, number] | undefined> { + public searchNestedMatch( + filter: IFilter, + from: number, + rev: boolean, + ): Promise<[number, number] | undefined> { return new Promise((resolve, reject) => { try { this._native @@ -853,6 +859,7 @@ export class RustSessionWrapper extends RustSession { is_word: filter.flags.word, }, from, + rev, ) .then(resolve) .catch((err: Error) => { diff --git a/application/client/src/app/service/session/dependencies/search.ts b/application/client/src/app/service/session/dependencies/search.ts index 6b7cc8b5d..56ba5d63e 100644 --- a/application/client/src/app/service/session/dependencies/search.ts +++ b/application/client/src/app/service/session/dependencies/search.ts @@ -110,7 +110,7 @@ export class Search extends Subscriber { }); } - public searchNestedMatch(): Promise<[number, number] | undefined> { + public searchNestedMatch(rev: boolean): Promise<[number, number] | undefined> { const filter = this.state().nested().get(); if (filter === undefined) { return Promise.resolve(undefined); @@ -121,7 +121,8 @@ export class Search extends Subscriber { new Requests.Search.NextNested.Request({ session: this._uuid, filter, - from: this.state().nested().nextPos(), + from: rev ? this.state().nested().prevPos() : this.state().nested().nextPos(), + rev, }), ) .then((response) => { diff --git a/application/client/src/app/service/session/dependencies/search/state.ts b/application/client/src/app/service/session/dependencies/search/state.ts index 3037ae6d4..d6ecd808e 100644 --- a/application/client/src/app/service/session/dependencies/search/state.ts +++ b/application/client/src/app/service/session/dependencies/search/state.ts @@ -4,7 +4,6 @@ import { Search } from '@service/session/dependencies/search'; import { unique } from '@platform/env/sequence'; import * as obj from '@platform/env/obj'; -import { FilterRequest } from './filters/request'; export interface ISearchFinishEvent { found: number; @@ -119,18 +118,21 @@ export class State { } public nested(): { + accept(action: Promise<[number, number] | undefined>): Promise; next(): Promise; prev(): Promise; set(filter: IFilter): Promise; nextPos(): number; + prevPos(): number; get(): IFilter | undefined; drop(): void; } { return { - next: (): Promise => { + accept: ( + action: Promise<[number, number] | undefined>, + ): Promise => { return new Promise((resolve, reject) => { - this._controller - .searchNestedMatch() + action .then((pos: [number, number] | undefined) => { if (pos === undefined) { this._nested.from = -1; @@ -148,8 +150,11 @@ export class State { }); }); }, + next: (): Promise => { + return this.nested().accept(this._controller.searchNestedMatch(false)); + }, prev: (): Promise => { - return Promise.resolve(0); + return this.nested().accept(this._controller.searchNestedMatch(true)); }, set: (filter: IFilter): Promise => { this._nested.filter = obj.clone(filter); @@ -163,6 +168,13 @@ export class State { return this._nested.from + 1; } }, + prevPos: (): number => { + if (this._nested.from <= 0) { + return this._controller.len() - 1; + } else { + return this._nested.from - 1; + } + }, get: (): IFilter | undefined => { return this._nested.filter; }, diff --git a/application/client/src/app/ui/views/toolbar/search/nested/component.ts b/application/client/src/app/ui/views/toolbar/search/nested/component.ts index 7e51883e3..262dd8232 100644 --- a/application/client/src/app/ui/views/toolbar/search/nested/component.ts +++ b/application/client/src/app/ui/views/toolbar/search/nested/component.ts @@ -15,9 +15,6 @@ import { Ilc, IlcInterface } from '@env/decorators/component'; import { SearchInput } from '../input/input'; import { List } from '@env/storages/recent/list'; import { ChangesDetector } from '@ui/env/extentions/changes'; -import { ISearchFinishEvent } from '@service/session/dependencies/search/state'; -import { Notification } from '@ui/service/notifications'; -import { IFilter } from '@platform/types/filter'; import { Owner } from '@schema/content/row'; @Component({ diff --git a/application/holder/src/service/sessions/requests/search/next_nested.ts b/application/holder/src/service/sessions/requests/search/next_nested.ts index c4f9e67a7..145e3a707 100644 --- a/application/holder/src/service/sessions/requests/search/next_nested.ts +++ b/application/holder/src/service/sessions/requests/search/next_nested.ts @@ -1,5 +1,5 @@ import { CancelablePromise } from 'platform/env/promise'; -import { sessions, Jobs } from '@service/sessions'; +import { sessions } from '@service/sessions'; import { Logger } from 'platform/log'; import { ICancelablePromise } from 'platform/env/promise'; @@ -24,7 +24,7 @@ export const handler = Requests.InjectLogger< } stored.session .getSearch() - .searchNestedMatch(request.filter, request.from) + .searchNestedMatch(request.filter, request.from, request.rev) .then((pos: [number, number] | undefined) => { resolve( new Requests.Search.NextNested.Response({ diff --git a/application/platform/ipc/request/search/next_nested.ts b/application/platform/ipc/request/search/next_nested.ts index 06c01c2a8..c15908477 100644 --- a/application/platform/ipc/request/search/next_nested.ts +++ b/application/platform/ipc/request/search/next_nested.ts @@ -8,13 +8,15 @@ export class Request extends SignatureRequirement { public session: string; public filter: IFilter; public from: number; + public rev: boolean; - constructor(input: { session: string; filter: IFilter; from: number }) { + constructor(input: { session: string; filter: IFilter; from: number; rev: boolean }) { super(); validator.isObject(input); this.session = validator.getAsNotEmptyString(input, 'session'); this.filter = validator.getAsObj(input, 'filter'); this.from = validator.getAsValidNumber(input, 'from'); + this.rev = validator.getAsBool(input, 'rev'); } }