Merge pull request #32135 from overleaf/mg-comment-empty-select

Allow adding comments on empty selection

GitOrigin-RevId: 14e742bb563fab99624be860691f1a9d2dabc00e
This commit is contained in:
Malik Glossop
2026-03-20 09:07:28 +00:00
committed by Copybot
parent 504005aa74
commit 6901a2a8de
9 changed files with 238 additions and 13 deletions
@@ -0,0 +1,44 @@
import { expect } from 'chai'
import { EditorState, EditorSelection } from '@codemirror/state'
import { isCursorOnEmptyLine } from '../../../../../frontend/js/features/source-editor/utils/is-cursor-on-empty-line'
function cursorState(doc: string, cursorPos: number) {
return EditorState.create({
doc,
selection: EditorSelection.cursor(cursorPos),
})
}
function selectionState(doc: string, from: number, to: number) {
return EditorState.create({
doc,
selection: EditorSelection.range(from, to),
})
}
describe('isCursorOnEmptyLine', function () {
it('returns true when cursor is on an empty line', function () {
const state = cursorState('hello\n\nworld', 6)
expect(isCursorOnEmptyLine(state)).to.be.true
})
it('returns true when cursor is on a whitespace-only line', function () {
const state = cursorState('hello\n \nworld', 8)
expect(isCursorOnEmptyLine(state)).to.be.true
})
it('returns false when cursor is on a line with content', function () {
const state = cursorState('hello world', 3)
expect(isCursorOnEmptyLine(state)).to.be.false
})
it('returns false when there is an active selection', function () {
const state = selectionState('hello\n\nworld', 0, 5)
expect(isCursorOnEmptyLine(state)).to.be.false
})
it('returns false when selection spans an empty line', function () {
const state = selectionState('hello\n\nworld', 4, 8)
expect(isCursorOnEmptyLine(state)).to.be.false
})
})
@@ -0,0 +1,81 @@
import { expect } from 'chai'
import { EditorState, EditorSelection } from '@codemirror/state'
import { selectHighlightedOrNearestToken } from '../../../../../frontend/js/features/source-editor/utils/select-highlighted-or-nearest-token'
function cursorState(doc: string, cursorPos: number) {
return EditorState.create({
doc,
selection: EditorSelection.cursor(cursorPos),
})
}
function selectionState(doc: string, from: number, to: number) {
return EditorState.create({
doc,
selection: EditorSelection.range(from, to),
})
}
function sliceRange(state: EditorState, range: { from: number; to: number }) {
return state.doc.sliceString(range.from, range.to)
}
describe('selectHighlightedOrNearestToken', function () {
it('returns current highlighted selection when it exists', function () {
const state = selectionState('hello world', 0, 5)
const result = selectHighlightedOrNearestToken(state)
expect(result).to.not.be.null
expect(sliceRange(state, result!)).to.equal('hello')
})
it('selects the token containing the cursor', function () {
const state = cursorState('hello world', 8)
const result = selectHighlightedOrNearestToken(state)
expect(result).to.not.be.null
expect(sliceRange(state, result!)).to.equal('world')
})
it('selects nearest token when cursor is in leading whitespace', function () {
const state = cursorState(' hello world', 0)
const result = selectHighlightedOrNearestToken(state)
expect(result).to.not.be.null
expect(sliceRange(state, result!)).to.equal('hello')
})
it('selects token when cursor is at its boundary', function () {
// pos 5 is immediately after "hello" (from=0, to=5) — pos equals to, so dist is 0
const state = cursorState('hello world', 5)
const result = selectHighlightedOrNearestToken(state)
expect(result).to.not.be.null
expect(sliceRange(state, result!)).to.equal('hello')
})
it('selects nearer token when cursor is between two tokens', function () {
// "hello world" — pos 6, "hello" ends at 5 (dist 1), "world" starts at 8 (dist 2)
const state = cursorState('hello world', 6)
const result = selectHighlightedOrNearestToken(state)
expect(result).to.not.be.null
expect(sliceRange(state, result!)).to.equal('hello')
})
it('selects earlier token when equidistant between two tokens', function () {
// "hello world" — pos 6 is equidistant from "hello" (ends at 5, dist 1) and "world" (starts at 7, dist 1)
const state = cursorState('hello world', 6)
const result = selectHighlightedOrNearestToken(state)
expect(result).to.not.be.null
expect(sliceRange(state, result!)).to.equal('hello')
})
it('returns null when line has no tokens', function () {
const state = cursorState(' \n\t ', 2)
const result = selectHighlightedOrNearestToken(state)
expect(result).to.be.null
})
})