KeyBindings - overriding `Ctrl-V` (and issues with `Alt` modifier)

I am implementing some different versions of custom paste logic (manipulating the content about to be pasted in different ways) and I want to bind the versions to different shortcuts. The CM instance is in a QWebEngineView (my dev environment is Windows) so not in a “regular” browser.

It would seem any key bindings for Ctrl-v and Ctrl-Shift-v (and possibly Alt-*-v as well, see below) never fire, they just trigger the paste handler. To get around this I am currently adding a EditorView.domEventHandler for keydown (and paste to block the default paste) and handling the “raw” keyboard events for the paste related shortcuts but that duplicates some of the code from CM as well as placing “shortcut logic in two different places”.
Is there a way to get a CM KeyBinding to fire for Ctrl-v et al?

Regarding the Alt modifier, I can’t get key bindings that involve Alt to fire (however the keypress reaches the DOM).
Any thoughts of why that may be? What am I missing?

Why do you think that? It doesn’t match what I’m seeing.

Can you set up an example of what you’re doing on codemirror.net/try?

Why do you think that? It doesn’t match what I’m seeing.

Ok, then it might be the environment I am in (QWebEngineView etc.). Indeed, making a simple example in the “playground” works as expected (the actual code I am working on is a bit complex to stream line down to a simple example that can be transpiled and put in the “playground”). I will have to dive into what it could be, thanks!

Ok, regarding the Alt key, I think this line is causing me issues (removing it fires the shortcuts as expected). I understand a separate treatment for AltGr but I can’t see any code that parses AltGr in normalizeKeyName? What is the correct way to set up a KeyBinding that works on a Windows machine for, say Ctrl+Alt+v?

As for the paste issue I am still looking into it.

I have now restructured my code and the Ctrl+V works as expected so the error was somewhere in my code. However the Alt issue remains, as earlier mentioned to me it seems that L250 in codemirror/view/src/keymap.ts/runHandlers blocks Ctrl-Alt based shortcuts on in an environment where windows is true?

  let scopeObj = map[scope], baseName, shiftName
  if (scopeObj) {
    if (runFor(scopeObj[prefix + modifiers(name, event, !isChar)])) {
      handled = true
    } else if (isChar && (event.altKey || event.metaKey || event.ctrlKey) &&
               // Ctrl-Alt may be used for AltGr on Windows
               !(browser.windows && event.ctrlKey && event.altKey) &&
               (baseName = base[event.keyCode]) && baseName != name) {
      if (runFor(scopeObj[prefix + modifiers(baseName, event, true)])) {
        handled = true
      } else if (event.shiftKey && (shiftName = shift[event.keyCode]) != name && shiftName != baseName &&
                 runFor(scopeObj[prefix + modifiers(shiftName, event, false)])) {
        handled = true
      }
    } else if (isChar && event.shiftKey &&
               runFor(scopeObj[prefix + modifiers(name, event, true)])) {
      handled = true
    }
    if (!handled && runFor(scopeObj._any)) handled = true
  }

I may be misunderstanding the code though, thoughts?

Sadly, DOM keyboard events do not provide a way to distinguish AltGr being held down.

The code you linked just disables the fallback to keyCode-based dispatch. If event.key is actually "v" and ctrlKey and altKey are true, the if branch about it should dispatch your key just fine.

A little more fiddling indicates that on a Win/QWebEngineView platform Ctrl-Alt results in an uppercase char being dispatched to CM whilst on the playground it is a lowercase char, i.e. it is the environment rather than CM itself. Example: Ctrl-Alt-v results in Ctrl-Alt-V on Win/Qt but Ctrl-Alt-v on the playground.

Since the CM code relies on upper/lower case char rather than Shift key state to differentiate between, say Ctrl-Alt-v and Ctrl-Alt-Shift-v, those inputs produce the same result on Win/Qt (Ctrl-Alt-V). NB: when I say Win/Qt I have really only checked Win11/Python/Qt6/QWebEngineView.

Fuller comparison

Here is fuller comparison (Sequence is what is actually pressed, the char is always stated as lowercase regardless of shift state etc.). The last column is a print of some vars in keymap.ts/runHandlers from Win/Qt (It becomes a little more involved to do the equivalent in the playground):

