mirror of
https://github.com/open-goal/opengoal-vscode.git
synced 2024-10-19 04:37:37 -04:00
goal: Basic parinfer integration (#173)
This commit is contained in:
parent
c8a0286ff3
commit
774f65b015
11
.github/workflows/build.yaml
vendored
11
.github/workflows/build.yaml
vendored
|
@ -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
|
||||
|
|
23
.github/workflows/lint.yaml
vendored
23
.github/workflows/lint.yaml
vendored
|
@ -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
|
||||
|
|
10
.github/workflows/release.yaml
vendored
10
.github/workflows/release.yaml
vendored
|
@ -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)
|
||||
|
||||
|
|
463
docs/parinfer-tester/assets/codemirror.css
Normal file
463
docs/parinfer-tester/assets/codemirror.css
Normal 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;
|
||||
}
|
11556
docs/parinfer-tester/assets/codemirror.js
Normal file
11556
docs/parinfer-tester/assets/codemirror.js
Normal file
File diff suppressed because it is too large
Load diff
733
docs/parinfer-tester/assets/parinfer-codemirror.js
Normal file
733
docs/parinfer-tester/assets/parinfer-codemirror.js
Normal 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
|
1622
docs/parinfer-tester/assets/parinfer.js
Normal file
1622
docs/parinfer-tester/assets/parinfer.js
Normal file
File diff suppressed because it is too large
Load diff
646
docs/parinfer-tester/assets/style.css
Normal file
646
docs/parinfer-tester/assets/style.css
Normal 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;
|
||||
}
|
||||
}
|
99
docs/parinfer-tester/assets/theme.css
Normal file
99
docs/parinfer-tester/assets/theme.css
Normal 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;
|
||||
}
|
32
docs/parinfer-tester/index.html
Normal file
32
docs/parinfer-tester/index.html
Normal 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
5421
package-lock.json
generated
File diff suppressed because it is too large
Load diff
23
package.json
23
package.json
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
346
src/goal/parinfer/parinfer.ts
Normal file
346
src/goal/parinfer/parinfer.ts
Normal 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
|
||||
)
|
||||
);
|
||||
}
|
7
src/utils/editor-utils.ts
Normal file
7
src/utils/editor-utils.ts
Normal 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);
|
||||
}
|
Loading…
Reference in a new issue