1/* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import m from 'mithril'; 18import {globals} from './globals'; 19 20interface ArgumentPopupArgs { 21 onArgumentChange: (arg: string) => void; 22 knownArguments: string[]; 23} 24 25function longestString(array: string[]): string { 26 if (array.length === 0) { 27 return ''; 28 } 29 30 let answer = array[0]; 31 for (let i = 1; i < array.length; i++) { 32 if (array[i].length > answer.length) { 33 answer = array[i]; 34 } 35 } 36 return answer; 37} 38 39// Component rendering popup for entering an argument name to use as a pivot. 40export class ArgumentPopup implements m.ClassComponent<ArgumentPopupArgs> { 41 argument = ''; 42 43 setArgument(attrs: ArgumentPopupArgs, arg: string) { 44 this.argument = arg; 45 attrs.onArgumentChange(arg); 46 globals.rafScheduler.scheduleFullRedraw(); 47 } 48 49 renderMatches(attrs: ArgumentPopupArgs): m.Child[] { 50 const result: m.Child[] = []; 51 52 for (const option of attrs.knownArguments) { 53 // Would be great to have smarter fuzzy matching, but in the meantime 54 // simple substring check should work fine. 55 const index = option.indexOf(this.argument); 56 57 if (index === -1) { 58 continue; 59 } 60 61 if (result.length === 10) { 62 break; 63 } 64 65 result.push( 66 m('div', 67 { 68 onclick: () => { 69 this.setArgument(attrs, option); 70 }, 71 }, 72 option.substring(0, index), 73 // Highlight the matching part with bold font 74 m('strong', this.argument), 75 option.substring(index + this.argument.length))); 76 } 77 78 return result; 79 } 80 81 view({attrs}: m.Vnode<ArgumentPopupArgs>): m.Child { 82 return m( 83 '.name-completion', 84 m('input', { 85 oncreate: (vnode: m.VnodeDOM) => 86 (vnode.dom as HTMLInputElement).focus(), 87 oninput: (e: Event) => { 88 const input = e.target as HTMLInputElement; 89 this.setArgument(attrs, input.value); 90 }, 91 value: this.argument, 92 }), 93 m('.arguments-popup-sizer', longestString(attrs.knownArguments)), 94 this.renderMatches(attrs)); 95 } 96} 97