Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Install ace through npm and use ace beautify #274

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
},
"devDependencies": {
"@types/webxr": "^0.5.1",
"ace-builds": "^1.23.4",
"concat-cli": "^4.0.0",
"css-loader": "^6.7.1",
"exports-loader": "^4.0.0",
Expand Down
12 changes: 7 additions & 5 deletions sample/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@
<script src="assets/js/babylon.objFileLoader.js"></script>

<!--Add manually cause only in the bundle -->
<script src="../vendors/ace.js" type="text/javascript"></script>
<script src="../vendors/ace-theme-monokai.js" type="text/javascript"></script>
<script src="../vendors/ace-mode-glsl.js" type="text/javascript"></script>
<script src="../vendors/ace-ext-searchbox.js" type="text/javascript"></script>
<link href="../vendors/ace-theme-override.css" rel="stylesheet" />
<script src="./node_modules/ace-builds/src-noconflict/ace.js" type="text/javascript"></script>
<script src="./node_modules/ace-builds/src-noconflict/theme-monokai.js" type="text/javascript"></script>
<script src="./node_modules/ace-builds/src-noconflict/mode-glsl.js" type="text/javascript"></script>
<script src="./node_modules/ace-builds/src-noconflict/ext-searchbox.js" type="text/javascript"></script>
<script src="./node_modules/ace-builds/src-noconflict/ext-beautify.js" type="text/javascript"></script>

<link href="./../src/embeddedFrontend/styles/ace-theme-override.css" rel="stylesheet" />

<style>
html,
Expand Down
11 changes: 10 additions & 1 deletion sample/js/simple.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,16 @@ var vertexShaderSource = "attribute vec3 aVertexPosition;" +

var fragmentShaderSource = "varying lowp vec4 vColor;" +

"void main(void) {" +
"void main(void) {\n" +

"#define TEST {}\n" +
"\n" +
"\n" +
"#ifdef TEST\n" +
"{}\n" +
"#endif\n" +
"{}\n" +

" gl_FragColor = vColor;" +
"}";

Expand Down
7 changes: 7 additions & 0 deletions src/embeddedFrontend/resultView/resultView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,12 @@ export class ResultView {
translatedSourceVertex: sourceCodeState.state.translatedSourceVertex,
});
});
this.sourceCodeComponent.onBeautifyChanged.add((sourceCodeState) => {
const state = this.mvx.getGenericState<ISourceCodeState>(this.sourceCodeComponentStateId);
state.beautify = (sourceCodeState.sender as HTMLInputElement).checked;
this.mvx.updateState(this.sourceCodeComponentStateId, state);
});


this.updateViewState();
}
Expand Down Expand Up @@ -286,6 +292,7 @@ export class ResultView {
fragment,
translated: false,
editable: commandState.capture.DrawCall.programStatus.RECOMPILABLE,
beautify: true
}, this.sourceCodeComponent);

this.commandDetailStateId = this.mvx.addChildState(this.contentStateId, null, this.commandDetailComponent);
Expand Down
196 changes: 27 additions & 169 deletions src/embeddedFrontend/resultView/sourceCode/sourceCodeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ export interface ISourceCodeState extends ISourceCodeChangeEvent {
fragment: boolean;
translated: boolean;
editable: boolean;
beautify: boolean;
}

