Skip to content

Commit 6d190c7

Browse files
Update README.md
1 parent d3d0f3c commit 6d190c7

File tree

1 file changed

+159
-1
lines changed

1 file changed

+159
-1
lines changed

src/05/README.md

+159-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,162 @@
11
# Adding a Text Editor
22
Adding a text editor to make it possible to edit files in the filesystem.
33

4-
The text editor for the fileystem makes use of the Chrome browser and the
4+
The text editor for the fileystem makes use of the Chrome browser and the Ace Javascript library that gives access to editor-like functionality.<br>
5+
One of the hardest challenges to this step was making an editor work for the Windows platform. The original idea was to make use of the <a href="https://github.com/bediger4000/kilo-in-go">Kilo-in-go</a> Golang terminal text editor to add a Vim-like editor to the filesystem. Though this was unsuccessful because of UNIX-like system calls the editor used and my inability to find equivalent Windows syscalls.<br><br>
6+
So this editor would run from the browser.<br><br>
7+
<img src="https://github.com/AlysonBee/GoVirtualFilesystem/blob/master/assets/webeditor.png" height="400" width="500" />
8+
9+
10+
## Putting together a "website" text editor
11+
12+
The first step was to write a web app in Go that could be turned on and off from the editor with the command "open". This I found to be a really Go specific thing because the original plan was to have a Python Flask app that could run and then be killed with signals but this didn't work at all.<br><br>
13+
14+
### Opening and Closing the App
15+
16+
This detail is reasonably important as it's the most creative part of this web app approach. Opening and closing the browser required a bit of out-of-the-box thinking. The main goal was to keep it as user-friendly and immersive as possible. I aimed to implement the functionality of Ctrl+S to save content and closing the browser tab as a way of closing the editor while also disabling the ability to crash the browser from the terminal. These three implementations went as follows;
17+
18+
#### Opening a File
19+
```
20+
open [filename]
21+
```
22+
A small segment from `shell.go` that's responsible for opening a new editor session;
23+
```
24+
....
25+
if _, exists := tmp.files[segments[len(segments)-1]]; exists {
26+
editingFile = tmp.files[segments[len(segments)-1]]
27+
editor()
28+
//s.readFile(tmp.files[segments[len(segments)-1]].rootPath)
29+
30+
}
31+
...
32+
```
33+
The function `editor()` is responsible for spinning up a new web session with the contents of the global variable `editingFile` which would dump the target file's contents into the browser by dereferencing the `content` section of the object (`editingFile.content`').<br>
34+
<br><br>
35+
36+
The first few lines of code after initializing a new server is to disable the `Ctrl+C` functionality to make crashing it from the terminla throw out an error message telling the user to close the browser tab.
37+
```
38+
c := make(chan os.Signal, 1)
39+
signal.Notify(c, os.Interrupt)
40+
go func() {
41+
for sig := range c {
42+
fmt.Println("Interrupt cancelled. Close text editor tab at :127.0.0.1:5000;", sig)
43+
}
44+
}()
45+
```
46+
The next piece of code opens the browser at the address `127.0.0.1:5000` and starts up our server to listen in on the same address.
47+
```
48+
...
49+
openbrowser("http://127.0.0.1:5000")
50+
server := http.Server{Addr: ":5000", Handler: mux}
51+
...
52+
```
53+
Finally, our endpoint is mapped to the function `indexHandler`.
54+
```
55+
mux.HandleFunc("/", indexHandler)
56+
```
57+
A global variable that sets up our template will allow us to send our `editingFile.content` to the HTML file `editor/editor.html`. The key issue with this design is the hardcoded path to `editor/editor.html`. With this path set this way, invoking the `open` command from anywhere that isn't the root of the directory will cause a crash. <b>This must be converted to an absolute path</b>.
58+
```
59+
...
60+
var tpl = template.Must(template.ParseFiles("editor/editor.html"))
61+
...
62+
```
63+
And lastly, our indexHandler code.
64+
```
65+
source := &sourceCode{
66+
Code: string(editingFile.content),
67+
Ext: "golang",
68+
}
69+
err := tpl.Execute(buf, source)
70+
```
71+
The Ace Editor library code responsible for displaying our source code in the editor inside `editor/editor.html`
72+
```
73+
...
74+
var editor = ace.edit("editor");
75+
editor.setTheme("ace/theme/monokai");
76+
editor.session.setMode("ace/mode/{{ .Ext }}");
77+
...
78+
79+
<div id="editor">{{ .Code }}</div>
80+
...
81+
...
82+
```
83+
#### Closing
84+
85+
Closing our text editor made use of Javascript Window interaction code. This made use of an AJAX trigger on the `beforeunload` window event (which basically means "before the window closes") which sent a call to the `/shutdown` endpoint in our Go code.<br><br>
86+
87+
Inside `editor/editor.html`
88+
89+
```
90+
$(window).on("beforeunload", function() {
91+
$.ajax({
92+
type: 'POST',
93+
url: '/shutdown',
94+
contentType: 'application/json;charset=UTF-8',
95+
data: JSON.stringify({'data':'exiting'})
96+
})
97+
})
98+
```
99+
Inside `editor.go`
100+
```
101+
mux.HandleFunc("/shutdown", func(w http.ResponseWriter, r *http.Request) {
102+
if r.Method == "POST" {
103+
server.Shutdown(context.Background())
104+
}
105+
})
106+
```
107+
The above code shuts down the running browser session and returns control back to the filesystem.
108+
109+
#### Saving
110+
Saving made use of Javascript code for key mapping code. When a keypress is detected and the simutaneous keys for `Ctrl` and `S` are pressed, the `save` endpoint is hit with a POST request to the backend.<br><br>
111+
112+
Inside `editor/editor.html`
113+
```
114+
document.addEventListener("keydown", function(e) {
115+
if ((window.navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) && e.keyCode == 83) {
116+
e.preventDefault();
117+
console.log(editor.getValue())
118+
$.ajax({
119+
type: 'POST',
120+
url: '/save',
121+
contentType: 'application/json;charset=UTF-8',
122+
data: JSON.stringify({'data': editor.getValue()}),
123+
success: function() {
124+
console.log("success")
125+
}
126+
});
127+
```
128+
Inside `editor.go`
129+
```
130+
func saveHandler(w http.ResponseWriter, r *http.Request) {
131+
if r.Method == "POST" {
132+
var data map[string]string
133+
json.NewDecoder(r.Body).Decode(&data)
134+
editingFile.content = []byte(data["data"https://itnext.io/go-virtual-filesystem-adding-a-text-editor-176f082e0109])
135+
.}
136+
}
137+
```
138+
The new "edited" code from the text editor overwrites the old value in `editingFile.content`.<br><br>
139+
140+
A more detailed version of this implementation can be seen in the corresponding article <a href="https://itnext.io/go-virtual-filesystem-adding-a-text-editor-176f082e0109">here</a>.
141+
142+
143+
144+
145+
146+
147+
148+
149+
150+
151+
152+
153+
154+
155+
156+
157+
158+
159+
160+
161+
162+

0 commit comments

Comments
 (0)