goal: Basic parinfer integration (#173)

This commit is contained in:
Tyler Wilding 2022-12-17 14:54:05 -05:00 committed by GitHub
parent c8a0286ff3
commit 774f65b015
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 17400 additions and 5440 deletions

View file

@ -22,9 +22,10 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 16
cache: npm
cache: yarn
- name: Install NPM Dependencies and Build Extension
run: |
npm ci
npm run compile
- name: Install Dependencies
run: yarn install --frozen-lockfile
- name: Build Extension
run: yarn compile

View file

@ -20,12 +20,14 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 16
cache: npm
cache: yarn
- name: Install Dependencies
run: yarn install --frozen-lockfile
- name: Check Linting
run: yarn lint
- name: Install Dependencies and Check Formatting
run: |
npm ci
npm run lint
formatting:
name: Formatting
runs-on: ubuntu-latest
@ -37,9 +39,10 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 16
cache: npm
cache: yarn
- name: Install Dependencies and Check Formatting
run: |
npm ci
npm run format:check
- name: Install Dependencies
run: yarn install --frozen-lockfile
- name: Check Linting
run: yarn format:check

View file

@ -25,7 +25,10 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 16
cache: npm
cache: yarn
- name: Install Dependencies
run: yarn install --frozen-lockfile
- name: Bump Version
env:
@ -33,9 +36,8 @@ jobs:
run: |
git config --global user.name "OpenGOALBot"
git config --global user.email "OpenGOALBot@users.noreply.github.com"
npm ci
npx vsce package
npx vsce publish ${{ github.event.inputs.bump }}
yarn vsce package
yarn vsce publish ${{ github.event.inputs.bump }}
git push
git push origin $(git tag --points-at HEAD)

1
decs.d.ts vendored Normal file
View file

@ -0,0 +1 @@
declare module "parinfer";

View file

@ -0,0 +1,463 @@
/* BASICS */
.CodeMirror {
/* Set height, width, borders, and global font properties here */
font-family: monospace;
height: 300px;
color: black;
}
/* PADDING */
.CodeMirror-lines {
padding: 4px 0; /* Vertical padding around content */
}
.CodeMirror pre {
padding: 0 4px; /* Horizontal padding of content */
}
.CodeMirror-scrollbar-filler,
.CodeMirror-gutter-filler {
background-color: white; /* The little square between H and V scrollbars */
}
/* GUTTER */
.CodeMirror-gutters {
border-right: 1px solid #ddd;
background-color: #f7f7f7;
white-space: nowrap;
}
.CodeMirror-linenumbers {
}
.CodeMirror-linenumber {
padding: 0 3px 0 5px;
min-width: 20px;
text-align: right;
color: #999;
white-space: nowrap;
}
.CodeMirror-guttermarker {
color: black;
}
.CodeMirror-guttermarker-subtle {
color: #999;
}
/* CURSOR */
.CodeMirror-cursor {
border-left: 1px solid black;
border-right: none;
width: 0;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.cm-fat-cursor .CodeMirror-cursor {
width: auto;
border: 0;
background: #7e7;
}
.cm-fat-cursor div.CodeMirror-cursors {
z-index: 1;
}
.cm-animate-fat-cursor {
width: auto;
border: 0;
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
background-color: #7e7;
}
@-moz-keyframes blink {
0% {
}
50% {
background-color: transparent;
}
100% {
}
}
@-webkit-keyframes blink {
0% {
}
50% {
background-color: transparent;
}
100% {
}
}
@keyframes blink {
0% {
}
50% {
background-color: transparent;
}
100% {
}
}
/* Can style cursor different in overwrite (non-insert) mode */
.CodeMirror-overwrite .CodeMirror-cursor {
}
.cm-tab {
display: inline-block;
text-decoration: inherit;
}
.CodeMirror-ruler {
border-left: 1px solid #ccc;
position: absolute;
}
/* DEFAULT THEME */
.cm-s-default .cm-header {
color: blue;
}
.cm-s-default .cm-quote {
color: #090;
}
.cm-negative {
color: #d44;
}
.cm-positive {
color: #292;
}
.cm-header,
.cm-strong {
font-weight: bold;
}
.cm-em {
font-style: italic;
}
.cm-link {
text-decoration: underline;
}
.cm-strikethrough {
text-decoration: line-through;
}
.cm-s-default .cm-keyword {
color: #708;
}
.cm-s-default .cm-atom {
color: #219;
}
.cm-s-default .cm-number {
color: #164;
}
.cm-s-default .cm-def {
color: #00f;
}
.cm-s-default .cm-variable,
.cm-s-default .cm-punctuation,
.cm-s-default .cm-property,
.cm-s-default .cm-operator {
}
.cm-s-default .cm-variable-2 {
color: #05a;
}
.cm-s-default .cm-variable-3 {
color: #085;
}
.cm-s-default .cm-comment {
color: #a50;
}
.cm-s-default .cm-string {
color: #a11;
}
.cm-s-default .cm-string-2 {
color: #f50;
}
.cm-s-default .cm-meta {
color: #555;
}
.cm-s-default .cm-qualifier {
color: #555;
}
.cm-s-default .cm-builtin {
color: #30a;
}
.cm-s-default .cm-bracket {
color: #997;
}
.cm-s-default .cm-tag {
color: #170;
}
.cm-s-default .cm-attribute {
color: #00c;
}
.cm-s-default .cm-hr {
color: #999;
}
.cm-s-default .cm-link {
color: #00c;
}
.cm-s-default .cm-error {
color: #f00;
}
.cm-invalidchar {
color: #f00;
}
.CodeMirror-composing {
border-bottom: 2px solid;
}
/* Default styles for common addons */
/* Parinfer edit: don't change the colors, we just want to style the background
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
*/
.CodeMirror-matchingtag {
background: rgba(255, 150, 0, 0.3);
}
.CodeMirror-activeline-background {
background: #e8f2ff;
}
/* STOP */
/* The rest of this file contains styles related to the mechanics of
the editor. You probably shouldn't touch them. */
.CodeMirror {
position: relative;
overflow: hidden;
background: white;
}
.CodeMirror-scroll {
overflow: scroll !important; /* Things will break if this is overridden */
/* 30px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -30px;
margin-right: -30px;
padding-bottom: 30px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
}
.CodeMirror-sizer {
position: relative;
border-right: 30px solid transparent;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
before actuall scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar,
.CodeMirror-hscrollbar,
.CodeMirror-scrollbar-filler,
.CodeMirror-gutter-filler {
position: absolute;
z-index: 6;
display: none;
}
.CodeMirror-vscrollbar {
right: 0;
top: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
bottom: 0;
left: 0;
overflow-y: hidden;
overflow-x: scroll;
}
.CodeMirror-scrollbar-filler {
right: 0;
bottom: 0;
}
.CodeMirror-gutter-filler {
left: 0;
bottom: 0;
}
.CodeMirror-gutters {
position: absolute;
left: 0;
top: 0;
z-index: 3;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
display: inline-block;
margin-bottom: -30px;
/* Hack to make IE7 behave */
*zoom: 1;
*display: inline;
}
.CodeMirror-gutter-wrapper {
position: absolute;
z-index: 4;
background: none !important;
border: none !important;
}
.CodeMirror-gutter-background {
position: absolute;
top: 0;
bottom: 0;
z-index: 4;
}
.CodeMirror-gutter-elt {
position: absolute;
cursor: default;
z-index: 4;
}
.CodeMirror-gutter-wrapper {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.CodeMirror-lines {
cursor: text;
min-height: 1px; /* prevents collapsing before first draw */
}
.CodeMirror pre {
/* Reset some styles that the rest of the page might have set */
-moz-border-radius: 0;
-webkit-border-radius: 0;
border-radius: 0;
border-width: 0;
background: transparent;
font-family: inherit;
font-size: inherit;
margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
z-index: 2;
position: relative;
overflow: visible;
-webkit-tap-highlight-color: transparent;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-linebackground {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
z-index: 2;
overflow: auto;
}
.CodeMirror-widget {
}
.CodeMirror-code {
outline: none;
}
/* Force content-box sizing for the elements where we expect it */
.CodeMirror-scroll,
.CodeMirror-sizer,
.CodeMirror-gutter,
.CodeMirror-gutters,
.CodeMirror-linenumber {
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-cursor {
position: absolute;
}
.CodeMirror-measure pre {
position: static;
}
div.CodeMirror-cursors {
visibility: hidden;
position: relative;
z-index: 3;
}
div.CodeMirror-dragcursors {
visibility: visible;
}
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
.CodeMirror-selected {
background: #d9d9d9;
}
.CodeMirror-focused .CodeMirror-selected {
background: #d7d4f0;
}
.CodeMirror-crosshair {
cursor: crosshair;
}
.CodeMirror-line::selection,
.CodeMirror-line > span::selection,
.CodeMirror-line > span > span::selection {
background: #d7d4f0;
}
.CodeMirror-line::-moz-selection,
.CodeMirror-line > span::-moz-selection,
.CodeMirror-line > span > span::-moz-selection {
background: #d7d4f0;
}
.cm-searching {
background: #ffa;
background: rgba(255, 255, 0, 0.4);
}
/* IE7 hack to prevent it from returning funny offsetTops on the spans */
.CodeMirror span {
*vertical-align: text-bottom;
}
/* Used to force a border model for a node */
.cm-force-border {
padding-right: 0.1px;
}
@media print {
/* Hide the cursor when printing */
.CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}
/* See issue #2901 */
.cm-tab-wrap-hack:after {
content: "";
}
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext {
background: none;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,733 @@
//
// Parinfer for CodeMirror 1.4.1
//
// Copyright 2017 © Shaun Lebron
// MIT License
//
//------------------------------------------------------------------------------
// JS Module Boilerplate
//------------------------------------------------------------------------------
(function (root, factory) {
if (typeof define === "function" && define.amd) {
define([], factory);
} else if (typeof module === "object" && module.exports) {
module.exports = factory();
} else {
root.parinferCodeMirror = factory();
}
})(this, function () {
// start module anonymous scope
"use strict";
//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
// We attach our Parinfer state to this property on the CodeMirror instance.
var STATE_PROP = "__parinfer__";
var PAREN_MODE = "paren";
var INDENT_MODE = "indent";
var SMART_MODE = "smart";
var MODES = [PAREN_MODE, INDENT_MODE, SMART_MODE];
var CLASSNAME_ERROR = "parinfer-error";
var CLASSNAME_PARENTRAIL = "parinfer-paren-trail";
var CLASSNAME_LOCUS_PAREN = "parinfer-locus-paren";
var CLASSNAME_LOCUS_LAYER = "parinfer-locus";
//------------------------------------------------------------------------------
// State
// (`state` represents the parinfer state attached to a single CodeMirror editor)
//------------------------------------------------------------------------------
function initialState(cm, mode, options) {
return {
cm: cm,
mode: mode,
options: options,
enabled: false,
cursorTimeout: null,
monitorCursor: true,
prevCursorX: null,
prevCursorLine: null,
callbackCursor: null,
callbackChanges: null,
};
}
//------------------------------------------------------------------------------
// Errors
//------------------------------------------------------------------------------
function error(msg) {
return "parinferCodeMirror: " + msg;
}
function ensureMode(mode) {
if (MODES.indexOf(mode) === -1) {
throw error(
'Mode "' +
mode +
'" is invalid. ' +
"Must be one of: " +
MODES.join(",")
);
}
}
function ensureState(cm) {
var state = cm[STATE_PROP];
if (!state) {
throw error(
"You must call parinferCodeMirror.init(cm) on a CodeMirror instance " +
"before you can use the rest of the API."
);
}
return state;
}
//------------------------------------------------------------------------------
// Data conversion
//------------------------------------------------------------------------------
function convertChanges(changes) {
return changes.map(function (change) {
return {
x: change.from.ch,
lineNo: change.from.line,
oldText: change.removed.join("\n"),
newText: change.text.join("\n"),
};
});
}
//------------------------------------------------------------------------------
// Markers
//------------------------------------------------------------------------------
function clearMarks(cm, className) {
var i;
var marks = cm.getAllMarks();
for (i = 0; i < marks.length; i++) {
if (marks[i].className === className) {
marks[i].clear();
}
}
}
function clearAllMarks(cm) {
clearMarks(cm, CLASSNAME_ERROR);
clearMarks(cm, CLASSNAME_PARENTRAIL);
}
function addMark(cm, lineNo, x0, x1, className) {
var from = { line: lineNo, ch: x0 };
var to = { line: lineNo, ch: x1 };
cm.markText(from, to, { className: className });
}
function updateErrorMarks(cm, error) {
clearMarks(cm, CLASSNAME_ERROR);
if (error) {
addMark(cm, error.lineNo, error.x, error.x + 1, CLASSNAME_ERROR);
if (error.extra) {
addMark(
cm,
error.extra.lineNo,
error.extra.x,
error.extra.x + 1,
CLASSNAME_ERROR
);
}
}
}
function updateParenTrailMarks(cm, parenTrails) {
clearMarks(cm, CLASSNAME_PARENTRAIL);
if (parenTrails) {
var i, trail;
for (i = 0; i < parenTrails.length; i++) {
trail = parenTrails[i];
addMark(
cm,
trail.lineNo,
trail.startX,
trail.endX,
CLASSNAME_PARENTRAIL
);
}
}
}
//------------------------------------------------------------------------------
// Tab Stops
//------------------------------------------------------------------------------
function getSelectionStartLine(cm) {
var selection = cm.listSelections()[0];
// head and anchor are reversed sometimes
return Math.min(selection.head.line, selection.anchor.line);
}
function expandTabStops(tabStops) {
if (!tabStops) {
return null;
}
var xs = [];
var i,
stop,
prevX = -1;
for (i = 0; i < tabStops.length; i++) {
stop = tabStops[i];
if (prevX >= stop.x) {
xs.pop();
}
xs.push(stop.x);
xs.push(stop.x + (stop.ch === "(" ? 2 : 1));
if (stop.argX != null) {
xs.push(stop.argX);
}
}
return xs;
}
function nextStop(stops, x, dx) {
if (!stops) {
return null;
}
var i, stop, right, left;
for (i = 0; i < stops.length; i++) {
stop = stops[i];
if (x < stop) {
right = stop;
break;
}
if (x > stop) {
left = stop;
}
}
if (dx === -1) {
return left;
}
if (dx === 1) {
return right;
}
}
function getIndent(cm, lineNo) {
var line = cm.getLine(lineNo);
var i;
for (i = 0; i < line.length; i++) {
if (line[i] !== " ") {
return i;
}
}
return null;
}
function indentSelection(cm, dx, stops) {
// Indent whole Selection
var lineNo = getSelectionStartLine(cm);
var x = getIndent(cm, lineNo);
var nextX = nextStop(stops, x, dx);
if (nextX == null) {
nextX = Math.max(0, x + dx * 2);
}
cm.indentSelection(nextX - x);
}
function indentLine(cm, lineNo, delta) {
var text = cm.getDoc().getLine(lineNo);
// cm.indentLine does not indent empty lines
if (text.trim() !== "") {
cm.indentLine(lineNo, delta);
return;
}
if (delta > 0) {
var spaces = Array(delta + 1).join(" ");
cm.replaceSelection(spaces);
} else {
var x = cm.getCursor().ch;
cm.replaceRange(
"",
{ line: lineNo, ch: x + delta },
{ line: lineNo, ch: x },
"+indent"
);
}
}
function indentAtCursor(cm, dx, stops) {
// Indent single line at cursor
var cursor = cm.getCursor();
var lineNo = cursor.line;
var x = cursor.ch;
var indent = getIndent(cm, cursor.line);
var stop = nextStop(stops, x, dx);
var useStops = indent == null || x === indent;
var nextX = stop != null && useStops ? stop : Math.max(0, x + dx * 2);
if (indent != null && indent < x && x < nextX) {
var spaces = Array(nextX - x + 1).join(" ");
cm.replaceSelection(spaces);
} else {
indentLine(cm, lineNo, nextX - x);
}
}
function onTab(cm, dx) {
var hasSelection = cm.somethingSelected();
var state = ensureState(cm);
var stops = expandTabStops(state.tabStops);
if (hasSelection) {
indentSelection(cm, dx, stops);
} else {
indentAtCursor(cm, dx, stops);
}
}
//------------------------------------------------------------------------------
// Locus/Guides layer
//------------------------------------------------------------------------------
function getLayerContainer(cm) {
var wrapper = cm.getWrapperElement();
var lines = wrapper.querySelector(".CodeMirror-lines");
var container = lines.parentNode;
return container;
}
function parenSelected(paren, sel) {
return sel.contains({ line: paren.lineNo, ch: paren.x }) !== -1;
}
function pointRevealsParenTrail(trail, pos) {
return (
pos.line === trail.lineNo &&
trail.startX <= pos.ch /* && cursor.ch <= trail.endX */
);
}
function hideParen(cm, paren) {
var sel = cm.getDoc().sel;
var sel0 = sel.ranges[0];
var shouldShowCloser =
paren.lineNo === paren.closer.lineNo ||
!paren.closer.trail ||
pointRevealsParenTrail(paren.closer.trail, sel0.anchor) ||
pointRevealsParenTrail(paren.closer.trail, sel0.head) ||
parenSelected(paren.closer, sel);
if (!shouldShowCloser) {
addMark(
cm,
paren.closer.lineNo,
paren.closer.x,
paren.closer.x + 1,
CLASSNAME_LOCUS_PAREN
);
}
hideParens(cm, paren.children);
}
function hideParens(cm, parens) {
var i;
for (i = 0; i < parens.length; i++) {
hideParen(cm, parens[i]);
}
}
function charPos(cm, paren) {
var p = cm.charCoords({ line: paren.lineNo, ch: paren.x }, "local");
var w = p.right - p.left;
return {
midx: p.left + w / 2,
right: p.right,
left: p.left,
top: p.top,
bottom: p.bottom,
};
}
function getRightBound(cm, startLine, endLine) {
var doc = cm.getDoc();
var maxWidth = 0;
var maxLineNo = 0;
var i;
for (i = startLine; i <= endLine; i++) {
var line = doc.getLine(i);
if (line.length > maxWidth) {
maxWidth = line.length;
maxLineNo = i;
}
}
var wall = charPos(cm, { lineNo: maxLineNo, x: maxWidth });
return wall.right;
}
function addBox(cm, paren) {
var layer = cm[STATE_PROP].layer;
var paper = layer.paper;
var charW = layer.charW;
var charH = layer.charH;
var open = charPos(cm, paren);
var close = charPos(cm, paren.closer);
var r = 4;
if (paren.closer.trail && paren.lineNo !== paren.closer.lineNo) {
switch (layer.type) {
case "guides":
paper.path(
["M", open.midx, open.bottom, "V", close.bottom].join(" ")
);
break;
case "locus":
var right = getRightBound(cm, paren.lineNo, paren.closer.lineNo);
paper.path(
[
"M",
open.midx,
open.top + r,
"A",
r,
r,
0,
0,
1,
open.midx + r,
open.top,
"H",
right - r,
"A",
r,
r,
0,
0,
1,
right,
open.top + r,
"V",
close.bottom,
"H",
open.midx,
"V",
open.bottom,
].join(" ")
);
break;
}
}
addBoxes(cm, paren.children);
}
function addBoxes(cm, parens) {
var i;
for (i = 0; i < parens.length; i++) {
addBox(cm, parens[i]);
}
}
function addLayer(cm, type) {
var layer = cm[STATE_PROP].layer;
layer.type = type;
var el = document.createElement("div");
el.style.position = "absolute";
el.style.left = "0";
el.style.top = "0";
el.style["z-index"] = 100;
el.className = CLASSNAME_LOCUS_LAYER;
layer.el = el;
layer.container.appendChild(el);
var pixelW = layer.container.clientWidth;
var pixelH = layer.container.clientHeight;
layer.paper = Raphael(el, pixelW, pixelH);
}
function clearLayer(cm) {
var layer = cm[STATE_PROP].layer;
if (layer && layer.el) {
layer.container.removeChild(layer.el);
}
}
function updateLocusLayer(cm, parens) {
clearMarks(cm, CLASSNAME_LOCUS_PAREN);
if (parens) {
hideParens(cm, parens);
clearLayer(cm);
// addLayer(cm, 'locus'); // don't draw boxes, just draw guides
addLayer(cm, "guides");
addBoxes(cm, parens);
}
}
function updateGuidesLayer(cm, parens) {
if (parens) {
clearLayer(cm);
addLayer(cm, "guides");
addBoxes(cm, parens);
}
}
//------------------------------------------------------------------------------
// Text Correction
//------------------------------------------------------------------------------
// If `changes` is missing, then only the cursor position has changed.
function fixText(state, changes) {
// Get editor data
var cm = state.cm;
var text = cm.getValue();
var hasSelection = cm.somethingSelected();
var selections = cm.listSelections();
var cursor = cm.getCursor();
var scroller = cm.getScrollerElement();
// Create options
var options = {
cursorLine: cursor.line,
cursorX: cursor.ch,
prevCursorLine: state.prevCursorLine,
prevCursorX: state.prevCursorX,
};
if (hasSelection) {
options.selectionStartLine = getSelectionStartLine(cm);
}
if (state.options) {
var p;
for (p in state.options) {
if (state.options.hasOwnProperty(p)) {
options[p] = state.options[p];
}
}
}
if (changes) {
options.changes = convertChanges(changes);
}
var locus = state.options && state.options.locus;
var guides = state.options && state.options.guides;
if (locus || guides) {
delete options.locus;
delete options.guides;
options.returnParens = true;
}
// Run Parinfer
var result;
var mode = SMART_MODE;
let output = `Mode: ${mode}\n`;
output += `Before Text:\n${text}\n\n`;
output += `Input Options:\n${JSON.stringify(options, null, 2)}\n\n`;
switch (mode) {
case INDENT_MODE:
result = parinfer.indentMode(text, options);
break;
case PAREN_MODE:
result = parinfer.parenMode(text, options);
break;
case SMART_MODE:
result = parinfer.smartMode(text, options);
break;
default:
ensureMode(mode);
}
output += `After Text:\n${result.text}\n\n`;
output += `Result:\n${JSON.stringify(result, null, 2)}\n\n`;
document.getElementById("output").innerHTML = output;
// Remember the paren tree.
state.parens = result.parens;
// Remember tab stops for smart tabbing.
state.tabStops = result.tabStops;
if (text !== result.text) {
// Backup history
var hist = cm.getHistory();
// Update text
cm.setValue(result.text);
// Update cursor and selection
state.monitorCursor = false;
if (hasSelection) {
cm.setSelections(selections);
} else {
cm.setCursor(result.cursorLine, result.cursorX);
}
// Restore history to avoid pushing our edits to the history stack.
cm.setHistory(hist);
setTimeout(function () {
state.monitorCursor = true;
}, 0);
// Update scroll position
cm.scrollTo(scroller.scrollLeft, scroller.scrollTop);
}
// Clear or add new marks
updateErrorMarks(cm, result.error);
updateParenTrailMarks(cm, result.parenTrails);
// Remember the cursor position for next time
state.prevCursorLine = result.cursorLine;
state.prevCursorX = result.cursorX;
if (locus) {
updateLocusLayer(cm, result.parens);
} else if (guides) {
updateGuidesLayer(cm, result.parens);
}
// Re-run with original mode if code was finally fixed in Paren Mode.
if (state.fixMode && result.success) {
state.fixMode = false;
return fixText(state, changes);
}
return result.success;
}
//------------------------------------------------------------------------------
// CodeMirror Integration
//------------------------------------------------------------------------------
function onCursorChange(state) {
clearTimeout(state.cursorTimeout);
if (state.monitorCursor) {
state.cursorTimeout = setTimeout(function () {
fixText(state);
}, 0);
}
}
function onTextChanges(state, changes) {
clearTimeout(state.cursorTimeout);
var origin = changes[0].origin;
if (origin !== "setValue") {
fixText(state, changes);
}
}
function on(state) {
if (state.enabled) {
return;
}
state.callbackCursor = function (cm) {
onCursorChange(state);
};
state.callbackChanges = function (cm, changes) {
onTextChanges(state, changes);
};
var cm = state.cm;
cm.on("cursorActivity", state.callbackCursor);
cm.on("changes", state.callbackChanges);
state.parinferKeys = {
Tab: function (cm) {
onTab(cm, 1);
},
"Shift-Tab": function (cm) {
onTab(cm, -1);
},
};
cm.addKeyMap(state.parinferKeys);
state.enabled = true;
}
function off(state) {
if (!state.enabled) {
return;
}
var cm = state.cm;
clearAllMarks(cm);
cm.off("cursorActivity", state.callbackCursor);
cm.off("changes", state.callbackChanges);
cm.removeKeyMap(state.parinferKeys);
state.enabled = false;
}
//------------------------------------------------------------------------------
// Public API
//------------------------------------------------------------------------------
function init(cm, mode, options) {
var state = cm[STATE_PROP];
if (state) {
throw error("init has already been called on this CodeMirror instance");
}
mode = mode || SMART_MODE;
ensureMode(mode);
state = initialState(cm, mode, options);
cm[STATE_PROP] = state;
state.layer = {
container: getLayerContainer(cm),
};
return enable(cm);
}
function enable(cm) {
var state = ensureState(cm);
// preprocess text to keep Parinfer from changing code structure
if (state.mode !== PAREN_MODE) {
state.fixMode = true;
}
on(state);
return fixText(state);
}
function disable(cm) {
var state = ensureState(cm);
off(state);
}
function setMode(cm, mode) {
var state = ensureState(cm);
ensureMode(mode);
state.mode = mode;
return fixText(state);
}
function setOptions(cm, options) {
var state = ensureState(cm);
state.options = options;
return fixText(state);
}
var API = {
version: "1.4.1",
init: init,
enable: enable,
disable: disable,
setMode: setMode,
setOptions: setOptions,
};
return API;
}); // end module anonymous scope

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,646 @@
* {
box-sizing: border-box;
}
body {
font-family: "Open Sans", Arial, freesans, sans-serif;
color: #555;
background: #f7f7f7;
}
#xkcd a {
border-bottom: 0;
}
#xkcd .caption {
padding-top: 0;
}
#toc {
text-align: left;
margin-left: 10px;
font-size: 0.9em;
}
a.header-link {
color: #ccc;
position: absolute;
left: -1em;
border-bottom: 0;
}
.toc-link a {
border-bottom: 0;
color: #111;
}
.toc-link {
line-height: 1.6em;
opacity: 0.4;
transition: opacity 0.3s;
margin-top: 2px;
margin-right: 2px;
}
.toc-hide {
display: none;
}
.toc-link.toc-active {
border-right: 2px solid #333;
margin-right: 0px;
opacity: 1;
}
.toc-link.toc-active-ancestor {
border-right: 2px solid #ccc;
margin-right: 0;
opacity: 1;
}
.toc-level-1 {
display: none;
}
.toc-level-2 {
padding-left: 10px;
}
.toc-level-3 {
padding-left: 30px;
}
.toc-level-4 {
padding-left: 40px;
}
#controls {
position: fixed;
right: 0;
top: 0;
background-color: rgba(0, 255, 255, 0.3);
margin: 0;
padding: 20px;
}
.interact {
padding: 20px;
background: #e7eae9;
border-radius: 8px;
margin: 32px 0;
}
.interact img {
float: left;
height: 58px;
margin-right: 20px;
}
.interact:after {
content: "";
display: block;
clear: both;
}
.paredit-table {
border-collapse: collapse;
}
.paredit-table th,
.paredit-table td {
padding: 14px;
border: 1px solid #cecece;
background: #fefefe;
vertical-align: top;
}
.paredit-table th {
color: #888;
font-style: italic;
background: none;
}
h1,
h2,
h3,
h4,
h5,
h6 {
color: #222;
margin-top: 0;
margin-bottom: 20px;
position: relative;
}
h1 {
margin: 32px 0;
text-align: center;
font-size: 48px;
font-weight: 200;
}
h1 em {
text-decoration: none;
font-style: normal;
opacity: 0.3;
}
.subtitle {
font-size: 20px;
font-weight: bold;
margin-top: -18px;
margin-bottom: 32px;
}
.subtitle em {
color: #111;
border-bottom: 1px solid #111;
}
.features {
color: #888;
list-style-type: square;
}
a {
border-bottom: 1px solid #cc3385;
color: #cc3385;
}
a:link,
a:visited,
a:hover,
a:active {
text-decoration: none;
}
p,
li {
line-height: 1.7em;
color: #555;
}
.sidebar {
width: 300px;
position: fixed;
left: 0;
top: 0;
padding-left: 20px;
padding-bottom: 32px;
height: 100%;
border-right: 1px solid #ddd;
background-color: #f1f1f1;
overflow-y: auto;
}
.sidebar-logo {
margin: 60px 0 0 70px;
display: block;
}
.credits {
font-size: 0.8em;
text-align: center;
margin-top: -20px;
margin-bottom: 20px;
}
.credits a {
opacity: 0.5;
color: #333;
border-bottom: 0;
}
#app {
position: absolute;
left: 300px;
top: 0;
right: 0;
}
.wrapper {
right: 0;
margin: 0;
border-top: 1px solid #dedede;
}
.wrapper.fold {
background: #fefefe;
}
.wrapper.fold .interact {
background: #f7f7f7;
border: 1px solid #dedede;
}
.wrapper.fold .title {
font-weight: bold;
font-style: italic;
}
section {
width: 862px;
padding: 70px;
}
.wrapper:first-child > section {
margin-top: 0;
padding-top: 0;
border-top: 0;
}
.caption {
padding-top: 20px;
margin-bottom: 5px;
margin-left: 5px;
font-style: italic;
color: #888;
}
code {
font-family: Menlo, Consolas, "DejaVu Sans Mono", monospace;
font-size: 16px;
}
.CodeMirror {
font-family: Menlo, Consolas, "DejaVu Sans Mono", monospace;
background: #fefefe;
border: 1px solid #eee;
padding: 5px;
margin-bottom: 20px;
}
.cm-bracket.cm-eol {
opacity: 0.4;
}
.two-col {
}
.col {
float: left;
width: 350px;
margin-right: 10px;
}
.two-col:after {
content: "";
display: block;
clear: both;
}
#cm-code-intro {
height: 200px;
margin-bottom: 80px;
}
#cm-code-lisp-expr,
#cm-code-c-expr {
height: 120px;
}
#cm-code-lisp-indent,
#cm-code-c-indent {
height: 174px;
}
#cm-code-skim,
#cm-code-inspect {
height: 96px;
}
#cm-code-skim span.cm-bracket {
opacity: 0.15;
color: #000;
}
#cm-code-inspect span.cm-keyword,
#cm-code-inspect span.cm-variable,
#cm-code-inspect span.cm-builtin,
#cm-code-inspect span.cm-string,
#cm-code-inspect span.cm-def,
#cm-code-inspect span.cm-atom,
#cm-code-inspect span.cm-number,
#cm-code-inspect span.cm-comment {
opacity: 0.15;
color: #000;
}
#cm-code-indent,
#cm-code-indent-far {
height: 70px;
}
#cm-code-indent-multi,
#cm-code-intro-indent {
height: 130px;
}
#cm-code-line,
#cm-code-intro-insert {
height: 150px;
}
#cm-code-comment,
#cm-code-intro-comment {
height: 130px;
}
#cm-code-wrap,
#cm-code-splice,
#cm-code-slurp,
#cm-code-barf {
height: 70px;
}
#cm-code-string {
height: 110px;
}
#cm-code-displaced,
#cm-code-not-displaced {
height: 90px;
}
#cm-code-indent-input,
#cm-code-indent-output {
height: 250px;
}
#cm-code-indent-output {
opacity: 0.75;
}
#cm-code-indent-output .cm-bracket.cm-eol {
opacity: 1;
background: #aaffc2;
}
#cm-code-indent-input .cm-bracket.cm-eol,
#cm-code-indent-input .cm-bracket.cm-sol {
opacity: 1;
background: #ffbebe;
}
.removed {
background: #ffbebe;
}
.kept {
background: #ffe8b0;
}
.inserted {
background: #aaffc2;
}
#cm-code-indent-output .cm-bracket.cm-mol,
#cm-code-indent-input .cm-bracket.cm-mol {
background: #ffe8b0;
}
#cm-code-indent-output span.cm-keyword,
#cm-code-indent-output span.cm-variable,
#cm-code-indent-output span.cm-builtin,
#cm-code-indent-output span.cm-string,
#cm-code-indent-output span.cm-def,
#cm-code-indent-output span.cm-atom,
#cm-code-indent-output span.cm-number,
#cm-code-indent-output span.cm-comment,
#cm-code-indent-input span.cm-keyword,
#cm-code-indent-input span.cm-variable,
#cm-code-indent-input span.cm-builtin,
#cm-code-indent-input span.cm-string,
#cm-code-indent-input span.cm-def,
#cm-code-indent-input span.cm-atom,
#cm-code-indent-input span.cm-number,
#cm-code-indent-input span.cm-comment {
opacity: 0.3;
color: #000;
}
#cm-code-warn-good,
#cm-code-warn-bad {
font-size: 14px;
height: 110px;
}
#cm-code-enter {
height: 180px;
}
#cm-code-paren-tune,
#cm-code-paren-frac,
#cm-code-paren-comment,
#cm-code-paren-wrap,
#cm-code-intro-paren {
height: 100px;
}
#cm-code-intro-paredit {
height: 100px;
}
#cm-code-displaced-after-balance,
#cm-code-not-displaced-on-enter,
#cm-code-displaced-after-cursor-leaves {
height: 80px;
}
p.centered {
text-align: center;
}
a.img-link {
border-bottom: 0;
}
blockquote.aside {
border-left: 5px solid #ddd;
margin: 0;
margin-top: 60px;
padding-left: 20px;
opacity: 0.8;
}
.side-point {
font-style: italic;
color: #bbb;
}
td .side-point {
margin-top: 5px;
}
.warning {
padding: 20px;
border-radius: 5px;
background: #ffff7b;
}
.warning-title {
font-weight: bold;
margin-bottom: 10px;
}
.warning-body {
margin-left: 34px;
color: #333;
}
.green {
color: #63ca63;
}
.red {
color: #e84545;
}
.question {
font-weight: bold;
}
.question i.fa {
color: #5380c7;
}
.answer {
margin-left: 22px;
}
i.fa {
margin-right: 5px;
}
#cm-code-editor {
height: auto;
float: left;
width: 50%;
}
#debug-state {
overflow-x: auto;
width: 50%;
white-space: pre;
}
.state-table {
margin-top: 6px;
font: 14px Menlo;
}
.state-table td {
margin: 0;
padding: 0px 10px;
height: 17px;
}
.state-table .line-no {
opacity: 0.5;
}
.state-table .line-dy {
text-align: right;
}
.state-table .active-line {
background: #ff0;
}
.gear {
opacity: 0.3;
transition: opacity 0.2s;
}
.gear.powered {
opacity: 0.8;
}
.gear.auto-indent-gear {
opacity: 0.8;
}
.gear.auto-indent-gear.invisible {
opacity: 0;
}
.parinfer-gear {
opacity: 0.15;
}
.gearbox {
margin-bottom: 80px;
}
.lego-img {
width: 100%;
border: 1px solid #eee;
}
.math-page {
background: #fefefe;
border: 1px solid #eee;
padding: 30px;
font-family: "Computer Modern Serif";
font-size: 18px;
}
.math-page ul li {
list-style-type: none;
}
.math-page li {
margin-bottom: 5px;
}
.where-x {
display: block;
margin: 0 auto;
width: 250px;
}
.math-page .MathJax {
font-size: 20px;
}
.math-page .side-note {
float: right;
font-style: italic;
font-size: 0.8em;
color: #bbb;
}
.only-mobile {
display: none;
}
@media only screen and (max-width: 850px) {
#app {
position: static;
}
.sidebar {
display: none;
}
section {
width: 782px;
padding: 30px;
}
.wrapper {
border: 0;
}
h1 {
font-size: 80px;
}
.credits {
font-size: 1.5em;
}
.only-mobile {
display: block;
}
}

