Markdown Monster
How to replace block text
Gravatar is a globally recognized avatar based on your email address. How to replace block text
  Charles Flatt
  All
  Jan 4, 2019 @ 12:53pm

Hi, Rick.

I'm writing a small snippet to right-align block text. One use case for this is right-aligning table column numbers.

SetSelection() isn't working because it replaces at the current cursor. There's a SetSelectionRange() method, but I don't find a corresponding method to tell me the current start/end row/col positions. I know you're replacing block text somehow!

Here's my current code. Suggestion?

Script Mode: Razor Template

@{
    string sel = Model.ActiveEditor.GetSelection().Replace("\r","");
    if (String.IsNullOrWhiteSpace(sel))
    {
        return;
    }
    var lines = sel.Split('\n').Select(a => a.Trim());
    var max = lines.Max(a => a.Length);
    var newLines = lines.Where(a => a.Length > 0).Select(a => a.PadLeft(max,' ')).ToList();
    Model.ActiveEditor.SetSelection(String.Join("\r\n", newLines));
}
Gravatar is a globally recognized avatar based on your email address. re: How to replace block text
  Rick Strahl
  Charles Flatt
  Jan 5, 2019 @ 01:23pm

Why wouldn't the current selection work? If you're right aligning a block the starting point would be the same no if you're selecting the lines of text?

I think you can:

  • editor.GetSelectionRange() - get the start and end selections row/col
  • Capture start and end row
  • editor.SetSelectionRange() - set start and end row as before, adjust the columns to 0

If that's not enough you can dig into the JavaScript functions that are available in editor.js. The top level object map is exposed in MM as ActiveEditor.AceEditor. Some of the functions are mapped on the .NET object, but there are many more in JavaScript that you can call. You can also get access to the underlying Ace Editor instance with ActiveEditor.AceEditor.editor which basically gives you access to all of Ace Editor (assuming the signatures can be called from .NET - most can).

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: How to replace block text
  Charles Flatt
  Rick Strahl
  Jan 5, 2019 @ 10:01pm

It took about five hours to get clear on this. Ace's documentation isn't stellar.

First, it may not have been clear that, when I said a column of text in a table, I meant a block selection.

I would have tried GetSelectionRange(), but it's not in the MM online API documentation so I didn't see it. It wouldn't have mattered, because the Ace editor's methods for getting and setting selections don't work as expected for blocks. Here's your setselection function:

setselection: function (text) {
    var range = te.editor.getSelectionRange();
    te.editor.session.replace(range, text);
    te.editor.renderer.scrollSelectionIntoView();
  },

After selecting a block of text, and examining the range variable in debug, you'll find the start and end rows are both the same--the last row. This is because, in block edit ("multiselect"), the selection object contains an array of ranges, one for each line. This is why the code I included in my original post was pasting the aligned text at the end of the selected block. In block edit, getSelectionRange() does not return the selected text's range.

I haven't finished the MM Snippet yet--it's late. But here's a full Ace html sample page that right aligns block text, regular text with line feeds, and a single line of text with spaces at the end. I expect I can use similar methods for the snippet (or C# script).

Bonus: This even works if you make multiple selections using CTRL-Click-Drag.

If it's OK, I'll add another comment with the snippet code when it's finished. Might be useful to someone else.

<!DOCTYPE html>
<html lang="en">

<head>
    <title>ACE in Action</title>
    <style type="text/css" media="screen">
        #editor {
            position: absolute;
            top: 50px;
            right: 0;
            bottom: 0;
            left: 0;
        }
    </style>
</head>

<body>
    <div>
        <button onclick="rightAlign();">Right Align</button>
    </div>

    <div id="editor">
|12    |asdf|asdf
|123   |asdf|asdf
|1     |asdf|asdf
|1234  |asdf|asdf
|12345 |asdf|asdf

12   
123  
1    
1234 
12345

