1// Copyright 2024 The Pigweed Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); you may not 4// use this file except in compliance with the License. You may obtain a copy of 5// the License at 6// 7// https://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, WITHOUT 11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12// License for the specific language governing permissions and limitations under 13// the License. 14 15import { LitElement, html, PropertyValues } from 'lit'; 16import { customElement, query, state } from 'lit/decorators.js'; 17import { EvalOutput, ReplKernel } from '../../repl-kernel'; 18import './code-editor'; 19import { CodeEditor } from './code-editor'; 20import { styles } from './repl.styles'; 21import { themeDark } from '../../themes/dark'; 22import { themeLight } from '../../themes/light'; 23 24@customElement('repl-component') 25export class Repl extends LitElement { 26 static styles = [themeDark, themeLight, styles]; 27 private codeEditor: CodeEditor; 28 private evalResults: EvalOutput[] = []; 29 private readonly LOCALSTORAGE_KEY = 'replHistory'; 30 31 // prettier-ignore 32 private welcomeMsg = html`Welcome to the Pigweed Web Console! 33 34 Input keyboard shortcuts: 35 Press <kbd>Enter</kbd> to run 36 Press <kbd>Shift</kbd> + <kbd>Enter</kbd> to create multi-line commands 37 Press <kbd>Up</kbd> or <kbd>Down</kbd> to navigate command history 38 39 Example Python commands: 40 device.rpcs.pw.rpc.EchoService.Echo(msg='hello!') 41 LOG.warning('Message appears in Host Logs window.') 42 DEVICE_LOG.warning('Message appears in Device Logs window.')`; 43 44 @query('#output') _output!: HTMLElement; 45 46 @state() 47 replTitle: string; 48 49 constructor( 50 private replKernel: ReplKernel, 51 title = 'REPL', 52 ) { 53 super(); 54 this.replTitle = title; 55 56 this.evalResults.push({ result: this.welcomeMsg }); 57 this.codeEditor = new CodeEditor( 58 '', 59 this.replKernel.autocomplete.bind(this.replKernel), 60 async (code: string) => { 61 const output = await this.replKernel.eval(code); 62 this.evalResults.push({ stdin: code, ...output }); 63 this.requestUpdate(); 64 }, 65 ); 66 } 67 68 firstUpdated() { 69 // Append the code-editor instance to the shadow DOM 70 this.shadowRoot!.querySelector('#editor')!.appendChild(this.codeEditor); 71 } 72 73 protected updated(_changedProperties: PropertyValues): void { 74 if (this._output.scrollHeight > this._output.clientHeight) { 75 this._output.scrollTop = this._output.scrollHeight; 76 } 77 } 78 79 /** Remove all results from output */ 80 private clearOutput() { 81 this.evalResults = []; 82 this.requestUpdate(); 83 } 84 85 render() { 86 return html` 87 <div id="repl"> 88 <div class="header"> 89 ${this.replTitle} 90 <span class="actions-container"> 91 <md-icon-button 92 @click=${this.clearOutput} 93 title="Clear Repl output" 94 > 95 <md-icon></md-icon> 96 </md-icon-button> 97 <md-icon-button 98 href="https://pigweed.dev/pw_web/repl.html#python-shell" 99 title="Go to the python shell documentation page" 100 > 101 <md-icon></md-icon> 102 </md-icon-button> 103 </span> 104 </div> 105 <ul id="output"> 106 ${this.evalResults.map( 107 (result) => html` 108 <li> 109 ${!result.stderr && 110 !result.stdout && 111 !result.result && 112 !result.stdin 113 ? html`<span class="stdout"><i>No output</i></span>` 114 : ''} 115 <span class="stdin">${result.stdin}</span> 116 <span class="stderr">${result.stderr}</span> 117 <span class="stdout">${result.stdout}</span> 118 <span class="stdout">${result.result}</span> 119 </li> 120 `, 121 )} 122 </ul> 123 <div id="editor"></div> 124 </div> 125 `; 126 } 127} 128