Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

search-in-workspace: focus on next and previous search results #12703

Merged
merged 12 commits into from
Feb 8, 2024
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
Loading