Using the Ace Code Editor can I lock or make readonly a segment of code but still allow other lines of code to be written or edited during a session?
Asked
Active
Viewed 6,541 times
18
-
Do you need just one segment or many? can full segment be deleted? If selection includes part of readonly segment and user presses delete, should the rest of selected text be deleted? – a user Jul 26 '14 at 10:04
3 Answers
22
Here is the start of a solution:
$(function() {
var editor = ace.edit("editor1")
, session = editor.getSession()
, Range = require("ace/range").Range
, range = new Range(1, 4, 1, 10)
, markerId = session.addMarker(range, "readonly-highlight");
session.setMode("ace/mode/javascript");
editor.keyBinding.addKeyboardHandler({
handleKeyboard : function(data, hash, keyString, keyCode, event) {
if (hash === -1 || (keyCode <= 40 && keyCode >= 37)) return false;
if (intersects(range)) {
return {command:"null", passEvent:false};
}
}
});
before(editor, 'onPaste', preventReadonly);
before(editor, 'onCut', preventReadonly);
range.start = session.doc.createAnchor(range.start);
range.end = session.doc.createAnchor(range.end);
range.end.$insertRight = true;
function before(obj, method, wrapper) {
var orig = obj[method];
obj[method] = function() {
var args = Array.prototype.slice.call(arguments);
return wrapper.call(this, function(){
return orig.apply(obj, args);
}, args);
}
return obj[method];
}
function intersects(range) {
return editor.getSelectionRange().intersects(range);
}
function preventReadonly(next, args) {
if (intersects(range)) return;
next();
}
});
see it working in this fiddle: http://jsfiddle.net/bzwheeler/btsxgena/
The major working pieces are:
- create start and end ace anchors which track the location of a 'readonly' portion as the document around it changes.
- create a range to encapsulate the anchors
- add a custom keyhandler to check if the current impending keypress will affect the readonly range and cancel it if so.
- add custom paste/cut handlers to protect against right-click menu and browser menu cut/paste actions

bzwheeler
- 221
- 2
- 5
-
I'm having trouble understanding what's going on in the 'before' function, but I know that 'origArgs' is never defined and causes 'preventReadOnly' to puke when it tries to call next() after a cut or paste. – Southerneer Feb 26 '16 at 02:43
-
Good catch! I edited the before() implementation to use `.call` and to pass along `args` and not `origArgs` which was undefined as you noticed. – bzwheeler Apr 05 '16 at 11:34
-
-
How to make multiple lines readonly. By passing arrayof range i.e start and end. – G Naga Subrahmanyam Apr 06 '18 at 16:37
3
You can do it by listening to the exec
events:
// Prevent editing first and last line of editor
editor.commands.on("exec", function(e) {
var rowCol = editor.selection.getCursor();
if ((rowCol.row === 0) || ((rowCol.row + 1) === editor.session.getLength())) {
e.preventDefault();
e.stopPropagation();
}
});

Hugodby
- 1,124
- 9
- 24
-
While this approach is nice and simple, the main problem with it is that it does not prevent you from removing the line break between first and second line, making it impossible to edit after that. Also, if you move your cursor using arrow keys, it gets stuck on first and last lines. – Nicolas Nov 05 '21 at 10:57
1
I suggest something else easier and more reliable to prevent range to be modified (check it!)
var old$tryReplace = editor.$tryReplace;
editor.$tryReplace = function(range, replacement) {
return intersects(range)?null:old$tryReplace.apply(this, arguments);
}
var session = editor.getSession();
var oldInsert = session.insert;
session.insert = function(position, text) {
return oldInsert.apply(this, [position, outsideRange(position)?text:""]);
}
var oldRemove = session.remove;
session.remove = function(range) {
return intersects(range)?false:oldRemove.apply(this, arguments);
}
var oldMoveText = session.moveText;
session.moveText = function(fromRange, toPosition, copy) {
if (intersects(fromRange) || !outsideRange(toPosition)) return fromRange;
return oldMoveText.apply(this, arguments)
}
outsideRange = function (position) {
var s0 = range.start;
if (position.row < s0.row || (position.row == s0.row && position.column <= s0.column)) return true; // position must be before range.start
var e0 = range.end;
if (position.row > e0.row || (position.row == e0.row && position.column >= e0.column)) return true; // or after range.end
return false;
}
intersects = function(withRange) {
var e = withRange.end, s0 = range.start, s = withRange.start, e0 = range.end;
if (e.row < s0.row || (e.row == s0.row && e.column <= s0.column)) return false; // withRange.end must be before range.start
if (s.row > e0.row || (s.row == e0.row && s.column >= e0.column)) return false; // or withRange.start must be after range.end
return true;
}

user6871461
- 11
- 3