import React from "react"; import { observer } from "mobx-react"; import { action, makeObservable, observable } from "mobx"; import { Splitter } from "eez-studio-ui/splitter"; import { IconAction } from "eez-studio-ui/action"; import * as notification from "eez-studio-ui/notification"; import type { InstrumentAppStore } from "instrument/window/app-store"; import { executeShortcut } from "instrument/window/script"; import { HistoryView, HistoryTools } from "instrument/window/history/history-view"; import { ShortcutsToolbar } from "instrument/window/terminal/toolbar"; import { CommandsBrowser } from "instrument/window/terminal/commands-browser"; import { parseScpi, SCPI_PART_QUERY } from "eez-studio-shared/scpi-parser"; import { AppBar } from "instrument/window/app"; import { TerminalState } from "instrument/window/terminal/terminalState"; //////////////////////////////////////////////////////////////////////////////// const CONF_COMMANDS_HISTORY_SIZE = 1000; //////////////////////////////////////////////////////////////////////////////// const Input = observer( class Input extends React.Component<{ appStore: InstrumentAppStore; sendCommand: () => void; sendFileToInstrumentHandler: (() => void) | undefined; showDocumentation: boolean; terminalState: TerminalState; }> { input: HTMLInputElement; constructor(props: any) { super(props); const commandsHistoryJSON = window.localStorage.getItem( `instrument/${this.props.appStore.history.oid}/window/terminal/commands-history` ); if (commandsHistoryJSON) { try { this.commandsHistory = JSON.parse(commandsHistoryJSON); } catch (error) { console.error(error); } } } commandsHistory: string[] = []; historyItemIndex: number | undefined; moveCursorToTheEnd: boolean; componentDidMount() { this.input.focus(); } componentDidUpdate() { if (this.moveCursorToTheEnd) { this.input.selectionStart = this.input.selectionEnd = this.props.terminalState.command.length; this.moveCursorToTheEnd = false; } } handleHelpClick = () => { this.props.appStore.toggleHelpVisible(); }; handleChange = (event: any) => { this.props.terminalState.command = event.target.value; }; sendCommand() { if (this.props.terminalState.command) { this.commandsHistory.push(this.props.terminalState.command); if (this.commandsHistory.length > CONF_COMMANDS_HISTORY_SIZE) { this.commandsHistory.splice(0, 1); } window.localStorage.setItem( `instrument/${this.props.appStore.history.oid}/window/terminal/commands-history`, JSON.stringify(this.commandsHistory) ); this.historyItemIndex = undefined; this.props.sendCommand(); } } findPreviousCommand() { while (true) { let historyItemIndex = this.historyItemIndex; if (historyItemIndex === undefined) { historyItemIndex = this.commandsHistory.length - 1; } else { if (historyItemIndex >= 0) { historyItemIndex--; } } this.historyItemIndex = historyItemIndex; if ( this.historyItemIndex < 0 || this.historyItemIndex >= this.commandsHistory.length ) { return undefined; } return this.commandsHistory[this.historyItemIndex]; } } findNextCommand() { while (true) { let historyItemIndex = this.historyItemIndex; if (historyItemIndex === undefined) { return undefined; } else { if (historyItemIndex < this.commandsHistory.length) { historyItemIndex++; } } this.historyItemIndex = historyItemIndex; if ( this.historyItemIndex < 0 || this.historyItemIndex >= this.commandsHistory.length ) { return undefined; } return this.commandsHistory[this.historyItemIndex]; } } handleKeyDown = (event: any) => { if (event.key === "Enter") { event.preventDefault(); this.sendCommand(); } else if (event.key === "ArrowUp") { event.preventDefault(); this.props.terminalState.command = this.findPreviousCommand() || ""; this.moveCursorToTheEnd = true; } else if (event.key === "ArrowDown") { event.preventDefault(); this.props.terminalState.command = this.findNextCommand() || ""; this.moveCursorToTheEnd = true; } }; handleSendCommandClick = () => { this.sendCommand(); }; render() { return (
{this.props.showDocumentation && ( )}
(this.input = ref)} className="mousetrap form-control" type="text" onKeyDown={this.handleKeyDown} value={this.props.terminalState.command} onChange={this.handleChange} disabled={ !this.props.appStore.instrument.connection .isConnected } />
{this.props.appStore.instrument.commandsProtocol != "SCPI" && (
)}
); } } ); export class TerminalComponent extends React.Component<{ appStore: InstrumentAppStore; showConnectionStatusBar: boolean; showShortcuts: boolean; showHelp: boolean; showSideBar: boolean; }> { inFocus: boolean = false; terminalState: TerminalState; constructor(props: any) { super(props); makeObservable(this, { inFocus: observable }); this.terminalState = new TerminalState(this.props.appStore.instrument); } onSelectHistoryItemsCancel = () => { this.props.appStore.selectHistoryItems(undefined); }; componentDidMount() { this.props.appStore.terminal = this; } componentWillUnmount() { this.props.appStore.terminal = null; } render() { const { appStore } = this.props; const instrument = appStore.instrument; const input = ( { try { await instrument.connection.acquire(true); let hasQuery = false; if (instrument.commandsProtocol == "SCPI") { try { const parts = parseScpi(this.terminalState.command); hasQuery = !!parts.find( part => part.tag == SCPI_PART_QUERY ); } catch (err) {} } const command = this.terminalState.command; this.terminalState.command = ""; if (hasQuery) { await instrument.connection.query(command); } else { await instrument.connection.send(command); } } catch (err) { notification.error(err.toString()); } finally { instrument.connection.release(); } }} sendFileToInstrumentHandler={ instrument.sendFileToInstrumentHandler } showDocumentation={this.props.showHelp} terminalState={this.terminalState} /> ); let history = (
); const terminal = (
{ this.inFocus = true; })} onBlur={action(() => { this.inFocus = false; })} > {this.props.showConnectionStatusBar && ( )} {history} {input} {this.props.showShortcuts && ( { executeShortcut(this.props.appStore, shortcut); }} /> )}
); if (!this.props.showHelp || !appStore.helpVisible) { return terminal; } return ( {terminal} ); } } export const Terminal = observer(TerminalComponent); export function render(appStore: InstrumentAppStore) { return ( ); } export function renderToolbarButtons(appStore: InstrumentAppStore) { return ; }