309 lines
8.1 KiB
Go
309 lines
8.1 KiB
Go
/*
|
|
AMU: Custom simple markup language
|
|
Copyright (C) 2021 Arsen Musayelyan
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package main
|
|
|
|
import (
|
|
//"fmt"
|
|
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"github.com/gotk3/gotk3/glib"
|
|
"github.com/gotk3/gotk3/gtk"
|
|
sourceview "github.com/linuxerwang/sourceview3"
|
|
"github.com/pkg/browser"
|
|
"github.com/rs/zerolog"
|
|
"github.com/rs/zerolog/log"
|
|
"github.com/sourcegraph/go-webkit2/webkit2"
|
|
"go.arsenm.dev/amu/ast"
|
|
"go.arsenm.dev/amu/formatter/html"
|
|
"go.arsenm.dev/amu/parser"
|
|
)
|
|
|
|
func init() {
|
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
|
}
|
|
|
|
var head = `<style>
|
|
/* Text formatting
|
|
body:not(.cd) {
|
|
white-space: pre-line;
|
|
}*/
|
|
|
|
body {
|
|
color: %s;
|
|
background-color: %s;
|
|
word-wrap: break-word;
|
|
}
|
|
|
|
@media print {
|
|
body {
|
|
color: black;
|
|
background-color: white;
|
|
}
|
|
}
|
|
|
|
/* Headings */
|
|
h1 { margin: 0; }
|
|
h2 { margin: 0; }
|
|
h3 { margin: 0; }
|
|
h4 { margin: 0; }
|
|
h5 { margin: 0; }
|
|
h6 { margin: 0; }
|
|
</style>
|
|
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.13.18/dist/katex.min.css" crossorigin="anonymous">
|
|
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.13.18/dist/katex.min.js" crossorigin="anonymous"></script>
|
|
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.13.18/dist/contrib/auto-render.min.js" integrity="sha384-vZTG03m+2yp6N6BNi5iM4rW4oIwk5DfcNdFfxkk9ZWpDriOkXX8voJBFrAO7MpVl" crossorigin="anonymous"
|
|
onload="renderMathInElement(document.body);"></script>
|
|
<!--script>
|
|
function renderMath() {
|
|
var math = document.getElementsByClassName("math");
|
|
for (var i = 0; i < math.length; i++) {
|
|
math[i].innerHTML = katex.renderToString(math[i].innerText, {throwOnError: false});
|
|
}
|
|
}
|
|
</script-->`
|
|
|
|
const document = `<!DOCTYPE html>
|
|
<html>
|
|
<head>%s</head>
|
|
<body>%s</body>
|
|
</html>`
|
|
|
|
var openedFile string
|
|
|
|
func main() {
|
|
// Lock goroutine to current thread for Gtk
|
|
runtime.LockOSThread()
|
|
|
|
// Initialize Gtk
|
|
gtk.Init(nil)
|
|
|
|
// Create new Gtk window
|
|
window, err := gtk.WindowNew(gtk.WINDOW_TOPLEVEL)
|
|
if err != nil {
|
|
log.Fatal().Err(err).Msg("Unable to create window")
|
|
}
|
|
// Set window title
|
|
window.SetTitle("AMULive")
|
|
// Stop Gtk main loop when window destroyed
|
|
window.Connect("destroy", gtk.MainQuit)
|
|
|
|
// Create new horizontal box layout with 6px padding
|
|
layout, err := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 6)
|
|
if err != nil {
|
|
log.Fatal().Err(err).Msg("Unable to create box")
|
|
}
|
|
// Add layout to window
|
|
window.Add(layout)
|
|
|
|
// Create new scrolled winfow
|
|
textScrollWindow, err := gtk.ScrolledWindowNew(nil, nil)
|
|
if err != nil {
|
|
log.Fatal().Err(err).Msg("Unable to create scrolled window")
|
|
}
|
|
// Create new source view
|
|
srcView, err := sourceview.SourceViewNew()
|
|
if err != nil {
|
|
log.Fatal().Err(err).Msg("Unable to create text virw")
|
|
}
|
|
// Set tab width to 4
|
|
srcView.SetProperty("tab-width", uint(4))
|
|
// Set auto indent to true
|
|
srcView.SetProperty("auto-indent", true)
|
|
// Set show line numbers to true
|
|
srcView.SetShowLineNumbers(true)
|
|
// Set left margin to 8 (separates text from line numbers)
|
|
srcView.SetLeftMargin(8)
|
|
// Set monospace to true
|
|
srcView.SetMonospace(true)
|
|
// Set wrap mode to wrap word char (3)
|
|
srcView.SetWrapMode(gtk.WRAP_WORD_CHAR)
|
|
// Add text to scrolled window
|
|
textScrollWindow.Add(srcView)
|
|
// Add scrolled window to layout with no padding
|
|
layout.PackStart(textScrollWindow, true, true, 0)
|
|
|
|
// Get source view style context
|
|
styleCtx, err := srcView.GetStyleContext()
|
|
if err != nil {
|
|
log.Fatal().Err(err).Msg("Unable to get style context of text view")
|
|
}
|
|
// Get style background color
|
|
bgCol, err := styleCtx.GetProperty("background-color", gtk.STATE_FLAG_NORMAL)
|
|
if err != nil {
|
|
log.Fatal().Err(err).Msg("Unable to get background-color property of style context")
|
|
}
|
|
// Get style foreground color
|
|
fgCol := styleCtx.GetColor(gtk.STATE_FLAG_NORMAL)
|
|
// Expand head tag template
|
|
head = fmt.Sprintf(head, fgCol, bgCol)
|
|
|
|
// Create new webview
|
|
webkit := webkit2.NewWebView()
|
|
// Enable devtools in webview
|
|
webkit.Settings().SetProperty("enable-developer-extras", true)
|
|
// Load empty base HTML document
|
|
webkit.LoadHTML(fmt.Sprintf(document, head, ""), "amu")
|
|
// Add webview to layout with no padding
|
|
layout.PackStart(webkit, true, true, 0)
|
|
|
|
// Get source view buffer
|
|
srcBuf, err := srcView.GetBuffer()
|
|
if err != nil {
|
|
log.Fatal().Err(err).Msg("Error getting buffer from text view")
|
|
}
|
|
|
|
// On source view change
|
|
srcBuf.Connect("changed", func() {
|
|
loadAMU(srcBuf, webkit)
|
|
})
|
|
|
|
// On load change
|
|
webkit.Connect("load-changed", func(_ *glib.Object, le webkit2.LoadEvent) {
|
|
switch le {
|
|
case webkit2.LoadStarted, webkit2.LoadCommitted, webkit2.LoadRedirected:
|
|
// Get current webview URL
|
|
curURL := webkit.URI()
|
|
// If url is not "amu"
|
|
if curURL != "amu" {
|
|
// Stop loading
|
|
webkit.RunJavaScript("window.stop();", nil)
|
|
// Open URL in browser concurrently
|
|
go browser.OpenURL(curURL)
|
|
// Load base HTML
|
|
webkit.LoadHTML(fmt.Sprintf(document, head, ""), "amu")
|
|
}
|
|
case webkit2.LoadFinished:
|
|
// If URL is "amu"
|
|
if webkit.URI() == "amu" {
|
|
// Load AMU from source buffer
|
|
loadAMU(srcBuf, webkit)
|
|
}
|
|
}
|
|
})
|
|
|
|
// Create new accelerator group
|
|
accelGroup, err := NewAccel()
|
|
if err != nil {
|
|
log.Fatal().Err(err).Msg("Error creating accelerator group")
|
|
}
|
|
|
|
// Set Ctrl+P to print via JavaScript
|
|
accelGroup.Add("<Control>p", func() {
|
|
webkit.RunJavaScript("window.print();", nil)
|
|
})
|
|
|
|
// Set Ctrl+S to save file
|
|
accelGroup.Add("<Control>s", func() {
|
|
err := saveFile(window, srcBuf)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Error saving file")
|
|
}
|
|
})
|
|
|
|
// Set Ctrl+O to open file
|
|
accelGroup.Add("<Control>o", func() {
|
|
err := openFile(window, srcBuf)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Error opening file")
|
|
}
|
|
})
|
|
|
|
// Add underlying gtk accelerator group to window
|
|
window.AddAccelGroup(accelGroup.AccelGroup)
|
|
|
|
// On window delete event
|
|
window.Connect("delete-event", func() bool {
|
|
// If source buffer not modified
|
|
if !srcBuf.GetModified() {
|
|
// Close window
|
|
return false
|
|
}
|
|
// Create confirmation dialog
|
|
dlg := gtk.MessageDialogNew(
|
|
window,
|
|
gtk.DIALOG_MODAL,
|
|
gtk.MESSAGE_WARNING,
|
|
gtk.BUTTONS_YES_NO,
|
|
"Are you sure you want to close?\nYou have unsaved changes.",
|
|
)
|
|
// Run confirmation dialog and get response
|
|
respType := dlg.Run()
|
|
dlg.Close()
|
|
switch respType {
|
|
case gtk.RESPONSE_YES:
|
|
return false
|
|
case gtk.RESPONSE_NO:
|
|
return true
|
|
}
|
|
return true
|
|
})
|
|
|
|
if len(os.Args) > 1 {
|
|
openedFile = os.Args[1]
|
|
data, err := ioutil.ReadFile(openedFile)
|
|
if err != nil {
|
|
log.Fatal().Err(err).Msg("Error opening start file")
|
|
}
|
|
srcBuf.SetText(string(data))
|
|
srcBuf.SetModified(false)
|
|
}
|
|
|
|
window.SetDefaultSize(800, 600)
|
|
window.ShowAll()
|
|
gtk.Main()
|
|
}
|
|
|
|
func loadAMU(srcBuf *sourceview.SourceBuffer, webkit *webkit2.WebView) {
|
|
// Get all text in buffer
|
|
src, err := srcBuf.GetText(srcBuf.GetStartIter(), srcBuf.GetEndIter(), true)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Error getting amu source from text view")
|
|
return
|
|
}
|
|
|
|
p := parser.New(strings.NewReader(src))
|
|
AST, err := p.Parse()
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Error parsing amu source")
|
|
return
|
|
}
|
|
|
|
formatter := html.NewFormatter(AST, ast.FuncMap{})
|
|
|
|
// Execute source from buffer
|
|
html := formatter.Format()
|
|
|
|
// Generate full HTML document and encode as JSON for JavaScript
|
|
data, err := json.Marshal(html)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("Error marshaling string as JSON")
|
|
return
|
|
}
|
|
|
|
// Update webview document
|
|
webkit.RunJavaScript(fmt.Sprintf(`document.body.innerHTML = %s; renderMathInElement(document.body);`, data), nil)
|
|
}
|