diff --git a/src/ansi-shell/readline/history.js b/src/ansi-shell/readline/history.js new file mode 100644 index 0000000..f04b3a3 --- /dev/null +++ b/src/ansi-shell/readline/history.js @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Phoenix Shell. + * + * Phoenix Shell is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +export class HistoryManager { + constructor({ enableLogging = false } = {}) { + this.items = []; + this.index_ = 0; + this.listeners_ = {}; + this.enableLogging_ = enableLogging; + } + + log(...a) { + // TODO: Command line option for configuring logging + if ( this.enableLogging_ ) { + console.log('[HistoryManager]', ...a); + } + } + + get index() { + return this.index_; + } + + set index(v) { + this.log('setting index', v); + this.index_ = v; + } + + get() { + return this.items[this.index]; + } + + // Save, overwriting the current history item + save(data, { opt_debug } = {}) { + this.log('saving', data, 'at', this.index, + ...(opt_debug ? [ 'from', opt_debug ] : [])); + this.items[this.index] = data; + + if (this.listeners_.hasOwnProperty('add')) { + for (const listener of this.listeners_.add) { + listener(data); + } + } + } + + append(data) { + if ( + this.items.length !== 0 && + this.index !== this.items.length + ) { + this.log('POP'); + // remove last item + this.items.pop(); + } + this.index = this.items.length; + this.save(data, { opt_debug: 'append' }); + this.index++; + } + + on(topic, listener) { + if (!this.listeners_.hasOwnProperty(topic)) { + this.listeners_[topic] = []; + } + this.listeners_[topic].push(listener); + } +} \ No newline at end of file diff --git a/src/ansi-shell/readline/readline.js b/src/ansi-shell/readline/readline.js index a4fe39d..2ac42c7 100644 --- a/src/ansi-shell/readline/readline.js +++ b/src/ansi-shell/readline/readline.js @@ -16,15 +16,16 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { Context } from "../../context/context.js"; -import { CommandCompleter } from "../../puter-shell/completers/command_completer.js"; -import { FileCompleter } from "../../puter-shell/completers/file_completer.js"; +import { Context } from '../../context/context.js'; +import { CommandCompleter } from '../../puter-shell/completers/command_completer.js'; +import { FileCompleter } from '../../puter-shell/completers/file_completer.js'; import { OptionCompleter } from '../../puter-shell/completers/option_completer.js'; -import { Uint8List } from "../../util/bytes.js"; -import { StatefulProcessorBuilder } from "../../util/statemachine.js"; -import { ANSIContext } from "../ANSIContext.js"; -import { readline_comprehend } from "./rl_comprehend.js"; -import { CSI_HANDLERS } from "./rl_csi_handlers.js"; +import { Uint8List } from '../../util/bytes.js'; +import { StatefulProcessorBuilder } from '../../util/statemachine.js'; +import { ANSIContext } from '../ANSIContext.js'; +import { readline_comprehend } from './rl_comprehend.js'; +import { CSI_HANDLERS } from './rl_csi_handlers.js'; +import { HistoryManager } from './history.js'; const decoder = new TextDecoder(); @@ -71,6 +72,13 @@ const ReadlineProcessorBuilder = builder => builder if ( locals.byte === consts.CHAR_ETX ) { externs.out.write('^C\n'); + // Exit if input line is empty + // FIXME: Check for 'process' is so we only do this on Node. How should we handle exiting in Puter terminal? + if ( process && ctx.vars.result.length === 0 ) { + process.exit(1); + return; + } + // Otherwise clear it ctx.vars.result = ''; ctx.setState('end'); return; @@ -315,51 +323,6 @@ const ReadlineProcessor = ReadlineProcessorBuilder( new StatefulProcessorBuilder() ); -class HistoryManager { - constructor () { - this.items = []; - this.index_ = 0; - this.listeners_ = {}; - } - - log (...a) { - // TODO: proper logging and verbosity config - // console.log('[HistoryManager]', ...a); - } - - get index () { - return this.index_; - } - - set index (v) { - this.log('setting index', v); - this.index_ = v; - } - - get () { - return this.items[this.index]; - } - - save (data, { opt_debug } = {}) { - this.log('saving', data, 'at', this.index, - ...(opt_debug ? ['from', opt_debug] : [])); - this.items[this.index] = data; - - if ( this.listeners_.hasOwnProperty('add') ) { - for ( const listener of this.listeners_.add ) { - listener(data); - } - } - } - - on (topic, listener) { - if ( ! this.listeners_.hasOwnProperty(topic) ) { - this.listeners_[topic] = []; - } - this.listeners_[topic].push(listener); - } -} - class Readline { constructor (params) { this.internal_ = {}; @@ -383,21 +346,8 @@ class Readline { commandCtx, }); - // TODO: this condition, redundant to the one in ANSIShell, - // is an indication that HistoryManager if ( result.trim() !== '' ) { - // console.log('[HistoryManager] len?', this.history.items.length); - if ( - this.history.items.length !== 0 && - this.history.index !== this.history.items.length - ) { - // console.log('[HistoryManager] POP'); - // remove last item - this.history.items.pop(); - } - this.history.index = this.history.items.length; - this.history.save(result, { opt_debug: 'post-readline' }); - this.history.index++; + this.history.append(result); } return result; diff --git a/src/pty/NodeStdioPTT.js b/src/pty/NodeStdioPTT.js index e0f5855..58cd947 100644 --- a/src/pty/NodeStdioPTT.js +++ b/src/pty/NodeStdioPTT.js @@ -29,10 +29,6 @@ export class NodeStdioPTT { process.stdin.on('data', chunk => { const input = new Uint8Array(chunk); readController.enqueue(input); - if (input.length === 1 && (input[0] === signals.SIGINT || input[0] === signals.SIGQUIT)) { - process.exit(1); - return; - } }); this.out = writestream_node_to_web(process.stdout);