Initial Commit

This commit is contained in:
2021-10-02 15:12:57 -07:00
commit 1ff241a74e
22 changed files with 3000 additions and 0 deletions

52
cmd/amu/main.go Normal file
View File

@@ -0,0 +1,52 @@
/*
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"
"log"
"os"
"go.arsenm.dev/amu"
)
func main() {
var src *os.File
// Get source file based on arguments
if len(os.Args) < 2 {
src = os.Stdin
} else {
// Open file in first argument
file, err := os.Open(os.Args[1])
if err != nil {
log.Fatalln(err)
}
// Set source to file
src = file
}
// Convert source to HTML
html, err := amu.ToHTML(src)
if err != nil {
log.Fatalln(err)
}
// Print output HTML
fmt.Println(html)
}

35
cmd/amulive/accel.go Normal file
View File

@@ -0,0 +1,35 @@
/*
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 "github.com/gotk3/gotk3/gtk"
type AccelGroup struct {
*gtk.AccelGroup
}
func NewAccel() (*AccelGroup, error) {
gtkAg, err := gtk.AccelGroupNew()
return &AccelGroup{AccelGroup: gtkAg}, err
}
func (ag *AccelGroup) Add(acc string, f interface{}) {
key, mods := gtk.AcceleratorParse(acc)
ag.Connect(key, mods, gtk.ACCEL_VISIBLE, f)
}

142
cmd/amulive/file.go Normal file
View File

@@ -0,0 +1,142 @@
/*
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 (
"io/ioutil"
"os"
"github.com/gotk3/gotk3/gtk"
sourceview "github.com/linuxerwang/sourceview3"
)
// saveFile opens a file selection dialog if the file has not already
// been saved and saves the current buffer contents at the chosen filename
func saveFile(win gtk.IWindow, buf *sourceview.SourceBuffer) error {
// If no opened file
if openedFile == "" {
// Create new file chooser dialog with two buttons
fcd, err := gtk.FileChooserDialogNewWith2Buttons(
"Save",
win,
gtk.FILE_CHOOSER_ACTION_SAVE,
"Cancel",
gtk.RESPONSE_CANCEL,
"Save",
gtk.RESPONSE_APPLY,
)
if err != nil {
return err
}
// Do not destroy dialog with parent
fcd.SetDestroyWithParent(false)
// Create new file filter
amuFilter, err := gtk.FileFilterNew()
if err != nil {
return err
}
// Add pattern to filter
amuFilter.AddPattern("*.amu")
// Add filter to dialog
fcd.AddFilter(amuFilter)
// Run dialog
fcdRt := fcd.Run()
if fcdRt == gtk.RESPONSE_APPLY {
// Set openedFile to chosen filename
openedFile = fcd.GetFilename()
} else {
// Destroy dialog
fcd.Destroy()
// Return no error
return nil
}
// Destroy dialog
fcd.Destroy()
}
// Create chosen file
file, err := os.Create(openedFile)
if err != nil {
return err
}
// Get text from source buffer
amu, err := buf.GetText(buf.GetStartIter(), buf.GetEndIter(), true)
if err != nil {
return err
}
// Wtite text to file
_, err = file.Write([]byte(amu))
if err != nil {
return err
}
// Set buffer modified to false
buf.SetModified(false)
return nil
}
// openFile loads the contents of the chosen file into the buffer
func openFile(win gtk.IWindow, buf *sourceview.SourceBuffer) error {
// Create new file chooser dialog with two buttons
fcd, err := gtk.FileChooserDialogNewWith2Buttons(
"Open",
win,
gtk.FILE_CHOOSER_ACTION_SAVE,
"Cancel",
gtk.RESPONSE_CANCEL,
"Open",
gtk.RESPONSE_APPLY,
)
if err != nil {
return err
}
// Do not destroy dialog with parent
fcd.SetDestroyWithParent(false)
// Create new file filter
amuFilter, err := gtk.FileFilterNew()
if err != nil {
return err
}
// Add pattern to file filter
amuFilter.AddPattern("*.amu")
// Add file filter to dialog
fcd.AddFilter(amuFilter)
// Run dialog
respType := fcd.Run()
if respType == gtk.RESPONSE_APPLY {
// Set openedFile to chosen filename
openedFile = fcd.GetFilename()
} else {
// Destroy dialog
fcd.Destroy()
// Return no error
return nil
}
// Destroy dialog
fcd.Destroy()
// Read opened file
data, err := ioutil.ReadFile(openedFile)
if err != nil {
return err
}
// Set text in buffer
buf.SetText(string(data))
// Set buffer modified to false
buf.SetModified(false)
return nil
}

308
cmd/amulive/main.go Normal file
View File

@@ -0,0 +1,308 @@
/*
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)
}