Skip to content

Commit 3f34a02

Browse files
authored
Add command search when pipe is open (#12)
1 parent a5b67e7 commit 3f34a02

File tree

5 files changed

+245
-38
lines changed

5 files changed

+245
-38
lines changed

assets/templates/console.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<body>
1616
<form style="margin-top: 15px; height: 100%;" action="{{ .root }}output/exec/{{ .unique }}" method="POST">
1717
<div class="form-group">
18-
<input type="text" class="form-input" name="command" value="" placeholder="Rizin command for example: pdf"/>
18+
<input type="text" class="form-input command-input" name="command" value="" placeholder="Rizin command for example: pdf"/>
1919
</div>
2020
<div class="form-group">
2121
<button class="btn btn-primary" type="submit">Exec</button>

assets/templates/page-view.tmpl

Lines changed: 162 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,43 @@
77
<link rel="stylesheet" href="{{ $root }}static/spectre.min.css">
88
<meta name="viewport" content="width=device-width, initial-scale=1" />
99
<style type="text/css">
10-
@font-face { font-family: 'Inconsolata'; src: URL('{{ .root }}static/inconsolata.ttf') format('truetype'); }
11-
.spacing-top { margin: 3px 0px 0px 0px !important; }
12-
.spacing-left { margin: 0px 3px 0px 0px !important; }
13-
.align-center { text-align: center; }
14-
.navbar-fixed-height { height: 32px; }
15-
.icon-height { height: 20px; }
16-
.title-margin { margin-top: auto; margin-bottom: auto; margin-right: 10px; }
17-
.md-heigh { height: 64px; }
18-
.pipe { color: #e85600; border-color: #e85600; }
19-
.pipe:hover { background: #e85600; border-color: #e85600; }
20-
iframe { border: 0px; width: 100%; height: 100% }
21-
.resizer { display: flex; margin: 0; padding: 0; resize: vertical; overflow: hidden }
22-
.resizer > .resized { flex-grow: 1; margin: 0; padding: 0; border: 0 }
23-
.syntax-menu { right: 35px; }
24-
.align-right { text-align: right; }
25-
textarea {
26-
margin: 5px;
27-
font-family: Inconsolata;
28-
background-color: inherit;
29-
color: inherit;
30-
line-height: 8px;
31-
resize: none;
32-
}
10+
@font-face { font-family: 'Inconsolata'; src: URL('{{ .root }}static/inconsolata.ttf') format('truetype'); }
11+
.spacing-top { margin: 3px 0px 0px 0px !important; }
12+
.spacing-left { margin: 0px 3px 0px 0px !important; }
13+
.align-center { text-align: center; }
14+
.navbar-fixed-height { height: 32px; }
15+
.icon-height { height: 20px; }
16+
.title-margin { margin-top: auto; margin-bottom: auto; margin-right: 10px; }
17+
.md-heigh { height: 64px; }
18+
.pipe { color: #e85600; border-color: #e85600; }
19+
.pipe:hover { background: #e85600; border-color: #e85600; }
20+
iframe { border: 0px; width: 100%; height: 100% }
21+
.resizer { display: flex; margin: 0; padding: 0; resize: vertical; overflow: hidden }
22+
.resizer > .resized { flex-grow: 1; margin: 0; padding: 0; border: 0 }
23+
.syntax-menu { right: 35px; }
24+
.align-right { text-align: right; }
25+
textarea {
26+
margin: 5px;
27+
font-family: Inconsolata;
28+
background-color: inherit;
29+
color: inherit;
30+
line-height: 8px;
31+
resize: none;
32+
}
33+
.button-help { width: 32px; float: left; }
34+
.command-input { margin: 0px 0px 0px 36px; display: flex; width: calc(100% - 36px); min-width: 0; }
35+
.modal-box { width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.8); }
36+
.modal-search { width: 80%; height: 80%; margin: 10px; }
37+
.modal-results { width: 100%; max-height: 500px; position: initial; overflow-y: scroll; overflow-x: hidden; }
38+
.modal-results li { -webkit-user-select: text !important; -ms-user-select: text !important; user-select: text !important; }
39+
.modal-name-big { font-size: 20px; }
40+
.modal-name { color: #1447e6; font-weight: bold; font-family: Inconsolata; }
41+
.modal-args { color: #178236; font-style: italic; font-family: Inconsolata; }
42+
.modal-detail { width: 100%; margin: 10px 0px 0px 0px; }
43+
.modal-entry { width: 100%; margin: 0px 0px 0px 20px; list-style: none; }
44+
.modal-entry-text { min-width: 200px; display: inline-block; }
45+
.modal-searchbar { width: calc(100% - 36px); }
46+
.modal-close { float: right; width: 32px; }
3347
</style>
3448
<script>
3549
function handle(obj) {
@@ -108,6 +122,114 @@
108122
event.preventDefault();
109123
}
110124
}
125+
{{ if $pipe }}
126+
var rizinCmds = JSON.parse({{ .cmds | stringify }});
127+
var rizinCmdsList = Object.keys(rizinCmds);
128+
function openCmdHelp() {
129+
var modal = document.getElementById("command-help");
130+
modal.className += " active";
131+
var search = document.getElementById("search-focus");
132+
search.focus();
133+
}
134+
function closeCmdHelp() {
135+
var modal = document.getElementById("command-help");
136+
modal.className = modal.className.replace(" active", "");
137+
}
138+
function cmdAsText(parent, command) {
139+
var details = (command.details || []);
140+
var element = document.createElement('span');
141+
element.className = "modal-name modal-name-big";
142+
element.textContent = command.cmd;
143+
parent.appendChild(element);
144+
145+
element = document.createElement('span');
146+
element.className = "modal-args modal-name-big";
147+
element.textContent = " " + command.args_str;
148+
parent.appendChild(element);
149+
150+
if ((command.summary || "") != "") {
151+
element = document.createElement('div');
152+
element.textContent = command.summary.replace(".", ".\n");
153+
parent.appendChild(element);
154+
}
155+
if ((command.description || "") != "") {
156+
element = document.createElement('div');
157+
element.textContent = command.description.replace(".", ".\n");
158+
parent.appendChild(element);
159+
}
160+
for (var i = 0; i < details.length; i++) {
161+
var entries = (details[i].entries || []);
162+
var detail = document.createElement('ul');
163+
detail.className = "modal-detail";
164+
detail.textContent = details[i].name;
165+
parent.appendChild(detail);
166+
for (var i = 0; i < entries.length; i++) {
167+
var entry = document.createElement('li');
168+
entry.className = "modal-entry";
169+
detail.appendChild(entry);
170+
171+
var text = document.createElement('span');
172+
text.className = "modal-entry-text";
173+
entry.appendChild(text);
174+
175+
if ((entries[i].text || "").length > 0) {
176+
element = document.createElement('span');
177+
element.className = "modal-name";
178+
element.textContent = entries[i].text;
179+
text.appendChild(element);
180+
}
181+
182+
if ((entries[i].arg_str || "").length > 0) {
183+
element = document.createElement('span');
184+
element.className = "modal-args";
185+
element.textContent = entries[i].arg_str;
186+
text.appendChild(element);
187+
}
188+
189+
if ((entries[i].comment || "").length > 0) {
190+
element = document.createElement('span');
191+
element.style.display = "inline-block";
192+
element.textContent = " # " + entries[i].comment;
193+
entry.appendChild(element);
194+
}
195+
}
196+
}
197+
}
198+
function findCmd(element) {
199+
var dropdown = document.getElementById("dropdown-results");
200+
var results = document.getElementById("search-results");
201+
results.innerHTML = "";
202+
var tokens = element.value.split(/\s+/);
203+
for (var i = 0, count = 0; i < rizinCmdsList.length; i++) {
204+
var key = rizinCmdsList[i];
205+
var descr = rizinCmds[key].description || "";
206+
var summr = rizinCmds[key].summary || "";
207+
var found = true;
208+
for (var j = 0; j < tokens.length; j++) {
209+
if (key.indexOf(tokens[j]) < 0 && descr.indexOf(tokens[j]) < 0 && summr.indexOf(tokens[j]) < 0) {
210+
found = false;
211+
break;
212+
}
213+
}
214+
if (!found) {
215+
continue;
216+
}
217+
var li = document.createElement('li');
218+
if (count > 0) {
219+
li.className = "divider";
220+
results.appendChild(li);
221+
li = document.createElement('li');
222+
}
223+
li.className = "menu-item";
224+
cmdAsText(li, rizinCmds[key]);
225+
results.appendChild(li);
226+
count++;
227+
}
228+
if (dropdown.className.indexOf(" active") < 0) {
229+
dropdown.className += " active";
230+
}
231+
}
232+
{{ end }}
111233
</script>
112234
</head>
113235
<body>
@@ -169,10 +291,27 @@
169291
{{ if $pipe }}
170292
&nbsp;
171293
<a class="btn" href="#" onclick="newcm()"><i class="icon icon-resize-horiz">Command Line</i> Command Line</a>
294+
&nbsp;
295+
<a class="btn" href="#" onclick="openCmdHelp()"><i class="icon icon-search">Help</i> Help</a>
172296
{{ end }}
173297
</div>
174298
</div>
175299
</div>
300+
{{ if $pipe }}
301+
<div id="command-help" class="modal modal-box">
302+
<div class="modal-content modal-search" onclick="" style="z-index: 999999;">
303+
<div class="modal-body">
304+
<div class="content">
305+
<div class="dropdown" id="dropdown-results" style="width: 100%; height: 100%; max-height: 500px;">
306+
<button class="btn btn-primary modal-close" onclick="closeCmdHelp()">&times;</button>
307+
<input class="form-input modal-searchbar" id="search-focus" onchange="findCmd(this);" onkeypress="findCmd(this);" onpaste="findCmd(this);" oninput="findCmd(this);" value="" placeholder="Search for a command or keyword in a description"/>
308+
<ul class="modal-results menu active" id="search-results"></ul>
309+
</div>
310+
</div>
311+
</div>
312+
</div>
313+
</div>
314+
{{ end }}
176315
</section>
177316
</section>
178317
</body>

notebook.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ type Notebook struct {
6363
pipes map[string]*Rizin
6464
jsvm *JavaScript
6565
rizin string
66+
cmds map[string]RizinCommand
6667
}
6768

6869
func NewNotebook(storage, rizinbin string) *Notebook {
@@ -71,6 +72,11 @@ func NewNotebook(storage, rizinbin string) *Notebook {
7172
prefix := path.Join(storage) + string(os.PathSeparator)
7273
suffix := string(os.PathSeparator) + PAGE_FILE
7374

75+
cmds, err := RizinCommands(rizinbin)
76+
if err != nil {
77+
panic(err)
78+
}
79+
7480
files, err := filepath.Glob(path.Join(storage, "*", PAGE_FILE))
7581
if err != nil {
7682
panic(err)
@@ -95,6 +101,7 @@ func NewNotebook(storage, rizinbin string) *Notebook {
95101
pipes: map[string]*Rizin{},
96102
jsvm: jsvm,
97103
rizin: rizinbin,
104+
cmds: cmds,
98105
}
99106
}
100107

pipe.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,44 @@ package main
33
import (
44
"bufio"
55
"bytes"
6+
"encoding/json"
67
"fmt"
78
"io"
89
"os/exec"
910
"strings"
1011
"sync"
1112
)
1213

14+
type RizinCommandArg struct {
15+
Type string `json:"type"`
16+
Name string `json:"name"`
17+
DefaultArg string `json:"default,omitempty"`
18+
Required bool `json:"required,omitempty"`
19+
IsOption bool `json:"is_option,omitempty"`
20+
IsArray bool `json:"is_array,omitempty"`
21+
Choices []string `json:"choices,omitempty"`
22+
}
23+
24+
type RizinCommandDetailEntry struct {
25+
Text string `json:"text,omitempty"`
26+
Comment string `json:"comment,omitempty"`
27+
Arg string `json:"arg_str,omitempty"`
28+
}
29+
30+
type RizinCommandDetail struct {
31+
Name string `json:"name"`
32+
Entries []RizinCommandDetailEntry `json:"entries,omitempty"`
33+
}
34+
35+
type RizinCommand struct {
36+
Command string `json:"cmd"`
37+
ArgsStr string `json:"args_str"`
38+
Args []RizinCommandArg `json:"args,omitempty"`
39+
Description string `json:"description,omitempty"`
40+
Summary string `json:"summary,omitempty"`
41+
Details []RizinCommandDetail `json:"details,omitempty"`
42+
}
43+
1344
type Rizin struct {
1445
pipe *exec.Cmd
1546
stdin io.WriteCloser
@@ -26,6 +57,17 @@ func RizinInfo(rizinbin string) ([]string, error) {
2657
return strings.Split(string(out), "\n"), nil
2758
}
2859

60+
func RizinCommands(rizinbin string) (map[string]RizinCommand, error) {
61+
out, err := exec.Command(rizinbin, "-qc", "?*j").Output()
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
var commands map[string]RizinCommand
67+
err = json.Unmarshal(out, &commands)
68+
return commands, err
69+
}
70+
2971
func NewRizin(rizinbin, file, project string) *Rizin {
3072
args := []string{
3173
"-2",
@@ -67,6 +109,22 @@ func NewRizin(rizinbin, file, project string) *Rizin {
67109
return rizin
68110
}
69111

112+
func (r *Rizin) getCommands(cmd string) (string, error) {
113+
r.mutex.Lock()
114+
defer r.mutex.Unlock()
115+
if _, err := fmt.Fprintln(r.stdin, cmd); err != nil {
116+
fmt.Println("pipe error:", err)
117+
return "", err
118+
}
119+
buf, err := bufio.NewReader(r.stdout).ReadString('\x00')
120+
if err != nil && err != io.EOF {
121+
fmt.Println("pipe error:", err)
122+
return "", err
123+
}
124+
buf = string(bytes.Trim([]byte(buf), "\x00"))
125+
return buf, nil
126+
}
127+
70128
func (r *Rizin) exec(cmd string) (string, error) {
71129
r.mutex.Lock()
72130
defer r.mutex.Unlock()

server_page.go

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,10 @@ func serverAddPage(root *gin.RouterGroup) {
7777
"root": webroot,
7878
"error": "cannot find page",
7979
})
80-
} else {
81-
c.Redirect(302, webroot)
80+
return
8281
}
82+
83+
c.Redirect(302, webroot)
8384
})
8485
root.GET("/edit/:unique", func(c *gin.Context) {
8586
unique := c.Param("unique")
@@ -89,13 +90,13 @@ func serverAddPage(root *gin.RouterGroup) {
8990
"root": webroot,
9091
"error": "cannot find page",
9192
})
92-
} else {
93-
c.HTML(200, "page-new.tmpl", gin.H{
94-
"root": webroot,
95-
"title": page["title"],
96-
"unique": unique,
97-
})
93+
return
9894
}
95+
c.HTML(200, "page-new.tmpl", gin.H{
96+
"root": webroot,
97+
"title": page["title"],
98+
"unique": unique,
99+
})
99100
})
100101
root.GET("/view/:unique", func(c *gin.Context) {
101102
unique := c.Param("unique")
@@ -105,12 +106,14 @@ func serverAddPage(root *gin.RouterGroup) {
105106
"root": webroot,
106107
"error": "cannot find a new page",
107108
})
108-
} else {
109-
c.HTML(200, "page-view.tmpl", gin.H{
110-
"root": webroot,
111-
"page": page,
112-
"pipe": notebook.open(unique, false) != nil,
113-
})
109+
return
114110
}
111+
112+
c.HTML(200, "page-view.tmpl", gin.H{
113+
"root": webroot,
114+
"page": page,
115+
"pipe": notebook.open(unique, false) != nil,
116+
"cmds": notebook.cmds,
117+
})
115118
})
116119
}

0 commit comments

Comments
 (0)