Noah W.org

Furthermark

In a recent blog post on my website redesign, I remarked that I had been using Markdown to write my blog posts. I have come to quite like Markdown as a format to use to write sine it keeps my hands on the keyboard and enables an easy way to format my posts without the need to use a full-featured work processing application.

I have been using MarkdownPad as my markdown editor as it appeared to be the only good editor available on Windows (side note: why is it that all of the interesting markdown development is happening on macOS?). The problem with MarkdownPad is that it hasn't been updated in some time (it seems new development has stopped) and on Windows 10 it requires some very specific developer library to be installed in order to get the live preview functionality to work. I haven't seen any good UWP markdown editors in the Windows Store, so I thought: why don't I make one myself?

Futhermark

Enter Furthermark. I created Furthermark as a simple UWP markdown editor that uses some of my favorite features of MarkdownPad. Namely:

  • A simple toolbar with common functions
  • Ability to insert a timestamp (I actually use this a lot)
  • A status bar to show common text editor items
  • An easy way to resize the live preview

I think I came pretty close. There's still a long road ahead, but I think I have a pretty good version 1.0.

Furthermark main interface

UWP is an interesting platform to develop for; the sandboxing and the built-in controls don't have the flexibility that Win32 or traditional .NET development allow for. As an application that will only target desktop platforms, Furthermark had to sacrifice some "traditional" application paradigms in order to get the results I wanted.

The biggest example is the resize slider for the preview window. Most windows control libraries (WinForms, WPF, MFC) have a control for a resize panel or a slider. UWP does not. I could (and would like to in a future version) implement my own control that behaves similarly to a resize panel, but it would take a lot of extra effort. So for now, I implemented a slider control to change the size. Not ideal, but it works.

The other example is the status bar. I wanted a status bar to show

  1. Word count
  2. Line count
  3. Character count/length
  4. Current line number and column

These are things that a lot of people expect from a text editor. Common editors from Notepad to Microsoft Word have some variation of these features. Should be pretty easy, right? As it turns out the status bar became the hardest part to implement. Getting the bar with no content was easy. Furthermark uses a single frame with a UWP Grid control, so my "status bar" is just the last row in my grid. Done.

The hard part were the items inside the status bar. In my first out of (hopefully) many posts, I want to outline how I got the status bar working the way I wanted it to. I plan to opensource Furthermark once I get it working the way I want and clean up some of the code.

Also, I want to warn you all, I wanted to brush up on some of my modern .NET Syntax, and I was lacking in the VB.NET arena, so as a challenge to myself, I wrote Furthermark in VB.NET. If you don't like it, you certainly don't have to keep reading.

Word Count

This was probably one of the easier ones. I made a read-only property for wordcount that I raise a property changed notification for when the text changes. This property calls a RegEx match to split out on certain types of whitespace. It's fast and fairly accurate.

Public ReadOnly Property WordCount As String
    Get
        Dim EditorString As String, Count As Integer = 0
        Editor.Document.GetText(Windows.UI.Text.TextGetOptions.None, EditorString)
        If Not String.IsNullOrWhiteSpace(EditorString) Then
            Dim col As MatchCollection = Regex.Matches(EditorString, "[\S]+")
            Count = col.Count
        End If
        Return Count.ToString("N0")
    End Get
End Property

Total Lines & Total Length

These two were the easiest. The RichEditBox control I'm using for the editor has methods to get the line count (and strings have a length property). All I do is set a private Integer variable to the line count and length and have read-only public properties to call ToString("N0") on these in order to give myself the comma separator for when we go over one thousand lines or characters.

The Status Notification

When you save the document or copy the HTML of the Markdown, you get a notification that fades in and out temporarily from the status bar. I have a method to raise a notification:

Private Async Sub ShowStatusNotification(ByVal Text As String)
    StatusText = Text
    RaisePropertyChanged(NameOf(StatusText))
    RaiseStatusText.Begin()
    Await Task.Delay(4000)
    ExitStatusText.Begin()
End Sub

I have a storyboard in the XAML to fade in and out over a duration of 1000ms (one second). This method sets the status text, brings in the text, waits for 4000ms (four seconds) and then fades it back out.

<StackPanel.Resources>
    <Storyboard x:Name="RaiseStatusText">
        <FadeInThemeAnimation TargetName="StatusTextTextBlock" Duration="1000"/>
    </Storyboard>
    <Storyboard x:Name="ExitStatusText">
        <FadeOutThemeAnimation TargetName="StatusTextTextBlock" Duration="1000"/>
    </Storyboard>
</StackPanel.Resources>

Ln & Col

This one wasn't a complete necessary, but I really wanted to have this implemented. The most simple text editor on Windows (Notepad) has this feature (event if it wasn't as simple as it seemed) and I wanted it too. My problem was overthinking the math behind trying to figure this out, and the inconsistent nature of pulling the text from the RichEditBox. The most important thing I learned was getting the text and replacing the line endings to use Lf rather than the Windows default of CrLf.

The next step was splitting the text and getting the cursor position to determine where in the line the cursor was.

Dim Col As Integer, LastNewLine As Integer, Text As String
ThisEditor.Document.GetText(TextGetOptions.UseLf, Text)
Col = ThisEditor.Document.Selection.EndPosition
While Col > Text.Length And Col > 0
    Col -= 1
End While
Text = Text.Substring(0, Col)
LastNewLine = Text.LastIndexOf(vbLf)
Me.Col = Col - LastNewLine
RaisePropertyChanged(NameOf(Col))

_Line = Text.Count(Function(a) a = CChar(vbLf)) + 1
RaisePropertyChanged(NameOf(Line))

What I do here is:

  1. Get the position of the cursor (if there text selected, get me the last position of the selected text)
  2. Subtract the column variable to equal either zero or the length of the document, whatever comes first (this is due to the CrLf issues I've had with this control)
  3. Get the text up to the position of the cursor
  4. Get the position of the last newline character in this substring
  5. The column cursor position in the line is knowable by taking the position of the column in the document and subtracting it by the position of the last newline in the document
  6. The line position is as easy as counting the number of newlines in the substring and adding 1 (since this is a zero-based number)

INS/OVR

This is the one item I actually really didn't want to have, but unfortunaly the RichEditBox control supports using the Insert key to change the text typing mode from insert to overwrite, and I wanted a way to show the user how the editor was going to behave. This wasn't super hard to implement in the end. It turns out the RichEditBox control will show what the type mode is as a enumeration flag when looking at the Document's Options property. So I have an event handler on the PreviewKeyDown event of the edit box (regular KeyDown doesn't register the "Insert" key) to set a private variable.

_IsOverwrite = Not ThisTextBox.Document.Selection.Options.HasFlag(SelectionOptions.Overtype)
RaisePropertyChanged(NameOf(InsOvr))

Then a read-only string property will get the text result of the status.

Public ReadOnly Property InsOvr As String
    Get
        Dim Result As String = "INS"
        If _IsOverwrite Then
            Result = "OVR"
        End If
        Return Result
    End Get
End Property

The Future

Like I mentioned above, I'd like to make the application opensource at some point, but I'm not quite ready for the world to see my mess of code. I will also post an update once the application is available on the Windows store, it's currently going through the certification process. I will post updates here on my blog as well as on the official website Furthermark.com.

Published: 7/9/2018 9:04 PM in Projects

Article by: Noah Wood