Is there a way to get the pixel position from the cursor position in the editor

i want to create a little menu that pops up over selected text. I have not found a way to get from the EditorPosition to a corresponding pixel position, which would be needed to create (for example) a context menu at that position.

Any help would be apreciated.

You can do it using CodeMirror, the editor library working behind Obsidian’s Editor interface. (I’m not sure if it is possible using Obsidian API only)

  1. Access EditorView associated with the current editor via editor.cm. This cm attribute is not exposed in the API, so you will have to // @ts-ignore it or write a module declaration to avoid a compilation error.
  2. EditorView instance has coordsAtPos method, which gets the screen coordinates at the given document position.
  3. Here, the position must be given as a number, so you cannot pass it the returned value of editor.getCursor() as is because it returns {line: number, ch: number}. Instead, you have to convert it into a plain number using editor.posToOffset before passing it to coordsAtPos.
editor.cm.coordsAtPos(editor.posToOffset(editor.getCursor()))

Edit: I played with the dev console a little bit and noticed that Obsidian’s Editor has a secret coordsAtPos method that is not exposed in the API.

1 Like

Thank you for the great answer! Works perfectly.
now i just need to find out how to create an element at this position for a custom menu. i thought about hijacking the Menu type, but it seems i can’t modify it enough.
Anyways, thanks for your help, i really appreciate it.

By the way what do you want to achieve? I’m not sure why you want to show a menu (do you mean context (right-click) menu?) at the position of the cursor rather than the right-clicked position. Perhaps you mean something like EditorSuggest?

I want to be able to quickly color text, idealy without having to use the mouse every time. This is how i want this to work:

  • upon calling the command a little menu pops up, showing 10 different colored, numbered squares, as well as an “edit” button.
  • this menu can be clicken, but is also listening to keypresses and will apply a text color to the selected text, once the corresponding number key is pressed.
  • the edit button opens another window/ modal, where you can edit your colors (now that i think about it i think that this could just be done in the settings)

The thing i am trying to figure out right now is how to create the custom little window/ menu. Right now i am having a look at the HoverPopover class, but i dont know if it is the right thing to use.

I see. If you implement it using Menu, it will look like this.

    this.addCommand({
      id: 'test',
      name: 'Test',
      editorCheckCallback: (check, editor, view) => {
        if (editor.somethingSelected()) {
          if (!check) {
            const menu = new Menu(this.app);
            doSomething(menu.dom);
            menu.addItem((item) => {
              doSomething(item.dom);
              item.onClick(...)
            })
            const pos = editor.cm.coordsAtPos(editor.posToOffset(editor.getCursor()));
            menu.showAtPosition(
              {x: pos.left, y: pos.top - someOffset},
            );    
          }
          return true
        }
        return false;
      }
    })

Note that menu.dom or item.dom is not exposed in the API. (see https://github.com/Fevol/obsidian-typings/blob/e1ff0b68a2d4e6d6ff42b23b5eb18f1e30a4054c/obsidian-ex.d.ts#L3362)

The Highlightr plugin’s “Open Highlightr” command will be a great reference.

Oh, thanks for the reference! i had not heard of Highlightr before but it seems kind of similar to what i want to achieve. I will have a look at how the Menu is used there.

It seems i have to somehow find a way to use menu after all, because somehow Menu.showAtPosition() modifies the position to be in the right place, while just applying the position to a HoverPopover leads to the popover sometimes ending up in seemingly random positions, for from the inteded one. I dont know what kind of hacks/ transformations are used to get the accurate position in the .showAtPosition() method and i wouldn’t know how to find out. Maybe you have another idea.

Example: