mirror of
https://github.com/Asraelite/littlebigcomputer.git
synced 2025-07-18 00:26:50 +00:00
Init commit
This commit is contained in:
commit
c45ad79440
48 changed files with 6786 additions and 0 deletions
93
assembler/ldt/README.md
Normal file
93
assembler/ldt/README.md
Normal file
|
@ -0,0 +1,93 @@
|
|||
# Lightweight Decorator for Textareas
|
||||
## In browser live syntax highlighting
|
||||
|
||||
LDT aims to provide a simple, lightweight, and highly extensible alternative to existing in-browser live syntax highlighting solutions by leveraging clever CSS and native functionality. Other solutions often re-implement large parts of the user interaction, or use inconsistent pseudo-standard features such as contentEditable and designMode. This results in either a lack of native functionality or a large code-base to compensate or both.
|
||||
|
||||
It behaves (mostly) like a textarea because it *is* a (transparent) textarea! The decorator maintains a styled copy of the content in a display layer which is aligned underneath the real textarea. By using a real textarea, we get all the native functionality for free! This usually includes keyboard input (one would hope), navigating with a blinking cursor, making selections, cut, copy, & paste; sometimes drag & drop and undo & redo*.
|
||||
|
||||
The idea of displaying content under a (semi) transparent input is by no means a new idea. In fact, Google uses this technique to offer suggestions in their search bar. Facebook used it to highlight the name of a friend while composing a post. Other live syntax highlighting solutions have also used this, such as EditArea and to a degree CodeMirror 2.
|
||||
|
||||
In a sense, LDT takes the UNIX approach; make small programs that do one thing really well. LDT is modular, consisting of the decorator object which maintains the display layer for the editor, and an optional parser object which tells the decorator how to style the content. I try not to do anything above providing a syntax highlighting in a native textarea. This way we keep it as lightweight as possible, but still extensible so it may be used for many applications. Since it is still really a textarea at heart, you can still hook in extended functionality. Two such modules are included, SelectHelper for selection utilities and modifying content programmatically, and Keybinder for mapping hotkeys.
|
||||
|
||||
The optional Parser is included to make it easy to generate fast highlightings using regular expressions. All you have to do is provide a mapping of CSS class names to RegExp objects. In your CSS you can specify styles to apply to each token class. You can also apply multiple classes to a token, just provide a space separated list in quotes. You can also write your own parser if you have your own way of generating tokens, just follow the parser interface.
|
||||
|
||||
LDT was developed by Colin Kuebler originally as part of *The Koala Project*. Special thanks to the *Rensselaer Center for Open Source* for their support.
|
||||
|
||||
*\* Undo & redo has been known to break when you modify the textarea's contents programmatically (which is why LDT doesn't do this by default). It might be possible to regain this functionality by implementing your own undo stack.*
|
||||
|
||||
## Using LDT
|
||||
Making an auto highlighting `textarea` is easy with LDT. Make sure to include the modules you need either directly in your code (less server requests) or using the HTML `script` tag. Minify in production for bandwidths sake. Below is a simple example of LDT usage. See `examples` directory for more.
|
||||
### HTML
|
||||
```html
|
||||
<!-- normal textarea fall-back, add an id to access it from javascript -->
|
||||
<textarea id='codeArea' class='ldt'></textarea>
|
||||
<noscript>Please enable JavaScript to allow syntax highlighting.</noscript>
|
||||
```
|
||||
### JS
|
||||
```js
|
||||
// create a parser with a mapping of css classes to regular expressions
|
||||
// everything must be matched, so 'whitespace' and 'other' are commonly included
|
||||
var parser = new Parser(
|
||||
{ whitespace: /\s+/,
|
||||
comment: /\/\/[^\r\n]*/,
|
||||
other: /\S/ } );
|
||||
// get the textarea with $ (document.getElementById)
|
||||
// pass the textarea element and parser to LDT
|
||||
var ldt = new TextareaDecorator( $('codeArea'), parser );
|
||||
```
|
||||
### CSS
|
||||
```css
|
||||
/* editor styles */
|
||||
.ldt {
|
||||
width: 400px;
|
||||
height: 300px;
|
||||
border: 1px solid black;
|
||||
}
|
||||
/* styles applied to comment tokens */
|
||||
.ldt .comment {
|
||||
color: silver;
|
||||
}
|
||||
```
|
||||
|
||||
## Browser Support
|
||||
LDT has been tested on
|
||||
|
||||
* Firefox 3.6 - 80
|
||||
* Internet Explorer 8 - 11
|
||||
* Chromium & Google Chrome 16 - 85
|
||||
* Midori 4.1
|
||||
* Opera 11.61
|
||||
* Epiphany
|
||||
|
||||
## API
|
||||
### TextareaDecorator
|
||||
|
||||
+ `new TextareaDecorator( textarea, parser )` Converts a HTML `textarea` element into an auto highlighting TextareaDecorator. `parser` is used to determine how to subdivide and style the content. `parser` can be any object which defines the `tokenize` and `identify` methods as described in the Parser API below.
|
||||
+ `.input` The input layer of the LDT, a `textarea` element.
|
||||
+ `.output` The output layer of the LDT, a `pre` element.
|
||||
+ `.update()` Updates the highlighting of the LDT. It is automatically called on user input. You shouldn't need to call this unless you programmatically changed the contents of the `textarea`.
|
||||
|
||||
### Parser
|
||||
|
||||
+ `new Parser( [rules], [i] )` Creates a parser. `rules` is an object whose keys are CSS classes and values are the regular expressions which match each token. `i` is a boolean which determines if the matching is case insensitive, it defaults to `false`.
|
||||
+ `.add( rules )` Adds a mapping of CSS class names to regular expressions.
|
||||
+ `.tokenize( string )` Splits `string` into an array of tokens as defined by `.rules`.
|
||||
+ `.identify( string )` Finds the CSS class name associated with the token `string`.
|
||||
|
||||
### Keybinder
|
||||
This is a singleton, you do not need to instantiate this object.
|
||||
|
||||
+ `.bind( element, [keymap] )` Adds Keybinder methods to `element`, optionally setting the element's `keymap`.
|
||||
+ `element.keymap` A mapping of key names to callbacks.
|
||||
|
||||
### SelectHelper
|
||||
This is a singleton, you do not need to instantiate this object.
|
||||
|
||||
+ `.add( element )` Adds SelectHelper methods to `element`.
|
||||
+ `element.insertAtCursor( string )` Inserts `string` into the `element` before the current cursor position.
|
||||
|
||||
## Contributing
|
||||
You can help by testing browser compatibility, submitting bug reports and fixes, and providing any sort of feedback. Optionally let me know if you end up using LDT, I would love to see what you do with it. Thank you for supporting open source software!
|
||||
|
||||
## License
|
||||
LDT is open sourced under your choice of GPL v3 or MIT. Full text for both licenses should be available in this directory.
|
85
assembler/ldt/lib/Keybinder.js
Normal file
85
assembler/ldt/lib/Keybinder.js
Normal file
|
@ -0,0 +1,85 @@
|
|||
/* Keybinder.js
|
||||
* written by Colin Kuebler 2012
|
||||
* Part of LDT, dual licensed under GPLv3 and MIT
|
||||
* Simplifies the creation of keybindings on any element
|
||||
*/
|
||||
|
||||
var Keybinder = {
|
||||
bind: function( element, keymap ){
|
||||
element.keymap = keymap;
|
||||
var keyNames = {
|
||||
8: "Backspace",
|
||||
9: "Tab",
|
||||
13: "Enter",
|
||||
16: "Shift",
|
||||
17: "Ctrl",
|
||||
18: "Alt",
|
||||
19: "Pause",
|
||||
20: "CapsLk",
|
||||
27: "Esc",
|
||||
33: "PgUp",
|
||||
34: "PgDn",
|
||||
35: "End",
|
||||
36: "Home",
|
||||
37: "Left",
|
||||
38: "Up",
|
||||
39: "Right",
|
||||
40: "Down",
|
||||
45: "Insert",
|
||||
46: "Delete",
|
||||
112: "F1",
|
||||
113: "F2",
|
||||
114: "F3",
|
||||
115: "F4",
|
||||
116: "F5",
|
||||
117: "F6",
|
||||
118: "F7",
|
||||
119: "F8",
|
||||
120: "F9",
|
||||
121: "F10",
|
||||
122: "F11",
|
||||
123: "F12",
|
||||
145: "ScrLk" };
|
||||
var keyEventNormalizer = function(e){
|
||||
// get the event object and start constructing a query
|
||||
var e = e || window.event;
|
||||
var query = "";
|
||||
// add in prefixes for each key modifier
|
||||
e.shiftKey && (query += "Shift-");
|
||||
e.ctrlKey && (query += "Ctrl-");
|
||||
e.altKey && (query += "Alt-");
|
||||
e.metaKey && (query += "Meta-");
|
||||
// determine the key code
|
||||
var key = e.which || e.keyCode || e.charCode;
|
||||
// if we have a name for it, use it
|
||||
if( keyNames[key] )
|
||||
query += keyNames[key];
|
||||
// otherwise turn it into a string
|
||||
else
|
||||
query += String.fromCharCode(key).toUpperCase();
|
||||
/* DEBUG */
|
||||
//console.log("keyEvent: "+query);
|
||||
// try to run the keybinding, cancel the event if it returns true
|
||||
if( element.keymap[query] && element.keymap[query]() ){
|
||||
e.preventDefault && e.preventDefault();
|
||||
e.stopPropagation && e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
// capture onkeydown and onkeypress events to capture repeating key events
|
||||
// maintain a boolean so we only fire once per character
|
||||
var fireOnKeyPress = true;
|
||||
element.onkeydown = function(e){
|
||||
fireOnKeyPress = false;
|
||||
return keyEventNormalizer(e);
|
||||
};
|
||||
element.onkeypress = function(e){
|
||||
if( fireOnKeyPress )
|
||||
return keyEventNormalizer(e);
|
||||
fireOnKeyPress = true;
|
||||
return true;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
55
assembler/ldt/lib/Parser.js
Normal file
55
assembler/ldt/lib/Parser.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
/* Parser.js
|
||||
* written by Colin Kuebler 2012
|
||||
* Part of LDT, dual licensed under GPLv3 and MIT
|
||||
* Generates a tokenizer from regular expressions for TextareaDecorator
|
||||
*/
|
||||
|
||||
function Parser(rules, useI) {
|
||||
/* INIT */
|
||||
const api = this;
|
||||
|
||||
// variables used internally
|
||||
const i = useI ? 'i' : '';
|
||||
let parseRegex = null;
|
||||
let ruleSrc = [];
|
||||
let ruleNames = [];
|
||||
let ruleMap = {};
|
||||
|
||||
api.add = function (rules) {
|
||||
for (const [name, rule] of Object.entries(rules)) {
|
||||
let s = rule.source;
|
||||
s = '(?<' + name + '>' + s + ')';
|
||||
ruleSrc.push(s);
|
||||
// ruleMap[rule] = new RegExp('^(' + s + ')$', i);
|
||||
ruleNames.push(name);
|
||||
}
|
||||
parseRegex = new RegExp(ruleSrc.join('|'), 'gm' + i);
|
||||
};
|
||||
api.tokenize = function (input) {
|
||||
const tokens = [];
|
||||
const lines = input.match(/.*\n?/gm);
|
||||
for (const [lineNumber, line] of lines.entries()) {
|
||||
for (const match of line.matchAll(parseRegex)) {
|
||||
for (const ruleName of ruleNames) {
|
||||
if (match.groups[ruleName] !== undefined) {
|
||||
tokens.push({
|
||||
tag: ruleName,
|
||||
text: match[0],
|
||||
lineNumber,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return tokens;
|
||||
};
|
||||
api.identify = function (token) {
|
||||
return token.tag;
|
||||
};
|
||||
|
||||
api.add(rules);
|
||||
|
||||
return api;
|
||||
};
|
||||
|
25
assembler/ldt/lib/SelectHelper.js
Normal file
25
assembler/ldt/lib/SelectHelper.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
/* SelectHelper.js
|
||||
* written by Colin Kuebler 2012
|
||||
* Part of LDT, dual licensed under GPLv3 and MIT
|
||||
* Convenient utilities for cross browser textarea selection manipulation
|
||||
*/
|
||||
|
||||
var SelectHelper = {
|
||||
add: function( element ){
|
||||
element.insertAtCursor = element.createTextRange ?
|
||||
// IE version
|
||||
function(x){
|
||||
document.selection.createRange().text = x;
|
||||
} :
|
||||
// standards version
|
||||
function(x){
|
||||
var s = element.selectionStart,
|
||||
e = element.selectionEnd,
|
||||
v = element.value;
|
||||
element.value = v.substring(0, s) + x + v.substring(e);
|
||||
s += x.length;
|
||||
element.setSelectionRange(s, s);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
65
assembler/ldt/lib/TextareaDecorator.css
Normal file
65
assembler/ldt/lib/TextareaDecorator.css
Normal file
|
@ -0,0 +1,65 @@
|
|||
/* TextareaDecorator.css
|
||||
* written by Colin Kuebler 2012
|
||||
* Part of LDT, dual licensed under GPLv3 and MIT
|
||||
* Provides styles for rendering a textarea on top of a pre with scrollbars
|
||||
*/
|
||||
|
||||
/* settings you can play with */
|
||||
|
||||
.ldt, .ldt label {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.ldt, .ldt pre, .ldt textarea {
|
||||
font-size: 16px !important;
|
||||
/* resize algorithm depends on a monospaced font */
|
||||
font-family: monospace !important;
|
||||
}
|
||||
|
||||
.ldt textarea {
|
||||
/* hide the text but show the text caret */
|
||||
color: transparent;
|
||||
/* Firefox caret position is slow to update when color is transparent */
|
||||
color: rgba(0, 0, 0, 0.004);
|
||||
caret-color: #000;
|
||||
}
|
||||
|
||||
/* settings you shouldn't play with unless you have a good reason */
|
||||
|
||||
.ldt {
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ldt pre {
|
||||
margin: 0;
|
||||
overflow: initial;
|
||||
}
|
||||
|
||||
.ldt label {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: inline;
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.ldt textarea {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: 0;
|
||||
outline: none;
|
||||
resize: none;
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
overflow: hidden;
|
||||
/* IE doesn't support rgba textarea, so use vendor specific alpha filter */
|
||||
filter: alpha(opacity = 20);
|
||||
}
|
||||
|
111
assembler/ldt/lib/TextareaDecorator.js
Normal file
111
assembler/ldt/lib/TextareaDecorator.js
Normal file
|
@ -0,0 +1,111 @@
|
|||
/* TextareaDecorator.js
|
||||
* written by Colin Kuebler 2012
|
||||
* Part of LDT, dual licensed under GPLv3 and MIT
|
||||
* Builds and maintains a styled output layer under a textarea input layer
|
||||
*/
|
||||
function TextareaDecorator(textarea, parser) {
|
||||
/* INIT */
|
||||
var api = this;
|
||||
this.parser = parser;
|
||||
this.errorMap = {};
|
||||
|
||||
// construct editor DOM
|
||||
var parent = document.createElement("div");
|
||||
var output = document.createElement("pre");
|
||||
parent.appendChild(output);
|
||||
var label = document.createElement("label");
|
||||
parent.appendChild(label);
|
||||
// replace the textarea with RTA DOM and reattach on label
|
||||
textarea.parentNode.replaceChild(parent, textarea);
|
||||
label.appendChild(textarea);
|
||||
// transfer the CSS styles to our editor
|
||||
parent.className = 'ldt ' + textarea.className;
|
||||
textarea.className = '';
|
||||
// turn off built-in spellchecking in firefox
|
||||
textarea.spellcheck = false;
|
||||
// turn off word wrap
|
||||
textarea.wrap = "off";
|
||||
|
||||
var getParser = () => {
|
||||
return this.parser;
|
||||
}
|
||||
|
||||
var getErrorMap = () => {
|
||||
return this.errorMap;
|
||||
}
|
||||
|
||||
// coloring algorithm
|
||||
var color = function (input, output, parser) {
|
||||
var oldTokens = output.childNodes;
|
||||
var newTokens = parser.tokenize(input);
|
||||
var firstDiff, lastDiffNew, lastDiffOld;
|
||||
output.innerHTML = '';
|
||||
|
||||
// add in modified spans
|
||||
for (const token of newTokens) {
|
||||
var span = document.createElement("span");
|
||||
span.className = token.tag;
|
||||
if (getErrorMap()[token.lineNumber]) {
|
||||
span.classList.add('error');
|
||||
}
|
||||
span.textContent = span.innerText = token.text;
|
||||
output.appendChild(span);
|
||||
}
|
||||
};
|
||||
|
||||
api.input = textarea;
|
||||
api.output = output;
|
||||
api.update = function () {
|
||||
var input = textarea.value;
|
||||
if (input) {
|
||||
color(input, output, getParser());
|
||||
// determine the best size for the textarea
|
||||
var lines = input.split('\n');
|
||||
// find the number of columns
|
||||
var maxlen = 0, curlen;
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
// calculate the width of each tab
|
||||
var tabLength = 0, offset = -1;
|
||||
while ((offset = lines[i].indexOf('\t', offset + 1)) > -1) {
|
||||
tabLength += 7 - (tabLength + offset) % 8;
|
||||
}
|
||||
var curlen = lines[i].length + tabLength;
|
||||
// store the greatest line length thus far
|
||||
maxlen = maxlen > curlen ? maxlen : curlen;
|
||||
}
|
||||
textarea.cols = maxlen + 1;
|
||||
textarea.rows = lines.length + 2;
|
||||
} else {
|
||||
// clear the display
|
||||
output.innerHTML = '';
|
||||
// reset textarea rows/cols
|
||||
textarea.cols = textarea.rows = 1;
|
||||
}
|
||||
};
|
||||
|
||||
// detect all changes to the textarea,
|
||||
// including keyboard input, cut/copy/paste, drag & drop, etc
|
||||
if (textarea.addEventListener) {
|
||||
// standards browsers: oninput event
|
||||
textarea.addEventListener("input", api.update, false);
|
||||
} else {
|
||||
// MSIE: detect changes to the 'value' property
|
||||
textarea.attachEvent("onpropertychange",
|
||||
function (e) {
|
||||
if (e.propertyName.toLowerCase() === 'value') {
|
||||
api.update();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
// initial highlighting
|
||||
api.update();
|
||||
|
||||
api.setErrorMap = (errors) => {
|
||||
this.errorMap = errors;
|
||||
api.update();
|
||||
}
|
||||
|
||||
return api;
|
||||
};
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue