Intuitive text input history saving

Console windows aren't complete without the ability to save an easily traversible input history.

The technique I describe below isn't only useful in console windows. Chat applications can also utilize it effectively.

Outline

Consoles are made up of 2 main GUI components:

The text area: Where command output and other information is displayed

The input field: Where the user inputs commands

When I use a console interface I expect the following intuitive behavior:

The console should save my commands in order of input in a list

I should be able to traverse this list using my up and down arrow keys when the input field is focused

The list should not contain the same value more than once

The console should "stash" the command I'm currently typing out while I traverse the list so I can always go back to it if needed

Code

Here's code I wrote in Dart that achieves all these effects by utilizing key listeners:

List<String> scrollback = [""]; int scrollbackIndex = 0; InputElement inputField = new InputElement(type: "text"); inputField ..onKeyDown.listen((KeyboardEvent event) { if (event.keyCode == KeyCode.UP || event.keyCode == KeyCode.DOWN) { event.preventDefault(); if (event.keyCode == KeyCode.UP) scrollbackIndex = min(scrollbackIndex + 1, scrollback.length - 1); if (event.keyCode == KeyCode.DOWN) scrollbackIndex = max(scrollbackIndex - 1, 0); inputField.value = scrollback[scrollbackIndex]; } }) ..onKeyUp.listen((KeyboardEvent event) { if (KeyCode.isCharacterKey(event.keyCode)) { if (scrollbackIndex != 0) scrollbackIndex = 0; if (scrollbackIndex == 0) scrollback[0] = inputField.value.trim(); } }) ..onKeyPress.listen((KeyboardEvent event) { String input = inputField.value.replaceAll(new RegExp("\\s+"), " "); if (event.keyCode == KeyCode.ENTER && input.length != 0) { scrollback[0] = input; int index = scrollback.lastIndexOf(input); if (index != 0) scrollback.removeAt(index); scrollback.insert(0, ""); scrollbackIndex = 0; inputField.value = ""; evaluateCommand(input); } });

Variable breakdown

scrollback is the list that will store the input history. I use the first index[0] as the "stash" (note that the list is initialized with an empty string at index[0])

is the list that will store the input history. I use the first index[0] as the "stash" (note that the list is initialized with an empty string at index[0]) scrollbackIndex keeps track of our place in the scrollback list as we traverse it

keeps track of our place in the scrollback list as we traverse it inputField is our input text field

Key listener breakdown

onKeyDown (fired when a key is depressed): Ensure that the key being depressed is either up or down arrow, if true: Prevent default key behavior, which is moving the caret to beginning or end of the text in the field (respectively) Manipulate scrollbackIndex depending on what key was pressed while making sure to constrain it within its bounds (low bound is 0, high bound is scrollback.length - 1 ) Set the text in the input field to scrollback[scrollbackIndex] , effectively displaying that command from the scrollback history

(fired when a key is depressed): onKeyUp (fired when a key is released): Ensure that the key is alphabetical (indicating that the user is typing in the field), if true: Check if scrollbackIndex isn't 0 (indicating that the user is typing over a command from the history). If true, set scrollbackIndex to 0 (this will ensure that what the user is currently typing gets stashed) Check if scrollbackIndex is 0 (indicating that the user is working on the stash). If true, trim the text from the input field and put it in the stash

(fired when a key is released): onKeyPressed : Use regex to remove any sequences of spaces that are longer than 1 from the input field text (i.e. " this is a test " will get turned into "this is a test") and save this new value in a variable named input . This is optional, I do it to keep things clean Check if the key is enter and that input isn't an empty string. If true: Put input in the stash (in case it isn't already) Ensure that the value of input doesn't appear more than once in scrollback by removing any doubles Insert an empty string at index[0] of scrollback effectively committing the command to history and resetting the stash Set scrollbackIndex to 0 Reset the input field Evaluate the command using whatever method

:

Demo

Click here to check out a demo featuring the Dart code above.