Insert Text at Caret on CodeMirror Textarea
Asked Answered
R

2

8

I am working on a Python editor and would like to add a feature to insert text at the position of the caret on a CodeMirror textarea.

There is a series of pictures which can be clicked. When one is clicked, the alt attribute of that picture gets saved, and then when you click again inside the textarea it gets copied to your mouse position (a demo fiddle: https://jsfiddle.net/t0k7yp7n/1/)

Here is a script for the text insertion part:

selected = '';

$('.insert').click(function() {
    console.log($(this).attr('alt'));
    selected = $(this).attr('alt');
});

$('#textbox').click(function() {
    insertAtCaret('textbox', selected)
        // Clear the selection so it isn't copied repeatedly
    selected = '';
});

function insertAtCaret(areaId, text) {
    var txtarea = document.getElementById(areaId);
    var scrollPos = txtarea.scrollTop;
    var strPos = 0;
    var br = ((txtarea.selectionStart || txtarea.selectionStart == '0') ?
        "ff" : (document.selection ? "ie" : false));
    if (br == "ie") {
        txtarea.focus();
        var range = document.selection.createRange();
        range.moveStart('character', -txtarea.value.length);
        strPos = range.text.length;
    } else if (br == "ff") strPos = txtarea.selectionStart;

    var front = (txtarea.value).substring(0, strPos);
    var back = (txtarea.value).substring(strPos, txtarea.value.length);
    txtarea.value = front + text + back;
    strPos = strPos + text.length;
    if (br == "ie") {
        txtarea.focus();
        var range = document.selection.createRange();
        range.moveStart('character', -txtarea.value.length);
        range.moveStart('character', strPos);
        range.moveEnd('character', 0);
        range.select();
    } else if (br == "ff") {
        txtarea.selectionStart = strPos;
        txtarea.selectionEnd = strPos;
        txtarea.focus();
    }
    txtarea.scrollTop = scrollPos;
}

And here is the CodeMirror textarea part:

var editor;

//<![CDATA[
window.onload = function() {
    editor = CodeMirror.fromTextArea(document.getElementById('textbox'), {
      mode: {
        name: "python",
        version: 2,
        singleLineStringErrors: false
      },
      lineNumbers: true,
      indentUnit: 4
    });
  } //]]>
<!DOCTYPE html>
<html>

<head>

  <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js" type="text/javascript"></script>
  <script src="http://www.skulpt.org/static/skulpt.min.js" type="text/javascript"></script>
  <script src="http://www.skulpt.org/static/skulpt-stdlib.js" type="text/javascript"></script>
  <script src="https://www.cs.princeton.edu/~dp6/CodeMirror/lib/codemirror.js" type="text/javascript"></script>
  <script src="https://www.cs.princeton.edu/~dp6/CodeMirror/mode/python/python.js" type="text/javascript"></script>
  <script src="skulpt-codemirror.js" type="text/javascript"></script>
  <script src="load-save-py.js" type="text/javascript"></script>
  <script src="insert.js" type="text/javascript"></script>
  <link href="https://www.cs.princeton.edu/~dp6/CodeMirror/lib/codemirror.css" rel="stylesheet" type="text/css">
  <title>Python Editor</title>
</head>

<body>
  Filename:
  <input id="inputFileNameToSaveAs">
  <button onclick="saveTextAsFile()">Save</button>
  <br>
  <input type="file" id="fileToLoad">
  <button onclick="loadFileAsText()">Open</button>
  <br>
  <br>
  <a href="#!">
    <img class="insert" alt="#1">
    <img class="insert" alt="#2">
    <img class="insert" alt="#3">
  </a>
  <br>
  <br>
  <textarea id="textbox" name="textbox"></textarea>
  <br>
  <button onclick="runit()" type="button">Run</button>
  <pre id="dynamicframe"></pre>
  <div id="canvas"></div>
</body>

</html>

When I put them together in one file, though, when I click the pictures their alts do not copy over to the textarea. Why is this and how do I fix it?

Redemptioner answered 23/8, 2016 at 6:22 Comment(0)
P
2

When using CodeMirror, your <textarea /> will be visually replaced by an editor provided by CodeMirror and most of your code relative to your <textarea /> won't be usable as is.

What's going on on the background is that your actual <textarea /> will first be marked with a style display: none;. Not displayed, no event binded on the <textarea /> will actually trigger. Then, CodeMirror will actually add his own code to the DOM to display a new editor at the position of your <textarea /> which is now not displayed.

For example, the HTML code for a newly initialized CodeMirror editor with the string 'Hello World' written in it would looks like:

<div class="CodeMirror-lines">
    <div style="position: relative; outline: none;">
        <div class="CodeMirror-measure">
            <div style="width: 50px; height: 50px; overflow-x: scroll;"></div>
        </div>
        <div class="CodeMirror-measure"></div>
        <div style="position: relative; z-index: 1;"></div>
        <div class="CodeMirror-cursors">
            <div class="CodeMirror-cursor" style="left: 74px; top: 0px; height: 13px;">&nbsp;</div>
        </div>
        <div class="CodeMirror-code">
            <div style="position: relative;">
                <div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;">
                    <div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">1</div>
                </div><pre><span style="padding-right: 0.1px;"><span class="cm-variable">Hello</span> <span class="cm-variable">World</span></span></pre></div>
        </div>
    </div>
</div>

Your <textarea /> is no longer being used.

CodeMirror provides natively a programming API which can be used to do what you want. Basically, the steps required are:

  • Check when the editor is focused.
  • When focused, check if a picture has previously been clicked and if a selected text is available (the alt of the image).
  • If yes, insert the selected text (the alt of the image) at the current position.

The JavaScript code associated to these steps would looks like:

// Listen to the editor focus events.
editor.on('focus', function () {
  // Only insert if a value has been previously selected.
  if (selected.length > 0) {
    // Fetch the current CodeMirror document.
    var doc = editor.getDoc();

    // Insert the text at the cursor position.
    doc.replaceSelection(selected);

    // Clear the selection so it isn't copied repeatedly.
    selected = '';
  }
});

You can check a working example on this JSFiddle.

Pianissimo answered 25/8, 2016 at 8:32 Comment(6)
thanks for this: I have another featured question about CodeMirror if you want to answer it: #39075983Redemptioner
Is this answer ok? I'll take a look asap at the other one if that's the case.Pianissimo
yes, thanks. I would be really grateful if you did look into it as the other answerer is having a bit of trouble, and you know how to use CodeMirror well!Redemptioner
What do you think of the other question? Can you answer it?Redemptioner
Hello again, can you help on a question to do with skulpt and codemirror? It is #39075983. ThanksRedemptioner
Hey again, can you answer it?Redemptioner
S
1

In Short, this code will replace code mirror current selection (or current cursor position when there isn't any selection) with a text:

codemirror.doc.replaceSelection('text to replace');

Seger answered 19/3, 2020 at 20:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.