// Declare Ace types here.
interface IAceEditorSession {
setUseSoftTabs(enabled: boolean): void;
setTabSize(size: number): void;
setMode(mode: string): void;
on(eventName: string, callback: (e: any) => void): void;
setAnnotations(annotations: any[]): void;
Expand All @@ -22,25 +25,23 @@ interface IAceEditor {
setReadOnly(readonly: boolean): void;
setShowPrintMargin(show: boolean): void;
}
interface IAceBeautify {
beautify(session: IAceEditorSession): boolean;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ace is actually written in TypeScript and the typings are available in ./node_modules/ace-builds/ace.d.ts and ./node_modules/ace-builds/ace-modules.d.ts, so all of these hacks could be removed.

type ace = {
require(path: string): unknown;
edit(sourceCodeComponent: Element): IAceEditor;
};
declare const ace: ace;

export class SourceCodeComponent extends BaseComponent<ISourceCodeState> {
private static readonly semicolonReplacementKey = "[[[semicolonReplacementKey]]]";
private static readonly semicolonReplacementKeyRegex = new RegExp("\\[\\[\\[semicolonReplacementKey\\]\\]\\]", "g");
private static readonly openCurlyReplacementKey = "[[[openCurlyReplacementKey]]]";
private static readonly openCurlyReplacementKeyRegex = new RegExp("\\[\\[\\[openCurlyReplacementKey\\]\\]\\]", "g");
private static readonly closeCurlyReplacementKey = "[[[closeCurlyReplacementKey]]]";
private static readonly closeCurlyReplacementKeyRegex = new RegExp("\\[\\[\\[closeCurlyReplacementKey\\]\\]\\]", "g");

public onTranslatedVertexSourceClicked: IStateEvent<ISourceCodeState>;
public onTranslatedFragmentSourceClicked: IStateEvent<ISourceCodeState>;
public onVertexSourceClicked: IStateEvent<ISourceCodeState>;
public onFragmentSourceClicked: IStateEvent<ISourceCodeState>;
public onSourceCodeCloseClicked: IStateEvent<ISourceCodeState>;
public onSourceCodeChanged: IStateEvent<ISourceCodeState>;
public onBeautifyChanged: IStateEvent<ISourceCodeState>;

private editor: IAceEditor;

Expand All @@ -52,6 +53,7 @@ export class SourceCodeComponent extends BaseComponent<ISourceCodeState> {
this.onFragmentSourceClicked = this.createEvent("onFragmentSourceClicked");
this.onSourceCodeCloseClicked = this.createEvent("onSourceCodeCloseClicked");
this.onSourceCodeChanged = this.createEvent("onSourceCodeChanged");
this.onBeautifyChanged = this.createEvent("onBeautifyChanged");
}

public showError(errorMessage: string) {
Expand Down Expand Up @@ -82,13 +84,13 @@ export class SourceCodeComponent extends BaseComponent<ISourceCodeState> {

public render(state: ISourceCodeState, stateId: number): Element {
const source = state.fragment ? state.sourceFragment : state.sourceVertex;
let formattedShader: string;
let originalShader: string;
// tslint:disable-next-line:prefer-conditional-expression
if (state.translated) {
formattedShader = state.fragment ? state.translatedSourceFragment : state.translatedSourceVertex;
originalShader = state.fragment ? state.translatedSourceFragment : state.translatedSourceVertex;
}
else {
formattedShader = source ? this._indentIfdef(this._beautify(source)) : "";
originalShader = source ?? "";
}

const htmlString = this.htmlTemplate`
Expand All @@ -102,20 +104,30 @@ export class SourceCodeComponent extends BaseComponent<ISourceCodeState> {
<li><a href="#" role="button" commandName="onSourceCodeCloseClicked">Close</a></li>
</ul>
</div>
$${
this.htmlTemplate`<div class="sourceCodeComponent">${formattedShader}</div>`
}
$${this.htmlTemplate`<div class="sourceCodeComponent">${originalShader}</div>`}
<div class="sourceCodeMenuComponentFooter">
<p>
<label><input type="checkbox" commandName="onBeautifyChanged" ${state.beautify ? "checked" : ""} /> Beautify</label>
</p>
</div>
</div>`;

const element = this.renderElementFromTemplate(htmlString.replace(/<br>/g, "\n"), state, stateId);

this.editor = ace.edit(element.querySelector(".sourceCodeComponent"));
this.editor.setTheme("ace/theme/monokai");
this.editor.getSession().setMode("ace/mode/glsl");
const session = this.editor.getSession();
session.setMode("ace/mode/glsl");
if (state.beautify) {
session.setUseSoftTabs(true);
session.setTabSize(4);
const beautify = ace.require("ace/ext/beautify") as IAceBeautify;
beautify.beautify(session);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Before this runs, the cursor is in line 1, col 1. But after this runs, the editor has no focus / no cursor.

Ideally the cursor would stay where it was using a sourcemap, but ace beautify doesn't seem to allow that.

}
this.editor.setShowPrintMargin(false);
let timeoutId = -1;
this.editor.setReadOnly(!state.editable && !state.translated);
this.editor.getSession().on("change", (e) => {
session.on("change", (e) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized all shaders are editable by default.. why? Is there any feature that benefits from that / uses modified shaders?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes this is one of the most important feature of spector https://github.com/BabylonJS/Spector.js/blob/master/documentation/extension.md#shader-editor :-)

if (timeoutId !== -1) {
clearTimeout(timeoutId);
}
Expand All @@ -137,158 +149,4 @@ export class SourceCodeComponent extends BaseComponent<ISourceCodeState> {
}
this.triggerEvent("onSourceCodeChanged", element, state, stateId);
}

/**
* Beautify the given string : correct indentation according to brackets
*/
private _beautify(glsl: string, level: number = 0): string {

// return condition : no brackets at all
glsl = glsl.trim();
glsl = this._adaptComments(glsl);
const brackets = this._getBracket(glsl);
const firstBracket = brackets.firstIteration;
const lastBracket = brackets.lastIteration;

let spaces = "";
for (let i = 0; i < level; i++) {
spaces += " "; // 4 spaces
}

let result: string;
// If no brackets, return the indented string
if (firstBracket === -1) {
glsl = spaces + glsl; // indent first line
glsl = glsl.replace(/;(?![^\(]*\))\s*(\/\/.*)?/g, (x) => x.trim() + "\n");
glsl = glsl.replace(/\s*([*+-/=><\s]*=)\s*/g, (x) => " " + x.trim() + " "); // space around =, *=, +=, -=, /=, ==, >=, <=
glsl = glsl.replace(/\s*(,)\s*/g, (x) => x.trim() + " "); // space after ,
glsl = glsl.replace(/\n[ \t]+/g, "\n"); // trim Start
glsl = glsl.replace(/\n/g, "\n" + spaces); // indentation
glsl = glsl.replace(/\s+$/g, "");
glsl = glsl.replace(/\n+$/g, "");
result = glsl;
}
else {
// if brackets, beautify the inside
// let insideWithBrackets = glsl.substr(firstBracket, lastBracket-firstBracket+1);
const left = glsl.substr(0, firstBracket);
const right = glsl.substr(lastBracket + 1, glsl.length);
const inside = glsl.substr(firstBracket + 1, lastBracket - firstBracket - 1).trim();
const prettyInside = this._beautify(inside, level + 1);
result = this._beautify(left, level) + " {\n" + prettyInside + "\n" + spaces + "}\n" + this._beautify(right, level);
result = result.replace(/\s*\n+\s*;/g, ";"); // Orphan ;
result = result.replace(/#endif[\t \f\v]*{/g, "\n {"); // Curly after #Endig
}

result = result.replace(SourceCodeComponent.semicolonReplacementKeyRegex, ";");
result = result.replace(SourceCodeComponent.openCurlyReplacementKeyRegex, "{");
result = result.replace(SourceCodeComponent.closeCurlyReplacementKeyRegex, "}");

return result;
}

private _adaptComments(str: string): string {
let singleLineComment = false;
let multiLineComment = false;

for (let index = 0; index < str.length; index++) {
const char = str[index];
if (char === "/") {
if (str[index - 1] === "*") {
multiLineComment = false;
}
else if (str[index + 1] === "*") {
if (!singleLineComment) {
multiLineComment = true;
index++;
}
}
else if (str[index + 1] === "/") {
if (!multiLineComment) {
singleLineComment = true;
index++;
}
}
}
else if (char === "\n") {
singleLineComment = false;
}
else if (char === ";") {
if (singleLineComment || multiLineComment) {
str = str.substr(0, index) + SourceCodeComponent.semicolonReplacementKey + str.substr(index + 1);
}
}
else if (char === "{") {
if (singleLineComment || multiLineComment) {
str = str.substr(0, index) + SourceCodeComponent.openCurlyReplacementKey + str.substr(index + 1);
}
}
else if (char === "}") {
if (singleLineComment || multiLineComment) {
str = str.substr(0, index) + SourceCodeComponent.closeCurlyReplacementKey + str.substr(index + 1);
}
}
}

return str;
}

/**
* Returns the position of the first "{" and the corresponding "}"
* @param str the Shader source code as a string
* @param searchFrom Search open brackets from this position
*/
private _getBracket(str: string, searchFrom = -1): { firstIteration: number, lastIteration: number } {
const fb = str.indexOf("{", searchFrom);
const arr = str.substr(fb + 1).split("");
let counter = 1;
let currentPosInString = fb;
let lastBracketIndex = 0;
for (const char of arr) {
currentPosInString++;

if (char === "{") {
counter++;
}
if (char === "}") {
counter--;
}
if (counter === 0) {
lastBracketIndex = currentPosInString;
break;
}
}

// More open than close.
if (fb > -1 && lastBracketIndex === 0) {
return this._getBracket(str, fb + 1);
}

return { firstIteration: fb, lastIteration: lastBracketIndex };
}

private _indentIfdef(str: string): string {
let level = 0;

const arr2 = str.split("\n");

for (let index = 0; index < arr2.length; index++) {
const line = arr2[index];
if (line.indexOf("#endif") !== -1) {
level--;
}
if (line.indexOf("#else") !== -1) {
level--;
}
let spaces = "";
for (let i = 0; i < level; i++) {
spaces += " "; // 4 spaces
}
arr2[index] = spaces + line;
if (line.indexOf("#if") !== -1 || line.indexOf("#else") !== -1) {
level++;
}
}
return arr2.join("\n");
}
}
11 changes: 10 additions & 1 deletion src/embeddedFrontend/styles/resultView.scss
Original file line number Diff line number Diff line change
Expand Up @@ -620,9 +620,18 @@ $commandDetailComponentWidth: 40%;
position: absolute;
left: 0;
top: 0;
bottom: 48px;
right: $commandDetailComponentWidth;
}

.sourceCodeMenuComponentFooter {
position: absolute;
left: 0;
right: $commandDetailComponentWidth;
bottom: 0;
padding: 0 15px;
}

.sourceCodeMenuComponent {
font-family: 'Montserrat', sans-serif;
// text-transform: uppercase;
Expand Down Expand Up @@ -759,7 +768,7 @@ $commandDetailComponentWidth: 40%;
position:absolute;
top: $menuHeight + $border;
left: 0;
bottom: 0;
bottom: 48px;
right: $commandDetailComponentWidth;
background: $background;
z-index: 9000;
Expand Down
Loading