Converted text entry to a bigger area, allowing multi-line entry. Added rudimentary spell checking (it's not perfect at the momenent)

This commit is contained in:
Brendan Robert 2015-09-12 18:40:08 -05:00
parent a4e87954a3
commit 8a88a147f2
7 changed files with 275453 additions and 15 deletions

View File

@ -17,6 +17,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
@ -41,6 +42,9 @@ import org.badvision.outlaweditor.data.xml.Scope;
import org.badvision.outlaweditor.data.xml.Script;
import org.badvision.outlaweditor.data.xml.UserType;
import org.badvision.outlaweditor.data.xml.Variable;
import org.badvision.outlaweditor.spelling.SpellChecker;
import org.badvision.outlaweditor.spelling.SpellResponse;
import org.badvision.outlaweditor.spelling.Suggestion;
import org.badvision.outlaweditor.ui.ApplicationUIController;
import org.badvision.outlaweditor.ui.MythosScriptEditorController;
import org.w3c.dom.Document;
@ -58,10 +62,12 @@ public class MythosEditor {
Stage primaryStage;
MythosScriptEditorController controller;
public static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n";
SpellChecker spellChecker;
public MythosEditor(Script theScript, Scope theScope) {
script = theScript;
scope = theScope;
spellChecker = new SpellChecker();
}
public void show() {
@ -146,7 +152,6 @@ public class MythosEditor {
// Called when the name of the root block is changed in the JS editor
public void setFunctionName(String name) {
if (script == null) {
System.out.println("How can the script be null?? wanted to set script name to " + name);
return;
}
script.setName(name);
@ -165,9 +170,11 @@ public class MythosEditor {
public List<Script> getGlobalFunctions() {
return getFunctions(getGlobalScope());
}
public List<Script> getLocalFunctions() {
return getFunctions(scope);
}
private List<Script> getFunctions(Scope scriptScope) {
if (scriptScope.getScripts() == null) {
return new ArrayList<>();
@ -191,17 +198,17 @@ public class MythosEditor {
private boolean isGlobalScope() {
return scope.equals(getGlobalScope());
}
public List<Variable> getLocalVariables() {
return getVariables(scope);
}
private List<Variable> getVariables(Scope scriptScope) {
if (scriptScope.getVariables() == null) {
return new ArrayList<>();
} else {
return scriptScope.getVariables().getVariable();
}
}
}
public List<Variable> getVariablesByType(String type) {
@ -219,18 +226,36 @@ public class MythosEditor {
public List<String> getParametersForScript(Script script) {
List<String> allArgs = new ArrayList();
if (script.getBlock() != null && script.getBlock().getFieldOrMutationOrStatement() != null) {
script.getBlock().getFieldOrMutationOrStatement()
.stream().filter((o) -> (o instanceof Mutation))
.map((o) -> (Mutation) o).findFirst().ifPresent((m) -> {
m.getArg().stream().forEach((a) -> {
allArgs.add(a.getName());
});
script.getBlock().getFieldOrMutationOrStatement()
.stream().filter((o) -> (o instanceof Mutation))
.map((o) -> (Mutation) o).findFirst().ifPresent((m) -> {
m.getArg().stream().forEach((a) -> {
allArgs.add(a.getName());
});
});
}
return allArgs;
}
public String checkSpelling(String value) {
SpellResponse result = spellChecker.check(value);
if (result.getErrors() == 0) {
return null;
} else {
StringBuilder message = new StringBuilder();
result.getCorrections().forEach((SpellResponse.Source source, Set<Suggestion> suggestions) -> {
message
.append(source.word)
.append(" : ")
.append(suggestions.stream().map(Suggestion::getWord).collect(Collectors.joining(", ")))
.append("\n");
});
return message.toString();
}
}
public void log(String message) {
Logger.getLogger(getClass().getName()).warning(message);
System.out.println(message);
}
}

View File

@ -10,6 +10,10 @@
package org.badvision.outlaweditor.data;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.badvision.outlaweditor.Application;
import org.badvision.outlaweditor.data.xml.Field;
@ -20,6 +24,8 @@ import org.badvision.outlaweditor.data.xml.Scope;
import org.badvision.outlaweditor.data.xml.Script;
public class DataUtilities {
private DataUtilities() {
}
public static void ensureGlobalExists() {
if (Application.gameData.getGlobal() == null) {
@ -98,4 +104,130 @@ public class DataUtilities {
cleanupScriptNames(Application.gameData.getGlobal());
Application.gameData.getMap().forEach(DataUtilities::cleanupScriptNames);
}
//------------------------------ String comparators
/**
* Rank two strings similarity in terms of distance The lower the number,
* the more similar these strings are to each other See:
* http://en.wikipedia.org/wiki/Levenshtein_distance#Computing_Levenshtein_distance
*
* @param s
* @param t
* @return Distance (higher is better)
*/
public static int levenshteinDistance(String s, String t) {
if (s == null || t == null || s.length() == 0 || t.length() == 0) {
return -1;
}
s = s.toLowerCase().replaceAll("[^a-zA-Z0-9\\s]", "");
t = t.toLowerCase().replaceAll("[^a-zA-Z0-9\\s]", "");
int m = s.length();
int n = t.length();
int[][] dist = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {
dist[i][0] = i;
}
for (int i = 1; i <= n; i++) {
dist[0][i] = i;
}
for (int j = 1; j <= n; j++) {
for (int i = 1; i <= m; i++) {
if (s.charAt(i - 1) == t.charAt(j - 1)) {
dist[i][j] = dist[i - 1][j - 1];
} else {
int del = dist[i - 1][j] + 1;
int insert = dist[i][j - 1] + 1;
int sub = dist[i - 1][j - 1] + 1;
dist[i][j] = Math.min(Math.min(del, insert), sub);
}
}
}
return Math.max(m, n) - dist[m][n];
}
/**
* Compare strings based on a tally of similar patterns found, using a fixed
* search window The resulting score is heavily penalized if the strings
* differ greatly in length This is not as efficient as levenshtein, so it's
* only used as a tie-breaker.
*
* @param c1
* @param c2
* @param width Search window size
* @return Overall similarity score (higher is beter)
*/
public static double rankMatch(String c1, String c2, int width) {
double score = 0;
String s1 = c1.toLowerCase();
String s2 = c2.toLowerCase();
for (int i = 0; i < s1.length() + 1 - width; i++) {
String m = s1.substring(i, i + width);
int j = 0;
while ((j = s2.indexOf(m, j)) > -1) {
score += width;
j++;
}
}
double l1 = s1.length();
double l2 = s2.length();
// If the two strings are equivilent in length, the score is higher
// If the two strings are different in length, the score is adjusted lower depending on how large the difference is
// This is offset just a hair for tuning purposes
double adjustment = (Math.min(l1, l2) / Math.max(l1, l2)) + 0.1;
return score * adjustment * adjustment;
}
public static class RankingComparator implements Comparator<String> {
String match;
public RankingComparator(String match) {
// Adding a space helps respect word boundaries as part of the match
// In the case of very close matches this is another tie-breaker
// Especially for very small search terms
this.match = match + " ";
}
@Override
public int compare(String o1, String o2) {
double s1 = levenshteinDistance(match, o1);
double s2 = levenshteinDistance(match, o2);
if (s2 == s1) {
s1 = rankMatch(o1, match, 3) + rankMatch(o1, match, 2);
s2 = rankMatch(o2, match, 3) + rankMatch(o2, match, 2);
if (s2 == s1) {
return (o1.compareTo(o2));
} else {
// Normalize result to -1, 0 or 1 so there is no rounding issues!
return (int) Math.signum(s2 - s1);
}
} else {
return (int) (s2 - s1);
}
}
}
/**
* Given a desired search string and a search space of recognized
* selections, identify the best match in the list
*
* @param match String to search for
* @param search Space of all valid results
* @return Best match found, or null if there was nothing close to a match
* found.
*/
public static String findBestMatch(String match, Collection<String> search) {
if (search == null || search.isEmpty()) {
return null;
}
RankingComparator r = new RankingComparator(match);
List<String> candidates = new ArrayList<>(search);
Collections.sort(candidates, r);
double score = levenshteinDistance(match, candidates.get(0));
if (score > 1) {
return candidates.get(0);
}
return null;
}
}

View File

@ -0,0 +1,108 @@
/*
* Copyright (C) 2015 The 8-Bit Bunch. Licensed under the Apache License, Version 1.1
* (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at <http://www.apache.org/licenses/LICENSE-1.1>.
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
* ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package org.badvision.outlaweditor.spelling;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.badvision.outlaweditor.data.DataUtilities;
/**
*
* @author blurry
*/
public class SpellChecker {
private static HashMap<Character, Set<String>> dictionary;
private final double SIMILARITY_THRESHOLD = 0.5;
public SpellChecker() {
loadDictionary();
}
public SpellResponse check(String value) {
SpellResponse response = new SpellResponse();
String[] words = value.split("[^A-Za-z]");
int pos = 0;
for (String word : words) {
Set<Suggestion> suggestions = getSuggestions(word);
if (suggestions != null && !suggestions.isEmpty()) {
Suggestion first = suggestions.stream().findFirst().get();
if (first.similarity == 1.0) {
continue;
} else {
SpellResponse.Source source = new SpellResponse.Source();
source.start = pos;
source.word = word;
response.corrections.put(source, suggestions);
}
}
pos += word.length() + 1;
}
return response;
}
private static void loadDictionary() {
if (dictionary == null) {
URL dictionaryPath = SpellChecker.class.getResource("/mythos/dictionary.txt");
try {
BufferedReader content = new BufferedReader(new InputStreamReader((InputStream) dictionaryPath.getContent()));
dictionary = new HashMap<>();
content.lines().forEach((String word)-> {
String lower = word.toLowerCase();
Set<String> words = dictionary.get(lower.charAt(0));
if (words == null) {
words = new LinkedHashSet<>();
dictionary.put(lower.charAt(0), words);
}
words.add(word);
});
} catch (IOException ex) {
Logger.getLogger(SpellChecker.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
private Set<Suggestion> getSuggestions(String word) {
TreeSet<Suggestion> suggestions = new TreeSet<>();
if (word == null || word.isEmpty()) {
return suggestions;
}
String lower = word.toLowerCase();
Character first = lower.charAt(0);
Set<String> words = dictionary.get(first);
if (words != null) {
if (words.contains(lower)) {
return null;
}
words.parallelStream().forEach((String dictWord) -> {
int distance = DataUtilities.levenshteinDistance(lower, dictWord);
double similarity = distance / ((double) Math.max(lower.length(), dictWord.length()));
if (similarity >= SIMILARITY_THRESHOLD) {
Suggestion suggestion = new Suggestion();
suggestion.similarity = similarity;
suggestion.word = dictWord;
suggestions.add(suggestion);
}
});
}
return suggestions;
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) 2015 The 8-Bit Bunch. Licensed under the Apache License, Version 1.1
* (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at <http://www.apache.org/licenses/LICENSE-1.1>.
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
* ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package org.badvision.outlaweditor.spelling;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
public class SpellResponse {
public static class Source {
public int start;
public String word;
}
Map<Source, Set<Suggestion>> corrections = new LinkedHashMap<>();
public int getErrors() {
return corrections.size();
}
public Map<Source, Set<Suggestion>> getCorrections() {
return corrections;
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright (C) 2015 The 8-Bit Bunch. Licensed under the Apache License, Version 1.1
* (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at <http://www.apache.org/licenses/LICENSE-1.1>.
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
* ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package org.badvision.outlaweditor.spelling;
public class Suggestion implements Comparable<Suggestion> {
public String word;
public double similarity;
public String getWord() {
return word;
}
public double getSimilarity() {
return similarity;
}
@Override
public int compareTo(Suggestion o) {
return (int) Math.signum(o.similarity - similarity);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -8,10 +8,10 @@
* governing permissions and limitations under the License.
*/
/* global Blockly */
/* global Blockly, goog */
if (typeof Mythos === "undefined") {
// Hook up the rename function to notify the java editor when changes occur
// Hook up the rename function to notify the java editor when changes occur
if (typeof window === "undefined") {
window = {};
}
@ -20,7 +20,6 @@ if (typeof Mythos === "undefined") {
Mythos.editor.setFunctionName(name);
return Blockly.Procedures.rename_old.call(this, name);
};
Mythos = {
setScriptXml: function (xml) {
Blockly.mainWorkspace.clear();
@ -89,8 +88,7 @@ if (typeof Mythos === "undefined") {
typeConstructor.appendValueInput(attribute.getName())
.setAlign(Blockly.ALIGN_RIGHT)
.setCheck(attribute.getType())
.appendField(attribute.getName())
.appendField(attribute.getName());
});
} catch (error) {
Mythos.editor.log(error);
@ -484,6 +482,63 @@ if (typeof Mythos === "undefined") {
this.setTooltip('Scrolls text window up one line');
}
};
Blockly.Blocks['text_area'] = {
init: function () {
var field = new Blockly.FieldTextArea('', this.checkSpelling);
field.block_ = this;
this.setHelpUrl(Blockly.Msg.TEXT_TEXT_HELPURL);
this.setColour(Blockly.Blocks.texts.HUE);
this.appendDummyInput()
.appendField("Big")
.appendField(this.newQuote_(true))
.appendField(field, 'TEXT')
.appendField(this.newQuote_(false));
this.setOutput(true, 'String');
this.setTooltip(Blockly.Msg.TEXT_TEXT_TOOLTIP);
// this.setMutator(new Blockly.Mutator(['text']));
},
newQuote_: function (open) {
var file;
if (open === this.RTL) {
file = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAqUlEQVQI1z3KvUpCcRiA8ef9E4JNHhI0aFEacm1o0BsI0Slx8wa8gLauoDnoBhq7DcfWhggONDmJJgqCPA7neJ7p934EOOKOnM8Q7PDElo/4x4lFb2DmuUjcUzS3URnGib9qaPNbuXvBO3sGPHJDRG6fGVdMSeWDP2q99FQdFrz26Gu5Tq7dFMzUvbXy8KXeAj57cOklgA+u1B5AoslLtGIHQMaCVnwDnADZIFIrXsoXrgAAAABJRU5ErkJggg==';
} else {
file = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAn0lEQVQI1z3OMa5BURSF4f/cQhAKjUQhuQmFNwGJEUi0RKN5rU7FHKhpjEH3TEMtkdBSCY1EIv8r7nFX9e29V7EBAOvu7RPjwmWGH/VuF8CyN9/OAdvqIXYLvtRaNjx9mMTDyo+NjAN1HNcl9ZQ5oQMM3dgDUqDo1l8DzvwmtZN7mnD+PkmLa+4mhrxVA9fRowBWmVBhFy5gYEjKMfz9AylsaRRgGzvZAAAAAElFTkSuQmCC';
}
return new Blockly.FieldImage(file, 12, 12, '"');
},
checkSpelling: function(value) {
this.block_.setCommentText(Mythos.editor.checkSpelling(value));
return value;
}
};
Blockly.Blocks['text'] = {
init: function () {
var field = new Blockly.FieldTextInput('', this.checkSpelling);
field.block_ = this;
this.setHelpUrl(Blockly.Msg.TEXT_TEXT_HELPURL);
this.setColour(Blockly.Blocks.texts.HUE);
this.appendDummyInput()
.appendField(this.newQuote_(true))
.appendField(field, 'TEXT')
.appendField(this.newQuote_(false));
this.setOutput(true, 'String');
this.setTooltip(Blockly.Msg.TEXT_TEXT_TOOLTIP);
// this.setMutator(new Blockly.Mutator(['text_area']));
},
newQuote_: function (open) {
var file;
if (open === this.RTL) {
file = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAqUlEQVQI1z3KvUpCcRiA8ef9E4JNHhI0aFEacm1o0BsI0Slx8wa8gLauoDnoBhq7DcfWhggONDmJJgqCPA7neJ7p934EOOKOnM8Q7PDElo/4x4lFb2DmuUjcUzS3URnGib9qaPNbuXvBO3sGPHJDRG6fGVdMSeWDP2q99FQdFrz26Gu5Tq7dFMzUvbXy8KXeAj57cOklgA+u1B5AoslLtGIHQMaCVnwDnADZIFIrXsoXrgAAAABJRU5ErkJggg==';
} else {
file = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAn0lEQVQI1z3OMa5BURSF4f/cQhAKjUQhuQmFNwGJEUi0RKN5rU7FHKhpjEH3TEMtkdBSCY1EIv8r7nFX9e29V7EBAOvu7RPjwmWGH/VuF8CyN9/OAdvqIXYLvtRaNjx9mMTDyo+NjAN1HNcl9ZQ5oQMM3dgDUqDo1l8DzvwmtZN7mnD+PkmLa+4mhrxVA9fRowBWmVBhFy5gYEjKMfz9AylsaRRgGzvZAAAAAElFTkSuQmCC';
}
return new Blockly.FieldImage(file, 12, 12, '"');
},
checkSpelling: function(value) {
this.block_.setCommentText(Mythos.editor.checkSpelling(value));
return value;
}
};
Blockly.Blocks['text_getstring'] = {
init: function () {
this.setHelpUrl(Mythos.helpUrl);
@ -553,3 +608,138 @@ if (typeof Mythos === "undefined") {
};
}
;
//------
goog.provide('Blockly.FieldTextArea');
goog.require('Blockly.Field');
goog.require('Blockly.Msg');
goog.require('goog.asserts');
goog.require('goog.userAgent');
/**
* Class for an editable text field.
* @param {string} text The initial content of the field.
* @param {Function} opt_changeHandler An optional function that is called
* to validate any constraints on what the user entered. Takes the new
* text as an argument and returns either the accepted text, a replacement
* text, or null to abort the change.FFF
* @extends {Blockly.Field}
* @constructor
*/
Blockly.FieldTextArea = function (text, opt_changeHandler) {
Blockly.FieldTextInput.superClass_.constructor.call(this, text);
this.changeHandler_ = opt_changeHandler;
};
goog.inherits(Blockly.FieldTextArea, Blockly.FieldTextInput);
/**
* Clone this FieldTextArea.
* @return {!Blockly.FieldTextArea} The result of calling the constructor again
* with the current values of the arguments used during construction.
*/
Blockly.FieldTextArea.prototype.clone = function () {
return new Blockly.FieldTextArea(this.getText(), this.changeHandler_);
};
/**
* Show the inline free-text editor on top of the text.
* @param {boolean=} opt_quietInput True if editor should be created without
* focus. Defaults to false.
* @private
*/
Blockly.FieldTextInput.prototype.showEditor_ = function (opt_quietInput) {
var quietInput = opt_quietInput || false;
if (!quietInput && (goog.userAgent.MOBILE || goog.userAgent.ANDROID ||
goog.userAgent.IPAD)) {
// Mobile browsers have issues with in-line textareas (focus & keyboards).
var newValue = window.prompt(Blockly.Msg.CHANGE_VALUE_TITLE, this.text_);
if (this.sourceBlock_ && this.changeHandler_) {
var override = this.changeHandler_(newValue);
if (override !== undefined) {
newValue = override;
}
}
if (newValue !== null) {
this.setText(newValue);
}
return;
}
Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, this.widgetDispose_());
var div = Blockly.WidgetDiv.DIV;
// Create the input.
var htmlInput = goog.dom.createDom('textarea', 'blocklyHtmlInput');
htmlInput.setAttribute('spellcheck', this.spellcheck_);
htmlInput.setAttribute('cols', 80);
htmlInput.setAttribute('rows', 7);
var fontSize = (Blockly.FieldTextInput.FONTSIZE *
this.sourceBlock_.workspace.scale) + 'pt';
div.style.fontSize = fontSize;
div.style.width = "30em";
htmlInput.style.width = "30em";
htmlInput.style.fontSize = fontSize;
htmlInput.style.backgroundColor = "#eee";
/** @type {!HTMLTextArea} */
Blockly.FieldTextInput.htmlInput_ = htmlInput;
div.appendChild(htmlInput);
htmlInput.value = htmlInput.defaultValue = this.text_;
htmlInput.oldValue_ = null;
this.validate_();
this.resizeEditor_();
if (!quietInput) {
htmlInput.focus();
htmlInput.select();
}
// Bind to keydown -- trap Enter without IME and Esc to hide.
htmlInput.onKeyDownWrapper_ =
Blockly.bindEvent_(htmlInput, 'keydown', this, this.onHtmlInputKeyDown_);
// Bind to keyup -- trap Enter; resize after every keystroke.
htmlInput.onKeyUpWrapper_ =
Blockly.bindEvent_(htmlInput, 'keyup', this, this.onHtmlInputChange_);
// Bind to keyPress -- repeatedly resize when holding down a key.
htmlInput.onKeyPressWrapper_ =
Blockly.bindEvent_(htmlInput, 'keypress', this, this.onHtmlInputChange_);
var workspaceSvg = this.sourceBlock_.workspace.getCanvas();
htmlInput.onWorkspaceChangeWrapper_ =
Blockly.bindEvent_(workspaceSvg, 'blocklyWorkspaceChange', this,
this.resizeEditor_);
};
/**
* Handle key down to the editor.
* @param {!Event} e Keyboard event.
* @private
*/
Blockly.FieldTextInput.prototype.onHtmlInputKeyDown_ = function (e) {
var htmlInput = Blockly.FieldTextInput.htmlInput_;
var escKey = 27;
if (e.keyCode === escKey) {
this.setText(htmlInput.defaultValue);
Blockly.WidgetDiv.hide();
}
};
/**
* Handle a change to the editor.
* @param {!Event} e Keyboard event.
* @private
*/
Blockly.FieldTextArea.prototype.onHtmlInputChange_ = function (e) {
var htmlInput = Blockly.FieldTextArea.htmlInput_;
if (e.keyCode === 27) {
// Esc
this.setText(htmlInput.defaultValue);
Blockly.WidgetDiv.hide();
} else {
// Update source block.
var text = htmlInput.value;
if (text !== htmlInput.oldValue_) {
htmlInput.oldValue_ = text;
this.setText(text);
this.validate_();
} else if (goog.userAgent.WEBKIT) {
// Cursor key. Render the source block to show the caret moving.
// Chrome only (version 26, OS X).
this.sourceBlock_.render();
}
this.resizeEditor_();
}
};