Initial Commit
This commit is contained in:
52
cmd/amu/main.go
Normal file
52
cmd/amu/main.go
Normal 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
35
cmd/amulive/accel.go
Normal 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
142
cmd/amulive/file.go
Normal 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
308
cmd/amulive/main.go
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user