Jump to: navigation, search

Orion/How Tos/Code Edit

< Orion‎ | How Tos
Revision as of 08:14, 6 March 2017 by Remy.suen.gmail.com (Talk | contribs) (How to provide a markdown type of hover service)

Contents

Code Edit Widget With Language Tooling

To make it easier to consume the Orion editor with the complete language tooling, a new widget called "Code Edit" has been introduced. The widget consists of the core editor and all Web development language tools, including the Tern analysis engine and Esprima parser. The widget is delivered as a zip file containing all the structural folders and files, making the core and the plugins all work together. You can easily embed this widget directly into any client side Web application. The language tooling plugins are only loaded when needed, making it easier to embed a powerful code editor without hurting page load time. The widget core is available both unminified, for debugging purpose and minified, for better loading time. The plugins are only available in minified version.

Getting the build

  • Users can grab the most recent "Code Edit" widget from the nightly builds or they can use the latest release available on the Orion download page.
  • From the Orion build page, users can click on a nightly build or a release and they will be presented with a page that now includes the code edit widget: the built-codeEdit.zip file contains all the files for you to embed the widget.

Using the build and the quick start demo

You can simply download the zip file and unzip it into a folder on your web server. Below is a complete run-able html file that you can host in your web server as a demo on how to consume the widget. Assume that you have created a folder named editorBuild under where your html file lives. Follow the 3 steps below and you can run the demo in just a few minutes. The same demo is also running here, where you can see it right away.

  • Download the code edit build and unzip it under the editorBuild folder.
  • Create a demo.html and paste the example contents into the demo.html.
  • Run the demo.html.
<!doctype html>
<html>
    <head>
		<meta name="copyright" content="Copyright (c) IBM Corporation and others 2010, 2014." >
		<meta http-equiv="Content-Language" content="en-us">
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<title>Pluggable Editor Demo</title>
	    <link rel="stylesheet" type="text/css" href="editorBuild/code_edit/built-codeEdit.css"/>
		<style type="text/css">
			.demoTitle{
				border: none;
				vertical-align: middle;
				overflow: hidden;
				text-align: left;
				margin-left: 15%;
				margin-right: 15%;
				padding-bottom: 5px;
				position: relative;
			}
			.demoBody{
				border: 1px solid;
				vertical-align: middle;
				border-color: blue;
				overflow: hidden;
				text-align: left;
				margin-left: 15%;
				margin-right: 15%;
				margin-bottom: 15px;
				padding-bottom: 5px;
				position: relative;
				height: 450px;
			}
			.orionPage {
			    background-color: white;
			    width: 100%;
			    height: 100%;
			}
		</style>
		<script src="editorBuild/code_edit/built-codeEdit.js"></script>
		<script>
			/*globals orion */
			window.onload=function(){
				var codeEdit = new orion.codeEdit();
				var contents = 'var foo = "bar";\n' +
									 "var bar = foo;\n" + 
									 "/*\n" + 
									 " * test demo\n" + 
									 "*/\n" + 
									 "function test(){\n" + 
									 "	var foo1 = bar.lastIndexOf(char, from);\n" + 
									 "}\n" + 
									"//Keep editing in this demo and try the content assist, problem validations and hover service!\n" +
									 "var foo2 = foo."; 
				codeEdit.create({parent: "embeddedEditor", contentType: "application/javascript", contents: contents}).then(function(editorViewer) {
					document.getElementById("progressMessageDiv").textContent = "Plugins loaded!";
					if (editorViewer.settings) {
						editorViewer.settings.contentAssistAutoTrigger = true;
						editorViewer.settings.showOccurrences = true;
					}
					//You can call APIs from editorViewer.editor  for further actions.
				});
			};
		</script>
    </head>
	<body id="orion-browser" spellcheck="false" class="orionPage">
		<div class="demoTitle">
			<p>This is a demo for the <b>Orion Code Edit</b> widget. This demo consumes the <b>built version</b> of the widget.</p> 
			<p>Keep editing in this demo and try:</p>
			<ol>
				<li>Content assist. E.g., put cursor after "foo." at the last line and press CTRL+space.</li>
				<li>Problem validations. E.g., modify something and you will see new validation markers coming up, if any.</li>
				<li>Hover service. Hover on any error markers or inside the editor.</li>
				<li>Syntax highlighting.</li>
				<li>Quick fix. Hover on a problem inside the editor, not on the ruler, e.g., (char, from) in this demo. Click on the quick fix and see.</li>
				<li>Find declaration. Select a variable and press F3.</li>
				<li>New tooling features coming while Orion is being improved...</li>
			</ol>
		</div>
		<div class="demoTitle">
			<span id = "progressMessageDiv" style="color: green">Loading language tooling plugins...</span>
		</div>
		</div>
		<div class="demoBody" id="embeddedEditor">
		</div>
	</body>
</html>

Your demo page should look like this, if you use CTRL+space as content assist after the page is loaded.

CodeEditDemo.png

Consuming the widget in different ways

Closer look at the build

If you unzip the build, the file structure looks like below:

WidgetCoreNew.png

There are several folders in the build, you should copy them all to your web server but only reference the files under the code_edit folder. All other files from other folders will be loaded dynamically as plugins depending on the file MIME type. The built-codeEdit.css file provides all the default css classes used in the widget. The built-codeEdit.min.js file provides the widget core code in a compact size while the built-codeEdit.js file provides the readable code for debugging purpose. Both of the .js files can be consumed by either loading the widget directly or using the RequireJS module loader.

Loading the widget directly

Assuming that you've unzipped the build into a folder called editorBuild, the following code in an html file shows you how to load the widget.

<link rel="stylesheet" type="text/css" href="editorBuild/code_edit/built-codeEdit.css"/>
<script src="editorBuild/code_edit/built-codeEdit.min.js"></script>
<script>
    window.onload=function(){
        var codeEdit = new orion.codeEdit();
        //Use codeEdit from here
    };
</script>

Note that orion.codeEdit is the only global function that the built-codeEdit.min.js exports after it is loaded. You need to call the create API to create an editor with your text contents. Details is addressed in a later section.

using the RequireJS module loader

Assuming that you've unzipped the build into a folder called editorBuild and you want to use the RequireJS module loader, the following code in an html file shows you how to load the widget. Note that the AMD build exposes a module called orion/codeEdit as the widget root. You can also load other Orion modules that are built into the widget, such as orion/Deferred, etc.

First option

<link rel="stylesheet" type="text/css" href="editorBuild/code_edit/built-codeEdit.css"/>
<script>
require.config({
    bundles: {
        "editorBuild/code_edit/built-codeEdit-amd": ["orion/codeEdit", "orion/Deferred"]
    }	    
});
require(["orion/codeEdit", "orion/Deferred"], function(mCodeEdit, Deferred) {
	var codeEdit = new mCodeEdit();
	var contents = 'var foo = "bar";'; 
	codeEdit.create({parent: "embeddedEditor"/*editor parent node id*/}).then(function(editorViewer) {
		editorViewer.setContents(contents, "application/javascript");
	});
});
</script>

Second option

Other than using require.config as above, you can also use another flavor of loading as below:

<link rel="stylesheet" type="text/css" href="editorBuild/code_edit/built-codeEdit.css"/>
<script>
require(["editorBuild/code_edit/built-codeEdit-amd"], function() {
    require(["orion/codeEdit", "orion/Deferred"], function(mCodeEdit, Deferred) {
	var codeEdit = new mCodeEdit();
	var contents = 'var foo = "bar";'; 
	codeEdit.create({parent: "embeddedEditor"/*editor parent node id*/}).then(function(editorViewer) {
		editorViewer.setContents(contents, "application/javascript");
	});
    });
});
</script>

Note that the way how the widget is loaded is slightly different but either way you end up with a widget instance called codeEdit. Lets talk about how to use the codeEdit instance in the next section.

Increase the module load timeout

RequireJS has default module load timout as 7 seconds. In normal cases this is good enough but if you are seeing errors like Uncaught Error: Load timeout for modules:..., you can increase the timeout:

require.config({ 
  waitSeconds : 30,
  //etc
});

using the codeEdit.create function

As described in the previous section, now you are holding an instance of the code edit widget called codeEdit. Every time you call codeEdit.create function, it creates an Orion editor instance and shows your editor text right away. There are two ways to call the codeEdit.create function.

A simple way to call codeEdit.create

If you want to create your editor and leave it there by using all the default settings, refer to the following code.

