Browse Source

Merge pull request #32114 from overleaf/mg-context-menu-cursor-movemenet

Move cursor on right-click within same line

GitOrigin-RevId: 8b622e9f557ecb1a33b7ba1a80d5752e05a72718
Malik Glossop 4 months ago
parent
commit
7bb4427f93

+ 4 - 6
services/web/frontend/js/features/source-editor/extensions/context-menu.ts

@@ -153,13 +153,11 @@ function isPositionInsideSelection(pos: number, from: number, to: number) {
 
 function isPositionInsideAnyRangeOrCursor(view: EditorView, pos: number) {
   for (const range of view.state.selection.ranges) {
-    // If it's a cursor, treat a right-click anywhere on the same line as "inside".
-    // This avoids collapsing multi-cursor selections when right-clicking on blank lines
-    // or to the right of the caret.
+    // If it's a cursor (not a selection), only treat it as "inside" when
+    // right-clicking exactly on the cursor position. This allows cursor
+    // movement when clicking elsewhere on the same line.
     if (range.from === range.to) {
-      const clickedLine = view.state.doc.lineAt(pos)
-      const cursorLine = view.state.doc.lineAt(range.from)
-      if (clickedLine.number === cursorLine.number) {
+      if (pos === range.from) {
         return true
       }
       continue

+ 55 - 0
services/web/test/frontend/features/source-editor/components/codemirror-editor-context-menu.spec.tsx

@@ -162,6 +162,61 @@ describe('editor context menu', { scrollBehavior: false }, function () {
     cy.findByRole('menu').should('not.exist')
   })
 
+  it('should move the cursor when right-clicking a different position on the same line', function () {
+    grantClipboardPermissions()
+
+    const pasteContent = 'XX'
+    const scope = mockScope()
+
+    cy.mount(
+      <TestContainer>
+        <EditorProviders scope={scope}>
+          <CodeMirrorEditor />
+        </EditorProviders>
+      </TestContainer>
+    )
+
+    // Stub clipboard to return known content for pasting
+    cy.window().then(win => {
+      const getTypeStub = cy.stub()
+      getTypeStub
+        .withArgs('text/plain')
+        .resolves(new Blob([pasteContent], { type: 'text/plain' }))
+
+      cy.stub(win.navigator.clipboard, 'read').resolves([
+        {
+          types: ['text/plain'],
+          getType: getTypeStub,
+        },
+      ])
+      cy.stub(win.navigator.clipboard, 'readText').resolves(pasteContent)
+    })
+
+    cy.get('.cm-line').eq(16).as('line')
+    cy.get('@line').click()
+    cy.get('@line').type('aaaa bbbb')
+
+    // Right-click the left side of the line — opens context menu with cursor near start
+    cy.get('@line').rightclick('left')
+    cy.findByRole('menu').should('be.visible')
+
+    // Right-click the right side of the same line while menu is still open —
+    // cursor should move to the end of the line
+    cy.get('@line').rightclick('right')
+    cy.findByRole('menu').should('be.visible')
+
+    // Paste via context menu — content goes wherever the cursor is
+    cy.findByRole('menu').within(() => {
+      cy.findByRole('menuitem', { name: pasteLabelMatcher }).click()
+    })
+
+    // If cursor moved: "aaaa bbbbXX" (pasted at end)
+    cy.get('@line').should($line => {
+      const text = $line.text()
+      expect(text).to.equal('aaaa bbbb' + pasteContent)
+    })
+  })
+
   it('should should close when clicking outside the editor', function () {
     const scope = mockScope()
     const outsideEditorButtonName = 'Recompile'