Sequence QWebEnginveView Playground runHandlers
Ctrl-v Ctrl-v Ctrl-v name: v, charCode: 118, isChar: true, prefix: , handled: false, prevented: false, stopPropagation: false, scopeObj: true, modifiers (false): Ctrl-v, modifiers (true): Ctrl-v
Caps-Ctrl-v Ctrl-v Ctrl-V name: v, charCode: 118, isChar: true, prefix: , handled: false, prevented: false, stopPropagation: false, scopeObj: true, modifiers (false): Ctrl-v, modifiers (true): Ctrl-v
Ctrl-Shift-v Ctrl-V Ctrl-V name: V, charCode: 86, isChar: true, prefix: , handled: false, prevented: false, stopPropagation: false, scopeObj: true, modifiers (false): Ctrl-V, modifiers (true): Shift-Ctrl-V
Caps-Ctrl-Shift-v Ctrl-V Ctrl-v name: V, charCode: 86, isChar: true, prefix: , handled: false, prevented: false, stopPropagation: false, scopeObj: true, modifiers (false): Ctrl-V, modifiers (true): Shift-Ctrl-V
Ctrl-Alt-v Ctrl-Alt-V Ctrl-Alt-v name: V, charCode: 86, isChar: true, prefix: , handled: false, prevented: false, stopPropagation: false, scopeObj: true, modifiers (false): Ctrl-Alt-V, modifiers (true): Ctrl-Alt-V
Caps-Ctrl-Alt-v Ctrl-Alt-V Ctrl-Alt-V name: V, charCode: 86, isChar: true, prefix: , handled: false, prevented: false, stopPropagation: false, scopeObj: true, modifiers (false): Ctrl-Alt-V, modifiers (true): Ctrl-Alt-V
Ctrl-Shift-Alt-v Ctrl-Alt-V Ctrl-Alt-V name: V, charCode: 86, isChar: true, prefix: , handled: false, prevented: false, stopPropagation: false, scopeObj: true, modifiers (false): Ctrl-Alt-V, modifiers (true): Shift-Ctrl-Alt-V
Caps-Ctrl-Shift-Alt-v Ctrl-Alt-V Ctrl-Alt-v name: V, charCode: 86, isChar: true, prefix: , handled: false, prevented: false, stopPropagation: false, scopeObj: true, modifiers (false): Ctrl-Alt-V, modifiers (true): Shift-Ctrl-Alt-V
Alt-v Alt-v Alt-v name: v, charCode: 118, isChar: true, prefix: , handled: false, prevented: false, stopPropagation: false, scopeObj: true, modifiers (false): Alt-v, modifiers (true): Alt-v
Caps-Alt-v Alt-V Alt-V name: V, charCode: 86, isChar: true, prefix: , handled: false, prevented: false, stopPropagation: false, scopeObj: true, modifiers (false): Alt-V, modifiers (true): Alt-V
Alt-Shift-v Alt-V Alt-V name: V, charCode: 86, isChar: true, prefix: , handled: false, prevented: false, stopPropagation: false, scopeObj: true, modifiers (false): Alt-V, modifiers (true): Shift-Alt-V
Caps-Alt-Shift-v Alt-v Alt-v name: v, charCode: 118, isChar: true, prefix: , handled: false, prevented: false, stopPropagation: false, scopeObj: true, modifiers (false): Alt-v, modifiers (true): Shift-Alt-v
AltGr-v Ctrl-Alt-V Ctrl-Alt-v name: V, charCode: 86, isChar: true, prefix: , handled: false, prevented: false, stopPropagation: false, scopeObj: true, modifiers (false): Ctrl-Alt-V, modifiers (true): Ctrl-Alt-V
Caps-AltGr-v Ctrl-Alt-V (nothing) name: V, charCode: 86, isChar: true, prefix: , handled: false, prevented: false, stopPropagation: false, scopeObj: true, modifiers (false): Ctrl-Alt-V, modifiers (true): Ctrl-Alt-V
AltGr-Shift-v Ctrl-Alt-V (nothing) name: V, charCode: 86, isChar: true, prefix: , handled: false, prevented: false, stopPropagation: false, scopeObj: true, modifiers (false): Ctrl-Alt-V, modifiers (true): Shift-Ctrl-Alt-V
Caps-AltGr-Shift-v Ctrl-Alt-V (nothing) name: V, charCode: 86, isChar: true, prefix: , handled: false, prevented: false, stopPropagation: false, scopeObj: true, modifiers (false): Ctrl-Alt-V, modifiers (true): Shift-Ctrl-Alt-V
Code used for the Win/Python/QWebEngineView

app.py

import sys, os
from PyQt6.QtWidgets import QApplication, QMainWindow
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWebEngineCore import QWebEnginePage
from PyQt6.QtCore import QUrl

class WebEnginePage(QWebEnginePage):
    def javaScriptConsoleMessage(self, level, message, lineNumber, sourceID):
        print(message)

class Window(QMainWindow):
    def __init__(self):
        super(Window, self).__init__()
        self.browser = QWebEngineView()
        self.page = WebEnginePage()
        self.browser.setPage(self.page)
        self.setCentralWidget(self.browser)
        self.browser.setHtml(
            '<html><body><script src="script.js"></script></body></html>',
            QUrl.fromLocalFile(os.path.join(os.path.dirname(__file__), 'script.js'))
        )
        self.show()

app = QApplication(sys.argv)
window = Window()
app.exec()

script.js

import { keymap } from "@codemirror/view"
import {EditorView, minimalSetup} from "codemirror"

const C_v = (view)=> {console.log('Triggered: Ctrl-v'); return true}
const C_V = (view) => {console.log('Triggered: Ctrl-V'); return true}
const CS_v = (view) => {console.log('Triggered: Ctrl-Shift-v'); return true}
const CS_V = (view) => {console.log('Triggered: Ctrl-Shift-V'); return true}
const CA_v = (view) => {console.log('Triggered: Ctrl-Alt-v'); return true}
const CA_V = (view) => {console.log('Triggered: Ctrl-Alt-V'); return true}
const CSA_v = (view) => {console.log('Triggered: Ctrl-Shift-Alt-v'); return true}
const CSA_V = (view) => {console.log('Triggered: Ctrl-Shift-Alt-V'); return true}
const A_v = (view) => {console.log('Triggered: Alt-v'); return true}
const A_V = (view) => {console.log('Triggered: Alt-V'); return true}
const AS_v = (view) => {console.log('Triggered: Alt-Shift-v'); return true}
const AS_V = (view) => {console.log('Triggered: Alt-Shift-V'); return true}

let editor = new EditorView({
  extensions: [
    keymap.of([
        {key: 'Ctrl-v', run: C_v, preventDefault: true},
        {key: 'Ctrl-V', run: C_V, preventDefault: true},
        {key: 'Ctrl-Shift-v', run: CS_v, preventDefault: true},
        {key: 'Ctrl-Shift-V', run: CS_V, preventDefault: true},
        {key: 'Ctrl-Alt-v', run: CA_v, preventDefault: true},
        {key: 'Ctrl-Alt-V', run: CA_V, preventDefault: true},
        {key: 'Ctrl-Shift-Alt-v', run: CSA_v, preventDefault: true},
        {key: 'Ctrl-Shift-Alt-V', run: CSA_V, preventDefault: true},
        {key: 'Alt-v', run: A_v, preventDefault: true},
        {key: 'Alt-V', run: A_V, preventDefault: true},
        {key: 'Alt-Shift-v', run: AS_v, preventDefault: true},
        {key: 'Alt-Shift-V', run: AS_V, preventDefault: true}
    ]),
    minimalSetup
  ],
  parent: document.body
})

Code inserted at L245 codemirror/view/src/keymap.ts:

if(isChar)
  console.log(`[runHandlers] name: ${name}, charCode: ${charCode}, isChar: ${isChar}, prefix: ${prefix}, handled: ${handled}, prevented:  ${prevented}, stopPropagation: ${stopPropagation}, scopeObj: ${scopeObj ? true : false}, modifiers (false): ${modifiers(name, event, false)}, modifiers (true): ${modifiers(name, event, true)}`)

Cheers

Ugh. That seems like a clear misbehavior. I’d report it that. I don’t really want to add workarounds for bugs in non-major platforms to the library.