codeEdit.create({parent: "myEditorDivId", contentType: "application/javascript", contents: "var foo;"});

An advanced way to call codeEdit.create

If you want to create your editor and reuse the editor instance for customization and even for different contents by the same editor instance, refer to the following code.

codeEdit.create({parent: "myEditorDivId"}).then(function(editorViewer) {
    editorViewer.setContents("var foo;", "application/javascript");
    //editorViewer.editor.textView.setText("var bar;");
});

Setting your editor to readonly after the creation

Although there are other ways to create a readonly editor but the simplest way is to set the editorViewer.readonly property to true.

codeEdit.create({parent: "myEditorDivId"}).then(function(editorViewer) {
    editorViewer.setContents("var foo;", "application/javascript");
    editorViewer.readonly = true;
});

Parameters used in the codeEdit.create call

The codeEdit.create function call returns a javascript promise object that you can reuse. Note that either you use the simple or advanced way to create your editor, there are 3 parameters required. When you use the simple way, you just pass an object with {parent, contentType, contents} and you do not care the promise. When you use the advanced way, you only pass {parent} but when you handle the promise object, which is the editorViewer object, you will pass the contents and content type to the editorViewer.setContents. Note that is you hold editorViewer.setContents instance somewhere in your code, you can just reuse the same instance for different contents. This is useful when you want to keep an editor open but keep changing the contents for the time being.

parent parameter

This can be either a DIV id or DIV node that holds your editor.

contents parameter

This is a string parameter that you use to initialize your editor contents. The contents can be modified after the editor is up.

contentType parameter

The code edit widget uses MIME types for syntax highlighting and language tooling. The parameter is a string that present a MIME type, such as "application/javascript". By default, the widget supports the syntax highlighting for the following MIME types.

  • "application/javascript"
  • "application/x-ejs"
  • "text/css"
  • "text/html"
  • "text/x-java-source"
  • "application/x-jsp"
  • "application/json"
  • "text/x-launch"
  • "text/x-jade"
  • "text/x-python"
  • "text/x-ruby"
  • "text/x-go"
  • "text/x-objective-c"
  • "text/x-php"
  • "text/x-swift"
  • "application/xml"
  • "application/xhtml+xml"
  • "text/x-yaml"
  • "text/x-arduino"
  • "text/x-csrc"
  • "text/x-c"
  • "text/x-csharp"
  • "text/x-cshtml"
  • "text/x-c++src"
  • "text/x-dockerfile"
  • "text/x-erlang"
  • "text/x-haml"
  • "text/x-lua"
  • "application/xquery"
  • "text/x-vb"
  • "text/x-vbhtml"

noFocus parameter

After you create an editor, the editor will get focus by default. There are cases that you may want your web page stays focus on a specific field outside of the editor. You can simply achieve this by adding an option {..., noFocus: true}

codeEdit.create({parent: "embeddedEditor", contentType: "application/json", contents: contents, noFocus: true}).then(....);

Knowing the key bindings

From version 13, you can use ALT+SHIFT+? keys to bring up the key assist dialogue as below:

KeyBindings1.png

You can also filter on the key binding list to find the command you want. For example, if you type "find", it will narrow down to all the find commands:

KeyBindings2.png

Theming the key assist dialogue

The key assist dialogue uses the default css settings built from the Orion code base. You can theme it to fit your web page by using the following css classes.

.keyAssistFloat {
}
.keyAssistFloat {
}
.keyAssistContents {
}
.keyAssistInput {
}
.keyAssistBindingInput {
}
.keyAssistList {
}
.keyAssistItem {
}
.keyAssistItem.selected {
}
.keyAssistItem:hover {
}
.keyAssistEditButton {
}
.keyAssistEditButtonVisible {
}
.keyAssistActions {
}
.keyAssistItem:hover .keyAssistEditButton  {
}
.keyAssistSpacer {
}
.keyAssistFloat h2 {
}
.keyAssistName {
}
.keyAssistAccel {
}

For instance, the following css changes make the dialogue font smaller and put a vertical scroller.

.keyAssistContents {
	overflow-y: auto;
}
.keyAssistInput {
    font-size: x-small;
}
.keyAssistFloat h2 {
	font-size: small;
}
.keyAssistName {
    font-size: x-small;
}
.keyAssistAccel {
    font-size: x-small;
}

KeyBindings3.png

Theming your editor

As described in the previous sections, if you use the advanced way to create your editor you will get hold of an editorViewer instance. The fundamental class of an Orion editor is called TextView that is theme-able in different ways. If you call editorViewer.editor.getTextView() it returns you an instance of TextView. You can then call editorViewer.editor.getTextView().setOptions({themeClass: "editorTheme"}) to theme your editor. Note that "editorTheme" is the class selector that is defined in the following css, as the default theme that the widget is using. You can change any of the classes for your own theme.

/*Editor syntax highlighting themes*/
/*Refer to https://manual.macromates.com/en/language_grammars at section 12.4 for the theme hierarchy*/
.editorTheme .comment {
	color: #3C802C;
}
.editorTheme .constant.numeric.hex {
	color: #9932CC;
}
.editorTheme .constant.numeric {
	color: #9932CC;
}
.editorTheme .constant {
	color: #9932CC;
}
.editorTheme .entity.name.function {
	color: #67BBB8;
	font-weight: bold;
}
.editorTheme .entity.name {
	color: #98937B;
}
.editorTheme .entity.other.attribute-name {
	color: #5F9EA0;
}
.editorTheme .keyword.control {
	color: #CC4C07;
	font-weight: bold;
}
.editorTheme .keyword.operator {
	color: #9F4177;
	font-weight: bold;
}
.editorTheme .keyword.other.documentation.task {
	color: #5595ff;
}
.editorTheme .keyword.other.documentation {
	color: #7F9FBF;
}
.editorTheme .meta.documentation.annotation {
	color: #7F9FBF;
}
.editorTheme .meta.documentation.tag {
	color: #7F7F9F;
}
.editorTheme .meta.preprocessor {
	color: #A4A4A4;
}
.editorTheme .meta.tag.attribute {
	color: #93a2aa;
}
.editorTheme .meta.tag {
	color: #CC4C07;
}
.editorTheme .punctuation.operator {
	color: #D1416F;
}
.editorTheme .string.interpolated {
	color: #151515;
}
.editorTheme .string {
	color: #446FBD;
}
.editorTheme .support.type.propertyName {
	color: #9F4177;
}
.editorTheme .variable.language {
	color: #7F0055;
	font-weight: bold;
}
.editorTheme .variable.other {
	color: #E038AD;
}
.editorTheme .variable.parameter {
	color: #D1416F;
}
/*Editor core themes*/
.editorTheme .annotationLine.currentLine {
	background-color: #EAF2FE;
}
.editorTheme .annotationRange.currentBracket {
	background-color: #00FE00;
}
.editorTheme .annotationRange.matchingBracket {
	background-color: #00FE00;
}
.editorTheme .annotationRange.matchingSearch.currentSearch {
	background-color: #53d1ff;
}
.editorTheme .annotationRange.matchingSearch {
	background-color: #c3e1ff;
}
.editorTheme .annotationRange.writeOccurrence {
	background-color: #ffff00;
}
.editorTheme .ruler.annotations {
	background-color: #ffffff;
}
.editorTheme .ruler.overview {
	background-color: #ffffff;
}
.editorTheme .ruler {
	background-color: #ffffff;
}
.editorTheme .rulerLines {
	color: #CCCCCC;
}
.editorTheme .textviewContent ::-moz-selection {
	background-color: #b4d5ff;
}
.editorTheme .textviewContent ::selection {
	background-color: #b4d5ff;
}
.editorTheme .textviewLeftRuler {
	border-color: #ffffff;
}
.editorTheme .textviewRightRuler {
	border-color: #ffffff;
}
.editorTheme .textviewSelection {
	background-color: #b4d5ff;
}
.editorTheme .textviewSelectionUnfocused {
	background-color: #b4d5ff;
}
.editorTheme.textview {
	background-color: #ffffff;
	color: #151515;
	font-family: "Source Code Pro", "Consolas", "Monaco", "Vera Mono", monospace;
	font-size: 14px;
}

You can copy the above contents and paste into your own css file and load the css file into your page. Then change your code to call editorViewer.editor.getTextView().setOptions({themeClass: "editorTheme"}) , after editorViewer.setContents is called. Refer to the snippet below, assuming that you've created the css file called editorTheme.css and loaded into your page.

codeEdit.create({parent: "myEditorDivId"}).then(function(editorViewer) {
    editorViewer.setContents("var foo;", "application/javascript");
    editorViewer.editor.getTextView().setOptions({themeClass: "editorTheme"});
});

Theming the syntax highlighting

Refer to TextMate Language Grammars at section 12.4 Naming Conventions on how to change the css class values in the editorTheme.css file. For instance if you change one of the classes as below, your syntax highlighting on the javascript control keyword will become green from dark orange(default).

.editorTheme .keyword.control {
	color: green;/*#CC4C07;*/
	font-weight: bold;
}

Customizing your editor

Passing parameters in the widget constructor

There are several parameters you can pass to the widget constructor, before you call the codeEdit.create API.

editorConfig Parameter

The code edit widget uses a json object to manage the editor configurations for all the editorViewer instances you will create. The default configuration shows as below.

	var defaults = {
		autoSave: true,
		autoSaveTimeout: 250,
		autoLoad: true,
		saveDiffs: true,
		contentAssistAutoTrigger: true,
		showOccurrences: true,
		autoPairParentheses: true,
		autoPairBraces: true,
		autoPairSquareBrackets: true,
		autoPairAngleBrackets: false,
		autoPairQuotations: true,
		autoCompleteComments: true,
		smartIndentation: true,
		trimTrailingWhiteSpace: false,
		tabSize: 4,
		expandTab: false,
		scrollAnimation: true,
		scrollAnimationTimeout: 300,
		annotationRuler: true,
		lineNumberRuler: true,
		foldingRuler: true,
		overviewRuler: true,
		zoomRuler: false,
		showWhitespaces: false,
		wordWrap: false,
		showMargin: false,
		marginOffset: 80,
		keyBindings: "Default",
		diffService: false
	};

If you want to change any properties of the configuration, you can easily pass an editorConfig option to the widget construction. You only need to pass the properties that you want to change. For example you want to show the white space as special characters and show a zoom ruler(a wide ruler on the right of the editor to overview the whole editor and tells you where you are), the following snippet tells you how to do it.

<link rel="stylesheet" type="text/css" href="editorBuild/code_edit/built-codeEdit.css"/>
<script src="editorBuild/code_edit/built-codeEdit.min.js"></script>
<script>
    window.onload=function(){
        var codeEdit = new orion.codeEdit({
            editorConfig: {showWhitespaces: true, zoomRuler: true}
        });
        codeEdit .create(...).then(function(editorViewer1){});
        codeEdit .create(...).then(function(editorViewer2){});
        //editorViewer1 and editorViewer2 will use the same editor config.    
    };
</script>

Note that the editorConfig is passed in the codeEdit construction, not in the codeEdit.create API. All the editorViewer instances will share the same configuration.

userPlugins Parameter

You can pass a list of your own plugins by passing the userPlugins option when you initialize the codeEdit instance. Refer to understanding the Orion plugins on how to write your own plugins in general. Refer to plugging into the editor on how to inject different types of plugins into your editor.

