Tw.gl.repl
A multi-line texteditor in the Max Jitter OpenGL window for interaction with your patch in a Livecoding-like style.
Install / Use
/learn @twhiston/Tw.gl.replREADME
GLRepl
About
GLRepl is a Max Repl (Read/Execute/Print/Loop) environment based on the
excellent th.gl.texteditor. It consists of two objects [tw.gl.repl] and
[tw.gl.repl.dynamic-size-helper].
At it's core this is the same idea as th.gl.texteditor but the way in which functions can be attached to keys now significantly extends what it is possible to do. There is a fundamental philosophical difference between the idea of having a text buffer repl which performs actions on run/execute and a program where additionally every key press triggers a specific function. It means there are subtle differences between this and th.gl.texteditor which are important to be aware of. For example when you load a text file into th.gl.texteditor it just fills the buffer, in tw.gl.repl it replays the keystrokes back through the input processing. This means that any function which is attached to the individual keypress will be executed again. In it's usual configuration this means that the text is added to the text buffer, but it does not necessarily hold that this is true in every possible configuration. It would be possible to attach functions to keypresses which maintain state for other parts of an application, or which trigger messages to be output immediately etc. This means you should think about where you put your functionality, does it need to be in the repl itself, ie should it be triggered every time the keypresses are played back? or does it need to be some routing and handling in max? Further to this the repl introduces the concept of output formatters, these can be attached to the repl and then used in the configuration file to alter the output in some way. This allows you to format text easily for whatever you are hooking the repl up for, for example concatenating the output into a single line, or checking that it has balanced brances, or ensuring whitespace is in a regular format. However it also means that it's possible to, for example, have a short dsl for the repl, which is expanded to a full DSL of the thing you wish to interface with. This is useful if you need to interact with a verbose javascript but don't want to do a lot of typing.
TLDR not only is it possible to output the contents of the repl buffer for processing in max, but it's possible to attach any function to a keypress in the repl, which can in turn do things including generate messages for output. The text you input can also be mutated on run/execute so that something different is output from the repl.
Simple use cases for the repl can be handled entirely in configuration, and more
complex use cases can be easily managed by including a user-repl.js file
inside your project in which you can further customize behaviour by attaching
your own custom functions to keypresses or your own custom formatters for output
message handling. Read on for more about this.
See the patch in the extras menu for a few examples of how you might use the repl.
Install
You should install this inside your Max packages directory, in a folder called GLRepl,
it should then be available in max after a restart.
See help files for some ideas on what you might do with it!
Download zip
1. download a release from the github release page for this project
2. unzip and place in Max Searchpath (eg. MacOS ~/Documents/Max 8/Packages)
3. restart Max8
Git clone
If you want to git clone the repo you will need to have npm and tsc installed
as the compiled sources are not included in the repo.
cd ~/Documents/Max\ 8/Packages
git clone https://github.com/twhiston/tw.gl.repl.git
cd GLRepl/javascript
npm install && npm compile
//start Max8
4. Go to the extras menu and open the "GLRepl Overview" patch
All source files loaded by max are in the dist folder and the typescript which
it is compiled from is found in src. Unless you have a more complex project in
mind you probably don't need to care about this and can use the config file and user-repl.js
to extend the functionality of the repl.
Execute/Run functionality
By default executing the code in the repl will run a series of formatters and output the resulting text from outlet 0. This allows you to write livecoding style commands in the repl, ensure they are formatted as needed, and then output them for further routing and processing in max.
Scaling
It's undeniably the most useful to have a repl that you can dynamically resize
and to this end a helper object is included. See the help file for information
on how to connect this, or hover the inlets and outlets in max. The scaling fits
some window sizes better than others, and sometimes it might unavoidably break a
boundary, you should just resize the window in a way that sorts this out.
You can also send a scale 1. value to the object, which the current scaling will
be multiplied by. No guarantee that the current scaling works super well with every
font either!
Config
Basic configuration of your repl can be achieved by loading a replkeys.json
file to reconfigure it. This file is an object
The config is in the following form:
{
"settings":{
"keypressProcessor": {
"overrideAlphaNum": true
}
}
"bindings": [
{
"id": "execute",
"asciiCode": 2044,
"functions": [
"return 'run'"
]
},
{
"id": "backspace",
"asciiCode": -7,
"functions": [
"ctx.backSpace()"
]
},
{
"id": "customSpace",
"asciiCode": -2,
"functions": [
"myCustomFunction"
]
}
]
}
Settings
Settings allow you to set the value of some repl settings instead of settings them through messages or in code. All currently available settings are as follows:
"settings": {
"repl": {
"INDENTATION": 4,
"CMNT": "//"
},
"keypressProcessor": {
"overrideAlphaNum": true
},
"textbuffer": {
"formatters": [
"whitespace",
"bracebalanced",
"singleline",
"commentremover"
//can also include your own custom formatters here
]
}
}
Bindings
Bindings are an array of of objects which bind a key number to a function. In
contrast to th.gl.editor there are no internal functions, so everything is
defined in this file and the user can override anything. As you can see there
are a number of ways to define the functions that are called, and it is possible
to call multiple functions with a single key. Functions can be defined as a
function body in text (which will be wrapped
new Function('k', 'ctx', funcString)), it can be a function from whatever context
is passed in (in the case of this application it is an instance of REPLManager),
or it can be a reference to a custom function.
There is one "special" keycode which is not defined in config, this is the binding
for 'ignore_keys'. This is hardcoded to option+d which is keycode 8706. This needs
to be handled outside of the javascript because you want to be able to re-enable
the keys. You can change this binding by sending the message ignore_keys_id and
the keycode id that you want.
Binding a simple function
You can create a simple key binding to output a message when a key is pressed with the following configuration
{
"id": "execute",
"asciiCode": 2044,
"functions": [
"return 'run'"
]
}
Each of the entries in functions will be wrapped in a
new Function('k', 'ctx', funcString) and will be executed on keypress. This
allows us to perform simple actions such as returning custom messages which we
can process further in max easily.
Context based functions
Because the functions called have the signature ('k', 'ctx') functions we create in
config will always contain the value of the key that was pressed in k. The ctx
parameter however will contain an instance of REPLManager, which means that its functions
and all the functions of the subclasses are available here. This allows you to create very
complex functionality in just the config file. The shortkey to replace a line of
text in the buffer with the pastebin is an exaxple of this
{
"id": "replaceLine-alt-p",
"asciiCode": 960,
"functions": [
"var pb = ctx.tb.pasteBinGet(); var startLine = ctx.c.line(); ctx.deleteLine(); if(ctx.c.line() < ctx.tb.length()-1){ctx.jumpLine(-1);ctx.jumpTo(1);} if(startLine === 0){ctx.jumpTo(0); ctx.newLine(); ctx.jumpTo(2); }else { ctx.newLine(); } for(var i = 0; i < pb.length; i++){for (var a = 0; a < pb[i].length; a++) {var char = pb[i].charCodeAt(a); ctx.keyPress(char)}}"
]
}
Including custom functions
One of the ways to extend the repl further is to attach or preload your own functions
so you can tie them to a key in the config.
To make this easier the package tries to load a file called user-repl.js, max should
load this fine if it's in your patch folder. Inside it you have access to glrepl.renderer
and glrepl.manager, you also have access to a Dict of replkeys.json in sKeys. Which
will be stringified and passed into the repl on init()
Most basic usage will be something like:
//Typescript signature is actually
//const functionOne = (k: number, ctx: {}) => {
const functionOne = (k, ctx) => {
return `some message`;
};
glrepl.manager.kp.preloadFunction('doSomething', functionOne);
You can then use this in your replkeys.json app config by binding it
to a key
{
"bindings": [
{
"id": "pushSpace",
