mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2025-04-02 11:02:17 -04:00
curated package predated the standard errors package introduced in go1.13 the standard package does a better job of what curated attempted to do the change of package also gave me a opportunity to clean up the error messages a little bit
184 lines
5.4 KiB
Go
184 lines
5.4 KiB
Go
// This file is part of Gopher2600.
|
|
//
|
|
// Gopher2600 is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Gopher2600 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 General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package sdlimgui
|
|
|
|
import (
|
|
"sync/atomic"
|
|
|
|
"github.com/jetsetilly/gopher2600/debugger/terminal"
|
|
"github.com/jetsetilly/gopher2600/logger"
|
|
)
|
|
|
|
type term struct {
|
|
// input from the terminal window
|
|
inputChan chan string
|
|
|
|
// input from other gui elements (eg. the run button in the control window)
|
|
// only one command can be serviced at a time, which may be inconvenient
|
|
sideChan chan string
|
|
|
|
// was last TermRead() from side channel
|
|
sideChanLast atomic.Value // bool
|
|
|
|
// output to the terminal window to present as a prompt
|
|
promptChan chan terminal.Prompt
|
|
|
|
// output to the terminal window to present in the main output window
|
|
outputChan chan terminalOutput
|
|
|
|
// the state of the last call to Silence()
|
|
silenced bool
|
|
|
|
// reference to tab completion. used by terminal window
|
|
tabCompletion terminal.TabCompletion
|
|
}
|
|
|
|
func newTerm() *term {
|
|
trm := &term{
|
|
// inputChan must not block
|
|
inputChan: make(chan string, 1),
|
|
|
|
// side-channel terminal input from other areas of the GUI. for
|
|
// example, we can have a menu item that writes "QUIT" to the side
|
|
// channel rather than calling a Quit() function directly.
|
|
//
|
|
// assigning a generous buffer. see pushCommand() for commentary.
|
|
sideChan: make(chan string, 10),
|
|
|
|
// promptChan must not block
|
|
promptChan: make(chan terminal.Prompt, 1),
|
|
|
|
// generous buffer for output channel
|
|
outputChan: make(chan terminalOutput, 4096),
|
|
}
|
|
trm.sideChanLast.Store(false)
|
|
return trm
|
|
}
|
|
|
|
// Initialise implements the terminal.Terminal interface.
|
|
func (trm *term) Initialise() error {
|
|
return nil
|
|
}
|
|
|
|
// CleanUp implements the terminal.Terminal interface.
|
|
func (trm *term) CleanUp() {
|
|
}
|
|
|
|
// RegisterTabCompletion implements the terminal.Terminal interface.
|
|
func (trm *term) RegisterTabCompletion(tc terminal.TabCompletion) {
|
|
trm.tabCompletion = tc
|
|
}
|
|
|
|
// Silence implements the terminal.Terminal interface.
|
|
func (trm *term) Silence(silenced bool) {
|
|
trm.silenced = silenced
|
|
}
|
|
|
|
// TermPrintLine implements the terminal.Output interface.
|
|
func (trm *term) TermPrintLine(style terminal.Style, s string) {
|
|
// do not print anything if last terminal event was from sidechannel
|
|
if trm.sideChanLast.Load().(bool) {
|
|
trm.sideChanLast.Store(false)
|
|
if style == terminal.StyleError || style == terminal.StyleLog {
|
|
logger.Log("term", s)
|
|
}
|
|
return
|
|
}
|
|
|
|
if trm.silenced && style != terminal.StyleError {
|
|
return
|
|
}
|
|
|
|
trm.outputChan <- terminalOutput{style: style, text: s}
|
|
}
|
|
|
|
// TermRead implements the terminal.Input interface.
|
|
func (trm *term) TermRead(buffer []byte, prompt terminal.Prompt, events *terminal.ReadEvents) (int, error) {
|
|
trm.promptChan <- prompt
|
|
|
|
// the debugger is waiting for input from the terminal but we still need to
|
|
// service gui events in the meantime.
|
|
for {
|
|
select {
|
|
case inp := <-trm.inputChan:
|
|
copy(buffer, inp+"\n")
|
|
return len(inp) + 1, nil
|
|
|
|
case inp := <-trm.sideChan:
|
|
copy(buffer, inp+"\n")
|
|
return len(inp) + 1, nil
|
|
|
|
case <-events.IntEvents:
|
|
return 0, terminal.UserInterrupt
|
|
|
|
case ev := <-events.PushedFunctions:
|
|
ev()
|
|
|
|
case ev := <-events.PushedFunctionsImmediate:
|
|
ev()
|
|
return 0, nil
|
|
|
|
case ev := <-events.UserInput:
|
|
err := events.UserInputHandler(ev)
|
|
if err != nil {
|
|
return 0, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TermRead implements the terminal.Input interface.
|
|
func (trm *term) TermReadCheck() bool {
|
|
// report on the number of pending items in inputChan and sideChan. if
|
|
// either of these have events waiting then that counts as true
|
|
return len(trm.inputChan) > 0 || len(trm.sideChan) > 0
|
|
}
|
|
|
|
// IsInteractive implements the terminal.Input interface.
|
|
func (trm *term) IsInteractive() bool {
|
|
return true
|
|
}
|
|
|
|
// IsRealTerminal implements the terminal.Input interface.
|
|
func (trm *term) IsRealTerminal() bool {
|
|
return false
|
|
}
|
|
|
|
// where possible the debugger issues commands via the terminal. this has the
|
|
// advntage of (a) simplicity and (b) consistency. A QUIT command, for example,
|
|
// will work in exactly the same way from the main manu or from the terminal.
|
|
//
|
|
// to achieve this functionality, the terminal has a side-channel to which a
|
|
// complete string is pushed (without a newline character please). the
|
|
// pushCommand() is a conveniently placed function to do this.
|
|
//
|
|
// ** do not to push commands if GUI is not in debug mode. there won't
|
|
// be anything to receive the input and so the channel will eventually
|
|
// fill up.
|
|
func (trm *term) pushCommand(input string) {
|
|
select {
|
|
case trm.sideChan <- input:
|
|
trm.sideChanLast.Store(true)
|
|
default:
|
|
// hopefully the side channel buffer is deep enough so that we don't
|
|
// ever have to drop input before the buffer can emptied in TermRead().
|
|
//
|
|
// in most instances a depth of one is sufficient but occasionally it
|
|
// is not (eg. the HALT/RUN commands sent by the rewind slider in
|
|
// win_control)
|
|
logger.Logf("term", "dropping from side channel (%s)", input)
|
|
}
|
|
}
|