Răsfoiți Sursa

started script completion (globals only) + refactor ScriptEditor

ncannasse 7 ani în urmă
părinte
comite
572014cf01

+ 4 - 1
bin/app.html

@@ -1,5 +1,8 @@
 <!DOCTYPE html>
 <html>
+<head>
+	<meta charset="utf-8" />
+</head>
 <body>
 <link rel="stylesheet" type="text/css" href="libs/goldenlayout-base.css"/>
 <link rel="stylesheet" type="text/css" href="libs/goldenlayout-dark-theme.css"/>
@@ -79,7 +82,7 @@
 
 <script>
   var path = './';
-  var fs = require('fs');  
+  var fs = require('fs');
   var reloadWatcher=fs.watch(path, function(_,file) {
 	var ext = file.split(".").pop().toLowerCase();
 	if( ext != "js" && ext != "css" ) return;

+ 1 - 1
bin/cdb.css

@@ -269,7 +269,7 @@
 .cdb .cdb-sheet td.t_file .preview:hover .previewContent {
   display: block;
 }
-.cdb .cdb-sheet div.cdb-script {
+.cdb .cdb-sheet div.cdb-script .scripteditor {
   min-width: 500px;
   min-height: 400px;
 }

+ 4 - 2
bin/cdb.less

@@ -300,8 +300,10 @@
 		}
 
 		div.cdb-script {
-			min-width: 500px;
-			min-height: 400px;
+			.scripteditor {
+				min-width: 500px;
+				min-height: 400px;
+			}
 			.scriptErrorLine {
 				background: #400;
 			}

+ 1 - 1
bin/defaultProps.json

@@ -57,7 +57,7 @@
 	// script support
 
 	"script.api.files" : [],
-	"script.api.cdbModule" : "Data",
+	"script.cdbModule" : "Data",
 	"script.api" : {
 		"TableName.fieldName" : {
 			"globals" : { "this" : "ThisClass" },

+ 1 - 0
hide/Ide.hx

@@ -854,6 +854,7 @@ class Ide {
 	static function main() {
 		h3d.pass.ShaderManager.STRICT = false; // prevent errors with bad renderer
 		hide.tools.Macros.include(["hide.view","h3d.prim","h3d.scene","h3d.pass","hide.prefab","hxd.prefab"]);
+		(monaco.Languages : Dynamic).typescript.javascriptDefaults.setCompilerOptions({ noLib: true, allowNonTsExtensions: true }); // disable js stdlib completion
 		new Ide();
 	}
 

+ 191 - 0
hide/comp/ScriptEditor.hx

@@ -0,0 +1,191 @@
+package hide.comp;
+
+typedef GlobalsDef = haxe.DynamicAccess<{
+	var globals : haxe.DynamicAccess<String>;
+	var context : String;
+	var cdbEnums : Bool;
+}>;
+
+class ScriptEditor extends Component {
+
+	static var INIT_DONE = false;
+
+	var editor : monaco.Editor;
+	var errorMessage : Element;
+	var checker : hscript.Checker;
+	var currrentDecos : Array<String> = [];
+	var props : hide.ui.Props;
+	public var documentName : String;
+	public var script(get,never) : String;
+
+	public var checkScript : Bool = false;
+	public var checkAsync : Bool = true;
+
+	public function new( documentName : String, script : String, props : hide.ui.Props, ?parent : Element, ?root : Element ) {
+		super(parent,root);
+		this.props = props;
+		this.documentName = documentName;
+
+		if( !INIT_DONE ) {
+			INIT_DONE = true;
+			monaco.Languages.registerCompletionItemProvider("javascript", {
+				provideCompletionItems : function(model,position,_,_) {
+					var comp : ScriptEditor = (model : Dynamic).__comp__;
+					return comp.getCompletion(position);
+				}
+			});
+		}
+
+		var root = element;
+		root.addClass("scripteditor");
+		root.on("keydown", function(e) e.stopPropagation());
+
+		editor = monaco.Editor.create(root[0],{
+			value : script,
+			language : "javascript",
+			automaticLayout : true,
+			wordWrap : true,
+			theme : "vs-dark",
+		});
+		(editor.getModel() : Dynamic).__comp__ = this;
+		editor.onDidChangeModelContent(doCheckScript);
+		editor.addCommand(monaco.KeyCode.KEY_S | monaco.KeyMod.CtrlCmd, function() onSave());
+		errorMessage = new Element('<div class="scriptErrorMessage"></div>').appendTo(root).hide();
+
+		checker = new hscript.Checker();
+
+		var files : Array<String> = props.get("script.api.files");
+		if( files.length >= 0 ) {
+			for( f in files ) {
+				// TODO : reload + recheck script when modified
+				var content = try sys.io.File.getContent(ide.getPath(f)) catch( e : Dynamic ) { ide.error(e); continue; };
+				checker.addXmlApi(Xml.parse(content).firstElement());
+			}
+		}
+
+		var parts = documentName.split("/");
+		var cdbMod : String = props.get("script.cdbModule");
+		while( parts.length > 0 ) {
+			var path = parts.join("/");
+			parts.pop();
+			var api = (props.get("script.api") : GlobalsDef).get(path);
+			if( api == null ) continue;
+
+			for( f in api.globals.keys() ) {
+				var tname = api.globals.get(f);
+				var t = checker.resolveType(tname);
+				if( t == null ) ide.error('Global type $tname not found in $files ($f)');
+				checker.setGlobal(f, t);
+			}
+
+			if( api.context != null ) {
+				var t = checker.resolveType(api.context);
+				if( t == null ) ide.error("Missing context type "+api.context);
+				while( t != null )
+					switch (t) {
+					case TInst(c, args):
+						for( fname in c.fields.keys() ) {
+							var f = c.fields.get(fname);
+							checker.setGlobal(f.name, f.t);
+						}
+						t = c.superClass;
+					default:
+						ide.error(api.context+" context is not a class");
+					}
+			}
+
+			if( api.cdbEnums ) {
+				for( s in ide.database.sheets ) {
+					if( s.props.hide ) continue;
+					for( c in s.columns )
+						if( c.type == TId ) {
+							var name = s.name.charAt(0).toUpperCase() + s.name.substr(1);
+							var kname = cdbMod+"."+name+"Kind";
+							var kind = checker.resolveType(kname);
+							if( kind == null )
+								kind = TEnum({ name : kname, params : [], constructors : new Map() },[]);
+							var cl : hscript.Checker.CClass = {
+								name : name,
+								params : [],
+								fields : new Map(),
+								statics : new Map()
+							};
+							for( o in s.getLines() ) {
+								var id = Reflect.field(o, c.name);
+								if( id == null || id == "" ) continue;
+								cl.fields.set(id, { name : id, params : [], t : kind, isPublic: true });
+							}
+							checker.setGlobal(name, TInst(cl,[]));
+						}
+				}
+			}
+		}
+
+		haxe.Timer.delay(function() doCheckScript(), 0);
+	}
+
+	function get_script() {
+		return editor.getValue({preserveBOM:true});
+	}
+
+	var rnd = Std.random(1000);
+
+	function getCompletion( position : monaco.Position ) : Array<monaco.Languages.CompletionItem> {
+		var globals = checker.getGlobals();
+		return [for( k in globals.keys() ) {
+			var t = globals.get(k);
+			if( checkAsync && StringTools.startsWith(k,"a_") ) {
+				t = checker.unasync(t);
+				k = k.substr(2);
+			}
+			var isFun = checker.follow(t).match(TFun(_));
+			if( isFun ) {
+				{
+					kind : Field,
+					label : k,
+					detail : checker.typeStr(t),
+					commitCharacters: ["("],
+				}
+			} else {
+				{
+					kind : Field,
+					label : k,
+					detail : checker.typeStr(t),
+				}
+			}
+		}];
+	}
+
+	function doCheckScript() {
+		var script = script;
+		try {
+			var expr = new hscript.Parser().parseString(script, "");
+			if( checkScript ) {
+				checker.allowAsync = checkAsync;
+				checker.check(expr);
+			}
+			if( currrentDecos.length != 0 )
+				currrentDecos = editor.deltaDecorations(currrentDecos,[]);
+			errorMessage.hide();
+		} catch( e : hscript.Expr.Error ) {
+			var linePos = script.substr(0,e.pmin).lastIndexOf("\n");
+			//trace(e, e.pmin, e.pmax, cur.substr(e.pmin, e.pmax - e.pmin + 1), linePos);
+			if( linePos < 0 ) linePos = 0 else linePos++;
+			var range = new monaco.Range(e.line,e.pmin + 1 - linePos,e.line,e.pmax + 2 - linePos);
+			currrentDecos = editor.deltaDecorations(currrentDecos,[
+				{ range : range, options : { inlineClassName: "scriptErrorContentLine", isWholeLine : true } },
+				{ range : range, options : { linesDecorationsClassName: "scriptErrorLine", inlineClassName: "scriptErrorContent" } }
+			]);
+			errorMessage.text(hscript.Printer.errorToString(e));
+			errorMessage.show();
+		}
+	}
+
+	public function focus() {
+		editor.focus();
+	}
+
+	public dynamic function onSave() {
+	}
+
+}

+ 6 - 91
hide/comp/cdb/ScriptTable.hx

@@ -1,55 +1,8 @@
 package hide.comp.cdb;
 
-typedef GlobalsDef = haxe.DynamicAccess<{
-	var globals : haxe.DynamicAccess<String>;
-	var context : String;
-	var cdbEnums : Bool;
-}>;
-
 class ScriptTable extends SubTable {
 
-	var edit : monaco.Editor;
-	var check : hscript.Checker;
-	var errorMessage : Element;
-	var currrentDecos : Array<String> = [];
-
-	public function new(editor:Editor,cell) {
-		super(editor,cell);
-		var files : Array<String> = editor.props.get("script.api.files");
-		if( files.length >= 0 ) {
-			check = new hscript.Checker();
-			check.allowAsync = true;
-			for( f in files ) {
-				var content = try sys.io.File.getContent(ide.getPath(f)) catch( e : Dynamic ) { ide.error(e); continue; };
-				check.addXmlApi(Xml.parse(content).firstElement());
-			}
-			var key = cell.table.sheet.name+"."+cell.column.name;
-			var api = (editor.props.get("script.api") : GlobalsDef).get(key);
-			if( api != null ) {
-				for( f in api.globals.keys() )	{
-					var tname = api.globals.get(f);
-					var t = check.resolveType(tname);
-					if( t == null ) ide.error('Global type $tname not found in $files ($f)');
-					check.setGlobal(f, t);
-				}
-				if( api.context != null ) {
-					var t = check.resolveType(api.context);
-					if( t == null ) ide.error("Missing context type "+api.context);
-					while( t != null )
-						switch (t) {
-						case TInst(c, args):
-							for( fname in c.fields.keys() ) {
-								var f = c.fields.get(fname);
-								check.setGlobal(f.name, f.t);
-							}
-							t = c.superClass;
-						default:
-							ide.error(api.context+" context is not a class");
-						}
-				}
-			}
-		}
-	}
+	var script : hide.comp.ScriptEditor;
 
 	override function makeSubSheet():cdb.Sheet {
 		var sheet = cell.table.sheet;
@@ -66,51 +19,13 @@ class ScriptTable extends SubTable {
 		}, key, { sheet : sheet, column : cell.columnIndex, line : index });
 	}
 
-	function checkScript() {
-		var cur = edit.getValue({preserveBOM:true});
-		try {
-			var expr = new hscript.Parser().parseString(cur, "");
-			if( check != null ) check.check(expr);
-			if( currrentDecos.length != 0 ) currrentDecos = edit.deltaDecorations(currrentDecos,[]);
-			errorMessage.hide();
-		} catch( e : hscript.Expr.Error ) {
-			var linePos = cur.substr(0,e.pmin).lastIndexOf("\n");
-			//trace(e, e.pmin, e.pmax, cur.substr(e.pmin, e.pmax - e.pmin + 1), linePos);
-			if( linePos < 0 ) linePos = 0 else linePos++;
-			var range = new monaco.Range(e.line,e.pmin + 1 - linePos,e.line,e.pmax + 2 - linePos);
-			currrentDecos = edit.deltaDecorations(currrentDecos,[
-				{ range : range, options : { inlineClassName: "scriptErrorContentLine", isWholeLine : true } },
-				{ range : range, options : { linesDecorationsClassName: "scriptErrorLine", inlineClassName: "scriptErrorContent" } }
-			]);
-			errorMessage.text(hscript.Printer.errorToString(e));
-			errorMessage.show();
-		}
-	}
-
 	override function refresh() {
-		var first = edit == null;
+		var first = script == null;
 		element.html("<div class='cdb-script'></div>");
-		element.off();
-		var lineElement = element.find(".cdb-script");
-		element.on("keydown", function(e) e.stopPropagation());
-		edit = monaco.Editor.create(lineElement[0],{
-			value : cell.value == null ? "" : cell.value,
-			language : "javascript",
-			automaticLayout : true,
-			wordWrap : true,
-			theme : "vs-dark",
-		});
-		errorMessage = new Element('<div class="scriptErrorMessage"></div>').appendTo(lineElement).hide();
-		edit.addCommand(monaco.KeyCode.KEY_S | monaco.KeyMod.CtrlCmd, function() {
-			var cur = edit.getValue({preserveBOM:true});
-			@:privateAccess cell.setValue(cur);
-		});
-		edit.onDidChangeModelContent(function() checkScript());
-		lines = [new Line(this,[],0,lineElement)];
-		if( first ) haxe.Timer.delay(function() {
-			edit.focus();
-			checkScript();
-		},0);
+		script = new ScriptEditor("cdb/"+cell.table.sheet.name+"/"+cell.column.name,cell.value, editor.props, element.find("div"));
+		script.checkScript = true;
+		lines = [new Line(this,[],0,script.element)];
+		if( first ) script.focus();
 	}
 
 }

+ 1 - 0
libs/monaco/Editor.hx

@@ -9,6 +9,7 @@ extern class Editor {
 	function onDidChangeModelContent( listener : Void -> Void ) : Void;
 	function focus() : Void;
 	function dispose() : Void;
+	function getModel() : Model;
 	function deltaDecorations( old : Array<String>, newDeco : Array<ModelDeltaDecoration> ) : Array<String>;
 
 	public static function create( elt : js.html.Element, ?options : Dynamic ) : Editor;

+ 61 - 0
libs/monaco/Languages.hx

@@ -0,0 +1,61 @@
+package monaco;
+
+typedef CompletionProvider = {
+	var ?triggerChars : Array<String>;
+	function provideCompletionItems( model : Model, position : Position, token : Any, context : CompletionContext ) : Array<CompletionItem>;
+}
+
+typedef CompletionContext = {
+	var ?triggerCharacter : String;
+	var triggerKind : SuggestTriggerKind;
+}
+
+enum abstract SuggestTriggerKind(Int) {
+	var Invoke;
+	var TriggerCharacter;
+	var TriggerForIncompleteCompletions;
+}
+
+enum abstract CompletionItemKind(Int) {
+	var Class = 6;
+	var Color = 15;
+	var Constructor = 3;
+	var Enum = 12;
+	var Field = 4;
+	var File = 16;
+	var Folder = 18;
+	var Function = 2;
+	var Interface = 7;
+	var Keyword = 13;
+	var Method = 1;
+	var Module = 8;
+	var Property = 9;
+	var Reference = 17;
+	var Snippet = 14;
+	var Text = 0;
+	var Unit = 10;
+	var Value = 11;
+	var Variable = 5;
+}
+
+typedef CompletionItem = {
+	var ?additionalTextEdits : Any;
+	var ?command : Any;
+	var ?commitCharacters : Array<String>;
+	var ?detail : String;
+	var ?documentation : MarkdownString;
+	var ?filterText : String;
+	var ?insertText : String;
+	var kind : CompletionItemKind;
+	var label : String;
+	var ?range : Range;
+	var ?sortText : String;
+	//var ?textEdit : deprecated
+}
+
+@:native("monaco.languages")
+extern class Languages {
+
+	public static function registerCompletionItemProvider( language : String, provider : CompletionProvider ) : Void;
+
+}

+ 4 - 0
libs/monaco/Model.hx

@@ -0,0 +1,4 @@
+package monaco;
+
+extern class Model {
+}

+ 6 - 0
libs/monaco/Position.hx

@@ -0,0 +1,6 @@
+package monaco;
+
+typedef Position = {
+	var column : Int;
+	var lineNumber : Int;
+}