View file

@ -0,0 +1,99 @@
/* DEFAULT THEME */
/*
.cm-s-default .cm-header {color: blue;}
.cm-s-default .cm-quote {color: #090;}
.cm-negative {color: #d44;}
.cm-positive {color: #292;}
.cm-header, .cm-strong {font-weight: bold;}
.cm-em {font-style: italic;}
.cm-link {text-decoration: underline;}
.cm-strikethrough {text-decoration: line-through;}
.cm-s-default .cm-keyword {color: #708;}
.cm-s-default .cm-atom {color: #219;}
.cm-s-default .cm-number {color: #164;}
.cm-s-default .cm-def {color: #00f;}
.cm-s-default .cm-variable,
.cm-s-default .cm-punctuation,
.cm-s-default .cm-property,
.cm-s-default .cm-operator {}
.cm-s-default .cm-variable-2 {color: #05a;}
.cm-s-default .cm-variable-3 {color: #085;}
.cm-s-default .cm-comment {color: #a50;}
.cm-s-default .cm-string {color: #a11;}
.cm-s-default .cm-string-2 {color: #f50;}gt
.cm-s-default .cm-meta {color: #555;}
.cm-s-default .cm-qualifier {color: #555;}
.cm-s-default .cm-builtin {color: #30a;}
.cm-s-default .cm-bracket {color: #997;}
.cm-s-default .cm-tag {color: #170;}
.cm-s-default .cm-attribute {color: #00c;}
.cm-s-default .cm-hr {color: #999;}
.cm-s-default .cm-link {color: #00c;}
.cm-s-default .cm-error {color: #f00;}
.cm-invalidchar {color: #f00;}
.CodeMirror-composing { border-bottom: 2px solid; }
/* Default styles for common addons */
/*
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
.CodeMirror-activeline-background {background: #e8f2ff;}
.CodeMirror-cursor {
border-left: 1px solid black;
border-right: none;
width: 0;
}
*/
.cm-s-github .cm-keyword {
color: #a71d5d;
} /* clojure core forms */
.cm-s-github .cm-builtin {
color: #a71d5d;
} /* clojure core library */
.cm-s-github .cm-string {
color: #183691;
} /* clojure strings */
.cm-s-github .cm-variable {
color: #333;
} /* clojure misc symbols */
.cm-s-github .cm-def {
color: #795da3;
} /* clojure function calls or defs */
.cm-s-github .cm-atom {
color: #0086b3;
} /* clojure keyword, bool, nil */
.cm-s-github .cm-number {
color: #0086b3;
} /* clojure number */
.cm-s-github .cm-bracket {
color: #333;
} /* clojure brackets */
.cm-s-github .cm-comment {
color: #969896;
font-style: italic;
} /* clojure comment */
div.CodeMirror span.CodeMirror-matchingbracket {
background: #e8f2ff;
color: inherit;
border-bottom: 1px solid #111;
}
div.CodeMirror span.CodeMirror-nonmatchingbracket {
}
/* don't change selection color based on an editor's focus status. */
.CodeMirror-selected {
background: #d7d4f0; /* #d9d9d9 */
}
.CodeMirror-focused .CodeMirror-selected {
background: #d7d4f0;
}