asdf   
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.2/ace.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.2/mode-markdown.js"></script>
<script>
    var editor = ace.edit("editor");
    editor.setTheme("ace/theme/textmate");
    editor.session.setMode("ace/mode/markdown");

    function rightAlign() {
        var text = editor.getSelectedText();
        var lines = text.split('\n');
        lines = padRightArray(lines);

        var selection = editor.getSelection();
        if (selection.rangeCount > 0) {
            //TODO: manage logic if isBackward=true.
            //Ace puts the last row first.
            var i = 0;
            var end = lines.length - 1;
            while (i <= end) {
                var t = lines[i];
                var r = selection.ranges[end - i];
                editor.session.replace(r, t);
                i++;
            }
        }
        else {
            var range = editor.getSelectionRange();
            if (lines.length > 1){
                text = lines.join('\n')
            }
            else {
                text = text.trim().padStart(text.length, ' ');
            }
            editor.session.replace(range, text);
        }
    }

    function padRightArray(lines){
        var i = 0;
        while (i <= lines.length - 1){
            var t = lines[i];
            t = t.trim().padStart(t.length,' ');
            lines[i] = t;
            i++;
        }
        return lines;
    }
</script>
</body>

</html>
Gravatar is a globally recognized avatar based on your email address. re: How to replace block text
  Rick Strahl
  Charles Flatt
  Jan 6, 2019 @ 03:11am

LOL! Yup, the ACE docs are pretty terrible, but there's a fair bit of information available if you dig on StackOverflow or their forums. But yeah hit or miss.

I would have tried GetSelectionRange(), but it's not in the MM online API documentation

Yeah I guess it's time to update the docs from the assemblies. Intellisense on the Editor helps...

If you get this working you can add a function to the bottom of your template and you can call that template from MM:

<script>
te.getSelectionBlock: function(parm1, parm2) {

}
</script>

Then from your Addin/Script you can do this:

Model.ActiveEditor.AceEditor.getSelectionBlock(parm1, parm2);

I've added a topic to the help file:

And I've also updated the docs with current class documentation. The latest pre-release (1.14.7.2+) is required for the editor-user-extensions.js file to work - otherwise you can add the script code to your editor.html file but note that file will be overwritten in the next update.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: How to replace block text
  Charles Flatt
  Rick Strahl
  Jan 6, 2019 @ 09:59am

This is a nice feature. However, running the installer to upgrade didn't replace the editor.htm file in my installation folder (%LocalAppData%\Markdown).

I manually updated that file, and got the Hello World to run. However, adding (or even only using) this line,

var ace = window.textEditor.editor;

causes MM to crash. I won't include the full stack trace, but here's the first bit. It looks like the dynamic COM interop loading is failing.

1/6/2019 10:51:40 AM - AppDispatcher Error: Exception from HRESULT: 0x80020101
Exception from HRESULT: 0x80020101
Markdown Monster v1.14.7.0
10.0.17763.1.x86fre.rs5_release.180914-1434 - en-US - NET 4.7.2 - 32 bit
en-US - en-US    
---
mscorlib
   at System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 errorCode, IntPtr errorInfo)
   at System.Dynamic.ComRuntimeHelpers.CheckThrowException(Int32 hresult, ExcepInfo& excepInfo, UInt32 argErr, String message)
   at CallSite.Target(Closure , CallSite , ComObject , Boolean )
   at CallSite.Target(Closure , CallSite , Object , Boolean )
   at MarkdownMonster.MarkdownDocumentEditor.SetEditorFocus() in C:\projects2010\MarkdownMonster\MarkdownMonster\_Classes\MarkdownDocumentEditor.cs:line 1382
   at MarkdownMonster.MainWindow.TabControl_OnPreviewMouseLeftButtonUp(Object sender, MouseButtonEventArgs e) in C:\projects2010\MarkdownMonster\MarkdownMonster\MainWindow.xaml.cs:line 2781

I'm going to backup my user files and do an uninstall/reinstall. Would you rather I moved this info to a Git issue?

Gravatar is a globally recognized avatar based on your email address. re: How to replace block text
  Rick Strahl
  Charles Flatt
  Jan 6, 2019 @ 12:40pm

Yeah you can't reference the editor at that point because it's not ready yet. The editor doesn't activate until the .NET tells it too. Recent change actually to avoid double formatting the document one way then re-formatting for display to the user options.

You should do:

var te = window.textEditor;
te.myFunction = function(parm1) {
   var ace = te.editor;
}

and that should work.

Not sure why MM is crashing though - the JavaScript error should just silently fail. Taking a look.

Ok, so took a look. I think the problem there is with ace as the variable name. Somehow that's interfering with ACE Editor if you change the variable to aceEditor then the code will still not run, but it won't crash MM.

var aceEditor = window.textEditor.editor;
alert(aceEditor);

This should show null (because it's not initialized yet) where ace as the var would bomb.

You need 1.14.5.3 for editor.htm with the update I believe for this to work - the preview release...

+++ Rick ---

© 1996-2019