Skip to content

Vibe code create Key show typeclass#31

Merged
noelwelsh merged 4 commits intocreativescala:mainfrom
kapunga:show-key
May 2, 2025
Merged

Vibe code create Key show typeclass#31
noelwelsh merged 4 commits intocreativescala:mainfrom
kapunga:show-key

Conversation

@kapunga
Copy link
Contributor

@kapunga kapunga commented Apr 29, 2025

This PR creates a cats Show instance for Key to make debugging individual key strokes. I've wanted to make this for a while, but it felt like it was mostly boiler plate and not very interesting to actually write. This morning I decided to give things a go with Claude Code to see if I could bang it out in a hurry and was pleasantly surprised. The whole interaction took around 12 minutes and cost around $0.40 in credits.

Open to making any changes, but I wanted to show the raw output first.

I've included the entire interaction with Claude Code for the curious:

╭────────────────────────────────────────────╮
│ ✻ Welcome to Claude Code research preview! │
│                                            │
│   /help for help                           │
│                                            │
│   cwd: /Users/kapunga/code/oss/terminus    │
╰────────────────────────────────────────────╯

> terminus/Key.scala has an unimplemented `given Show[Key]` and I'm wondering if you can fill out the implementation. I would like to to
  print out any `KeyModifiers` in the first part of the string, so for instance if you have `Control` and `Shift` modifiers for
  `KeyCode.F(3)`, it should output "Control-Shift-F3".

⏺ Let me implement the given Show[Key] in the Key.scala file.

⏺ Read(core/shared/src/main/scala/terminus/Key.scala)…
  ⎿  Read 203 lines (ctrl+r to expand)

⏺ Read(core/shared/src/main/scala/terminus/KeyModifier.scala)…
  ⎿  Read 62 lines (ctrl+r to expand)

⏺ Read(core/shared/src/main/scala/terminus/KeyCode.scala)…
  ⎿  Read 51 lines (ctrl+r to expand)

⏺ Now I'll implement the given Show[Key] instance:

⏺ Update(core/shared/src/main/scala/terminus/Key.scala)…
  ⎿  Updated core/shared/src/main/scala/terminus/Key.scala with 21 additions and 1 removal
     197    val controlShiftPageDown = Key(KeyModifier.ControlShift, KeyCode.PageDown)
     198
     199    given Show[Key] = (key: Key) => {
     200 -    ""
     200 +    val modifiersStr = {
     201 +      val parts = List(
     202 +        if (key.modifiers.hasControl) Some("Control") else None,
     203 +        if (key.modifiers.hasShift) Some("Shift") else None,
     204 +        if (key.modifiers.hasAlt) Some("Alt") else None,
     205 +        if (key.modifiers.hasSuper) Some("Super") else None,
     206 +        if (key.modifiers.hasHyper) Some("Hyper") else None,
     207 +        if (key.modifiers.hasMeta) Some("Meta") else None
     208 +      ).flatten
     209 +
     210 +      if (parts.isEmpty) "" else parts.mkString("-") + "-"
     211 +    }
     212 +
     213 +    val codeStr = key.code match {
     214 +      case KeyCode.Character(char) => char.toString
     215 +      case KeyCode.F(value) => s"F$value"
     216 +      case KeyCode.Unknown(code) => s"Unknown($code)"
     217 +      case other => other.toString.replaceAll("^[^.]*\\.", "")
     218 +    }
     219 +
     220 +    modifiersStr + codeStr
     221    }
     222  }

⏺ I've implemented the given Show[Key] instance that prints the key modifiers followed by the key code. The implementation:

  1. Builds a list of active modifiers (Control, Shift, Alt, etc.)
  2. Joins them with "-" and adds a trailing dash if any modifiers exist
  3. Formats the key code appropriately (special formatting for F keys, characters, and others)
  4. Returns the combined string

  For example, a key with Control and Shift modifiers plus F3 would display as "Control-Shift-F3".

> Hey, do you know if the cats library has a string interpolator that uses `Show`?

⏺ Search(pattern: "ShowInterpolator|show interpolator|showInterpolator", include: "*.scala")…
  ⎿  Found 0 files

⏺ Yes, Cats provides a string interpolator for Show instances. It's called show. You can use it like this:

  import cats.syntax.show._

  val key: Key = Key.controlShiftF3
  println(show"Key pressed: $key")

  This uses the Show instance for Key that you just implemented.

> Okay, this looks good so far, but we need some unit tests for this. Can you help me write a unit test? I would like the general format of
  the unit test to have a map of `Key` -> `String` where the key is the key input and the string is the expected value and then the test
  just tries every key value pair and reports any that are incorrect.

⏺ I'll help you write a unit test for the Show[Key] implementation. Let's create a test file in the appropriate location.

⏺ Search(pattern: "**/test/**/*Suite.scala")…
  ⎿  Found 7 files (ctrl+r to expand)

⏺ Read(core/shared/src/test/scala/terminus/KeyModifierSuite.scala)…
  ⎿  Read 36 lines (ctrl+r to expand)