var codeEdit = new orion.codeEdit({userPlugins: ["your plugin URL1", "your plugin URL2"]})
codeEdit.create({parent: "myEditorDivId").then(function(editorViewer) {
    editorViewer.setContents("var foo;", "application/javascript");
});

Note that the user plugins live together with the widget's built-in plugins. All the plugins including the user plugins are loaded lazily when the first time you call codeEdit.create API. If you create several editor instances in your page by calling the codeEdit.create API several times, the plugins are shared among editors.

_defaultPlugins Parameter

The _defaultPlugins parameter is normally used internally but if you want to trim the default plugins you can trim them off. There are 3 default plugins that the widget will load automatically for you but you can trim them off. The following snippet shows on how to trim some of them off while using your own plugins.

var defaultPluginURLs = [
    //"../javascript/plugins/javascriptPlugin.html",
    //"../webtools/plugins/webToolsPlugin.html",
    "../plugins/embeddedToolingPlugin.html"
];
var codeEdit = new orion.codeEdit({_defaultPlugins: defaultPluginURLs, userPlugins: ["your plugin URL1", "your plugin URL2"]})
codeEdit.create({parent: "myEditorDivId").then(function(editorViewer) {
    editorViewer.setContents("var foo;", "application/javascript");
});

The codeEdit.serviceRegistry instance

Once you create the code edit instance, there is a service registry created under the widget. The service registry will be shared by all the editor viewers you will create later. Before you call codeEdit.creat() API, you can register your services to the widget first and they will be consumed properly by the editor viewers with related content type.

<link rel="stylesheet" type="text/css" href="editorBuild/code_edit/built-codeEdit.css"/>
<script src="editorBuild/code_edit/built-codeEdit.min.js"></script>
<script>
    window.onload=function(){
        var codeEdit = new orion.codeEdit();
        codeEdit.serviceRegistry.registerService(your service );
        codeEdit.create(...);
        codeEdit.create(...);
    };
</script>

The codeEdit.serviceRegistry is an instance of Orion serviceRegistry by which you can register your own services to extend your editor. Below is a sample snippet on how to register a simple(fake) content assist service on an xml file.

var contentAssistProvider = {
    computeProposals: function(buffer, offset, context) {
        var result = [];
        for(var i = 0; i < 10; i++){
            result.push("proposal " + i);
        }
        return result;
    }
};
codeEdit.serviceRegistry.registerService("orion.edit.contentassist",
    contentAssistProvider,
    { name: "xmlContentAssist",
      contentType: ["application/xml"]
    });

Note that you can register all your services against different content types, before you call create() API. Then when you call the create() API even multiple time, or, use the same editorViewer instance to call setContents multiple times, those registered services will be filtered according to the current content type.

Unregister a service instance

We do not recommend un-registering a service but if you have cases you really want to un-register it, refer to the following snippet.

var myServiceEntry = codeEdit.serviceRegistry.registerService(your service );
//.........
myServiceEntry.unregister();

The editorViewer promise

As mentioned in the previous section, when you call codeEdit.create API a promise object editorViewer is returned. There are 3 major instances wrapped by the editorViewer, by which you can use to customize your editor further.

  • editorViewer.serviceRegistry
  • editorViewer.editor
  • editorViewer.inputManager

The editorViewer.serviceRegistry instance

The editorViewer.serviceRegistry is pointing to the codeEdit.serviceRegistry.

The editorViewer.editor instance

The editorViewer.editor is an instance of Orion editor by which you can use to customize your editor in details. Below is a sample snippet on how to call the editor's APIs to set your editor's theme and disable some editor rulers.

editorView.editor.getTextView().setOptions({themeClass: "editorTheme"});
editorView.editor.setLineNumberRulerVisible(false);
editorView.editor.setAnnotationRulerVisible(false);
editorView.editor.setOverviewRulerVisible(false);
editorView.editor.setFoldingRulerVisible(false);

The editorViewer.editor.getTextView() API

The editorViewer.editor.getTextView() returns an instance of TextView by which you can access a lot of editor core APIs to customize your editor in more details. Below are some of the frequently used APIs you may need. Assume that you have "initial text" as your initial text contents.

var text = editorView.editor.getTextView().getText();// text is "initial text"
editorView.editor.getTextView().setText("0123456789");// Overwrite the whole text in the editor
text = editorView.editor.getTextView().getText(2, 5);// text is "234"
editorView.editor.getTextView().setText("insert", 2, 5);// Your new text will be "01insert56789"

Dispatching and listening events in TextView

As mentioned in the above section, you can get a TextView instance from the editorViewer.editor.getTextView() call. As TextView is also an instance of EventTarget, you can use the TextView instance to dispatch your own events and listen to them. Below is an example on how to do this.

window.setTimeout(function() {
	console.log(editorViewer.editor.getTextView().getText(2, 5));
	editorViewer.editor.getTextView().setText("insert", 2, 5);
	editorViewer.editor.getTextView().dispatchEvent({
		type: "myEvent",
		event: "I have inserted some text"
	});
}, 200);
editorViewer.editor.getTextView().addEventListener("myEvent",function(evt){
	console.log(evt.event);
});

The editorViewer.inputManager instance

The editorViewer.inputManager is an instance of editor input manager by which you can use to customize the input for an editor. The most important API you may want to use is the setAutoSaveTimeout. By default the editorViewer.inputManager sets the autoSaveTimeOut as 300ms to trigger code validation. If you do not want to trigger code validation automatically use the code as below to turn it off.

editorViewer.inputManager.setAutoSaveTimeout(-1);

Using the editorViewer.serviceRegistry

Instead of passing your own plugins during the widget construction, you can also register your plugins as services directly from the editorViewer.serviceRegistry. All the plugins described in plugging into the editor are plug-able after you call codeEdit.create API.

var codeEdit = new orion.codeEdit()
codeEdit.create({parent: "myEditorDivId").then(function(editorViewer) {
    editorViewer.serviceRegistry.registerService(your service );
    editorViewer.setContents("var foo;", "application/javascript");
});

Note that the editorViewer.serviceRegistry are shared among editors. If you have multiple editors, all editorViewer.serviceRegistry references are pointing to the same serviceregistry that is the codeEdit.serviceRegistry mentioned before.

Add your own content type

If you have your own content types other than the default ones that the code edit widget offers, the first thing you want to do is to register your content type once the widget instance is created, before you call the create() API. Again you can achieve this by using either user plugins or codeEdit.serviceRegistry. Here is a complete snippet to do it from codeEdit.serviceRegistry:

var codeEdit = new orion.codeEdit();
var cto = {
	id: "text/myID",//use this MIME type ID to call create()
	extension: ["myExt"],//file extension
	name: 'My content type',
	'extends': 'text/plain'//Your type has to extends 'text/plain'
};
codeEdit.serviceRegistry.registerService('orion.core.contenttype', {}, {contentTypes: [cto]});

Note that you have to register your own content type before you can call codeEdit.create() API, because the create() API requires your content type.

Add your own command

You can add your own command on your own file content type, to the service registry. Refer to the following snippet on how to do it.

function simpleCommand(param) {
	if(param && param.setText) {
		param.setText("//my command execution!\n", 0, 0);
	}
}
editorViewer.serviceRegistry.registerService('orion.edit.command', {execute: simpleCommand}, {
	name: 'my command',
	id: 'my customized command',
	key: ['l', true, true],
	contentType: ["application/myContentType"]
});		

Note that the snippet uses key binding CTRL+SHIFT+L to execute the command, which simply insert "//my command execution!" at the first line in the editor.

Saving your editor contents

Code edit widget uses an underlying in-memory file system to handle all the file read and write. Unless you implement a real file system, the save action triggered by the CTRL+s or CMD+s will not save your contents automatically. Also, as a reserved key binding from the widget, CTRL+s is not only for the contents saving but also triggers auto-validation, etc. We do not recommend overriding the key binding so you will need to refer to the following snippet to simply accomplish the saving action without losing other features while saving.

var codeEdit = new orion.codeEdit()
codeEdit.create({parent: "myEditorDivId").then(function(editorViewer) {
    //If you do not want to use auto save feature then use the following line to disable it.
    editorViewer.inputManager.setAutoSaveTimeout(-1);
    //There is an event fired against editorViewer.editor every time when the editor contents changes. The evt.contentsSaved is a flag indicating a save action is required or not.
    editorViewer.editor.addEventListener("InputChanged", function(evt) {
        if(evt.contentsSaved) {
	    //Save your editor contents;
	}
    });
    //Remove the listener when you destruct the editorViewer.
    //editorViewer.editor.removeEventListener("InputChanged", yourListener);
});

Note that the widget's in-memory file system does not do the real save at all. The auto-save flag is turned on by default, which triggers the auto-validation. You may not want the auto-save and you only want validation based on CTRL+s or CMD+s. The Save your editor contents part can be asynchronous. Your implementation of the save action is completely separated from the Code Edit widget.

Advanced customizing options and APIs

codeEdit.startup API

When you first time use the codeEdit.create API, it loads all the plugins lazily. But there are other cases where you just want to load all the plugins, initialize the serviceRegistry, etc. The codeEdit.startup API can server this purpose. Refer to the following snippet.

var codeEdit = new orion.codeEdit()
codeEdit.startup().then(function() {
    ...
    codeEdit.create(...);
});

codeEdit.importFiles API

When you use codeEdit.create API, it creates an entry in the in-memory file system. The URL and the file name of that file are invisible for you. There are cases that you have multiple file contents with certain names, which you want to import to the widget first and refer to them later. Refer to the following snippet.

var files2import = [
		{
			name: ".tern-project",
			contents:'{"sourceType": "module","ecmaVersion": 6}'
		},
		{
			name: ".eslintrc",
			contents:''
		}
];
var codeEdit = new orion.codeEdit()
codeEdit.startup().then(function() {
    codeEdit.importFiles(files2import).then(function(/*results*/) {
        ...
    });
});

Note that you have to use codeEdit.startup API first in order to initialize the in-memory file system. The full file URL of the imported file will be "/in_memory_fs/project/yourFileName".

Create an editor instance by the imported file URL

Refer to the following snippet on how to create an editor from a in-memory file URL.

var files2import = [
		{
			name: "yourFileName1",
			contents:'{"sourceType": "module","ecmaVersion": 6}'
		},
		{
			name: "yourFileName2",
			contents:''
		}
];
var codeEdit = new orion.codeEdit()
codeEdit.startup().then(function() {
    codeEdit.importFiles(files2import).then(function(/*results*/) {
	codeEdit.create({parent: "yourDivID"}).then(function(editorViewer) {
	    editorViewer.inputManager.setInput("/in_memory_fs/project/yourFileName1");
	});	
    });
});

codeEdit.exportFiles API

If you imported some files and edit them, they are saved into the in-memory file system. You can use the codeEdit.exportFiles API to export the file contents by one call. Refer to the following snippet.

var files2import = [
		{
			name: ".tern-project",
			contents:'{"sourceType": "module","ecmaVersion": 6}'
		},
		{
			name: ".eslintrc",
			contents:''
		}
];
var files2export = [
		{
			name: ".tern-project"
		},
		{
			name: ".eslintrc"
		}
];
var codeEdit = new orion.codeEdit()
codeEdit.startup().then(function() {
    codeEdit.importFiles(files2import).then(function(/*results*/) {
        //Create editors to edit them
        ...
	codeEdit.exportFiles(files2export).then(function(exportResults) {
	    exportResults.forEach(...);
	});
    });
});

Note that you have to use codeEdit.importFiles API first in order to export them.

Options to tune your .tern-project and .eslintrc files

Orion has fantastic language tooling for Web developers using javascript, html and css. In addition to that, you can configure the .tern-project and .eslintrc files to change the default language validation behaviors. You can just use codeEdit.import API to load those two files into the in-memory file system as described in the above sections.

All together demo

Refer to the demo page to see how those two files are affecting the default validation behavior. Refer to the following run-able html file on how the APIs are hooked together.

<!doctype html>
<html>
    <head>
		<meta http-equiv="Content-Language" content="en-us">
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<title>Pluggable Editor Demo</title>
	    <link rel="stylesheet" type="text/css" href="editorBuild/code_edit/built-codeEdit.css"/>
		<style type="text/css">
			.embeddedEditorParentOuter{
				border: 1px solid;
				vertical-align: middle;
				border-color: blue;
				overflow: hidden;
				text-align: left;
				margin-left: 5%;
				margin-right: 5%;
				margin-bottom: 15px;
				padding-bottom: 2px;
				position: relative;
				height: 280px;
			}
			.embeddedEditorTitleOuter{
				vertical-align: middle;
				overflow: hidden;
				text-align: center;
				margin-left: 5%;
				margin-right: 5%;
				margin-bottom: 2px;
				position: relative;
				height: 18px;
			}
			.embeddedEditorParentInner{
				border: 1px solid;
				vertical-align: middle;
				width: 49%;
				font-size: 90%;
				height: 100%;
				border-color: blue;
				overflow: hidden;
				text-align: left;
				position: relative;
			}
			.embeddedEditorDemoTextParent{
				border: none;
				font-size: 90%;
				vertical-align: middle;
				overflow: hidden;
				text-align: left;
				margin-left: 5%;
				margin-right: 5%;
				padding-bottom: 5px;
				position: relative;
			}
			.orionPage {
			    background-color: white;
			    width: 100%;
			    height: 100%;
			}
		</style>
		<script src="editorBuild/code_edit/built-codeEdit.min.js"></script>
		<script>
			/*globals orion */
			var contents = 'import "somelib";//Hover on the error marker on "import" and click on the quick fix. The .tern-project file will be updated with new contents.\n' +
								 '//In the .tern-project file, select all text and press CTRL+X. The error marker on "import" comes back.\n' +
								 '//In the .tern-project file, press CTRL+V. The error marker on "import" goes away.\n' +
								 'var foo = "bar";\n' +
								 "var bar = foo;\n" + 
								 "/*\n" + 
								 " * test demo\n" + 
								 "*/\n" + 
								 "function test(){\n" + 
								 '	var foo1 = bar.lastIndexOf(char, from);;;;//change line 28 in .eslintrc file to "no-extra-semi": 2. The warning on on ";;;" becomes error.\n' + 
								 '//change line 28 in .eslintrc file to "no-extra-semi": 0. The error on ";;;" goes away.\n' +
								 "}\n" + 
								"//Keep editing in this demo and try the content assist, problem validations and hover service!\n" +
								 "var foo2 = foo."; 
			var ruleData = {
				"rules": {
					"accessor-pairs" : 1,
					"check-tern-plugin" : 1,
					"curly" : 0,
					"eqeqeq": 1,
					"missing-doc" : 0, 
					"missing-nls" : 0,
					'missing-requirejs': 1,
					"new-parens" : 1,
					"no-caller": 1,
					"no-comma-dangle" : 0, 
					"no-cond-assign" : 2,
					"no-console" : 0, 
					"no-constant-condition" : 2,
					"no-control-regex" : 2,
					"no-debugger" : 1,
					"no-dupe-keys" : 2,
					"no-duplicate-case": 2,
					"no-else-return" : 1,
					"no-empty-block" : 0,
					"no-empty-character-class" : 2,
					"no-empty-label" : 2,
					"no-eq-null" : 1,
					"no-eval" : 0,
					"no-extra-boolean-cast" : 2,
					"no-extra-parens" : 1,
					"no-extra-semi": 1,
					"no-fallthrough" : 2, 
					"no-implied-eval" : 0,
					"no-invalid-regexp": 2,
					"no-irregular-whitespace" : 0,
					"no-iterator": 2, 
					"no-jslint" : 1, 
					"no-mixed-spaces-and-tabs" : 0,
					"no-negated-in-lhs" : 2,
					"no-new-array": 1,
					"no-new-func" : 1,
					"no-new-object" : 1,
					"no-new-wrappers" : 1,
					"no-obj-calls" : 2,
					"no-proto" : 2, 
					"no-redeclare" : 1,
					"no-regex-spaces" : 2,
					"no-reserved-keys" : 2,
					"no-self-compare" : 2,
					"no-self-assign" : 2,
					"no-shadow" : 1,
					"no-shadow-global" : 1,
					"no-sparse-arrays" : 1, 
					"no-throw-literal" : 1,
					"no-undef" : 2,
					"no-undef-expression": 1,
					"no-undef-init" : 1,
					"no-unreachable" : 1, 
					"no-unused-params" : 1,
					"no-unused-vars" : 1,
					"no-use-before-define" : 1,
					"no-with" : 1,
					"radix" : 1,
					"semi" : 1,
					"type-checked-consistent-return" : 0,
					"unnecessary-nls" : 0,
					"unknown-require": 1,
					"use-isnan" : 2,
					"valid-typeof" : 2
				}
			};
			var files2import = [
				{
					name: ".tern-project",
					contents:''//{"sourceType": "module","ecmaVersion": 6}'
				},
				{
					name: ".eslintrc",
					contents:''
				}
			];
			var files2export = [
				{
					name: ".tern-project"
				},
				{
					name: ".eslintrc"
				}
			];
	
			window.onload=function(){
				var codeEdit = new orion.codeEdit({editorConfig: {showWhitespaces: false, zoomRuler: true}});
				var startup = function() {
					codeEdit.create({parent: "embeddedEditor"}).then(function(editorViewer) {
						editorViewer.setContents(contents, "application/javascript");
						var fileClient = codeEdit.serviceRegistry.getService("orion.core.file.client");
						if (fileClient) {
							//Listen to the .tern-project file changes.
							fileClient.addEventListener("Changed", function(evt) { //$NON-NLS-0$
								if(evt && evt.modified) {
									if(evt.modified.some(function(loc){
										return "/in_memory_fs/project/.tern-project" === loc || "/in_memory_fs/project/.eslintrc" === loc;
										})) {
											editorViewer.refreshSyntaxCheck();
										}
								}
							});
						}
					});
					codeEdit.create({parent: "embeddedEditor1"}).then(function(editorViewer) {
						editorViewer.inputManager.setInput("/in_memory_fs/project/.tern-project");
					});	
					codeEdit.create({parent: "embeddedEditor2"}).then(function(editorViewer) {
						editorViewer.inputManager.setInput("/in_memory_fs/project/.eslintrc");
					});	
				};
				codeEdit.startup().then(function() {
					document.getElementById("progressMessageDiv").textContent = "Plugins loaded!";
					files2import[1].contents = JSON.stringify(ruleData, undefined, 4);
					codeEdit.importFiles(files2import).then(function(/*results*/) {
						startup();
						codeEdit.exportFiles(files2export).then(function(exportResults) {
							console.log(exportResults);				
						});
					});
				});
			};
		</script>
    </head>
	<body id="orion-browser" spellcheck="false" class="orionPage">
		<div class="embeddedEditorDemoTextParent">
			<p>This demo shows how the <b>.tern-project</b> and <b>.eslintrc</b> files are used to configure the javascript validation in the <b>Orion codeEdit</b> widget. 
			The <b>.tern-project</b> file can be modified automatically by the quick fix. 
			You can also manually update the <b>.tern-project</b> or the <b>.eslintrc</b> files and the javascript file on the top will be revalidated right away.
			
			</p> 
		</div>
		<!--*<form> -->
		<div class="embeddedEditorDemoTextParent">
			<span id = "progressMessageDiv">Loading language tooling plugins...</span>
		</div>
		<div class="embeddedEditorParentOuter" id="embeddedEditor">
		</div>
		<div class="embeddedEditorTitleOuter" style="border: none">
			<div class="embeddedEditorParentInner" style="float: left; border: none">
				<span>.tern-project. Details are described <span>
				<a href="http://ternjs.net/doc/manual.html#configuration">here.</a>
			</div>
			<div class="embeddedEditorParentInner" style="float: right; border: none">
				<span>.eslintrc file. Rules are described <span>
				<a href="https://wiki.eclipse.org/Orion/ESLint">here.</a>
			</div>
		</div>
		<div class="embeddedEditorParentOuter" style="border: none">
			<div class="embeddedEditorParentInner" id="embeddedEditor1" style="float: left">
			</div>
			<div class="embeddedEditorParentInner" id="embeddedEditor2" style="float: right">
			</div>
		</div>
		<!-- </form> -->
	</body>
</html>

Note that the demo also includes a line of code to turn the zoomRuler to true in the editorConfig parameter described in the previous section.

editorViewer.editor.addFoldingAnnotation

Orion 13 contains a new function called editorViewer.editor.addFoldingAnnotation. This allows end users to add their own folding annotations based on text model change events. The following demo page assumes that you want to fold all blocks with pattern "FOLD.*DLOF" dynamically while you are typing.

FoldingAnno.png

<!doctype html>
<html>
    <head>
        <meta name="copyright" content="Copyright (c) IBM Corporation and others 2010, 2016." >
        <meta http-equiv="Content-Language" content="en-us">
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Pluggable Editor Demo</title>
        <style type="text/css">
            .demoTitle {
                border: none;
                vertical-align: middle;
                overflow: hidden;
                text-align: left;
                margin-left: 15%;
                margin-right: 15%;
                padding-bottom: 5px;
                position: relative;
            }
            .demoBody {
                border: 1px solid;
                vertical-align: middle;
                border-color: blue;
                overflow: hidden;
                text-align: left;
                margin-left: 15%;
                margin-right: 15%;
                margin-bottom: 15px;
                padding-bottom: 5px;
                position: relative;
                height: 450px;
            }
        </style>
        <link rel="stylesheet" type="text/css" href="editorBuild/code_edit/built-codeEdit.css"/>
        <script src="editorBuild/code_edit/built-codeEdit.js"></script>
        <script>
            /*globals orion */
            window.onload=function(){
                var codeEdit = new orion.codeEdit();
                var contents = 'FOLD\n' +
                    "111;\n" + 
                    "111\n" + 
                    "DLOF\n" + 
                    "222\n" + 
                    "\n" + 
                    "FOLD\n" + 
                    "333\n" + 
                    "333\n" +
                    "DLOF\n"; 
                codeEdit.create({parent: "embeddedEditor", contentType: "application/foo", contents: contents}).then(function(editorViewer) {
                    document.getElementById("progressMessageDiv").textContent = "Plugins loaded!";
                        //Use the editor text model to listen to the editor contents change.
                        var editorModel = editorViewer.editor.getModel();
                        var foldingAnnotations = [];
                        editorViewer.editor.addEventListener("TextViewInstalled", function(evt) {
                            console.log(evt);
                        });

                        editorModel.addEventListener("Changed", function(/*evt*/) {
                            //	var modelChangedEvent = {
                            //		type: "Changed", //$NON-NLS-0$
                            //		start: eventStart,
                            //		removedCharCount: removedCharCount,
                            //		addedCharCount: addedCharCount,
                            //		removedLineCount: removedLineCount,
                            //		addedLineCount: addedLineCount
                            //	};
						
                        //Remove all the customized folding annotation first
                        var annotationModel = editorViewer.editor.getAnnotationModel();
                        foldingAnnotations.forEach(function(anno){
                            annotationModel.removeAnnotation(anno);
                            anno.expand();
                        });
                        foldingAnnotations = [];
                        var text = editorModel.getText();
                        //Use this regex to find all the blocks like "FOLD ******** DLOF" in the editor contents
                        var regEx = /^FOLD([\s\S]*?)DLOF$/gm;
                        var match = regEx.exec(text);
                        window.setTimeout(function(){
                            while (match !== null) {
                                if (match.index !== null && match[0]) {
                                    //Create a single folding annotation based on a blok "FOLD ******** DLOF"
                                    var fAnno = editorViewer.editor.addFoldingAnnotation(match.index, match.index + match[0].length);
                                    if (fAnno) {
                                        foldingAnnotations.push(fAnno);
                                    }
                                    match = regEx.exec(text);
                                }
                            }
                        }, 0);
                    });
                });
            };
        </script>
    </head>
    <body id="orion-browser" spellcheck="false" class="orionPage">
        <div class="demoTitle">
            <p>This is a demo for the <b>Orion Code Edit</b> widget. This demo consumes the <b>built version</b> of the widget.</p> 
            <p>Keep editing in this demo and try:</p>
            <ol>
            </ol>
        </div>
        <div class="demoTitle">
            <span id = "progressMessageDiv" style="color: green">Loading language tooling plugins...</span>
        </div>
        <div class="demoBody" id="embeddedEditor">
        </div>
    </body>
</html>

Handling collapsed annotations

Note that the demo simply removes all of the existing folding annotations and regenerates them whenever you change the editor contents. We are not implementing a complicated demo that handles the combination of collapsed and expanded annotations. By chance you may see unexpected results if you have collapsed annotations while editing. To maintain the collapsed/expanded state of existing annotations when making changes you need to use the properties of the change event.

The range of the content change

Refer to the following properties regarding the "evt" in the demo:

var modelChangedEvent = {
	type: "Changed", //$NON-NLS-0$
	start: eventStart,
	removedCharCount: removedCharCount,
	addedCharCount: addedCharCount,
	removedLineCount: removedLineCount,
	addedLineCount: addedLineCount
};

Logical text model vs. visual text model

If you have collapsed annotations in your editor then the logical text model differs from the visual text model. The visual text model represents the text you can see (ie.- is not hidden within a collapsed fold), while the logical text model represents all text in the document.

var logicalTextModel = editorViewer.editor.getModel();
var visualTextModel = editorViewer.editor.getTextView().getModel();

Note that the demo uses the logical text model to listen for changes.

Regenerating annotations

For a smarter annotation regeneration you want to maintain existing annotations that are not affected by the change event, change/remove the affected annotations, and add new ones if needed. You can use the following properties of existing annotations to see if they fall within the change event's affected range.

foldingAnnotation.start
foldingAnnotation.end
foldingAnnotation.expanded

Editing a collapsed annotation

When editing within a collapsed annotation you may want to call the annotation.expand() to expand that annotation to match your logical text model.

Accessibility

In progress

Internationalization

Orion uses requirejs i18n to internationalize message bundles. All the displayed strings in the widget are defined in different messages bundles. The widget build has all the required English(root) messages built in. Messages in other languages can be loaded dynamically, by consuming the RequireJS build. There are 3 steps to configure and distribute messages translations.

Configure the translations

In order for RequireJS to know which language translations are available, you have to tell RequireJS a list of language locales. This has to be done per message bundle.

require.config({
    config: { 
        'messageBundle1 Path': {root: true, 'ja': true, 'fr': true},
        'messageBundle2 Path': {root: true, 'ja': true, 'fr': true},
        ......
    }
});

Configure the core translations

There is one core message bundle that you need to configure before you load the widget by RequireJS.

require.config({
    config: { 
        'orion/editor/nls/messages': {root: true, 'ja': true, 'fr': true}
    }
});

Configure the user plugin translations

In your <userPlugin>.html, put the configuration of required message bundle path. For example, if your plugin requires a message bundle called 'orion/myMessage/nls/messages'

require.config({
    config: { 
        'orion/myMessage/nls/messages': {root: true, 'ja': true, 'fr': true}
    }
});

Distribute the translations files in your web server

Copy and paste translation files on your web server

Distributing the translations is very straight forward. For example, when you distribute the translation of 'orion/editor/nls/messages' bundle, the translation files on your web server will look like this:

TranslationFiles.png

Note that when you load your web page with Geman(de), for example, RequireJS will send http request asking 'orion/editor/nls/de/messages'. Your web server has to resolve the path. There are 2 ways to do so.

Resolve the path from http context on your server

Your web server should configure the 'orion/editor/nls/messages' path by http context. Or, you can use requirejs.config.

Use requirejs.config

require.config({
    path:{
        'orion/editor/nls/messages': 'yourFolder/orion/editor/nls/messages'
    },
    //etc
});

Load translations in your web page

Once your translations are configured and distributed, they can be loaded into your web page by two options.

Change the browser's locale

Refer to browsing Orion in a different language for details.

Use requirejs.config

If your web app has its own current locale settings regardless to browser's locale, you can assign your settings to requirejs.

requirejs.config({
    config: {
        //Set the config for the i18n
        //module ID
        i18n: {
            locale: 'ja'//you should be able to change this dynamically
        }
    }
});

Example code to use all the configurations

require.config({
    waitSeconds: 60,
    path:{
    	'orion/editor/nls/messages': 'yourFolder/orion/editor/nls/messages'
    },
    config: {
    	'orion/editor/nls/messages': {root: true, 'ja': true, 'fr': true},
        i18n: {
            locale: 'ja'//'ja' can be a variable assigned by your web app.
        },
    },
    bundles: {
        "editorBuild/code_edit/built-codeEdit-amd": ["orion/codeEdit"],
    }
});

Frequent asked questions

How to provide a markdown type of hover service

Assume that you want to provide a hover service as shown below.

FAQ hoverExample.png

There you want your hover service title as bold and the contents of the hover information as paragraphs with bold, italic, etc. By default the hover title is styled as normal font but you can change it by overriding the css as below:

.textviewTooltip .hoverTooltipTitle {
    font-weight: bold;
}

For the different fonts and paragraphs of the contents, you can use the markdown specification. The snippet below shows how to achieve this as an example.

var hoverProvider = {
    computeHoverInfo: function (editorContext, context) {
        var pContent = "_This text will be italic_  \n\n__This text will be bold__  \nThis text is normal";
        return { title: "This is the title",
		 content: pContent,
		 type: "markdown"};
    }
};
codeEdit.serviceRegistry.registerService("orion.edit.hover",
    hoverProvider,
    { name: "my hover service",
      contentType: ["text/myContentType"]
});

How to provide a simple content assist service

Assume that you want to provide a content assist service. Refer to the following snippet to provide a simple one.

var contentAssistProvider = {
    computeProposals: function(buffer, offset, context) {
        var result = [];
        for(var i = 0; i < 10; i++){
            result.push({ proposal: "proposal " + i });
        }
        return result;
    }
};
 
codeEdit.serviceRegistry.registerService("orion.edit.contentassist",
    contentAssistProvider,
    {	name: "my content assit",
        contentType: ["text/myContentType"]
});

How to provide a content assist service with tool tips

In the previous two sections we talked about providing hover and content assist services. Now lets see how to integrate the two services together to provide a fancier content assist implementation that will render a tooltip on the right hand side of the selected proposal.

Add one more property into the proposal object

The best way to provide a tooltip for a content assist proposal is to add a hover property to the returned proposal. In the next step, your hover service provider will check each proposal for this hover property and use it to create the tool tip contents. The hover property must contain content and type properties. The most common type to use is markdown, but all of the types in the Hover API are supported. Your code will look like this:

var contentAssistProvider = {
    computeProposals: function(buffer, offset, context) {
        var result = [];
        for(var i = 0; i < 10; i++){
            result.push({proposal: "proposal " + i,
                         hover: {content: "my tool tip" + i, type: "markdown"}});
        }
        return result;
    }
};

Add special code into your hover service provider

The hover property you've just added to the proposal will be then rendered by your hover service. So your implementation of the hover service has to be changed a little bit to reflect the changes you've made on the proposal object. In the computeHoverInfo: function (editorContext, context) API, the selected proposal from content assist will be passed in the context parameter. So your new implementation of the hover service will look like below:

var hoverProvider = {
    computeHoverInfo: function (editorContext, context) {
	if(context.proposal && context.proposal.hover) {
	    return context.proposal.hover;
	}
        var pContent = "_This text will be italic_  \n\n__This text will be bold__  \nThis text is normal";
        return { title: "This is the title",
		 content: pContent,
		 type: "markdown"};
    }
};

Now your hover service does two things for you: normal hover service and tool tips on the content assist.

Everything together

Your final code will look like this:

var hoverProvider = {
    computeHoverInfo: function (editorContext, context) {
	if(context.proposal && context.proposal.hover) {
	    return context.proposal.hover;
	}
        var pContent = "_This text will be italic_  \n\n__This text will be bold__  \nThis text is normal";
        return { title: "This is the title",
		 content: pContent,
		 type: "markdown"};
    }
};
var contentAssistProvider = {
    computeProposals: function(buffer, offset, context) {
        var result = [];
        for(var i = 0; i < 10; i++){
            result.push({proposal: "proposal " + i,
                         hover: {content: "my tool tip" + i, type: "markdown"}});
        }
        return result;
    }
};
codeEdit.serviceRegistry.registerService("orion.edit.hover",
    hoverProvider,
    { name: "my hover service",
      contentType: ["text/myContentType"]
});
codeEdit.serviceRegistry.registerService("orion.edit.contentassist",
    contentAssistProvider,
    {	name: "my content assit",
        contentType: ["text/myContentType"]
});

How to turn on/off the readonly flag on the same editorViewer.editor instance

The readonly flag can be set after the editor creation but assume that you hold an editorViewer instance for the time being and want to turn on/off the readonly flag depending on what type of contents are reloaded into the same editorViewer.editor instance. You can use the two lines of API calls as show below:

editorViewer.readonly = true;
editorViewer.editor.getTextView().setOptions({"readonly": true});

How to highlight a line in the editor and set cursor on that line

Assume that you want to highlight a line in your editor with your own line and gutter style as below.

FAQ highlightLine.png

For example, when your editor is up you want to double click on the gutter of a certain line and highlight the gutter with red background color and dotted outline for the line of text. If you double click on an existing gutter, the gutter and the line highlight go away. Below is a completely run-able HTML file that shows you how to achieve this.

<!doctype html>
<html>
    <head>
		<meta name="copyright" content="Copyright (c) IBM Corporation and others 2010, 2014." >
		<meta http-equiv="Content-Language" content="en-us">
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<title>Pluggable Editor Demo</title>
		<style type="text/css">
			.lineHighlight{
				outline: 2px dotted black;
			}
			.lineHighlightGutter{
				background-color: red;
			}
			.demoTitle{
				border: none;
				vertical-align: middle;
				overflow: hidden;
				text-align: left;
				margin-left: 15%;
				margin-right: 15%;
				padding-bottom: 5px;
				position: relative;
			}
			.demoBody{
				border: 1px solid;
				vertical-align: middle;
				border-color: blue;
				overflow: hidden;
				text-align: left;
				margin-left: 15%;
				margin-right: 15%;
				margin-bottom: 15px;
				padding-bottom: 5px;
				position: relative;
				height: 450px;
			}
		</style>
	    <link rel="stylesheet" type="text/css" href="editorBuild/code_edit/built-codeEdit.css"/>
		<script src="editorBuild/code_edit/built-codeEdit.js"></script>
		<script>
			/*globals orion */
			function setupOnce(editorViewer) {
				editorViewer.editor.addEventListener("InputChanged", function(){ //$NON-NLS-0$
		 			/*
		 			 * Things you only need to do once per editor
		 			 */
		 			var editor = editorViewer.editor;
		 		 	//Get the line styler inside the editor
				 	var annoStyler = editor.getAnnotationStyler();
		 		 	//Add your annotation type to the editor 
		 		 	annoStyler.addAnnotationType("my.customize.linehighlight");
		 		 	//Add the same annotation type ot the annotation ruler(gutter)
		 		 	editor.getAnnotationRuler().addAnnotationType("my.customize.linehighlight");
		 		 	editor.getAnnotationRuler().onDblClick = function(lineIndex, e) {
						if (lineIndex === undefined) { return; }
						if (lineIndex === -1) { return; }
						var view = editor.getTextView();
						var viewModel = view.getModel();
						var annotationModel = editor.getAnnotationModel();
						var lineStart = editor.mapOffset(viewModel.getLineStart(lineIndex));
						var lineEnd = editor.mapOffset(viewModel.getLineEnd(lineIndex));
						var annotations = annotationModel.getAnnotations(lineStart, lineEnd);
						var gutter = null;
						while (annotations.hasNext()) {
							var annotation = annotations.next();
							if (annotation.type === "my.customize.linehighlight") {
								gutter = annotation;
								break;
							}
						}
		 		 		setCursorAndGotoLine(editorViewer, lineIndex, gutter);
		 		 	};
			 	});
			}
			function setCursorAndGotoLine(editorViewer, lineNumber/*zero based*/, gutter) {
		 		editorViewer.editor.onGotoLine(lineNumber, 0, 0, function() {
		 			var editor = editorViewer.editor;
		 		 	//annotationModel is the handler you add or remove you annotation models
		 		 	var annotationModel = editor.getAnnotationModel();
		  		 	if(!annotationModel){
				 		return;
		 		 	}
		 		 	//As the annotation model is a range that is based on the charater offset of the {star, end}, you have to use the textModel to calculate that)
		 		 	var textModel = editor.getTextView().getModel();
		 		 	var startIndex = textModel.getLineStart(lineNumber);
		 		 	var endIndex = textModel.getLineEnd(lineNumber);
		 		 	
		  		 	//Add and/or remove your annotation models
		 		 	//The first param is an array of the annotations you want to remove
		 		 	//The second param is an array of the annotations you want to add
		 		 	var firstParam = [], secondParam = [],
		 		 	param = [{
			 		 	start: startIndex,
			 		 	end: endIndex,
			 		 	title: "",
			 		 	type: "my.customize.linehighlight",
			 		 	html: "",
			 		 	style: {styleClass: "lineHighlightGutter"}, //Gutter style at the line		 		 		 		 
			 		 	lineStyle: {styleClass: "lineHighlight"} //The line style in the editor
		 		 	}];
		 		 	if(gutter) {
		 		 		firstParam = [gutter];
		 		 	} else {
		 		 		secondParam = param;
		 		 	}
		 		 	annotationModel.replaceAnnotations(firstParam, secondParam);
		 		});
			}
			window.onload=function(){
				var codeEdit = new orion.codeEdit();
				var contents = 'var foo = "bar";\n' +
									 "var bar = foo;\n" + 
									 "/*\n" + 
									 " * test demo\n" + 
									 "*/\n" + 
									 "function test(){\n" + 
									 "	var foo1 = bar.lastIndexOf(char, from);\n" + 
									 "}\n" + 
									"//Keep editting in this demo and try the content assit, probem validations and hover service!\n" +
									 "var foo2 = foo."; 
				codeEdit.create({parent: "embeddedEditor", contentType: "application/javascript", contents: contents}).then(function(editorViewer) {
					document.getElementById("progressMessageDiv").textContent = "Plugins loaded!";
					setupOnce(editorViewer);
				});
			};
		</script>
    </head>
	<body id="orion-browser" spellcheck="false" class="orionPage">
		<div class="demoTitle">
			<span id = "progressMessageDiv" style="color: green">Loading language tooling plugins...</span>
		</div>
		</div>
		<div class="demoBody" id="embeddedEditor">
		</div>
	</body>
</html>

How to iterate all the errors and warnings

If an editor has the validator service registered, there will be error and warning markers rendered in the editor. Assume that you want to walk through all the problems objects from the editor whenever there are problem changes. You can use the following snippet. Note that the markerService in the snippet is based per editor. If you have multiple editor instances in your page and you want to do the same thing for other editors, you have to listen to the "problemsChanged" event on other editors as well.

var markerService = editorViewer.serviceRegistry.getService(editorViewer.problemsServiceID);
if(markerService) {
    markerService.addEventListener("problemsChanged", function(evt) { //$NON-NLS-0$
        if(evt.problems) {
            evt.problems.forEach(function(problem) {
                console.log(problem);
	    });
        }
    });
}

There is another way to get all the errors and warnings as well but we do not recommend this unless you want to use it by your own. For example if you do not want the "problemsChanged" event to trigger your walk-through. Note that the setTimeout call is just an example way to show you how it works.

window.setTimeout(function() {
    var annotationModel = editorViewer.editor.getAnnotationModel();
    var annotations = annotationModel.getAnnotations();
    while(annotations.hasNext()) {
        var annotation = annotations.next();
        if(annotation.type === "orion.annotation.error" || annotation.type === "orion.annotation.warning") {
	    console.log(annotation);
	}
   }
}, 500);

How to change the editor's font dynamically

If you want to change the editor's font size dynamically please refer to following snippet. Assume that you have a function called changeFontDynamically() that you want to call from your action to increase the font size by 1px each time it is called.

var fontSizeCounter = 9;
var themeClass = "myTheme";
var settings = {
	"className": "myTheme",
	"name": "myTheme",
	"styles": {
		"fontSize": "9px"
	}
};
function changeFontDynamically() {
	var theme = editorViewer.editor.getTextView().getOptions("theme");
	settings["styles"]["fontSize"] = fontSizeCounter + "px";
	theme.setThemeClass(themeClass, theme.buildStyleSheet(themeClass, settings));
	fontSizeCounter++;
}

Note that this will change the font of all the editor instances if you have multiple editors in your page.

How to pass a status reporter to the editor during its creation

The editorViewer is constantly feeding its status to the user of the widget. For example, the status like the (line, column) number to tell you where your caret is, "string not found" message when you use CTRL+F for search but not hitting a result. Theses status is fed to a callback function. We assume that each editor has its own status reporter. You can now pass a statusReporter option in the codeEdit.create API. In the multiple editor instances case if you want to share the status reporter, just pass the same function.

	var statusReporter = function(message, type, isAccessible) {
		var statusDiv = document.getElementById("statusBarDiv");
		if(!message || !statusDiv) {
			return;
		}
		if (type === "progress") {
			statusDiv.style = "color: green";
			statusDiv.textContent = "Progress message: " + message;
		} else if (type === "error") { //$NON-NLS-0$
			statusDiv.style = "color: red";
			statusDiv.textContent = "Error message: " + message;
		} else {
			statusDiv.style = "color: green";
			statusDiv.textContent = "Normal message: " + message;
		}
	};
	codeEdit.create({parent: "embeddedEditorID", 
					 statusReporter: statusReporter,
					 contentType: "application/javascript", 
					 contents: contents}).then(function(editorViewer) {});

Note that the statusReporter function is completely separate from the codeEdit widget. The "statusBarDiv" div in the example can be anything outside of the widget. You can decide if you want to share the reporter function among all the editorViewer instances or otherwise.

How to listen to the editor's mode changes after its creation

The editorViewer has several modes like the tab mode(CTRL+M to toggle), wrap mode(CTRL+ALT+W) and insert mode(insert key to toggle). If you want to listen to the mode changes and somehow display them, refer to the following snippet.

	editorViewer.editor.getTextView().addEventListener("Options",function(evt){
		if(evt.options) {
			if(evt.options.tabMode !== undefined) {
				//CTRL+m keys
				//True: you can tab inside the editor. False: Tab will get out of the editor DIV
				statusReporter("Tab mode has been changed to: " + evt.options.tabMode);
			} else if(evt.options.wrapMode !== undefined) {
				//CTRL+ALT+w keys
				statusReporter("Wrap mode has been changed to: " + evt.options.wrapMode);
			} else if(evt.options.overwriteMode !== undefined) {
				//Insert key
				statusReporter("Overwrite mode has been changed to: " + evt.options.overwriteMode);
			} else {
				statusReporter("Other options has been changed: ");
			}
		}
	});

Note that the event type is "Options" against editorViewer.editor.getTextView(), which is the core component in the widget. In the example above, we also use the statusReporter addressed in the previous section. If you do not want the mode status to be over written when your caret changes, you need another DIV to render the mode status.

How to manipulate editor contents programmatically

Refer to the editorViewer.editor.getTextView() API section.

How to dispatch and listen to your own events in the editor

Refer to the Dispatching and listening events in TextView section.

How to disable the editor focus after creation

Refer to the noFocus parameter section.

How to repaint the editor when the parent DIV is resized

If your parent DIV that holds the editor is resize-able, you have to capture the parent DIV resize event and call the editorViewer API to repaint the editor. The editor only listens to to the browser's window resize event.

editorViewer.editor.resize();

Note that listening to the DIV resize event is not quite straight forward.

How to resolve Uncaught Error: Load timeout for modules

Refer to the this section

How to trim off default plugins

Refer to the this section

How to pass customized parameters to the validation service

If you register your own validation service for your own content type, the computeProblems(editorContext, options) API will be called automatically once your codeEdit.create(...) is done. The parameters (editorContext, options) are decided by the widget. However, if you have created 2+ editor instances with the same content type and you want to pass additional parameters to the computeProblems API, you need some advanced steps to achieve this.

Know the underlying file URL

In the editor contents saving section, we mentioned that code edit widget uses an underlying in-memory file system to handle all the file read and write. When you create an editor instance by either the simple way or the advancede way, you can get a unique file URL once the creation promise is returned to you. You can then use the file URL for the next step. Follow the snippet below on how to get the file URL.

codeEdit.create({parent: "myEditorDivId01", contentType: "myContentType", contents: "myContent"}).then(function(editorViewer) {
    var fileURL = editorViewer.inputManager.getInput();
});
codeEdit.create({parent: "myEditorDivId02"}).then(function(editorViewer) {
    editorViewer.setContents("var foo;", "application/javascript").then(function(){
        var fileURL = editorViewer.inputManager.getInput();
    });
});

Use the file URL to build a hash table

You can now use a global hash table to store different parameters for your later use. For example, if you have 3 editor instances created:

var computeProblemsParams = {};
computeProblemsParams[fileURL1] = {property1, property2, ...};
computeProblemsParams[fileURL2] = {property3, property4, ...};
computeProblemsParams[fileURL3] = {property5, property6, ...};

Match the file URL in your computeProblems implementation

You will need to get the file URL from the (editorContext, options) parameter first. There are 2 ways to get the file URL associated with computeProblems call.

editorContext.getFileMetadata().then(function(meta) {
    var fileURLOnThisEditorInstance = meta.location;
})
var fileURLOnThisEditorInstance = options.title;

Use the hash table in your computeProblems implementation

Now you can simply find your additional parameters in the computeProblems call.

myAdditionalParam = computeProblemsParams[fileURLOnThisEditorInstance];

Use the same approach in other service implementations

Note that you can use the same approach for other editor services that use (editorContext, options) parameter.

How to override an editor's default action

The editor core supports plenty of built-in actions like CTRL+F to bring up a local find bar for search/replace inside the editor, CTRL+L to bring up a browser prompt to go to a specific line, etc. You can customize the UI if you have your own. Lets take CTRL+L as an example on how to do it. Follow the snippet below. Other customizations will be similar.

codeEdit.create({...}).then(function(editorViewer) {
    var textView = editorViewer.editor.getTextView();
    textView.setAction("gotoLine", function (/*data*/) { //$NON-NLS-0$
        var line;
        var editor = editorViewer.editor;
        var model = editor.getModel();
        line = model.getLineAtOffset(editor.getCaretOffset());
        line = prompt("go to line", line + 1);//Customize your UI here
        if (line) {
            line = parseInt(line, 10);
        }
        if (line) {
            editor.onGotoLine(line - 1, 0);
        }
        return true;
    }, {name: "yourActionName"});
}

How to customize your folding annotation

Refer to the this section

To be continued...

This FAQ section is being actively maintained. If you have any other questions on using the Code Edit widget please feel free to ask Orion mailing list. We will generalize questions and answers and keep adding them here.