summary refs log tree commit diff stats
path: root/doc/pydoc/ranger.fsobject.file.html
blob: 5365ad59160d28564fca5b405cf4246adcd14a35 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html><head><title>Python: module ranger.fsobject.file</title>
</head><body bgcolor="#f0f0f8">

<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="heading">
<tr bgcolor="#7799ee">
<td valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial">&nbsp;<br><big><big><strong><a href="ranger.html"><font color="#ffffff">ranger</font></a>.<a href="ranger.fsobject.html"><font color="#ffffff">fsobject</font></a>.file</strong></big></big></font></td
><td align=right valign=bottom
><font color="#ffffff" face="helvetica, arial"><a href=".">index</a><br><a href="file:/home/hut/ranger/ranger/fsobject/file.py">/home/hut/ranger/ranger/fsobject/file.py</a></font></td></tr></table>
    <p><tt>#&nbsp;Copyright&nbsp;(C)&nbsp;2009,&nbsp;2010&nbsp;&nbsp;Roman&nbsp;Zimbelmann&nbsp;&lt;romanz@lavabit.com&gt;<br>
#<br>
#&nbsp;This&nbsp;program&nbsp;is&nbsp;free&nbsp;software:&nbsp;you&nbsp;can&nbsp;redistribute&nbsp;it&nbsp;and/or&nbsp;modify<br>
#&nbsp;it&nbsp;under&nbsp;the&nbsp;terms&nbsp;of&nbsp;the&nbsp;GNU&nbsp;General&nbsp;Public&nbsp;License&nbsp;as&nbsp;published&nbsp;by<br>
#&nbsp;the&nbsp;Free&nbsp;Software&nbsp;Foundation,&nbsp;either&nbsp;version&nbsp;3&nbsp;of&nbsp;the&nbsp;License,&nbsp;or<br>
#&nbsp;(at&nbsp;your&nbsp;option)&nbsp;any&nbsp;later&nbsp;version.<br>
#<br>
#&nbsp;This&nbsp;program&nbsp;is&nbsp;distributed&nbsp;in&nbsp;the&nbsp;hope&nbsp;that&nbsp;it&nbsp;will&nbsp;be&nbsp;useful,<br>
#&nbsp;but&nbsp;WITHOUT&nbsp;ANY&nbsp;WARRANTY;&nbsp;without&nbsp;even&nbsp;the&nbsp;implied&nbsp;warranty&nbsp;of<br>
#&nbsp;MERCHANTABILITY&nbsp;or&nbsp;FITNESS&nbsp;FOR&nbsp;A&nbsp;PARTICULAR&nbsp;PURPOSE.&nbsp;&nbsp;See&nbsp;the<br>
#&nbsp;GNU&nbsp;General&nbsp;Public&nbsp;License&nbsp;for&nbsp;more&nbsp;details.<br>
#<br>
#&nbsp;You&nbsp;should&nbsp;have&nbsp;received&nbsp;a&nbsp;copy&nbsp;of&nbsp;the&nbsp;GNU&nbsp;General&nbsp;Public&nbsp;License<br>
#&nbsp;along&nbsp;with&nbsp;this&nbsp;program.&nbsp;&nbsp;If&nbsp;not,&nbsp;see&nbsp;&lt;<a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>&gt;.</tt></p>
<p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#ee77aa">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial"><big><strong>Classes</strong></big></font></td></tr>
    
<tr><td bgcolor="#ee77aa"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
<td width="100%"><dl>
<dt><font face="helvetica, arial"><a href="ranger.fsobject.fsobject.html#FileSystemObject">ranger.fsobject.fsobject.FileSystemObject</a>(<a href="ranger.shared.mimetype.html#MimeTypeAware">ranger.shared.mimetype.MimeTypeAware</a>, <a href="ranger.shared.html#FileManagerAware">ranger.shared.FileManagerAware</a>)
</font></dt><dd>
<dl>
<dt><font face="helvetica, arial"><a href="ranger.fsobject.file.html#File">File</a>
</font></dt></dl>
</dd>
</dl>
 <p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#ffc8d8">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#000000" face="helvetica, arial"><a name="File">class <strong>File</strong></a>(<a href="ranger.fsobject.fsobject.html#FileSystemObject">ranger.fsobject.fsobject.FileSystemObject</a>)</font></td></tr>
    
