WRITE A SIMPLE MARKDOWN EDITOR WITH GO-QML

-Migrated from my original blog

BACKGROUND:

Golang has really become my go to language, and has replaced Python as my main scripting language. I love how easy it is to write. I love how fast it is to compile, and is a compiled language, so I can just distribute a single binary instead of hoping that there is a Python run-time install (I.E. small container images). And with distributing these binaries, I can easily cross compile Golang to different architectures, and OS’s without installing a cumbersome tool-chain.

One big downside to the Go language is that it has very little GUI support, and really doesn’t come with default support what so ever. Well there are a couple pretty good 3rd party GUI libs such as Go-GTK, Go-UI, and finally Go-QML.

ABOUT QML

I have played with QML and Qt Widgets with C++ in the past, and really enjoyed it. QML especially is nice. Instead of the classic XML style syntax that you find in WPF or even Qt Widgets UI files, QML uses a more JavaScript/JSON like syntax. While I did just say JSON, it uses object like syntax instead of a string like syntax (meaning you won’t have a bunch of double quotes).

Also QML can embed JavaScript directly inline or in a separate .js file to manipulate actions on the UI. Love or hate JavaScript, it is the MOST used language in the world. The great part about this implantation is that, on the newer versions of Qt/QML, it doesn’t run a whole implantation of Chromium in the background, os less overhead as compared to Electron and NW.js.

Combine that with the speed and power of the Go language, and you can create fast GUI applications easily for a lot of architectures and OS’s.

GETTING STARTED

To start understand I am using Arch Linux, and all the packages I install through the system package manager will be for that system. All the system packages will be for Go-QML, with the exception is for clipboard go lib, on Linux you will need either xclip or xsel.

// Ubuntu (based), Debian
# apt-get install xclip
// Cent-OS, RHEL
# yum install xclip
// Fedora
# dnf install xclip
// OpenSUSE, SEL
# zypper in xclip
// Arch Linux
# pacman -S xclip

To use Go-QML and all the QML imports in this project, install:

# pacman -S qt5-{base,declarative,quickcontrol}

OK, so that command may look a little weird. Using curly braces in your bash shell is a VERY powerful thing. Basically the command above is equivalent to:

# pacman -S qt5-base qt5-declarative qt5-quickcontrol

Also note that there is no spaces in the curly brackets, that is very important. You can use that type of syntax when cp files or many other things

Now that we have the system packages installed now we need to make sure you have the Go language installed and $GOPATH set. For more info on that go to golang.org, for more information.

Now that everything is set let’s install the 3rd party Golang packages:

$ go get -u -v gopkg.in/qml.v1

Any errors that pop up while installing this package means you need to install more dependencies in your system. See the Go-QML README for more info.

Now we will install the markdown parser:

$ go get -u -v github.com/russross/blackfriday

This tutorial will use the basic settings of what you can do with blackfriday. For production use, you may want to sanitize the markdown. All of that is covered in the blackfriday README.

Finally we need to install the clipboard support:

$ go get -u -v github.com/atotto/clipboard

REFERENCES

I used a couple references in figuring out how to write this program.

GOLANG VERSION ISSUES

At the time of writing I am using the most recent version of the Go language (1.6.2). Changes in cgo in 1.6 has made it so GO-QML won’t compile when the QML code tries communicating back to Go. The work around referenced in the the tut I referenced at the end of the last section says to:

$ GODEBUG=cgocheck=0 go run main.go

So every time you run your program run it with those vars set. I do not recommend exporting those vars and making them global, because they may put other go programs at risk of possible security issues and errors.

You shouldn’t have this issue with Go language version =< 1.5.x

MAKING YOUR DIRECTORIES

Let’s go to your $GOPATH/src dir, and create your directories. Note: I prefer to have my GUI files in their own sub-dir in the project no matter how small the project is.

$ cd $GOPATH/src && mkdir -p qml-md/assets && cd qml-md && touch main.go assets/main.qml

This will create all the needed dir tree and needed files

Finally we can open up your favorite text editor and write some code!

TO THE CODE:

Here you can see we’ve imported the 3 3rd party libs, the os, and the fmt libs from the std.

Now we will get the QML file to load in the go app

Here we create a run function that returns an error. Inside the run func we create a QML engine, and Listen for a “quit” signal to gracefully quit.
Load the QML file. And finally create, open, and hold the QML window open.

In the main function we Run the run function to initiate the QML window.

As you can see QML is a simple markup language that makes it very fast and easy to write UIs. We create an ApplicationWindow as our base entity, and a RowLayout to organize its children. 2 TextAreas, one for Markdown, and another for the RichText/HTML. The reason I say RichText/HTML will be apparent later when we we make it so you can see it a formatted text or raw HTML.

In the comments above I say “Suggest”. I say this because when using Layout.param, this says that the child of the layout should follow this command as log as it does not cause the ParentLayout to break its defined parameters.

Now we need to set a parameter in the QML and in Go to set the text in each TextArea. Fist we will remove the static text in mdarea.text, and rtarea.text with a variable names to reference in to Golang code. When I say something like mdarea.text I mean:

  • We set the TextArea id in the Markdown text area as mdarea
  • And I use dot syntax to reference the text parameter

Now we can go to the Go code and create a context variable, which will be global so we access it from other parts of our code as needed. Then use the context to set vars before the window is created, to set the text on load.

The above Go code will now set the mdtxt and rttxt properties in the QML UI when the app loads. Now we need to have a mechanism to parse and sync the markdown text area with the rich text text area. We will do this in a couple steps.

  • First we will create a custom type from the Go code to import in to the QML
  • Then we will use a keyboard event in the mdarea entity to listen for a change in the text
  • When the text changes in the mdarea it will then send the mdarea.text contents to the Go code to then parse the markdown and set the text in the rendered HTML in the rtarea.text as RichText

First we create a global struct HTMLText (note: the fist letter is uppercase so the struct us exportable), that takes a qml.Object, and a string

Now we create a method SetHTMLText (again exportable) to call from QML to manipulate the the data stored in HTMLText and set the text in rttext using the ctx pointer.

Now we need a way to create the HTMLText object in the QML file, for this we need to register types in QML from the Go code before the engine is initiated.

See these 2 parts of the Go-QML GoDoc: qml.RegisterTypes, and qml.TypeSpec

The above code is where things get complicated. We created an importable set of object for QML. We will do that now.

The GoDoc for Go-QML explains the lower casing: Lowercasing_of_names

In the above code you will see that I used Keys.onReleased, at first I did onPressed but that sends the signal before the new character is added so rtarea was always one character behind.

Now the app will auto sync and parse the markdown you write in mdarea and format it to look nice in the rtarea. But what if we wanted to see the HTML code. Well what we will do next is make it so if we click into the rtarea it will automatically show the raw HTML.

You may ask why I took the extra 2 steps at the end of the else statement. Well in QML once you set the TextArea to show RichText, QML adds the html, header, and body tags. As well as it adds the font styling info as CSS which we do not want if we just want simple markdown HTML. So once we set the TextArea to PlainText it will only show exactly what it is sent.

Now the final step will be to add a Copy To HTML button, to easily extract the raw HTML. And paste it wherever.

The code above will now create a ToolBar and add the Copy To HTML Button, and call the currently non-existent method of HTMLText.CopyToClip(string). Let’s build that now.

and that’s it! While there are a lot of other things you can do with this, like sanitizing the markdown. But now you can experiment with stuff like saving an HTML file and so forth.

Good Luck!

The complete source code is found: https://github.com/dtoebe/qml_markdown