Skip to content

Commit

Permalink
Implement basic key parsing
Browse files Browse the repository at this point in the history
This is not complete, but it already does the basics.

- Define Key type
- Define KeyReader effect for reading Keys
- Implement basic KeyReader for terminal escape codes. Not complete
- Implement for JLineTerminal
- Update Prompt example to use KeyReader
  • Loading branch information
noelwelsh committed Feb 7, 2025
1 parent fc484d6 commit bb08941
Show file tree
Hide file tree
Showing 9 changed files with 349 additions and 54 deletions.
3 changes: 2 additions & 1 deletion core/jvm/src/main/scala/terminus/JLineTerminal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ import org.jline.terminal.Terminal as JTerminal
import org.jline.terminal.TerminalBuilder
import org.jline.utils.InfoCmp.Capability
import terminus.effect.Eof
import terminus.effect.TerminalKeyReader
import terminus.effect.Timeout

import scala.concurrent.duration.Duration

class JLineTerminal(terminal: JTerminal) extends Terminal {
class JLineTerminal(terminal: JTerminal) extends Terminal, TerminalKeyReader {
private val reader = terminal.reader()
private val writer = terminal.writer()

Expand Down
42 changes: 14 additions & 28 deletions core/jvm/src/main/scala/terminus/Prompt.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@

package terminus

import terminus.effect.Ascii
import terminus.effect.Eof
import terminus.effect.Key

import scala.annotation.tailrec

// The only keys we care about
enum KeyCode {
case Down
case Up
case Enter
case Up
case Down
}

// Clear the text we've written
Expand All @@ -47,34 +50,17 @@ def write(selected: Int): Program[Unit] = {
Terminal.flush()
}

@tailrec
def read(): Program[KeyCode] = {
Terminal.read() match {
Terminal.readKey() match {
case Eof =>
throw new Exception("Received an EOF")
case char: Char =>
char match {
case Ascii.LF | Ascii.CR => KeyCode.Enter
case Ascii.ESC =>
Terminal.read() match {
// Normal mode
case '[' =>
Terminal.read() match {
case 'A' => KeyCode.Up
case 'B' => KeyCode.Down
case other => read()
}

// Application mode
case 'O' =>
Terminal.read() match {
case 'A' => KeyCode.Up
case 'B' => KeyCode.Down
case other => read()
}

case other => read()
}
case other => read()
case key: Key =>
key match {
case Key.Enter => KeyCode.Enter
case Key.Up => KeyCode.Up
case Key.Down => KeyCode.Down
case other => read()
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions core/jvm/src/main/scala/terminus/Terminal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ trait Terminal
effect.Cursor,
effect.Display[Terminal],
effect.Erase,
effect.KeyReader,
effect.NonBlockingReader,
effect.Peeker,
effect.RawMode[Terminal],
Expand All @@ -37,6 +38,7 @@ object Terminal
Cursor,
Display,
Erase,
KeyReader,
NonBlockingReader,
Peeker,
RawMode,
Expand Down
27 changes: 27 additions & 0 deletions core/shared/src/main/scala/terminus/KeyReader.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2024 Creative Scala
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package terminus

import terminus.effect.Eof
import terminus.effect.Key

trait KeyReader {

/** Block waiting for a key. */
def readKey(): effect.KeyReader ?=> Eof | Key =
effect ?=> effect.readKey()
}
173 changes: 173 additions & 0 deletions core/shared/src/main/scala/terminus/effect/Key.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
* Copyright 2024 Creative Scala
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package terminus.effect

/** Enumeration describing the key presses we recognize from the terminal */
enum Key {

/** A standard character, like 'A' or 'z.' */
case Character(char: Char)
case Space
case Escape
case ShiftEscape
case Return
case ControlAt
case ControlA
case ControlB
case ControlC
case ControlD
case ControlE
case ControlF
case ControlG
case ControlH
case ControlI
case ControlJ
case ControlK
case ControlL
case ControlM
case ControlN
case ControlO
case ControlP
case ControlQ
case ControlR
case ControlS
case ControlT
case ControlU
case ControlV
case ControlW
case ControlX
case ControlY
case ControlZ
case Control1
case Control2
case Control3
case Control4
case Control5
case Control6
case Control7
case Control8
case Control9
case Control0
case ControlShift1
case ControlShift2
case ControlShift3
case ControlShift4
case ControlShift5
case ControlShift6
case ControlShift7
case ControlShift8
case ControlShift9
case ControlShift0
case ControlBackslash
case ControlSquareClose
case ControlCircumflex
case ControlUnderscore
case Left
case Right
case Up
case Down
case Home
case End
case Insert
case Delete
case PageUp
case PageDown
case ControlLeft
case ControlRight
case ControlUp
case ControlDown
case ControlHome
case ControlEnd
case ControlInsert
case ControlDelete
case ControlPageUp
case ControlPageDown
case ShiftLeft
case ShiftRight
case ShiftUp
case ShiftDown
case ShiftHome
case ShiftEnd
case ShiftInsert
case ShiftDelete
case ShiftPageUp
case ShiftPageDown
case ControlShiftLeft
case ControlShiftRight
case ControlShiftUp
case ControlShiftDown
case ControlShiftHome
case ControlShiftEnd
case ControlShiftInsert
case ControlShiftDelete
case ControlShiftPageUp
case ControlShiftPageDown
case BackTab
case Backspace
case Enter
case Tab
case F1
case F2
case F3
case F4
case F5
case F6
case F7
case F8
case F9
case F10
case F11
case F12
case F13
case F14
case F15
case F16
case F17
case F18
case F19
case F20
case F21
case F22
case F23
case F24
case ControlF1
case ControlF2
case ControlF3
case ControlF4
case ControlF5
case ControlF6
case ControlF7
case ControlF8
case ControlF9
case ControlF10
case ControlF11
case ControlF12
case ControlF13
case ControlF14
case ControlF15
case ControlF16
case ControlF17
case ControlF18
case ControlF19
case ControlF20
case ControlF21
case ControlF22
case ControlF23
case ControlF24
case ScrollUp
case ScrollDown
}
21 changes: 21 additions & 0 deletions core/shared/src/main/scala/terminus/effect/KeyReader.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright 2024 Creative Scala
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package terminus.effect

trait KeyReader extends Effect {
def readKey(): Eof | Key
}
Loading

0 comments on commit bb08941

Please sign in to comment.