<tr><td bgcolor="#ffc8d8"><tt>&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
<td width="100%"><dl><dt>Method resolution order:</dt>
<dd><a href="ranger.fsobject.file.html#File">File</a></dd>
<dd><a href="ranger.fsobject.fsobject.html#FileSystemObject">ranger.fsobject.fsobject.FileSystemObject</a></dd>
<dd><a href="ranger.shared.mimetype.html#MimeTypeAware">ranger.shared.mimetype.MimeTypeAware</a></dd>
<dd><a href="ranger.shared.html#FileManagerAware">ranger.shared.FileManagerAware</a></dd>
<dd><a href="ranger.shared.html#Awareness">ranger.shared.Awareness</a></dd>
<dd><a href="__builtin__.html#object">__builtin__.object</a></dd>
</dl>
<hr>
Methods defined here:<br>
<dl><dt><a name="File-is_binary"><strong>is_binary</strong></a>(self)</dt></dl>

<hr>
Data descriptors defined here:<br>
<dl><dt><strong>firstbytes</strong></dt>
</dl>
<hr>
Data and other attributes defined here:<br>
<dl><dt><strong>is_file</strong> = True</dl>

<hr>
Methods inherited from <a href="ranger.fsobject.fsobject.html#FileSystemObject">ranger.fsobject.fsobject.FileSystemObject</a>:<br>
<dl><dt><a name="File-__init__"><strong>__init__</strong></a>(self, path)</dt></dl>

<dl><dt><a name="File-__repr__"><strong>__repr__</strong></a>(self)</dt></dl>

<dl><dt><a name="File-__str__"><strong>__str__</strong></a>(self)</dt><dd><tt>returns&nbsp;a&nbsp;string&nbsp;containing&nbsp;the&nbsp;absolute&nbsp;path</tt></dd></dl>

<dl><dt><a name="File-get_description"><strong>get_description</strong></a>(self)</dt></dl>

<dl><dt><a name="File-get_permission_string"><strong>get_permission_string</strong></a>(self)</dt></dl>

<dl><dt><a name="File-go"><strong>go</strong></a>(self)</dt><dd><tt>enter&nbsp;the&nbsp;directory&nbsp;if&nbsp;the&nbsp;filemanager&nbsp;is&nbsp;running</tt></dd></dl>

<dl><dt><a name="File-is_older_than"><strong>is_older_than</strong></a>(self, seconds)</dt><dd><tt>returns&nbsp;whether&nbsp;this&nbsp;object&nbsp;wasn't&nbsp;<a href="#File-use">use</a>()d&nbsp;in&nbsp;the&nbsp;last&nbsp;n&nbsp;seconds</tt></dd></dl>

<dl><dt><a name="File-load"><strong>load</strong></a>(self)</dt><dd><tt>reads&nbsp;useful&nbsp;information&nbsp;about&nbsp;the&nbsp;filesystem-object&nbsp;from&nbsp;the<br>
filesystem&nbsp;and&nbsp;caches&nbsp;it&nbsp;for&nbsp;later&nbsp;use</tt></dd></dl>

<dl><dt><a name="File-load_if_outdated"><strong>load_if_outdated</strong></a>(self)</dt><dd><tt>Calls&nbsp;<a href="#File-load">load</a>()&nbsp;if&nbsp;the&nbsp;currently&nbsp;cached&nbsp;information&nbsp;is&nbsp;outdated<br>
or&nbsp;nonexistant.</tt></dd></dl>

<dl><dt><a name="File-load_once"><strong>load_once</strong></a>(self)</dt><dd><tt>calls&nbsp;<a href="#File-load">load</a>()&nbsp;if&nbsp;it&nbsp;has&nbsp;not&nbsp;been&nbsp;called&nbsp;at&nbsp;least&nbsp;once&nbsp;yet</tt></dd></dl>

<dl><dt><a name="File-mark"><strong>mark</strong></a>(self, boolean)</dt></dl>

<dl><dt><a name="File-set_mimetype"><strong>set_mimetype</strong></a>(self)</dt><dd><tt>assign&nbsp;attributes&nbsp;such&nbsp;as&nbsp;self.<strong>video</strong>&nbsp;according&nbsp;to&nbsp;the&nbsp;mimetype</tt></dd></dl>

