search(query) {
        let { matcher, matchProcessor } = this.config.search;

        // Don't search if query empty
        if (!query || (isString(query) && isEmpty(query))) {
            return Promise.resolve(this.clearSearch());
        }

        this.batch();

        // Reset states
        this.recurseDown(node => {
            node.state('hidden', true);
            node.state('matched', false);
        });

        this.end();

        // Query nodes for any matching the query
        matcher = isFunction(matcher) ? matcher : (matchQuery, resolve) => {
            const matches = new TreeNodes(this);

            // Convery the query into a usable predicate
            if (isString(matchQuery)) {
                matchQuery = new RegExp(matchQuery, 'i');
            }

            let predicate;
            if (isRegExp(matchQuery)) {
                predicate = node => matchQuery.test(node.text);
            }
            else {
                predicate = matchQuery;
            }

            // Recurse down and find all matches
            this.model.recurseDown(node => {
                if (!node.removed()) {
                    if (predicate(node)) {
                        // Return as a match
                        matches.push(node);
                    }
                }
            });

            resolve(matches);
        };

        // Process all matching nodes.
        matchProcessor = isFunction(matchProcessor) ? matchProcessor : matches => {
            matches.each(node => {
                node.show().state('matched', true);

                node.expandParents().collapse();

                if (node.hasChildren()) {
                    node.children.showDeep();
                }
            });
        };

        // Wrap the search matcher with a promise since it could require async requests
        return new Promise((resolve, reject) => {
            // Execute the matcher and pipe results to the processor
            matcher(query, matches => {
                // Convert to a TreeNodes array if we're receiving external nodes
                if (!InspireTree.isTreeNodes(matches)) {
                    matches = this.nodes(_map(matches, 'id'));
                }

                this.batch();

                matchProcessor(matches);

                if (this.config.selection.require) {
                    const result = matches.find(node => node.available() && node.selectable());
                    if (result) {
                        result.select();
                    }
                }

                this.end();

                resolve(matches);
            }, reject);
        });
    }
Search nodes, showing only those that match and the necessary hierarchy.

@param {*} query Search string, RegExp, or function.
@return {Promise<TreeNodes>} Promise resolved with an array of matching node objects.