Skip to content

Commit

Permalink
search-in-workspace: focus on next and previous search results (#12703)
Browse files Browse the repository at this point in the history
  • Loading branch information
vladarama authored Feb 8, 2024
1 parent 913eb3e commit b7ed5e9
Show file tree
Hide file tree
Showing 5 changed files with 444 additions and 0 deletions.
109 changes: 109 additions & 0 deletions packages/core/src/browser/tree/test/mock-selectable-tree-model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// *****************************************************************************
// Copyright (C) 2023 Ericsson and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { CompositeTreeNode } from '../tree';
import { SelectableTreeNode } from '../tree-selection';
import { ExpandableTreeNode } from '../tree-expansion';

export namespace MockSelectableTreeModel {

export interface SelectableNode {
readonly id: string;
readonly selected: boolean;
readonly focused?: boolean;
readonly children?: SelectableNode[];
}

export namespace SelectableNode {
export function toTreeNode(root: SelectableNode, parent?: SelectableTreeNode & CompositeTreeNode): SelectableTreeNode {
const { id } = root;
const name = id;
const selected = false;
const focus = false;
const expanded = true;
const node: CompositeTreeNode & SelectableTreeNode = {
id,
name,
selected,
focus,
parent: parent,
children: []
};
const children = (root.children || []).map(child => SelectableNode.toTreeNode(child, node));
if (children.length === 0) {
return node;
} else {
node.children = children;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(node as any).expanded = expanded;
return node as CompositeTreeNode & SelectableTreeNode & ExpandableTreeNode;
}
}
}

export const HIERARCHICAL_MOCK_ROOT = () => SelectableNode.toTreeNode({
'id': '1',
'selected': false,
'children': [
{
'id': '1.1',
'selected': false,
'children': [
{
'id': '1.1.1',
'selected': false,
},
{
'id': '1.1.2',
'selected': false,
}
]
},
{
'id': '1.2',
'selected': false,
'children': [
{
'id': '1.2.1',
'selected': false,
'children': [
{
'id': '1.2.1.1',
'selected': false,
},
{
'id': '1.2.1.2',
'selected': false,
}
]
},
{
'id': '1.2.2',
'selected': false,
},
{
'id': '1.2.3',
'selected': false,
}
]
},
{
'id': '1.3',
'selected': false,
}
]
});
}
74 changes: 74 additions & 0 deletions packages/core/src/browser/tree/tree-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,21 +99,41 @@ export interface TreeModel extends Tree, TreeSelectionService, TreeExpansionServ
*/
navigateBackward(): Promise<void>;

/**
* Selects the previous tree node, regardless of its selection or visibility state.
*/
selectPrev(): void;

/**
* Selects the previous node relatively to the currently selected one. This method takes the expansion state of the tree into consideration.
*/
selectPrevNode(type?: TreeSelection.SelectionType): void;

/**
* Returns the previous tree node, regardless of its selection or visibility state.
*/
getPrevNode(node?: TreeNode): TreeNode | undefined;

/**
* Returns the previous selectable tree node.
*/
getPrevSelectableNode(node?: TreeNode): SelectableTreeNode | undefined;

/**
* Selects the next tree node, regardless of its selection or visibility state.
*/
selectNext(): void;

/**
* Selects the next node relatively to the currently selected one. This method takes the expansion state of the tree into consideration.
*/
selectNextNode(type?: TreeSelection.SelectionType): void;

/**
* Returns the next tree node, regardless of its selection or visibility state.
*/
getNextNode(node?: TreeNode): TreeNode | undefined;

/**
* Returns the next selectable tree node.
*/
Expand Down Expand Up @@ -294,13 +314,23 @@ export class TreeModelImpl implements TreeModel, SelectionProvider<ReadonlyArray
}
}

selectPrev(): void {
const node = this.getPrevNode();
this.selectNodeIfSelectable(node);
}

selectPrevNode(type: TreeSelection.SelectionType = TreeSelection.SelectionType.DEFAULT): void {
const node = this.getPrevSelectableNode();
if (node) {
this.addSelection({ node, type });
}
}

getPrevNode(node: TreeNode | undefined = this.getFocusedNode()): TreeNode | undefined {
const iterator = this.createBackwardTreeIterator(node);
return iterator && this.doGetNode(iterator);
}

getPrevSelectableNode(node: TreeNode | undefined = this.getFocusedNode()): SelectableTreeNode | undefined {
if (!node) {
return this.getNextSelectableNode(this.root);
Expand All @@ -309,18 +339,40 @@ export class TreeModelImpl implements TreeModel, SelectionProvider<ReadonlyArray
return iterator && this.doGetNextNode(iterator, this.isVisibleSelectableNode.bind(this));
}

selectNext(): void {
const node = this.getNextNode();
this.selectNodeIfSelectable(node);
}

selectNextNode(type: TreeSelection.SelectionType = TreeSelection.SelectionType.DEFAULT): void {
const node = this.getNextSelectableNode();
if (node) {
this.addSelection({ node, type });
}
}

getNextNode(node: TreeNode | undefined = this.getFocusedNode()): TreeNode | undefined {
const iterator = this.createTreeIterator(node);
return iterator && this.doGetNode(iterator);
}

getNextSelectableNode(node: TreeNode | undefined = this.getFocusedNode() ?? this.root): SelectableTreeNode | undefined {
const iterator = this.createIterator(node);
return iterator && this.doGetNextNode(iterator, this.isVisibleSelectableNode.bind(this));
}

protected selectNodeIfSelectable(node: TreeNode | undefined): void {
if (SelectableTreeNode.is(node)) {
this.addSelection(node);
}
}

protected doGetNode(iterator: TreeIterator): TreeNode | undefined {
iterator.next();
const result = iterator.next();
return result.done ? undefined : result.value;
}

protected doGetNextNode<T extends TreeNode>(iterator: TreeIterator, criterion: (node: TreeNode) => node is T): T | undefined {
// Skip the first item. // TODO: clean this up, and skip the first item in a different way without loading everything.
iterator.next();
Expand All @@ -338,6 +390,17 @@ export class TreeModelImpl implements TreeModel, SelectionProvider<ReadonlyArray
return SelectableTreeNode.isVisible(node);
}

protected createBackwardTreeIterator(node: TreeNode | undefined): TreeIterator | undefined {
const { filteredNodes } = this.treeSearch;
if (filteredNodes.length === 0) {
return node ? new BottomUpTreeIterator(node!, { pruneCollapsed: false }) : undefined;
}
if (node && filteredNodes.indexOf(node) === -1) {
return undefined;
}
return Iterators.cycle(filteredNodes.slice().reverse(), node);
}

protected createBackwardIterator(node: TreeNode | undefined): TreeIterator | undefined {
const { filteredNodes } = this.treeSearch;
if (filteredNodes.length === 0) {
Expand All @@ -349,6 +412,17 @@ export class TreeModelImpl implements TreeModel, SelectionProvider<ReadonlyArray
return Iterators.cycle(filteredNodes.slice().reverse(), node);
}

protected createTreeIterator(node: TreeNode | undefined): TreeIterator | undefined {
const { filteredNodes } = this.treeSearch;
if (filteredNodes.length === 0) {
return node && new TopDownTreeIterator(node, { pruneCollapsed: false });
}
if (node && filteredNodes.indexOf(node) === -1) {
return undefined;
}
return Iterators.cycle(filteredNodes, node);
}

protected createIterator(node: TreeNode | undefined): TreeIterator | undefined {
const { filteredNodes } = this.treeSearch;
if (filteredNodes.length === 0) {
Expand Down
Loading

0 comments on commit b7ed5e9

Please sign in to comment.