<dl><dt><a name="File-use"><strong>use</strong></a>(self)</dt><dd><tt>mark&nbsp;the&nbsp;filesystem-object&nbsp;as&nbsp;used&nbsp;at&nbsp;the&nbsp;current&nbsp;time</tt></dd></dl>

<hr>
Data descriptors inherited from <a href="ranger.fsobject.fsobject.html#FileSystemObject">ranger.fsobject.fsobject.FileSystemObject</a>:<br>
<dl><dt><strong>filetype</strong></dt>
</dl>
<dl><dt><strong>shell_escaped_basename</strong></dt>
</dl>
<hr>
Data and other attributes inherited from <a href="ranger.fsobject.fsobject.html#FileSystemObject">ranger.fsobject.fsobject.FileSystemObject</a>:<br>
<dl><dt><strong>accessible</strong> = False</dl>

<dl><dt><strong>audio</strong> = False</dl>

<dl><dt><strong>basename</strong> = None</dl>

<dl><dt><strong>basename_lower</strong> = None</dl>

<dl><dt><strong>container</strong> = False</dl>

<dl><dt><strong>content_loaded</strong> = False</dl>

<dl><dt><strong>dirname</strong> = None</dl>

<dl><dt><strong>document</strong> = False</dl>

<dl><dt><strong>exists</strong> = False</dl>

<dl><dt><strong>extension</strong> = None</dl>

<dl><dt><strong>force_load</strong> = False</dl>

<dl><dt><strong>image</strong> = False</dl>

<dl><dt><strong>infostring</strong> = None</dl>

<dl><dt><strong>is_directory</strong> = False</dl>

<dl><dt><strong>islink</strong> = False</dl>

<dl><dt><strong>last_used</strong> = None</dl>

<dl><dt><strong>loaded</strong> = False</dl>

<dl><dt><strong>marked</strong> = False</dl>

<dl><dt><strong>media</strong> = False</dl>

<dl><dt><strong>mimetype_tuple</strong> = ()</dl>

<dl><dt><strong>path</strong> = None</dl>

<dl><dt><strong>permissions</strong> = None</dl>

<dl><dt><strong>readlink</strong> = None</dl>

<dl><dt><strong>runnable</strong> = False</dl>

<dl><dt><strong>size</strong> = 0</dl>

<dl><dt><strong>stat</strong> = None</dl>

<dl><dt><strong>stopped</strong> = False</dl>

<dl><dt><strong>tagged</strong> = False</dl>

<dl><dt><strong>type</strong> = 'unknown'</dl>

<dl><dt><strong>video</strong> = False</dl>

<hr>
Data descriptors inherited from <a href="ranger.shared.mimetype.html#MimeTypeAware">ranger.shared.mimetype.MimeTypeAware</a>:<br>
<dl><dt><strong>__dict__</strong></dt>
<dd><tt>dictionary&nbsp;for&nbsp;instance&nbsp;variables&nbsp;(if&nbsp;defined)</tt></dd>
</dl>
<dl><dt><strong>__weakref__</strong></dt>
<dd><tt>list&nbsp;of&nbsp;weak&nbsp;references&nbsp;to&nbsp;the&nbsp;object&nbsp;(if&nbsp;defined)</tt></dd>
</dl>
<hr>
Data and other attributes inherited from <a href="ranger.shared.mimetype.html#MimeTypeAware">ranger.shared.mimetype.MimeTypeAware</a>:<br>
<dl><dt><strong>mimetypes</strong> = {}</dl>

<hr>
Data and other attributes inherited from <a href="ranger.shared.html#FileManagerAware">ranger.shared.FileManagerAware</a>:<br>
<dl><dt><strong>fm</strong> = None</dl>

</td></tr></table></td></tr></table><p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#55aa55">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial"><big><strong>Data</strong></big></font></td></tr>
    
<tr><td bgcolor="#55aa55"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
<td width="100%"><strong>N_FIRST_BYTES</strong> = 20<br>
<strong>control_characters</strong> = set(['<font color="#c040c0">\x00</font>', '<font color="#c040c0">\x01</font>', '<font color="#c040c0">\x02</font>', '<font color="#c040c0">\x03</font>', '<font color="#c040c0">\x04</font>', '<font color="#c040c0">\x05</font>', ...])</td></tr></table>
</body></html>
span> } function $panel() { return $('.active-p'); } function hasCls($elt, cls) { return $elt.classList.contains(cls); } function date() { return (new Date()).toLocaleDateString('en-CA'); } function makeElt(elt, cls, attrs, html, value) { let e = document.createElement(elt); cls.forEach(c => e.classList.add(c)); Object.keys(attrs).forEach(k => e.setAttribute(k, attrs[k])); html && (e.innerHTML = html); value && (e.value = value); return e; } function togCls(cls, sel, $elt) { $$(sel).forEach((e) => e.classList.remove(cls)); $elt.classList.add(cls); } function setActive($section) { togCls('active-p', 'section', $section); } function closeSearch() { $search().classList.remove('searching'); $content().focus(); } function refPane(name) { let p; $$('section').forEach(e => e.querySelector('.name').value.startsWith('+ref') && (p = e)); if (!p) { p = createPane(); } setActive(p); load(name); return p; } function runPane(content) { let p; save(); let old = $panel(); $$('section').forEach(e => e.querySelector('.name').value.startsWith('+run') && (p = e)); let err, rv; storeKeys().forEach(k => { if (k === 'main') return; try { global[k] = eval(global, parse(storeGet(k)), true); } catch (e) { err = `in ${k}: ${e}` } }); if (!p) { p = createPane(); load('+run'); } setActive(p); if (err) { $content().value = err; return; } try { $content().value = asString(exec(storeGet('main'))); } catch (e) { $content().value = e; } setActive(old); $content().focus(); } // function runPane(content) { // let p; // $$('section').forEach(e => e.querySelector('.name').value.startsWith('+run') && (p = e)); // let err, rv; // try { // if ($name().value !== 'main') { // global[$name().value] = eval(global, parse(content), true); // rv = asString(global[$name().value]); // } // } catch (e) { // err = e; // } // // if (!p) { // p = createPane(); // load('+run'); // } // setActive(p); // if (err) { // $content().value = err; // return; // } // if (rv) { // $content().value = rv; // return; // } // try { // $content().value = asString(exec(content)); // } catch (e) { // $content().value = e; // } // } function isChordNav(e) { return e.metaKey || e.ctrlKey; } function isChordNavBlank(e) { return isChordNav(e) && e.altKey; } function navBlank(name) { findOrCreatePane(name); } function debounce(fn, ms) { let timeout; return (...args) => { clearTimeout(timeout); timeout = setTimeout(() => fn(...args), ms); } } function basename(path) { const n = path.split('/').pop(); return n.split('.').shift(); } function download(data, name, mime) { const link = document.createElement('a'); link.download = name; link.href = window.URL.createObjectURL(new Blob([data], {type: mime})); document.body.appendChild(link); link.click(); document.body.removeChild(link); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // storage let store = {}; function storeSet(k, v) { if (isStoreKey(k) && v === storeGet(k, v)) return; $('#app').classList.add('dirty'); store[k] = v; } function storeGet(k) { return store[k] || ''; } function storeDel(k) { delete store[k]; } function storeKeys(sorted) { let rv = Object.keys(store); if (sorted) return rv.sort(); return rv; } function isStoreKey(k) { return storeKeys().includes(k); } function storeClear() { store = {}; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Core function save() { let name = $name().value; let content = $content().value; $panel().dataset['name'] = name; if (!name || name === '+run') return; storeSet(name, content); refresh(); } const saveD = debounce(save, 200); function updateHistory(name) { let cname = $panel().dataset['name']; if (!name || name === cname) return; hpush(hist().back, cname); hist().forward = []; } function ls(prefix) { return storeKeys(true).filter(k => !prefix || k.startsWith(prefix)).join('\n'); } function orph() { return storeKeys().map(k => { const v = storeGet(k); const m = [...v.matchAll(/\[\[([\w\.\-]+)\]\]/g)].map(match => match[1]); const r = m.filter(e => !storeGet(e)); if (!r.length) return null; return ['[[' + k + ']]', '----------', ...r.map(e => '[[' + e + ']]'), ''].join('\n'); }).filter(Boolean).join('\n'); } function load(name, noHist) { if (!name) { $name().value = ''; $content().value = ''; return; } !noHist && updateHistory(name) if (name === '+orph') { $content().value = orph(); } else if (name.startsWith('+ls')) { $content().value = ls(name.split(':')[1]); } else if (name.startsWith('+search')) { $content().value = name.split(':')[1] ? lookup(name.split(':')[1]) : ''; } else if (name.startsWith('+ref')) { $content().value = name.split(':')[1] ? lookup('[[' + name.split(':')[1] + ']]') : ''; } else { $content().value = storeGet(name); } $name().value = name; $panel().dataset['name'] = name; $panel().querySelector('.back').classList.toggle('hist', !!hist().back.length); $panel().querySelector('.forward').classList.toggle('hist', !!hist().forward.length); } function refresh() { let $elt = $('#files'); $elt.innerHTML = ''; storeKeys().forEach(k => $elt.appendChild(makeElt('option', [], {}, null, k))); $elt = $('#globals'); $elt.innerHTML = ''; Object.keys(global).forEach(k => $elt.appendChild(makeElt('option', [], {}, null, k))); } let paneID = 0; function createHistory(paneID) { history[paneID] = {back: [], forward: []}; } function removeHistory(paneID) { delete history[paneID]; } function deletePane($pane) { removeHistory($pane.dataset['id']); $pane.remove(); } function createPane() { const header = document.createElement('header'); header.append( makeElt('span', ['back', 'action'], {}, '<'), makeElt('span', ['forward', 'action'], {}, '>'), makeElt('input', ['name'], {list: 'files', autocomplete: 'off'}), makeElt('span', ['max', 'action'], {}, '+'), makeElt('span', ['move', 'action'], {}, '~'), makeElt('span', ['close', 'action'], {}, 'x') ); const id = paneID++; const section = document.createElement('section'); const article = document.createElement('article'); article.append(makeElt('textarea', ['content'], {spellcheck: false, onkeyup: 'saveD()'})); section.setAttribute('data-id', '' + id); section.append( header, makeElt('div', ['parse'], {}), article, makeElt('div', ['h-resize'], {}) ); createHistory(id); $(".column.active").appendChild(section); setActive(section); $content().focus(); return section; } function lookup(str) { return storeKeys(true).map(k => { const v = storeGet(k); const m = v.split('\n').filter(l => l.includes(str)); if (!m.length) return null; return ['[[' + k + ']]', '----------', ...m, ''].join('\n'); }).filter(Boolean).join('\n'); } function mv(before, after) { return storeKeys().forEach(k => { const v = storeGet(k); storeSet(k, v.replaceAll('[[' + before + ']]', '[[' + after + ']]')) }) } function link(textarea) { const text = textarea.value; const pos = textarea.selectionStart; let start, end; for (start = pos - 1; start > -1 && /[^\s\(\)]/.test(text[start]); start--) { } for (end = pos; end < text.length && /[^\s\(\)]/.test(text[end]); end++) { } return text.substring(start + 1, end); } function insert(textarea, text) { const start = textarea.selectionStart; const end = textarea.selectionEnd; const before = textarea.value.substring(0, start); const after = textarea.value.substring(end, textarea.value.length); textarea.value = before + text + after; textarea.selectionStart = textarea.selectionEnd = start + text.length; } const write = text => document.execCommand('insertText', false, text); const history = {}; function hist() { return history[$panel().dataset['id']]; } function hpush(h, name) { if (h[h.length - 1] === name) return; h.push(name); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Menu function menuNew() { createPane(); } function menuLs() { $name() || createPane(); load('+ls'); } function menuReset() { if (confirm('delete everything?')) { storeClear(); } } function menuRun() { runPane($content().value); } function menuMv() { let prev = $panel().dataset['name']; if (prev === $name().value) return; mv(prev, $name().value); save(); storeDel(prev); $prevPanel = $panel(); $$('section').forEach($pane => { setActive($pane); load($name().value, false); }) setActive($prevPanel); refresh(); } function quine() { const regex = /let store = (.*)/; return ['<!DOCTYPE html>', `<head>${document.head.innerHTML}</head>`, '<body>', '<div id="app"></div>', `<script>${$('script').innerHTML.replace(regex, "let store = " + JSON.stringify(store) .replaceAll('</' + 'script', "' + '</' + 'script' + '") + ';') + '</'}script>`, '</body>'].join('\n'); } function menuSave() { $('#app').classList.remove('dirty'); download(quine(), basename(window.location.href) + '.html', 'text/html'); } function menuExport() { download(JSON.stringify(store), basename(window.location.href) + '.json', 'text/json'); } function menuDel() { let name = $name().value; if (name && confirm('delete ' + name + '?')) { storeDel(name); refresh(); deletePane($panel()); } } function menuSettings() { $('#settings').classList.toggle('visible'); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Pane actions function paneFold(e) { closest$(e, 'section').classList.toggle('folded'); } function paneMax(e) { if (closest$(e, 'section').classList.contains('maxed')) { closest$(e, 'section').classList.remove('maxed'); closest$(e, '.column').classList.remove('folded'); } else { closest$(e, '.column').querySelectorAll('section').forEach(e => e.classList.remove("maxed")); closest$(e, 'section').classList.add("maxed"); closest$(e, '.column').classList.add('folded'); } } function paneMove(e) { let $c; $$('.column').forEach(c => { if (c !== closest$(e, '.column')) $c = c; }); $c.appendChild(closest$(e, 'section')); } function paneClose(e) { deletePane(closest$(e, 'section')); if ($('section')) { setActive($('section')); $content().focus; } } function paneHist(from, to) { return function (e) { if (!from.length) return; let name = from.pop(); if (isChordNavBlank(e)) { from.push(name); createPane(); } else if (isChordNav(e)) { from.push(name); setActive($prevPanel); load(name); $content().focus(); } else { hpush(to, $panel().dataset['name']); } load(name, true); } } function findOrCreatePane(name) { let p; $$('section').forEach(e => e.querySelector('.name').value === name && (p = e)); if (!p) { p = createPane(); } setActive(p); load(name); return p; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Scheme interpreter const global = {}; const arity = (fn, n) => (s, env) => { if (collect(cdr(s)).length !== n) throw new Error(`${asString(car(s))}: Wrong number of arguments`); return fn(s, env); } const primitive = { define: arity((s, env) => { return env[cadr(s)] = eval(env, cadr(cdr(s))); }, 2), quote: arity((s, env) => cadr(s), 1), car: arity((s, env) => { let rv = eval(env, cadr(s)); if (rv === null) throw new Error('You cannot ask for the car of the empty list'); else if (isAtom(rv)) throw new Error('You cannot ask for the car of an atom'); return car(rv) }, 1), cdr: arity((s, env) => { let rv = eval(env, cadr(s)); if (rv === null) throw new Error('You cannot ask for the cdr of the empty list'); if (isAtom(rv)) throw new Error('You cannot ask for the cdr of an atom'); return cdr(rv) }, 1), cons: arity((s, env) => { let r = eval(env, cadr(cdr(s))); if (!isList(r)) throw new Error('Second argument of cons must be a list'); return cons(eval(env, cadr(s)), r); }, 2), 'null?': arity((s, env) => { let r = eval(env, cadr(s)); if (!isList(r)) throw new Error('null? is defined only for lists'); return r === null; }, 1), 'zero?': arity((s, env) => { let r = eval(env, cadr(s)); if (!isAtom(r)) throw new Error('zero? is defined only for atoms'); return r === 0; }, 1), 'atom?': arity((s, env) => { return isAtom(eval(env, cadr(s))); }, 1), 'number?': arity((s, env) => { return isNumber(eval(env, cadr(s))); }, 1), 'eq?': arity((s, env) => { let l = eval(env, cadr(s)); let r = eval(env, cadr(cdr(s))); if (!isAtom(l) || isNumber(l) || !isAtom(r) || isNumber(r)) throw new Error('eq? takes two non-numeric atoms'); return l === r; }, 2), or: arity((s, env) => { return eval(env, cadr(s)) || eval(env, cadr(cdr(s))); }, 2), and: arity((s, env) => { return eval(env, cadr(s)) && eval(env, cadr(cdr(s))); }, 2), add1: arity((s, env) => eval(env, cadr(s)) + 1, 1), sub1: arity((s, env) => eval(env, cadr(s)) - 1, 1), lambda: arity((s, env) => { return { args: collect(cadr(s)), body: cadr(cdr(s)), env: Object.fromEntries(Object.entries(env)) } }, 2), letrec: arity((s, env) => { let f = eval(env, car(cdr(car(cadr(s))))); f.env[car(car(car(cdr(s))))] = f; return eval(f.env, car(cdr(cdr(s)))); }, 2), cond: (s, env) => { let b = collect(cdr(s)); for (let i = 0; i < b.length; i++) { if (car(b[i]) === 'else' || eval(env, car(b[i]))) return eval(env, cadr(b[i])); } } } const cons = (car, cdr) => [car, cdr]; const car = c => c[0]; const cdr = c => c[1]; const cadr = c => car(cdr(c)); const isAtom = s => s !== null && !Array.isArray(s); const isList = s => Array.isArray(s) || s === null; const isNumber = s => typeof s === 'number'; const parse = src => ast(src.replaceAll('(', ' ( ').replaceAll(')', ' ) ').replaceAll(/;.*$/gm, '').split(/\s/).filter(Boolean)); const exec = src => eval(global, parse(src), true); function ast(tokens, d = {depth: 0}) { if (!tokens.length) { if (d.depth > 0) throw new Error('Unexpected EOF!'); else return null; } let t = tokens.shift(); if (t === ')') { if (d.depth-- === 0) throw new Error('Unexpected closing parenthesis'); return null; } else if (t === '(') { d.depth++; return cons(ast(tokens, d), ast(tokens, d)); } else if (t[0] === '#') { if (tokens.length) return cons(t[1] === 't', ast(tokens, d)); return t[1] === 't'; } else if (isNaN(t)) { if (tokens.length) return cons(t, ast(tokens, d)); return t; } if (tokens.length) return cons(+t, ast(tokens, d)); return +t; } function collect(s) { if (!s) return []; return [car(s), ...collect(cdr(s))]; } function eval(env, s, isSrc) { if (s === null) throw new Error('Evaluating empty list'); if (isAtom(s)) { if (isNaN(s)) { if (env[s] !== undefined) return env[s]; else if (Object.keys(primitive).includes(s)) return s; throw new Error('Undefined variable ' + s); } return s; } else if (isSrc) { let rv = eval(env, car(s)); if (cdr(s)) return eval(env, cdr(s), isSrc); return rv; } let proc = eval(env, car(s)); if (Object.keys(primitive).includes(proc || car(s))) { return primitive[proc || car(s)](s, env) } try { let args = {}; collect(cdr(s)).forEach((a, i) => { args[proc.args[i]] = eval(env, a); }); return arity((_, env) => eval(env, proc.body), proc.args.length)(s, Object.assign({}, env, proc.env, args)); } catch (e) { throw new Error(`${asString(car(s))}: ${e}`); } } function asString(s) { function build(s, acc = [], nl = true) { if (s === null) { acc.push('()'); return acc; } else if (s.body) { acc.push('<proc (' + s.args.join(' ') + ')>'); return acc; } else if (isAtom(s)) { acc.push(s); return acc; } else { nl && acc.push('('); build(s[0], acc, true); s[1] && build(s[1], acc, false); nl && acc.push(')'); } return acc; } return build(s).join(' ').replaceAll('( ', '(').replaceAll(' )', ')'); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Listeners let $prevPanel; let $resize; let $hresize; function handleSearch(e) { e.preventDefault(); e.stopPropagation(); if (isChordNavBlank(e)) { navBlank(t$(e).value); closeSearch(); } else if (isChordNav(e)) { if (!$content()) createPane(); closeSearch(); load(t$(e).value); } else { if (!$content()) return; closeSearch(); if (!isStoreKey(t$(e).value)) { storeSet(t$(e).value, ''); refresh(); } write(t$(e).value); } } document.getElementById('app').innerHTML = ` <datalist id="files"></datalist> <datalist id="globals"></datalist> <table> <tr> <td colspan="2" class="menu" style="height: 0;"> <span class="new">new</span><span class="ls">ls</span><span class="mv">mv</span><span class="del">del</span><span class="save">save</span><span class="run">run</span><span class="settings">import</span><span class="export">export</span><span class="reset">reset</span> </td> </tr> <tr id="settings"> <td colspan="2" class="menu" style="height: 0;"> <input type="file" id="import" value=""> </td> </tr> <tr> <td class="col"> <div class="active column"></div> <div class="resize"></div> </td> <td> <div class="column"></div> </td> </tr> </table> <input list="files" spellcheck="false" id="search" autocomplete="off"/> `; document.addEventListener('mousedown', function (e) { if (hasCls(t$(e), 'resize')) { e.preventDefault(); $resize = closest$(e, 'td'); } else if (hasCls(t$(e), 'h-resize')) { e.preventDefault(); $hresize = closest$(e, 'section').querySelector('article'); } $prevPanel = $panel() || $prevPanel; let section = closest$(e, 'section'); section && setActive(section); let column = closest$(e, '.column'); if (column && (!e.altKey && !e.metaKey && !e.ctrlKey)) { togCls('active', '.column', column); } }); document.addEventListener('mousemove', function (e) { if ($resize) $resize.width = e.clientX; else if ($hresize) { const $sib = $hresize.closest('section').nextSibling; const rect = $hresize.getBoundingClientRect(); let h = e.y - rect.top < 0 ? 0 : e.y - rect.top; let diff = h - rect.height; if ($sib && $sib.nextSibling) { const $sArt = $sib.querySelector('article'); const sRect = $sArt.getBoundingClientRect(); if (sRect.height - diff <= 0) { $hresize.style.height = `${+$hresize.style.height.split('px')[0] + sRect.height}px`; $sArt.style.height = `0px`; return; } $sArt.style.height = `${sRect.height - diff}px`; } $hresize.style.height = `${h}px`; } }) document.addEventListener('mouseup', () => { $resize = null; $hresize = null; }); document.addEventListener('click', function (e) { // @formatter:off switch (t$(e).classList[0]) { case 'new': menuNew(); return; case 'ls': menuLs(); return; case 'reset': menuReset(); return; case 'save': menuSave(); return; case 'mv': menuMv(); return; case 'del': menuDel(); return; case 'settings': menuSettings(); return; case 'run': menuRun(); return; case 'export': menuExport(); return; case 'close': paneClose(e); return; case 'max': paneMax(e); return; case 'move': paneMove(e); return; case 'back': paneHist(hist().back, hist().forward)(e); return; case 'forward': paneHist(hist().forward, hist().back)(e); return; } // @formatter:on if (isChordNavBlank(e) && e.button === 0) { link($content()) && navBlank(link($content())); } else if (isChordNav(e) && e.button === 0) { let name = link($content()); if (name) { setActive($prevPanel); load(name); $content().focus(); } } }); window.addEventListener('beforeunload', e => { if ($('.dirty')) { e.preventDefault(); e.returnValue = true; } }) function offset(text) { const stack = []; let o = 0; text.split('').forEach((c, i) => { if (c === '(') stack.push({spaces: i - o, total: i}); else if (c === ')') stack.pop(); else if (c === '\n') o = i + 1; }); return stack.pop() || 0; } const move = (e, pos) => e.setSelectionRange(pos, pos); const caret = e => e.value.substring(0, e.selectionStart); let last; document.addEventListener('keydown', function (e) { if (e.key === 'Enter') { if (t$(e).id === 'search') { handleSearch(e); } else if (isChordNav(e)) { $search().value = ''; $search().classList.add('searching'); $search().focus(); } else if (hasCls(e.target, 'name')) { load($name().value); } else { e.preventDefault(); let s = offset(caret(t$(e))); if (s.spaces === undefined) write('\n') else write('\n' + Array(s.spaces + 3).join(' ')); } } else if (e.key === 'Escape' && e.target.id === 'search') { $search().classList.remove('searching'); $content().focus(); } else if (t$(e).classList[0] === 'content') { const textarea = t$(e); if (e.key === '(') { e.preventDefault(); write('()'); move(textarea, textarea.selectionStart - 1); } if (e.key === '\'') { e.preventDefault(); write('(quote )'); move(textarea, textarea.selectionStart - 1); } else if (e.key === 'Tab') { e.preventDefault(); let c = caret(textarea); if (c[c.length - 1] === '(') { return move(textarea, last); } let o = offset(c); if (o.spaces === undefined) return; last = c.length; move(textarea, o.total + 1); } else if (e.key === 'r' && (e.metaKey || e.ctrlKey)) { runPane(textarea.value); } } }); document.getElementById('import').addEventListener('change', function (e) { const file = e.target.files[0]; const r = new FileReader(); r.onload = e => { let data = JSON.parse('' + e.target.result); Object.keys(data).forEach(k => storeSet(k, data[k])); refresh(); } r.readAsText(file); refresh(); }); document.addEventListener('keyup', function (e) { if (t$(e).className === 'content') { let $parse = closest$(e, 'section').querySelector('.parse'); try { parse($content().value); $parse.innerHTML = ''; } catch (e) { $parse.innerHTML = e; } } }) refresh(); createPane(); load((location.hash && location.hash.substring(1)) || 'main'); </script> </body> </html>