⏺ Now I'll create a new test file for the Show[Key] implementation. I'll create it in the same directory as the KeyModifierSuite.scala.

⏺ Write(file_path: core/shared/src/test/scala/terminus/KeyShowSuite.scala)…

╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Create file                                                                                                                                   │
│ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │
│ │ core/shared/src/test/scala/terminus/KeyShowSuite.scala                                                                                    │ │
│ │                                                                                                                                           │ │
│ │ /*                                                                                                                                        │ │
│ │  * 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 munit.FunSuite                                                                                                                     │ │
│ │ import cats.Show                                                                                                                          │ │
│ │ import cats.syntax.show._                                                                                                                 │ │
│ │                                                                                                                                           │ │
│ │ class KeyShowSuite extends FunSuite {                                                                                                     │ │
│ │   test("Show[Key] produces the expected string representation") {                                                                         │ │
│ │     val testCases = Map[Key, String](                                                                                                     │ │
│ │       // Basic character keys                                                                                                             │ │
│ │       Key('a') -> "a",                                                                                                                    │ │
│ │       Key('Z') -> "Z",                                                                                                                    │ │
│ │       Key('1') -> "1",                                                                                                                    │ │
│ │       Key(' ') -> " ",                                                                                                                    │ │
│ │                                                                                                                                           │ │
│ │       // Basic special keys                                                                                                               │ │
│ │       Key.enter -> "Enter",                                                                                                               │ │
│ │       Key.escape -> "Escape",                                                                                                             │ │
│ │       Key.backspace -> "Backspace",                                                                                                       │ │
│ │       Key.tab -> "Tab",                                                                                                                   │ │
│ │       Key.up -> "Up",                                                                                                                     │ │
│ │       Key.down -> "Down",                                                                                                                 │ │
│ │       Key.left -> "Left",                                                                                                                 │ │
│ │       Key.right -> "Right",                                                                                                               │ │
│ │                                                                                                                                           │ │
│ │       // Function keys                                                                                                                    │ │
│ │       Key.f1 -> "F1",                                                                                                                     │ │
│ │       Key.f12 -> "F12",                                                                                                                   │ │
│ │                                                                                                                                           │ │
│ │       // Single modifier keys                                                                                                             │ │
│ │       Key.shift('a') -> "Shift-a",                                                                                                        │ │
│ │       Key.control('c') -> "Control-c",                                                                                                    │ │
│ │       Key(KeyModifier.Alt, KeyCode.Character('x')) -> "Alt-x",                                                                            │ │
│ │       Key(KeyModifier.Super, KeyCode.Character('w')) -> "Super-w",                                                                        │ │
│ │                                                                                                                                           │ │
│ │       // Modifier + special keys                                                                                                          │ │
│ │       Key.shiftUp -> "Shift-Up",                                                                                                          │ │
│ │       Key.controlDown -> "Control-Down",                                                                                                  │ │
│ │       Key.controlF12 -> "Control-F12",                                                                                                    │ │
│ │                                                                                                                                           │ │
│ │       // Multiple modifiers                                                                                                               │ │
│ │       Key(KeyModifier.ControlShift, KeyCode.Character('a')) -> "Control-Shift-a",                                                         │ │
│ │       Key.controlShiftUp -> "Control-Shift-Up",                                                                                           │ │
│ │       Key(KeyModifier.Control.or(KeyModifier.Alt), KeyCode.F(5)) -> "Control-Alt-F5",                                                     │ │
│ │                                                                                                                                           │ │
│ │       // Complex modifier combinations                                                                                                    │ │
│ │       Key(KeyModifier.Control.or(KeyModifier.Shift).or(KeyModifier.Alt), KeyCode.F(4)) -> "Control-Shift-Alt-F4",                         │ │
│ │       Key(KeyModifier.Control.or(KeyModifier.Shift).or(KeyModifier.Super), KeyCode.Enter) -> "Control-Shift-Super-Enter",                 │ │
│ │                                                                                                                                           │ │
│ │       // Unknown key code                                                                                                                 │ │
│ │       Key.unknown("test") -> "Unknown(test)"                                                                                              │ │
│ │     )                                                                                                                                     │ │
│ │                                                                                                                                           │ │
│ │     testCases.foreach { case (key, expected) =>                                                                                           │ │
│ │       val actual = key.show                                                                                                               │ │
│ │       assertEquals(                                                                                                                       │ │
│ │         actual,                                                                                                                           │ │
│ │         expected,                                                                                                                         │ │
│ │         s"Key $key did not produce the expected string representation"                                                                    │ │
│ │       )                                                                                                                                   │ │
│ │     }                                                                                                                                     │ │
│ │   }                                                                                                                                       │ │
│ │ }                                                                                                                                         │ │
│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ Do you want to create KeyShowSuite.scala?                                                                                                     │
│ ❯ 1. Yes✔                                                                                                                                     │
│   2. Yes, and don't ask again this session (shift+tab)                                                                                        │
│   3. No, and tell Claude what to do differently (esc)                                                                                         │
│                                                                                                                                               │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

⏺ I've created a comprehensive test suite for the Show[Key] implementation with the following features:

  1. The test uses a map of Key -> String pairs as requested
  2. It covers a wide range of test cases:
    - Basic character keys (letters, numbers, space)
    - Special keys (enter, escape, arrows)
    - Function keys
    - Single modifier keys (Shift, Control, Alt, Super)
    - Modifier + special key combinations
    - Multiple modifier combinations
    - Complex modifier combinations
    - Unknown key code
  3. The test iterates through each test case, compares the actual output with the expected output, and provides a helpful error message if
  they don't match.
  4. It uses key.show which comes from the Cats show extension method on showable types.

  You can run this test to verify that your Show[Key] implementation works correctly for all these cases.

> Okay, this looks good, however I would like to make some modifications. I would like `Show` for ' ' to print 'Space', '\t' to print 'Tab',
   and '\n' to print 'Newline', and I would like the unit test to reflect this.

⏺ I'll update both the Show[Key] implementation and the unit test to reflect these changes.

⏺ Update(core/shared/src/main/scala/terminus/Key.scala)…
  ⎿  Updated core/shared/src/main/scala/terminus/Key.scala with 6 additions and 1 removal
     211      }
     212
     213      val codeStr = key.code match {
     214 -      case KeyCode.Character(char) => char.toString
     214 +      case KeyCode.Character(char) => char match {
     215 +        case ' ' => "Space"
     216 +        case '\t' => "Tab"
     217 +        case '\n' => "Newline"
     218 +        case other => other.toString
     219 +      }
     220        case KeyCode.F(value) => s"F$value"
     221        case KeyCode.Unknown(code) => s"Unknown($code)"
     222        case other => other.toString.replaceAll("^[^.]*\\.", "")

⏺ Update(core/shared/src/test/scala/terminus/KeyShowSuite.scala)…
  ⎿  Updated core/shared/src/test/scala/terminus/KeyShowSuite.scala with 3 additions and 1 removal
     27        Key('a') -> "a",
     28        Key('Z') -> "Z",
     29        Key('1') -> "1",
     30 -      Key(' ') -> " ",
     30 +      Key(' ') -> "Space",
     31 +      Key('\t') -> "Tab",
     32 +      Key('\n') -> "Newline",
     33
     34        // Basic special keys
     35        Key.enter -> "Enter",

⏺ Update(core/shared/src/test/scala/terminus/KeyShowSuite.scala)…
  ⎿  Updated core/shared/src/test/scala/terminus/KeyShowSuite.scala with 2 additions
     50        Key.control('c') -> "Control-c",
     51        Key(KeyModifier.Alt, KeyCode.Character('x')) -> "Alt-x",
     52        Key(KeyModifier.Super, KeyCode.Character('w')) -> "Super-w",
     53 +      Key(KeyModifier.Shift, KeyCode.Character(' ')) -> "Shift-Space",
     54 +      Key(KeyModifier.Control, KeyCode.Character('\n')) -> "Control-Newline",
     55
     56        // Modifier + special keys
     57        Key.shiftUp -> "Shift-Up",

⏺ I've updated both the Show[Key] implementation and the unit test to handle the special characters as requested:

  1. In the Show[Key] implementation, I've added a nested match expression to handle special characters:
    - ' ' (space) now prints as "Space"
    - '\t' (tab) now prints as "Tab"
    - '\n' (newline) now prints as "Newline"
  2. In the unit test, I've updated the test cases to reflect these changes:
    - Added test cases for basic character keys including space, tab, and newline
    - Added test cases for modifier keys with these special characters to ensure they work correctly with modifiers too

  These changes should ensure that keys with these special characters are displayed in a more readable way in the terminal.

kapunga added 2 commits April 29, 2025 09:23
Show for pretty-printing a keystroke. Can be used for debugging
applications, showing keystrokes.

PR made with help from Claude Code.
}
case KeyCode.F(value) => s"F$value"
case KeyCode.Unknown(code) => s"Unknown($code)"
case other => other.toString.replaceAll("^[^.]*\\.", "")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this regex does anything useful. (I don't think it actually does anything at all, as the toString doesn't contain any dot-separated prefix. E.g. Key.pageUp.code.toString is PageUp.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was wondering how it came about. It looks like the LLM expected the canonical name?

Copy link
Contributor

@noelwelsh noelwelsh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to see the regex justified or removed before merging this.

kapunga added 2 commits April 30, 2025 10:59
Regex did not perform any function.
Made additional changes for simplification and readability.
@kapunga
Copy link
Contributor Author

kapunga commented May 1, 2025

I'd like to see the regex justified or removed before merging this.

Fixed and made a couple more tweaks that I think are simpler and easier to read.

@kapunga kapunga requested a review from noelwelsh May 1, 2025 20:27
@noelwelsh
Copy link
Contributor

Looks good!

@noelwelsh noelwelsh merged commit 1a62bd4 into creativescala:main May 2, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants