microsoft / monaco-editor

A browser based code editor
https://microsoft.github.io/monaco-editor/
MIT License
40.02k stars 3.57k forks source link

Provide ability to block editing specific lines #953

Open sinaa opened 6 years ago

sinaa commented 6 years ago

Monaco is a great editor for providing programmatic interfaces for users to an application.

One feature that'd significantly help with the user experience is to make specific lines read-only, such that user can enter their code within a specific block (e.g., between a function block).

Would it be possible to include this as a core features/API of the editor?

rcjsuen commented 6 years ago

@sinaa This is not possible. See #874.

sinaa commented 6 years ago

Thank you for the reply. I have seen the previous issue, and this request is essentially another plea to reconsider adding back the feature (or providing some sort of native alternative).

Providing editable/read-only ranges is really useful, and the hack suggested is rather dirty/doesn't make for a good experience.

For example, consider an educational software where the following template is created, and the user/student has to enter their code in between:

function foo(arg1, arg2) {
   // your code goes here

}
bcdev-com commented 6 years ago

This would be very useful for me as well.

infinite-system commented 6 years ago

Yes, this would be amazingly useful for me as well, I am even considering to switch from Ace editor, if this feature can be implemented in Monaco.

zewa666 commented 6 years ago

Agree this would be indeed very helpful for a scenario I'm having with a current app. Based on the comment in the first line (which contains meta) additional rules are applied. So having the first line locked down in order to prevent the user from unintentionally deleting it (CTRL+A DEL) would be awesome.

ng2dev commented 5 years ago

yes please lets make this feature happen!

Kang-Jun-sik commented 5 years ago

SetEditableRange Function is really useful i think....

virus2016 commented 5 years ago

Is this possible?

Kang-Jun-sik commented 5 years ago

@virus2016 nope... but if you use monaco editor version 0.4.X ... you can do that...

ntohidi commented 5 years ago

Yeah, that would be really useful for me as well, I am thinking to use monaco editor for our production

chetanladdha commented 5 years ago

Is there any plan to add this feature in future releases?

iMoses commented 5 years ago

@alexandrudima is it something that can be implemented elegantly with the editor's current design? will you accept a PR adding back this functionality, assuming you answered yes on the former question?

I'm not sure from reading these discussions if this is a hopeless cause or simply lacking interest and manpower.

Perhaps this can be solved by a different kind of solution, like adding a before-change event which offers a canceling mechanism for changes... Any alternative which isn't a hack would be a blessing..

CosmoMyzrailGorynych commented 4 years ago

Another use case is wrapping code in a function when the context is different from globalThis. E.g. in my game editor, I have fields for editing several functions that are executed by specific classes, and thus I need a code like this to tell the context to Typescript, with "frozen" first and last lines:

function onCreate(this: Copy) {
   // User-written code
}
LoganDark commented 4 years ago

I would personally appreciate a little more granularity than just 'lines', possibly a character range, with helper methods for doing lines?

mehulmpt commented 4 years ago

Is there any update on this issue? Any possible workarounds for non-js files meanwhile?

Kang-Jun-sik commented 4 years ago

@mehulmpt probably... there was no update for this issues for about 2 year... maybe? m.m

GNSubrahmanyam commented 4 years ago

Is there any update on this issue?

GNSubrahmanyam commented 4 years ago

@alexdima Any updates on this? Or any alternative ?

GNSubrahmanyam commented 4 years ago

Still no updates

GNSubrahmanyam commented 4 years ago

@rcjsuen How to achive this using existing monaco api?

rcjsuen commented 4 years ago

@rcjsuen How to achive this using existing monaco api?

@GNSubrahmanyam Sorry, I have no idea. This is not something I have ever tried to investigate as I do not have this use case in my own use of the Monaco Editor.

GNSubrahmanyam commented 4 years ago

Example image

from line 14 it should me editable.

Kang-Jun-sik commented 4 years ago

Example image

from line 14 it should me editable.

If you use SetEditableRange function() then you should use previous Version of Monaco Editor. Probabley... I think you can find previous version in release history in github page.. (0.13.X?... I think) good luck.

Pranomvignesh commented 4 years ago

@GNSubrahmanyam, I think this post will be useful for you. I have created a way to restrict editable area in monaco-editor. This basically undos the values typed in the restricted area. You can see the demo here. I am not sure whether it will have good performance ( Try it by yourself in the demo ) , But I thought something is better than nothing. You can refer the complete code here

shmish111 commented 3 years ago

This is a feature that we need, we have had to hack around it, just wanted to add a +1

ricardotestuser commented 3 years ago

up

leopsidom commented 3 years ago

It would be awesome to have this feature implemented

corbane commented 3 years ago

it's not really complicated to implement this:

const editor = monaco.editor.create(document.getElementById("container"), {
    value: [
        "const readonlyRange = new monaco.Range (10, 0, 15, 0)",
        "editor.onKeyDown (e => {",
        "    const contains = editor.getSelections ().findIndex (range => readonlyRange.intersectRanges (range))",
        "    if (contains !== -1) {",
        "        e.stopPropagation ()",
        "        e.preventDefault ()",
        "    }",
        "})",
    ].join ("\n"),
    language: "javascript"
})

const readonlyRange = new monaco.Range (5, 0, 7, 0)
editor.onKeyDown (e => {
    const contains = editor.getSelections ().findIndex (range => readonlyRange.intersectRanges (range))
    if (contains !== -1) {
        e.stopPropagation ()
        e.preventDefault () // for Ctrl+C, Ctrl+V
    }
})
Pranomvignesh commented 3 years ago

it's not really complicated to implement this:

const editor = monaco.editor.create(document.getElementById("container"), {
  value: [
        "const readonlyRange = new monaco.Range (10, 0, 15, 0)",
        "editor.onKeyDown (e => {",
        "    const contains = editor.getSelections ().findIndex (range => readonlyRange.intersectRanges (range))",
        "    if (contains !== -1) {",
        "        e.stopPropagation ()",
        "        e.preventDefault ()",
        "    }",
        "})",
    ].join ("\n"),
  language: "javascript"
})

const readonlyRange = new monaco.Range (5, 0, 7, 0)
editor.onKeyDown (e => {
    const contains = editor.getSelections ().findIndex (range => readonlyRange.intersectRanges (range))
    if (contains !== -1) {
        e.stopPropagation ()
        e.preventDefault () // for Ctrl+C, Ctrl+V
    }
})

This idea is quite simple and elegant, but i guess this has its downsides. These ranges seems static, so if I press enter on line no 4 , it becomes line 5 and my previous content ( content which has to be readonly ) in line 7 goes to 8, thus making it editable

Please refer the video for demo

https://user-images.githubusercontent.com/29809906/113811043-1abf7200-9789-11eb-8628-06b55aface6d.mov

PS : If you are interested please refer my way of implementing restricted editor which will handle the above downside. The link for the website is available here

corbane commented 3 years ago

Indeed my comment is a little naive. There are many special cases to deal with. I hadn't seen your post and code, and created mine with a different approach.

I have not tested it in real cases but you can try it in the playground: https://gist.github.com/corbane/37c8751b6c60018a03fa4dd3ea48bf3e

rgov commented 3 years ago

I developed an application based on Monaco in which the user can only edit the last line (like a terminal). There are a lot of cases to test out like pasting in content with newlines, pressing backspace on boundaries, and so on.

I found it very reliable to simply call undo() when I detect that any change is outside the writable bounds. It is performant, handles all of the cases I've thrown at it, and doesn't seem to have any side effects. If someone knows of a reason it should fail, I would be interested to know.

I also added an editor.ignoreChange property that lets my own code make changes that are exempt from the restrictions. (A little hacky, perhaps?)

editor.onDidChangeModelContent((e) => {
    if (e.isUndoing || editor.ignoreChange) { return; }

    for (const ch of e.changes) {
        if (<some test of ch.range to check if it is invalid>) {
            editor.getModel().undo();
            return;
        }
    }
}
dustinlacewell commented 3 years ago

Oh I'm crushed this isn't a feature.

aboqasem commented 3 years ago

Wow a lot need this feature. I also need it for my final year project, I guess I'd have to fork and try to hack around until it works.

LoganDark commented 3 years ago

It appears Microsoft won't implement anything that isn't directly useful to VS Code

xswei commented 2 years ago

I developed an application based on Monaco in which the user can only edit the last line (like a terminal). There are a lot of cases to test out like pasting in content with newlines, pressing backspace on boundaries, and so on.

I found it very reliable to simply call undo() when I detect that any change is outside the writable bounds. It is performant, handles all of the cases I've thrown at it, and doesn't seem to have any side effects. If someone knows of a reason it should fail, I would be interested to know.

I also added an editor.ignoreChange property that lets my own code make changes that are exempt from the restrictions. (A little hacky, perhaps?)

editor.onDidChangeModelContent((e) => {
    if (e.isUndoing || editor.ignoreChange) { return; }

    for (const ch of e.changes) {
        if (<some test of ch.range to check if it is invalid>) {
            editor.getModel().undo();
            return;
        }
    }
}

I tried this way,but need to pay special attention to that when formatting the code, it may be misjudged

instance.onDidChangeModelContent((e) => {
        const model = instance.getModel();
        let validatePass = false;
        if (frozenHeaderLine && model) {
          const headerContent = model.getLineContent(1);
          if (frozenHeaderLine === headerContent) { // here use === 
            validatePass = true;
          } else {
            instance.trigger('undo', 'undo', '');
            onWarning?.('can't edit headerline! ');
            return;
          }
        }
}
Pranomvignesh commented 2 years ago

I have developed a plugin named constrained-editor-plugin to add this feature to the monaco editor. This plugin is available as a node package

Demo

https://user-images.githubusercontent.com/29809906/140028096-740cfdae-b232-4848-99f9-bdb65ff262f7.mov

Links for Playground and Sample code is available in API Documentation

pawansingh00 commented 2 years ago

This is the only thing stopping me to move from Ace Editor to Monaco Editor. This will be really useful.

pawansingh00 commented 2 years ago

@Progyan1997 Hi were you able to find any solution for this -- https://github.com/microsoft/monaco-editor/issues/874#issuecomment-459674988

dustinlacewell commented 2 years ago

@pawansingh00 a person right above you mentions releasing an NPM package to do it...

pawansingh00 commented 2 years ago

@dustinlacewell Saw it, and other solutions mentioned too (Yours one too -- https://github.com/microsoft/monaco-editor/issues/953#issuecomment-834099015 ) and already trying it out too. But was hesitant to use it because of this comment from author -- https://github.com/microsoft/monaco-editor/issues/953#issuecomment-675077171

So, was looking for any other alternative solution. Thanks for pointing it out though.

Pranomvignesh commented 2 years ago

But was hesitant to use it because of this comment from author -- #953 (comment)

@pawansingh00 The performance problem which I told was about the older version of that restricted editor, in which I used regex recursively to validate the changes in the content. The npm package works in a different way. The latest one uses range values and compares the position of the entered content with the set of allowable ranges. If the current position of the content is not in the allowed ranges it will undo the changes.

I am using this package for quite a while now and I haven't felt any performance issues till now.

chethan-devopsnow commented 2 years ago

But was hesitant to use it because of this comment from author -- #953 (comment)

@pawansingh00 The performance problem which I told was about the older version of that restricted editor, in which I used regex recursively to validate the changes in the content. The npm package works in a different way. The latest one uses range values and compares the position of the entered content with the set of allowable ranges. If the current position of the content is not in the allowed ranges it will undo the changes.

I am using this package for quite a while now and I haven't felt any performance issues till now.

@Pranomvignesh can you please help me figure out a way to integrate this with https://www.npmjs.com/package/@monaco-editor/react ?

DavraYoung commented 2 years ago

The hack that was proposed earlier with Undo does not work if user does Redo operation. I solved it with another hack, which removes future stack from undoRedoService in monaco-editor@0.34.0

// usually here goes editor.onDidChangeModelContent callback with content validation....
// ....

editor.trigger("undo", "undo", null);
(
  model as unknown as {
    _undoRedoService: {
      _editStacks: Map<string, { _future: [] }>;
    };
  }
)._undoRedoService._editStacks
.get(model.uri.toString())
?._future.pop()

This code is not recommended to run in next versions of Monaco, as it is not guaranteed to have same properties

fmorshed52 commented 1 year ago

Any progress on this feature? For me, I need the ability to make a property readonly in the monaco editor JSON language. See https://stackoverflow.com/questions/75033605/how-do-i-make-a-property-readonly-in-monaco-editor-json-language

mmazzarolo commented 1 year ago

👋 I'm using a mixture of JS + CSS to disable editing a specific set of lines. Sharing in case anyone is interested.

// Disable editing specification_id, version, and app values
editor.createDecorationsCollection([
  {
    range: new monaco.Range(1, 0, 3, 0), // Block lines 1 to 3
    options: {
      isWholeLine: true,
      className: 'editor-line-readonly',
    },
  },
]);
// Disable line editing (handled from the CSS side but here as well to be safe)
editor.onKeyDown(e => {
  const isInBlockedRange = editor
    .getSelections()
    ?.findIndex(range => new monaco.Range(1, 0, 4, 0).intersectRanges(range)) !== -1;  // Block lines 1 to 3
  if (isInBlockedRange) {
    e.stopPropagation();
    e.preventDefault();
  }
});
/* ⚠️ HACK
 * The Monaco editor doesn't provide a way to disable editing specific lines. 
 * As a workaround, from the JS side we apply a `.editor-line-readonly` classname decoration to the lines we want
 * to disable and make them non-editable from the CSS side. 
 * The decoration elements are layered underneath the code lines and are absolutely placed within a parent `div`, so 
 * to target such `div` we use the `:has` pseudoclass (notice it's not available in Firefox yet). 
 * DOM example for reference:
 * - div class="view-overlays"
 *   - div (one for each code line)
 *     - div class="editor-line-readonly" (positioned absolutely)
 * - div class="view-lines"
 *   - div (one for each code line)
 *     - span (the code line text)
 */
div:has(>.editor-line-readonly) {
  background-color: #00000005;
  cursor: not-allowed;
  z-index: 999; /* Moves this div above the code line making it not selectable */
}
TiagoSilvaPereira commented 1 year ago

Pranomvignesh

@Pranomvignesh thank you for creating this plugin

https://github.com/Pranomvignesh/constrained-editor-plugin

jhk-mjolner commented 1 year ago

Inspired by @mmazzarolo's example above I tried setting the entire editor to readonly when a selection intersects a locked line. It seems to work great, and can include a message for the user. It also handles right-click operations, whereas the above only blocks keyboard events. That is, if a selection includes a locked line, the right-click menu will only include 'copy', not 'cut' and 'paste'.

private lockLinesViaReadOnlyEditor(editor: MonacoEditor.IStandaloneCodeEditor, lockedLineNumbers: number[]) {
    editor.onDidChangeCursorSelection(_ => {
      const selectionInLockedRange = editor.getSelections()?.some(selection => {
        return lockedLineNumbers.some(lockedLineNumber => {
          return selection.intersectRanges(new Range(lockedLineNumber, 0, lockedLineNumber + 1, 0));
        });
      });
      editor.updateOptions({readOnly: selectionInLockedRange, readOnlyMessage: {value: 'Cannot edit locked lines.'}});
    });
  }

I'm using individual line numbers in this example, but you can change it to use ranges if you like.

Yrobot commented 11 months ago

I have developed a plugin named constrained-editor-plugin to add this feature to the monaco editor. This plugin is available as a node package

Demo

Screen.Recording.2021-11-03.at.1.49.33.PM.mov Links for Playground and Sample code is available in API Documentation

very nice, this plugin just cover my needs. thx

surajpothuganti commented 10 months ago

This Can be possible as Monaco is not Supporting ReadOnly Ranges but it is still supporting ReadOnly so we can get the cursor position and change the editor options accordingly Here is the following which I have done In my angular Project HTML <ngx-monaco-editor class="my-code-editor" id="editor" [options]="editorOptions" formControlName="code" (onInit)="onInit($event)"></ngx-monaco-editor> Ts file onInit(editor) { // save the editor instance for later use this.editor = editor; this.editor.onDidChangeCursorPosition((event) => { const position = event.position; if (position.lineNumber < this.editStart || position.lineNumber > this.editEnd) { this.editor.updateOptions({ readOnly: true }); } else { this.editor.updateOptions({ readOnly: false }); } }); }

starball5 commented 2 months ago

Related on Stack Overflow: How to set read only for certain lines in VS Code?