// Copyright (c) 2013-2019 by Michael Dvorkin and contributors. All Rights Reserved. // Use of this source code is governed by a MIT-style license that can // be found in the LICENSE file. package mop import ( "regexp" "strings" "strconv" "github.com/nsf/termbox-go" ) // LineEditor kicks in when user presses '+' or '-' to add or delete stock // tickers. The data structure and methods are used to collect the input // data and keep track of cursor movements (left, right, beginning of the // line, end of the line, and backspace). type LineEditor struct { command rune // Keyboard command such as '+' or '-'. cursor int // Current cursor position within the input line. prompt string // Prompt string for the command. input string // User typed input string. screen *Screen // Pointer to Screen. quotes *Quotes // Pointer to Quotes. regex *regexp.Regexp // Regex to split comma-delimited input string. currentTextIndex int } // Returns new initialized LineEditor struct. func NewLineEditor(screen *Screen, quotes *Quotes) *LineEditor { return &LineEditor{ screen: screen, quotes: quotes, regex: regexp.MustCompile(`[,\s]+`), currentTextIndex: -1, } } // Prompt displays a prompt in response to '+' or '-' commands. Unknown commands // are simply ignored. The prompt is displayed on the 3rd line (between the market // data and the stock quotes). func (editor *LineEditor) Prompt(command rune) *LineEditor { filterPrompt := `Set filter: ` if filter := editor.quotes.profile.Filter; len(filter) > 0 { filterPrompt = `Set filter (` + filter + `): ` } prompts := map[rune]string{ '+': `Add tickers: `, '-': `Remove tickers: `, 'f': filterPrompt, } if prompt, ok := prompts[command]; ok { editor.prompt = prompt editor.command = command editor.screen.DrawLine(0, 3, ``+editor.prompt+``) termbox.SetCursor(len(editor.prompt), 3) termbox.Flush() } return editor } func (editor *LineEditor) insertString(str string) { for _, ch := range str { editor.insertCharacter(ch) } } // Handle takes over the keyboard events and dispatches them to appropriate // line editor handlers. As user types or edits the text cursor movements // are tracked in `editor.cursor` while the text itself is stored in // `editor.input`. The method returns true when user presses Esc (discard) // or Enter (process). func (editor *LineEditor) Handle(ev termbox.Event) bool { defer termbox.Flush() switch ev.Key { case termbox.KeyEsc: return editor.done() case termbox.KeyEnter: return editor.execute().done() case termbox.KeyBackspace, termbox.KeyBackspace2: editor.deletePreviousCharacter() case termbox.KeyCtrlB, termbox.KeyArrowLeft: editor.moveLeft() case termbox.KeyCtrlF, termbox.KeyArrowRight: editor.moveRight() case termbox.KeyCtrlA: editor.jumpToBeginning() case termbox.KeyCtrlE: editor.jumpToEnd() case termbox.KeySpace: editor.insertCharacter(' ') case termbox.KeyArrowUp: // Add some text when the up arrow is pressed editor.currentTextIndex++ if(editor.currentTextIndex >= len(editor.quotes.stocks)) { editor.currentTextIndex = len(editor.quotes.stocks) - 1 }/* fullstr := editor.quotes.stocks[editor.currentTextIndex].Ticker indexnum, err := strconv.Atoi(fullstr[:2]) editor.input = strconv.Itoa(indexnum) //editor.insertString(showstr) editor.screen.DrawLine(len(editor.prompt), 3, editor.input+` `) topic := "my/topic" itemsel := editor.quotes.getitembyscode(editor.quotes.stocks[editor.currentTextIndex].Dividend) data := map[string]interface{}{ "scode": itemsel.Scode, "tier": 0, "daysback": itemsel.Daysback, "stdprice": itemsel.Enterprice, "name": editor.quotes.stocks[editor.currentTextIndex].Ticker, "ed": itemsel.AnalyseDay, } jsonData, err := json.Marshal(data) if err != nil { fmt.Println(err) } message := string(jsonData) token := editor.quotes.client.Publish(topic, 0, false, message) token.Wait() */ case termbox.KeyArrowDown: if 0 != len(editor.input) { indexnum, err := strconv.Atoi(editor.input) if err == nil && indexnum > 0 && indexnum <= len(editor.quotes.totalstocks) { editor.quotes.Sendstockgraphreq(indexnum ,true) } } default: if ev.Ch != 0 { editor.insertCharacter(ev.Ch) } } return false } //----------------------------------------------------------------------------- func (editor *LineEditor) deletePreviousCharacter() *LineEditor { if editor.cursor > 0 { if editor.cursor < len(editor.input) { // Remove character in the middle of the input string. editor.input = editor.input[0:editor.cursor-1] + editor.input[editor.cursor:len(editor.input)] } else { // Remove last input character. editor.input = editor.input[:len(editor.input)-1] } editor.screen.DrawLine(len(editor.prompt), 3, editor.input+` `) // Erase last character. editor.moveLeft() } return editor } //----------------------------------------------------------------------------- func (editor *LineEditor) insertCharacter(ch rune) *LineEditor { if editor.cursor < len(editor.input) { // Insert the character in the middle of the input string. editor.input = editor.input[0:editor.cursor] + string(ch) + editor.input[editor.cursor:len(editor.input)] } else { // Append the character to the end of the input string. editor.input += string(ch) } editor.screen.DrawLine(len(editor.prompt), 3, editor.input) editor.moveRight() return editor } //----------------------------------------------------------------------------- func (editor *LineEditor) moveLeft() *LineEditor { if editor.cursor > 0 { editor.cursor-- termbox.SetCursor(len(editor.prompt)+editor.cursor, 3) } return editor } //----------------------------------------------------------------------------- func (editor *LineEditor) moveRight() *LineEditor { if editor.cursor < len(editor.input) { editor.cursor++ termbox.SetCursor(len(editor.prompt)+editor.cursor, 3) } else if 0 != len(editor.input) { indexnum, err := strconv.Atoi(editor.input) if err == nil && indexnum > 0 && indexnum <= len(editor.quotes.totalstocks) { editor.quotes.Sendstockgraphreq(indexnum, false) } } return editor } //----------------------------------------------------------------------------- func (editor *LineEditor) jumpToBeginning() *LineEditor { editor.cursor = 0 termbox.SetCursor(len(editor.prompt)+editor.cursor, 3) return editor } //----------------------------------------------------------------------------- func (editor *LineEditor) jumpToEnd() *LineEditor { editor.cursor = len(editor.input) termbox.SetCursor(len(editor.prompt)+editor.cursor, 3) return editor } //----------------------------------------------------------------------------- func (editor *LineEditor) execute() *LineEditor { switch editor.command { case '+': tickers := editor.tokenize() if len(tickers) > 0 { if added, _ := editor.quotes.AddTickers(tickers); added > 0 { editor.quotes.Addstockcodetofile([]string{}) editor.screen.Draw(editor.quotes) } } case '-': tickers := editor.tokenize() if len(tickers) > 0 { before := len(editor.quotes.profile.Tickers) if removed, _ := editor.quotes.RemoveTickers(tickers); removed > 0 { editor.screen.Draw(editor.quotes) // Clear the lines at the bottom of the list, if any. after := before - removed for i := before + 1; i > after; i-- { editor.screen.ClearLine(0, i+4) } } } case 'f': if len(editor.input) == 0 { editor.input = editor.quotes.profile.Filter } editor.quotes.profile.SetFilter(editor.input) case 'F': editor.quotes.profile.SetFilter("") } return editor } //----------------------------------------------------------------------------- func (editor *LineEditor) done() bool { editor.screen.ClearLine(0, 3) termbox.HideCursor() return true } // Split by whitespace/comma to convert a string to array of tickers. Make sure // the string is trimmed to avoid empty tickers in the array. func (editor *LineEditor) tokenize() []string { input := strings.Trim(editor.input, `, `)//strings.ToUpper( return editor.regex.Split(input, -1) }