Hello, I’ve been trying to create an editor where the first line and last line are readonly. Is there a way where I can mark certain ranges as readonly in CodeMirror6?
The recommended way to do this is to create a transaction filter that stops transactions which affect those ranges.
I’ve been able to do this with a changeFilter
. Issue I have now is that inserting characters before the readonly range doesn’t preserve the readonly range location (readonly ranged is fixed in place instead of moving).
I’ve been able to apply the underline mark to a range. I’d like to combine the two, so any underlined range becomes readonly. Is there a prop somewhere that allows me to check if the current selection is part of a marked range? Haven’t been able to find that anywhere so far.
@marijn Thank you so much for CodeMirror (6 especially). It’s really great on mobile. I’m sure that wasn’t easy. CM6 provides a lot of great lower level primitives. Means steeper learning curve but also much more powerful. For me the tradeoff is worth it, so thank you!
Alright… I have it 90% working with this code…
I’m using the underline decorator example. Then for any transaction I check tr.changes.touchesRange()
and compare against the decorator ranges. If there’s overlap I block the transaction (thus getting read-only ranges).
This works really well except for a few problems:
- If I delete an entire readOnly range
tr.changes.touchesRange()
returnsfalse
and so it just deletes it. From the docs I assume it should returncover
in this case. Did I misinterpret that? - For the life of me I can’t figure out how to see
deletes
show up intr.changes
. Is there a setting to enable that? - If I apply this to a line with a single char (closing bracket
}
) it doesn’t protect the character, but the next (empty) line. I’m guessing the line-ending/line-break is somehow interfering. - It took me a LONG time to write the iterating code below. Is there an easier way to iterate and compare changeRanges vs stored decorator ranges?
Any thoughts or ideas?
function readOnlyTransactionFilter() {
return EditorState.transactionFilter.of((tr) => {
if (tr.docChanged && !tr.annotation(Transaction.remote)) {
const newTr = [];
var readonlyRangeSet = tr.state.field(underlineField, false);
var readonlyRanges = readonlyRangeSet?.chunk || [];
// bail early if nothing was marked as read-only
if (!readonlyRangeSet?.chunk) {
return tr;
}
for (var i=0; i<readonlyRanges.length; i++) {
var chunk = readonlyRanges[i];
var offset = readonlyRangeSet?.chunkPos[i];
for (var j=0; j<chunk.from.length; j++) {
var start = chunk.from[j] + offset;
var end = chunk.to[j] + offset;
var readonlyRangesAffected = tr.changes.touchesRange(start, end);
if(readonlyRangesAffected) return [];
}
}
return tr;
}
Definitely don’t use undocumented properties like RangeSet.chunk
, those may break on any release. Also, you’ll want to use a consistent coordinate system, so using the read-only ranges from tr.startState
and the original-document coordinates in the change set seems safer. Something like this (untested) might work:
function readOnlyTransactionFilter() {
return EditorState.transactionFilter.of((tr) => {
let readonlyRangeSet = tr.startState.field(underlineField, false)
if (readonlyRangeSet && tr.docChanged && !tr.annotation(Transaction.remote)) {
let block = false
tr.changes.iterChangedRanges((chFrom, chTo) => {
readonlyRangeSet.between(chFrom, chTo, (roFrom, roTo) => {
if (chTo > roFrom && chFrom < roTo) block = true
})
})
if (block) return []
}
return tr
}
}
I’m seeing that for a covered delete operation the stateFields seem to get updated immediately. I’ll see if I can use the prior state for those numbers instead.
This worked BEAUTIFULLY! Thank you! I think the only change I had to make was to add a closing paren )
at the end of the 2nd-to-last line.
No rush, but I’m curious, what does a
vs b
represent here in fromA
vs fromB
(see image below)? I wasn’t certain which of these to use earlier. It looks like you’re dropping fromB
and toB
in the code above.
fromA
and toA
are the range in the original document, which is replaced by the fromB
to toB
range in the new document. (So for an insertion, fromA == toA
, and for a deletion, fromB == toB
).
Ah fascinating! I’ll need to experiment and see if I can use this to disallow either insertion
or deletion
only.
Thanks!
I just released codemirror-readonly-ranges
extension that easy allow to work with read-only ranges on CodeMirror6.
For anyone who is interested on the solution:
package: https://www.npmjs.com/package/codemirror-readonly-ranges
full documentation: https://andrebnassis.github.io/codemirror-readonly-ranges