text: /*\
  title: $:/core/modules/widgets/edit-codemirror.js
  type: application/javascript
  module-type: widget
  
  Codemirror-based text editor widget
  
  Config options "$:/config/CodeMirror" e.g. to allow vim key bindings
   {
  	"require": [
  		"$:/plugins/tiddlywiki/codemirror/addon/dialog/dialog.js",
  		"$:/plugins/tiddlywiki/codemirror/addon/search/searchcursor.js",
  		"$:/plugins/tiddlywiki/codemirror/keymap/vim.js"
  	],
  	"configuration": {
  			"keyMap": "vim",
  			"showCursorWhenSelecting": true
  	}
  }
  
  \*/
  (function(){
  
  /*jslint node: true, browser: true */
  /*global $tw: false */
  "use strict";
  
  var CODEMIRROR_OPTIONS = "$:/config/CodeMirror";
  
  // Install CodeMirror
  if($tw.browser && !window.CodeMirror) {
  	window.CodeMirror = require("$:/plugins/tiddlywiki/codemirror/lib/codemirror.js");
  	// Install required CodeMirror plugins
  	var configOptions = $tw.wiki.getTiddlerData(CODEMIRROR_OPTIONS,{}),
  		req = configOptions.require;
  	if(req) {
  		if($tw.utils.isArray(req)) {
  			for(var index=0; index<req.length; index++) {
  				require(req[index]);
  			}
  		} else {
  			require(req);
  		}
  	}
  }
  
  var MIN_TEXT_AREA_HEIGHT = 100; // Minimum height of textareas in pixels
  
  var Widget = require("$:/core/modules/widgets/widget.js").widget;
  
  var EditCodeMirrorWidget = function(parseTreeNode,options) {
  	this.initialise(parseTreeNode,options);
  };
  
  /*
  Inherit from the base widget class
  */
  EditCodeMirrorWidget.prototype = new Widget();
  
  /*
  Render this widget into the DOM
  */
  EditCodeMirrorWidget.prototype.render = function(parent,nextSibling) {
  	var self = this;
  	// Save the parent dom node
  	this.parentDomNode = parent;
  	// Compute our attributes
  	this.computeAttributes();
  	// Execute our logic
  	this.execute();
  	// Get the configuration options for the CodeMirror object
  	var config = $tw.wiki.getTiddlerData(CODEMIRROR_OPTIONS,{}).configuration || {},
  		editInfo = this.getEditInfo();
  	if(!("lineWrapping" in config)) {
  		config.lineWrapping = true;
  	}
  	if(!("lineNumbers" in config)) {
  		config.lineNumbers = true;
  	}
  	config.mode = editInfo.type;
  	config.value = editInfo.value;
  	// Create the CodeMirror instance
  	var cm = window.CodeMirror(function(domNode) {
  		parent.insertBefore(domNode,nextSibling);
  		self.domNodes.push(domNode);
  	},config);
  	// Set up a change event handler
  	cm.on("change",function() {
  		self.saveChanges(cm.getValue());
  	});
  	this.codeMirrorInstance = cm;
  };
  
  /*
  Get the tiddler being edited and current value
  */
  EditCodeMirrorWidget.prototype.getEditInfo = function() {
  	// Get the edit value
  	var self = this,
  		value,
  		type = "text/plain",
  		update;
  	if(this.editIndex) {
  		value = this.wiki.extractTiddlerDataItem(this.editTitle,this.editIndex,this.editDefault);
  		update = function(value) {
  			var data = self.wiki.getTiddlerData(self.editTitle,{});
  			if(data[self.editIndex] !== value) {
  				data[self.editIndex] = value;
  				self.wiki.setTiddlerData(self.editTitle,data);
  			}
  		};
  	} else {
  		// Get the current tiddler and the field name
  		var tiddler = this.wiki.getTiddler(this.editTitle);
  		if(tiddler) {
  			// If we've got a tiddler, the value to display is the field string value
  			value = tiddler.getFieldString(this.editField);
  			if(this.editField === "text") {
  				type = tiddler.fields.type || "text/vnd.tiddlywiki";
  			}
  		} else {
  			// Otherwise, we need to construct a default value for the editor
  			switch(this.editField) {
  				case "text":
  					value = "Type the text for the tiddler '" + this.editTitle + "'";
  					type = "text/vnd.tiddlywiki";
  					break;
  				case "title":
  					value = this.editTitle;
  					break;
  				default:
  					value = "";
  					break;
  			}
  			if(this.editDefault !== undefined) {
  				value = this.editDefault;
  			}
  		}
  		update = function(value) {
  			var tiddler = self.wiki.getTiddler(self.editTitle),
  				updateFields = {
  					title: self.editTitle
  				};
  			updateFields[self.editField] = value;
  			self.wiki.addTiddler(new $tw.Tiddler(self.wiki.getCreationFields(),tiddler,updateFields,self.wiki.getModificationFields()));
  		};
  	}
  	if(this.editType) {
  		type = this.editType;
  	}
  	return {value: value || "", type: type, update: update};
  };
  
  /*
  Compute the internal state of the widget
  */
  EditCodeMirrorWidget.prototype.execute = function() {
  	// Get our parameters
  	this.editTitle = this.getAttribute("tiddler",this.getVariable("currentTiddler"));
  	this.editField = this.getAttribute("field","text");
  	this.editIndex = this.getAttribute("index");
  	this.editDefault = this.getAttribute("default");
  	this.editType = this.getAttribute("type");
  };
  
  /*
  Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
  */
  EditCodeMirrorWidget.prototype.refresh = function(changedTiddlers) {
  	var changedAttributes = this.computeAttributes();
  	// Completely rerender if any of our attributes have changed
  	if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index) {
  		this.refreshSelf();
  		return true;
  	} else if(changedTiddlers[this.editTitle]) {
  		this.updateEditor(this.getEditInfo().value);
  		return true;
  	}
  	return false;
  };
  
  /*
  Update the editor with new text. This method is separate from updateEditorDomNode()
  so that subclasses can override updateEditor() and still use updateEditorDomNode()
  */
  EditCodeMirrorWidget.prototype.updateEditor = function(text) {
  	this.updateEditorDomNode(text);
  };
  
  /*
  Update the editor dom node with new text
  */
  EditCodeMirrorWidget.prototype.updateEditorDomNode = function(text) {
  	if(this.codeMirrorInstance) {
  		if(!this.codeMirrorInstance.hasFocus()) {
  			this.codeMirrorInstance.setValue(text);
  		}
  	}
  };
  
  EditCodeMirrorWidget.prototype.saveChanges = function(text) {
  	var editInfo = this.getEditInfo();
  	if(text !== editInfo.value) {
  		editInfo.update(text);
  	}
  };
  
  exports["edit-codemirror"] = EditCodeMirrorWidget;
  
  })();
  