• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2018 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import * as m from 'mithril';
16
17import {Actions} from '../common/actions';
18import {QueryResponse} from '../common/queries';
19import {EngineConfig} from '../common/state';
20
21import {globals} from './globals';
22
23const QUERY_ID = 'quicksearch';
24
25let selResult = 0;
26let numResults = 0;
27let mode: 'search'|'command' = 'search';
28let omniboxValue = '';
29
30function clearOmniboxResults() {
31  globals.queryResults.delete(QUERY_ID);
32  globals.dispatch(Actions.deleteQuery({queryId: QUERY_ID}));
33}
34
35function onKeyDown(e: Event) {
36  e.stopPropagation();
37  const key = (e as KeyboardEvent).key;
38
39  // Avoid that the global 'a', 'd', 'w', 's' handler sees these keystrokes.
40  // TODO: this seems a bug in the pan_and_zoom_handler.ts.
41  if (key === 'ArrowUp' || key === 'ArrowDown') {
42    e.preventDefault();
43    return;
44  }
45  const txt = (e.target as HTMLInputElement);
46  omniboxValue = txt.value;
47  if (key === ':' && txt.value === '') {
48    mode = 'command';
49    globals.rafScheduler.scheduleFullRedraw();
50    e.preventDefault();
51    return;
52  }
53  if (key === 'Escape' && mode === 'command') {
54    txt.value = '';
55    mode = 'search';
56    globals.rafScheduler.scheduleFullRedraw();
57    return;
58  }
59  if (key === 'Backspace' && txt.value.length === 0 && mode === 'command') {
60    mode = 'search';
61    globals.rafScheduler.scheduleFullRedraw();
62    return;
63  }
64}
65
66function onKeyUp(e: Event) {
67  e.stopPropagation();
68  const key = (e as KeyboardEvent).key;
69  const txt = e.target as HTMLInputElement;
70  omniboxValue = txt.value;
71  if (key === 'ArrowUp' || key === 'ArrowDown') {
72    selResult += (key === 'ArrowUp') ? -1 : 1;
73    selResult = Math.max(selResult, 0);
74    selResult = Math.min(selResult, numResults - 1);
75    e.preventDefault();
76    globals.rafScheduler.scheduleFullRedraw();
77    return;
78  }
79  if (txt.value.length <= 0 || key === 'Escape') {
80    clearOmniboxResults();
81    globals.rafScheduler.scheduleFullRedraw();
82    return;
83  }
84  if (mode === 'search') {
85    const name = txt.value.replace(/'/g, '\\\'').replace(/[*]/g, '%');
86    const query = `select str from strings where str like '%${name}%' limit 10`;
87    globals.dispatch(
88        Actions.executeQuery({engineId: '0', queryId: QUERY_ID, query}));
89  }
90  if (mode === 'command' && key === 'Enter') {
91    globals.dispatch(Actions.executeQuery(
92        {engineId: '0', queryId: 'command', query: txt.value}));
93  }
94}
95
96
97class Omnibox implements m.ClassComponent {
98  oncreate(vnode: m.VnodeDOM) {
99    const txt = vnode.dom.querySelector('input') as HTMLInputElement;
100    txt.addEventListener('blur', clearOmniboxResults);
101    txt.addEventListener('keydown', onKeyDown);
102    txt.addEventListener('keyup', onKeyUp);
103  }
104
105  view() {
106    const msgTTL = globals.state.status.timestamp + 1 - Date.now() / 1e3;
107    let enginesAreBusy = false;
108    for (const engine of Object.values(globals.state.engines)) {
109      enginesAreBusy = enginesAreBusy || !engine.ready;
110    }
111
112    if (msgTTL > 0 || enginesAreBusy) {
113      setTimeout(
114          () => globals.rafScheduler.scheduleFullRedraw(), msgTTL * 1000);
115      return m(
116          `.omnibox.message-mode`,
117          m(`input[placeholder=${globals.state.status.msg}][readonly]`, {
118            value: '',
119          }));
120    }
121
122    // TODO(primiano): handle query results here.
123    const results = [];
124    const resp = globals.queryResults.get(QUERY_ID) as QueryResponse;
125    if (resp !== undefined) {
126      numResults = resp.rows ? resp.rows.length : 0;
127      for (let i = 0; i < resp.rows.length; i++) {
128        const clazz = (i === selResult) ? '.selected' : '';
129        results.push(m(`div${clazz}`, resp.rows[i][resp.columns[0]]));
130      }
131    }
132    const placeholder = {
133      search: 'Search or type : to enter command mode',
134      command: 'e.g., select * from sched left join thread using(utid) limit 10'
135    };
136
137    const commandMode = mode === 'command';
138    return m(
139        `.omnibox${commandMode ? '.command-mode' : ''}`,
140        m(`input[placeholder=${placeholder[mode]}]`, {
141          onchange: m.withAttr('value', v => omniboxValue = v),
142          value: omniboxValue,
143        }),
144        m('.omnibox-results', results));
145  }
146}
147
148export class Topbar implements m.ClassComponent {
149  view() {
150    const progBar = [];
151    const engine: EngineConfig = globals.state.engines['0'];
152    if (globals.state.queries[QUERY_ID] !== undefined ||
153        (engine !== undefined && !engine.ready)) {
154      progBar.push(m('.progress'));
155    }
156    return m('.topbar', m(Omnibox), ...progBar);
157  }
158}
159