All files / src/ui/searchers ByString.js

6.35% Statements 4/63
0% Branches 0/39
0% Functions 0/13
7.41% Lines 4/54

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 1651x 1x 1x                                                                                                                       1x                                                                                                                                                                                                            
import {Component, createRef} from 'react';
import PropTypes from 'prop-types/prop-types';
import {skipCollapsed, poscmp, skipWhile, getNodeContainingBiased } from '../../utils';
 
/**
 * Returns a query from settings. If the query is a regex but is invalid (indicating
 * that users are still in the middle of writing regex),
 * returns an always failing regex instead.
 */
function getQueryFromSettings(state) {
  let query = state.searchString;
  let isRegex = state.isRegex;
  let isExactMatch = state.isExactMatch;
  if (isExactMatch && !isRegex) {
    query = query.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
    isRegex = true;
  }
  if (!isRegex) return query;
  if (isExactMatch) query = '\\b' + query + '\\b';
  try {
    return new RegExp(query);
  } catch (e) {
    return /$^/;
  }
}
 
class SearchOption extends Component {
  static propTypes = {
    onChange: PropTypes.func.isRequired,
    setting: PropTypes.object.isRequired,
    name: PropTypes.string.isRequired,
    value: PropTypes.string.isRequired,
  }
  render() {
    const {name, value, setting, onChange} = this.props;
    return (
      <label>
        <input type="checkbox" name={name} onChange={onChange}
               checked={setting[name]} />
        {value}
      </label>
    );
  }
}
 
// function getResults(settings, cm, {ast, collapsedList}, limit=Infinity) {
//   const query = getQueryFromSettings(settings);
//   const collapsedNodeList = collapsedList.map(ast.getNodeById);
//   const searchCursor = cm.getSearchCursor(
//     query, null, {caseFold: settings.isIgnoreCase}
//   );
//   const searchMatches = [];
//   while (searchCursor.findNext() && searchMatches.length < limit) {
//     const node = ast.getNodeContaining(searchCursor.from());
//     // make sure we're not just matching a comment
//     if (node && !collapsedNodeList.some(collapsed => ast.isAncestor(collapsed.id, node.id))) {
//       searchMatches.push(node);
//     }
//   }
//   return searchMatches;
// }
 
export default {
  label: 'Search by string',
  setting: {
    searchString: '',
    isRegex: false,
    isExactMatch: false,
    isIgnoreCase: false
  },
  component: class extends Component {
    static propTypes = {
      setting: PropTypes.object.isRequired,
      onChange: PropTypes.func.isRequired,
      firstTime: PropTypes.bool
    }
 
    displayName = 'Search by String'
 
    constructor(props) {
      super(props);
      this.inputRef = createRef();
    }
 
    componentDidMount() {
      if(this.props.firstTime) this.inputRef.current.select();
    }
 
    handleChange = e => {
      this.props.onChange({
        ...this.props.setting,
        [e.target.name]: e.target.type === 'checkbox' ? e.target.checked : e.target.value,
      });
    }
 
    render() {
      const {setting} = this.props;
      return (
        <>
          <div className="form-group">
            <input type="text" className="form-control search-input"
                   name="searchString"
                   ref={this.inputRef}
                   aria-label="Search String"
                   onChange={this.handleChange}
                   value={setting.searchString}
                   spellCheck="false"/>
          </div>
          <div className="search-options">
            <SearchOption setting={setting} onChange={this.handleChange}
                          name="isIgnoreCase" value="Ignore case" />
            <SearchOption setting={setting} onChange={this.handleChange}
                          name="isExactMatch" value="Exact match" />
            <SearchOption setting={setting} onChange={this.handleChange}
                          name="isRegex" value="Regex" />
          </div>
        </>
      );
    }
  },
  search: (cur, settings, cm, state, forward) => {
    const {ast, collapsedList} = state;
    const collapsedNodeList = collapsedList.map(ast.getNodeById);
 
    if (settings.searchString === '') {
      let node = getNodeContainingBiased(cur, ast);
      if (node) {
        node = skipCollapsed(node, node => forward ? node.next : node.prev, state);
        if (node) return {node, cursor: node.from};
        return null;
      }
      node = forward ? ast.getNodeAfterCur(cur) : ast.getNodeBeforeCur(cur);
      if (node) return {node, cursor: node.from};
      return null;
    }
 
    const options = {caseFold: settings.isIgnoreCase};
    const query = getQueryFromSettings(settings);
 
    function next(searchCursor) {
      const result = searchCursor.find(!forward);
      if (result) return searchCursor;
      return null;
    }
 
    let searchCursor = next(cm.getSearchCursor(query, cur, options));
    if (forward && searchCursor && poscmp(searchCursor.from(), cur) === 0) {
      searchCursor = next(searchCursor);
    }
    const newSearchCur = skipWhile(searchCur => {
      if (!searchCur) return false;
      const node = getNodeContainingBiased(searchCur.from(), ast);
      return !!node && collapsedNodeList.some(
        collapsed => ast.isAncestor(collapsed.id, node.id)
      );
    }, searchCursor, next);
    if (newSearchCur) {
      const node = getNodeContainingBiased(newSearchCur.from(), ast);
      if (node) return {node, cursor: newSearchCur.from()};
    }
    return null;
  }
};