View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>Parinfer - Tester</title>
<meta name="description" content="Parinfer - simpler Lisp editing" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="./assets/codemirror.css" />
<link rel="stylesheet" href="./assets/style.css" />
<link rel="stylesheet" href="./assets/theme.css" />
</head>
<body class="no-mathjax">
<textarea id="code-indent"></textarea>
<textarea id="output" cols="80" rows="50"></textarea>
<div id="controls-container"><noscript data-reactid=".1"></noscript></div>
<script src="./assets/codemirror.js"></script>
<script src="./assets/parinfer.js"></script>
<script src="./assets/parinfer-codemirror.js"></script>
<script>
var myCodeMirror = CodeMirror.fromTextArea(
document.getElementById("code-indent")
);
parinferCodeMirror.init(myCodeMirror);
</script>
</body>
</html>

5421
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -42,6 +42,7 @@
"comment-json": "^4.2.3",
"follow-redirects": "^1.15.2",
"glob": "^8.0.3",
"parinfer": "^3.13.1",
"vscode-languageclient": "^8.0.1"
},
"activationEvents": [
@ -171,6 +172,28 @@
}
],
"configuration": [
{
"id": "opengoal-goal",
"title": "GOAL Tooling",
"properties": {
"opengoal.parinferMode": {
"type": "string",
"default": "DISABLED",
"enum": [
"DISABLED",
"SMART",
"PAREN",
"INDENT"
],
"enumDescriptions": [
"Disable parinfer",
"Parinfer will try it's best to deduce when to run Paren mode and when to run Indent mode",
"Parinfer will handle the indentation, you handle the parens",
"Parinfer will handle the parens, you handle the indentation"
]
}
}
},
{
"id": "opengoal-decomp",
"title": "Decompilation",

View file

@ -4,6 +4,7 @@ export function getConfig() {
const configOptions = vscode.workspace.getConfiguration("opengoal");
return {
opengoalParinferMode: configOptions.get<string>("parinferMode"),
launchLspOnStartup: configOptions.get<boolean>("launchLspOnStartup"),
opengoalLspVersion: configOptions.get<string>("opengoalLspVersion"),
opengoalLspPath: configOptions.get<string>("opengoalLspPath"),
@ -109,3 +110,12 @@ export async function updateJak2DecompConfigDirectory(dir: string) {
vscode.ConfigurationTarget.Global
);
}
export async function updateOpengoalParinferMode(mode: string) {
const userConfig = vscode.workspace.getConfiguration();
await userConfig.update(
"opengoal.parinferMode",
mode,
vscode.ConfigurationTarget.Global
);
}

View file

@ -14,6 +14,11 @@ import { IRInlayHintsProvider } from "./languages/ir2/ir2-inlay-hinter";
import { OpenGOALDisasmRenameProvider } from "./languages/opengoal/disasm/opengoal-disasm-renamer";
import { activateMiscDecompTools } from "./decomp/misc-tools";
import { IR2RenameProvider } from "./languages/ir2/ir2-renamer";
import {
onChangeSelection,
onChangeTextDocument,
registerParinferCommands,
} from "./goal/parinfer/parinfer";
export async function activate(context: vscode.ExtensionContext) {
try {
@ -75,6 +80,11 @@ export async function activate(context: vscode.ExtensionContext) {
// Start the LSP
lsp.activate(context);
// Parinfer
registerParinferCommands(context);
vscode.workspace.onDidChangeTextDocument(onChangeTextDocument);
vscode.window.onDidChangeTextEditorSelection(onChangeSelection);
} catch (err) {
vscode.window.showErrorMessage(
"Failed to activate OpenGOAL extension, see logs for details"

View file

@ -0,0 +1,346 @@
import { indentMode, smartMode, parenMode } from "parinfer";
import * as vscode from "vscode";
import { integer } from "vscode-languageclient";
import { getConfig, updateOpengoalParinferMode } from "../../config/config";
import { getEditorRange } from "../../utils/editor-utils";
// TODO:
// - iron out some quirks around undoing
// - tab stops
// - highlight errors
// - initial paren mode should also trim empty lines
const opacityDecoration = vscode.window.createTextEditorDecorationType({
opacity: "0.6",
});
function parinferRangeToVSCodeRange(parenTrail: any) {
return new vscode.Range(
parenTrail.lineNo,
parenTrail.startX,
parenTrail.lineNo,
parenTrail.endX
);
}
function dimParenTrails(editor: vscode.TextEditor, parenTrails: any) {
const parenTrailsRanges = parenTrails.map(parinferRangeToVSCodeRange);
editor.setDecorations(opacityDecoration, parenTrailsRanges);
}
function updateParenTrails(editor: vscode.TextEditor, parenTrails: any) {
dimParenTrails(editor, parenTrails);
}
export enum ParinferMode {
DISABLED = "DISABLED",
INDENT = "INDENT",
PAREN = "PAREN",
SMART = "SMART",
}
enum EventQueueItemType {
TEXT_CHANGED = "TEXT_CHANGED",
SELECTION_CHANGED = "SELECTION_CHANGED",
}
interface ParinferChangeEntry {
lineNo: integer;
x: integer;
oldText: string;
newText: string;
}
interface EventQueueItem {
type: EventQueueItemType;
text: string;
cursorLine?: integer;
cursorX?: integer;
changes?: ParinferChangeEntry[];
prevCursorLine?: integer;
prevCursorX?: integer;
}
interface ParinferOptions {
cursorLine: integer;
cursorX: integer;
prevCursorLine?: integer;
prevCursorX?: integer;
}
const eventQueue: EventQueueItem[] = [];
const maxEventQueueSize = 10;
let currentParinferMode = ParinferMode.DISABLED;
const parinferStatusItem = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Left,
0
);
function updateStatus() {
switch (currentParinferMode) {
case ParinferMode.DISABLED:
parinferStatusItem.text = "$(json) Parinfer Disabled";
parinferStatusItem.tooltip = "Currently doing nothing! - Change Mode";
parinferStatusItem.command = "opengoal.parinfer.changeMode";
break;
case ParinferMode.SMART:
parinferStatusItem.text = "$(json) Smart Parinfer";
parinferStatusItem.tooltip =
"Automatically runs in Paren or Indent mode - Change Mode";
parinferStatusItem.command = "opengoal.parinfer.changeMode";
break;
case ParinferMode.PAREN:
parinferStatusItem.text = "$(json) Paren Parinfer";
parinferStatusItem.tooltip =
"You handle the parens while parinfer handles indentation! - Change Mode";
parinferStatusItem.command = "opengoal.parinfer.changeMode";
break;
case ParinferMode.INDENT:
parinferStatusItem.text = "$(json) Parinfer Disabled";
parinferStatusItem.tooltip =
"You handle the indentation while parinfer handles the parens! - Change Mode";
parinferStatusItem.command = "opengoal.parinfer.changeMode";
break;
default:
break;
}
}
async function changeParinferMode(mode: ParinferMode) {
currentParinferMode = mode;
await updateOpengoalParinferMode(mode);
updateStatus();
}
function cleanUpEventQueue() {
if (eventQueue.length > maxEventQueueSize) {
eventQueue.length = maxEventQueueSize;
}
}
setInterval(cleanUpEventQueue, 5 * 1000);
function applyParinfer(
editor: vscode.TextEditor,
text: string,
options: ParinferOptions,
mode: ParinferMode
) {
// console.log(`Options Before - ${JSON.stringify(options, null, 2)}`);
let parinferResult: any; // TODO - make a type def for this
if (mode === ParinferMode.DISABLED) {
return;
} else if (mode === ParinferMode.PAREN) {
parinferResult = parenMode(text, options);
} else if (mode === ParinferMode.INDENT) {
parinferResult = indentMode(text, options);
} else if (mode === ParinferMode.SMART) {
parinferResult = smartMode(text, options);
}
// TODO - clear decorations when text is removed
if (text === parinferResult.text) {
updateParenTrails(editor, parinferResult.parenTrails);
return;
}
editor
.edit(
(selectedText) => {
selectedText.replace(getEditorRange(editor), parinferResult.text);
},
{
undoStopAfter: false,
undoStopBefore: false,
}
)
.then(function (editWasApplied) {
if (editWasApplied) {
// set the new cursor position
const newCursorPosition = new vscode.Position(
parinferResult.cursorLine,
parinferResult.cursorX
);
const nextCursor = new vscode.Selection(
newCursorPosition,
newCursorPosition
);
editor.selection = nextCursor;
updateParenTrails(editor, parinferResult.parenTrails);
} else {
// TODO: should we do something here if the edit fails?
}
});
}
function processEventQueue() {
if (
eventQueue.length === 0 ||
eventQueue[0].type !== EventQueueItemType.SELECTION_CHANGED
) {
return;
}
// TODO - wire this up to an event listener too (what order though!)
const activeEditor = vscode.window.activeTextEditor;
if (activeEditor === undefined) {
return;
}
const selectionEvent = eventQueue[0];
if (
selectionEvent.cursorLine === undefined ||
selectionEvent.cursorX === undefined
) {
return;
}
const options: ParinferOptions = {
cursorLine: selectionEvent.cursorLine,
cursorX: selectionEvent.cursorX,
};
// check the last two events for previous cursor information
// TODO - not a huge fan of this code -- prevCursor info is only on selectionEvents
// depending on queue ordering is kinda sketchy though
if (eventQueue[1] && eventQueue[1].cursorLine !== undefined) {
options.prevCursorLine = eventQueue[1].cursorLine;
options.prevCursorX = eventQueue[1].cursorX;
} else if (eventQueue[2] && eventQueue[2].cursorLine !== undefined) {
options.prevCursorLine = eventQueue[2].cursorLine;
options.prevCursorX = eventQueue[2].cursorX;
}
// Document change events happen first, then selection change events
// In otherwords, they should always be in pairs
applyParinfer(
activeEditor,
selectionEvent.text,
options,
currentParinferMode
);
}
export function onChangeSelection(
event: vscode.TextEditorSelectionChangeEvent
) {
const editor = event.textEditor;
if (editor.document.languageId !== "opengoal") {
return;
}
const selection = event.selections[0];
const newQueueEntry: EventQueueItem = {
type: EventQueueItemType.SELECTION_CHANGED,
text: editor.document.getText(),
cursorLine: selection.active.line,
cursorX: selection.active.character,
};
eventQueue.unshift(newQueueEntry);
processEventQueue();
}
function getTextFromRange(txt: string, range: vscode.Range, length: integer) {
if (length === 0) return "";
const firstLine = range.start.line;
const firstChar = range.start.character;
const lines = txt.split("\n");
const line = lines[firstLine];
return line.substring(firstChar, firstChar + length);
}
function convertChangeObjects(
oldText: string,
changeEvent: vscode.TextDocumentContentChangeEvent
) {
return {
lineNo: changeEvent.range.start.line,
newText: changeEvent.text,
oldText: getTextFromRange(
oldText,
changeEvent.range,
changeEvent.rangeLength
),
x: changeEvent.range.start.character,
};
}
export function onChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
// drop any events that do not contain document changes
// (usually the first event to a document)
if (event.contentChanges && event.contentChanges.length === 0) {
return;
}
if (event.document.languageId !== "opengoal") {
return;
}
const newQueueEntry: EventQueueItem = {
type: EventQueueItemType.TEXT_CHANGED,
text: event.document.getText(),
};
// only create a "changes" property if we have a prior event
if (eventQueue[0] && eventQueue[0].text) {
const prevText = eventQueue[0].text;
const convertFn = convertChangeObjects.bind(null, prevText);
newQueueEntry.changes = event.contentChanges.map(convertFn);
}
eventQueue.unshift(newQueueEntry);
}
function showChangeModeMenu() {
const items: vscode.QuickPickItem[] = [
{
label: ParinferMode.DISABLED,
description: "Disable parinfer",
},
{
label: ParinferMode.SMART,
description:
"Parinfer will try it's best to deduce when to run Paren mode and when to run Indent mode",
},
{
label: ParinferMode.PAREN,
description:
"Parinfer will handle the indentation, you handle the parens",
},
{
label: ParinferMode.INDENT,
description:
"Parinfer will handle the parens, you handle the indentation",
},
];
void vscode.window
.showQuickPick(items, { title: "OpenGOAL Change Parinfer Mode" })
.then((v) => {
if (v !== undefined) {
changeParinferMode(v.label as ParinferMode);
}
});
}
export function registerParinferCommands(
context: vscode.ExtensionContext
): void {
changeParinferMode(
(getConfig().opengoalParinferMode as ParinferMode) ?? ParinferMode.DISABLED
);
updateStatus();
parinferStatusItem.show();
context.subscriptions.push(
vscode.commands.registerCommand(
"opengoal.parinfer.changeMode",
showChangeModeMenu
)
);
}

View file

@ -0,0 +1,7 @@
import * as vscode from "vscode";
export function getEditorRange(editor: vscode.TextEditor) {
const document = editor.document;
const invalidRange = new vscode.Range(0, 0, document.lineCount + 5, 0);
return document.validateRange(invalidRange);
}

1827
yarn.lock Normal file

File diff suppressed because it is too large Load diff