diff --git a/bundles/org.eclipse.orion.client.core/web/orion/bidiUtils.js b/bundles/org.eclipse.orion.client.core/web/orion/bidiUtils.js
index 2583928041..5cbeff170a 100644
--- a/bundles/org.eclipse.orion.client.core/web/orion/bidiUtils.js
+++ b/bundles/org.eclipse.orion.client.core/web/orion/bidiUtils.js
@@ -12,12 +12,6 @@ define ([
],
function(util) { /* BDL */
- var bidiEnabledStorage = "/orion/preferences/bidi/bidiEnabled"; //$NON-NLS-0$
- var bidiLayoutStorage = "/orion/preferences/bidi/bidiLayout"; //$NON-NLS-0$
- var LRE = "\u202A"; //$NON-NLS-0$
- var PDF = "\u202C"; //$NON-NLS-0$
- var RLE = "\u202B"; //$NON-NLS-0$
-
function setBrowserLangDirection() {
var lang;
@@ -29,7 +23,7 @@ function(util) { /* BDL */
}
var isBidi = lang && "ar iw he".indexOf(lang.substring(0, 2)) !== - 1;
- if (isBidi && isBidiEnabled()) {
+ if (isBidi) {
var htmlElement = document.getElementsByTagName("html")[0];
if (htmlElement){ //should be always true
htmlElement.setAttribute ("dir", "rtl");
@@ -39,6 +33,12 @@ function(util) { /* BDL */
setBrowserLangDirection();
+ var bidiEnabledStorage = "/orion/preferences/bidi/bidiEnabled"; //$NON-NLS-0$
+ var bidiLayoutStorage = "/orion/preferences/bidi/bidiLayout"; //$NON-NLS-0$
+ var LRE = "\u202A"; //$NON-NLS-0$
+ var PDF = "\u202C"; //$NON-NLS-0$
+ var RLE = "\u202B"; //$NON-NLS-0$
+
var bidiLayout = getBidiLayout();
/**
@@ -146,7 +146,16 @@ function(util) { /* BDL */
}
}
- function enforceTextDir(range) {
+ function createNewStyle ( oldStyle, textDir ) {
+ var newStyle = oldStyle;
+ if (typeof newStyle.attributes === "undefined") {
+ newStyle.attributes = {};
+ }
+ newStyle.attributes.dir = textDir;
+ return newStyle;
+ }
+
+ function enforceTextDir( range, textDir ) {
var comments = [{name:"comment block"},
{name:"comment line double-slash"},
{name:"comment block documentation"},
@@ -163,13 +172,17 @@ function(util) { /* BDL */
var newStyle = style;
if (typeof newStyle.attributes === "undefined") {
newStyle.attributes = {};
- }
- newStyle.attributes.dir = getTextDirection(text);
- range.style = newStyle;
+ }
+ range.style = createNewStyle( style, getTextDirection(text) );
return range;
}
}
}
+ else if (style && textDir && textDir.length > 0) {
+ range.style = createNewStyle( style, textDir );
+ return range;
+ }
+
return range;
}
diff --git a/bundles/org.eclipse.orion.client.core/web/orion/util.js b/bundles/org.eclipse.orion.client.core/web/orion/util.js
index a186dd3253..f2e2b69b6f 100644
--- a/bundles/org.eclipse.orion.client.core/web/orion/util.js
+++ b/bundles/org.eclipse.orion.client.core/web/orion/util.js
@@ -30,6 +30,8 @@ define(function() {
var isTouch = typeof document !== "undefined" && "ontouchstart" in document.createElement("input"); //$NON-NLS-1$ //$NON-NLS-0$
var platformDelimiter = isWindows ? "\r\n" : "\n"; //$NON-NLS-1$ //$NON-NLS-0$
+ var LtrMarker = "\u202A\u202A\u202A\u202A\u202A"; //$NON-NLS-0$
+ var RtlMarker = "\u202B\u202B\u202B\u202B\u202B"; //$NON-NLS-0$
function formatMessage(msg) {
var args = arguments;
@@ -85,6 +87,9 @@ define(function() {
/** Capabilities */
isTouch: isTouch,
- platformDelimiter: platformDelimiter
+ platformDelimiter: platformDelimiter,
+ LtrMarker: LtrMarker,
+ RtlMarker: RtlMarker
+
};
});
\ No newline at end of file
diff --git a/bundles/org.eclipse.orion.client.editor/web/orion/editor/keyModes.js b/bundles/org.eclipse.orion.client.editor/web/orion/editor/keyModes.js
index 5f2589b255..43a54f4d5d 100644
--- a/bundles/org.eclipse.orion.client.editor/web/orion/editor/keyModes.js
+++ b/bundles/org.eclipse.orion.client.editor/web/orion/editor/keyModes.js
@@ -231,6 +231,8 @@ define("orion/editor/keyModes", [ //$NON-NLS-0$
bindings.push({actionID: "escape", keyBinding: new KeyBinding(27), predefined: true}); //$NON-NLS-0$
bindings.push({actionID: "selectAll", keyBinding: new KeyBinding('a', true), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
bindings.push({actionID: "toggleTabMode", keyBinding: new KeyBinding('m', true), predefined: true}); //$NON-NLS-1$ //$NON-NLS-0$
+ bindings.push({actionID: "dirLTR", keyBinding: new KeyBinding(36, null, true, true), predefined: true}); //$NON-NLS-0$
+ bindings.push({actionID: "dirRTL", keyBinding: new KeyBinding(35, null, true, true), predefined: true}); //$NON-NLS-0$
if (util.isMac) {
bindings.push({actionID: "deleteNext", keyBinding: new KeyBinding(46, null, true), predefined: true}); //$NON-NLS-0$
bindings.push({actionID: "deleteWordPrevious", keyBinding: new KeyBinding(8, null, null, true), predefined: true}); //$NON-NLS-0$
diff --git a/bundles/org.eclipse.orion.client.editor/web/orion/editor/nls/root/messages.js b/bundles/org.eclipse.orion.client.editor/web/orion/editor/nls/root/messages.js
index f21c3cd5b8..87fdfde9ab 100644
--- a/bundles/org.eclipse.orion.client.editor/web/orion/editor/nls/root/messages.js
+++ b/bundles/org.eclipse.orion.client.editor/web/orion/editor/nls/root/messages.js
@@ -86,6 +86,8 @@ define({//Default message bundle
"toggleWrapMode": "Toggle Wrap Mode", //$NON-NLS-1$ //$NON-NLS-0$
"toggleTabMode": "Toggle Tab Mode", //$NON-NLS-1$ //$NON-NLS-0$
"toggleOverwriteMode": "Toggle Overwrite Mode", //$NON-NLS-1$ //$NON-NLS-0$
+ "dirLTR": "Set Left to Right Text Direction", //$NON-NLS-1$ //$NON-NLS-0$
+ "dirRTL": "Set Right to Left Text Direction", //$NON-NLS-1$ //$NON-NLS-0$
"committerOnTime": "${0} on ${1}", //$NON-NLS-1$ //$NON-NLS-0$
diff --git a/bundles/org.eclipse.orion.client.editor/web/orion/editor/projectionTextModel.js b/bundles/org.eclipse.orion.client.editor/web/orion/editor/projectionTextModel.js
index b00af8dc5b..2a5e8ccd87 100644
--- a/bundles/org.eclipse.orion.client.editor/web/orion/editor/projectionTextModel.js
+++ b/bundles/org.eclipse.orion.client.editor/web/orion/editor/projectionTextModel.js
@@ -339,6 +339,18 @@ define("orion/editor/projectionTextModel", ['orion/editor/textModel', 'orion/edi
getLineDelimiter: function() {
return this._model.getLineDelimiter();
},
+ /**
+ * @see orion.editor.TextModel#getLineTextDir
+ */
+ getLineTextDir: function(lineIndex) {
+ return this._model.getLineTextDir(lineIndex);
+ },
+ /**
+ * @see orion.editor.TextModel#getTextForSave
+ */
+ getTextForSave: function() {
+ return this._model.getTextForSave();
+ },
/**
* @see orion.editor.TextModel#getLineEnd
*/
diff --git a/bundles/org.eclipse.orion.client.editor/web/orion/editor/textModel.js b/bundles/org.eclipse.orion.client.editor/web/orion/editor/textModel.js
index a10a811913..751d52c30f 100644
--- a/bundles/org.eclipse.orion.client.editor/web/orion/editor/textModel.js
+++ b/bundles/org.eclipse.orion.client.editor/web/orion/editor/textModel.js
@@ -38,6 +38,7 @@ define("orion/editor/textModel", ['orion/editor/eventTarget', 'orion/regex', 'or
this._lastLineIndex = -1;
this._text = [""];
this._lineOffsets = [0];
+ this._textDirs = [""];
this.setText(text);
this.setLineDelimiter(lineDelimiter);
}
@@ -370,6 +371,23 @@ define("orion/editor/textModel", ['orion/editor/eventTarget', 'orion/regex', 'or
}
return this._lineOffsets[lineIndex];
},
+ /**
+ * Returns the text direction of the given line.
+ *
+ * The valid indices are 0 to line count exclusive. Returns ""
+ * if the index is out of range.
+ *
+ *
+ * @param {Number} lineIndex the zero based index of the line.
+ * @return {String} the text direction of the line""
if out of range.
+ *
+ */
+ getLineTextDir: function(lineIndex) {
+ if (!(0 <= lineIndex && lineIndex < this.getLineCount())) {
+ return "";
+ }
+ return this._textDirs[lineIndex];
+ },
/**
* Returns the text for the given range.
*
@@ -416,6 +434,23 @@ define("orion/editor/textModel", ['orion/editor/eventTarget', 'orion/regex', 'or
var afterText = this._text[lastChunk].substring(0, end - lastOffset);
return beforeText + this._text.slice(firstChunk+1, lastChunk).join("") + afterText;
},
+ /**
+ * Returns the whole text for save. We keep the text direction of each line by adding the ltr/rtl marker.
+ *
+ */
+ getTextForSave: function() {
+ var text = "";
+ for (var i = 0; i
@@ -508,6 +543,8 @@ define("orion/editor/textModel", ['orion/editor/eventTarget', 'orion/regex', 'or
if (start === undefined) { start = 0; }
if (end === undefined) { end = this.getCharCount(); }
if (start === end && text === "") { return; }
+ var ltrMarker = util.LtrMarker;
+ var rtlMarker = util.RtlMarker;
var startLine = this.getLineAtOffset(start);
var endLine = this.getLineAtOffset(end);
var eventStart = start;
@@ -517,12 +554,21 @@ define("orion/editor/textModel", ['orion/editor/eventTarget', 'orion/regex', 'or
var addedLineCount = 0;
var lineCount = this.getLineCount();
- var cr = 0, lf = 0, index = 0;
+ var cr = 0, lf = 0, index = 0, shift = 0;
var newLineOffsets = [];
- while (true) {
+ var newTextDirs = [];
+ var origText = text;
+
+
+ while (true) {
if (cr !== -1 && cr <= index) { cr = text.indexOf("\r", index); } //$NON-NLS-0$
if (lf !== -1 && lf <= index) { lf = text.indexOf("\n", index); } //$NON-NLS-0$
if (lf === -1 && cr === -1) { break; }
+ if (text.substr(index, ltrMarker.length) == ltrMarker) {
+ shift += ltrMarker.length;
+ } else if (text.substr(index, rtlMarker.length) == rtlMarker) {
+ shift += rtlMarker.length;
+ }
if (cr !== -1 && lf !== -1) {
if (cr + 1 === lf) {
index = lf + 1;
@@ -534,8 +580,20 @@ define("orion/editor/textModel", ['orion/editor/eventTarget', 'orion/regex', 'or
} else {
index = lf + 1;
}
- newLineOffsets.push(start + index);
+ newLineOffsets.push(start + index - shift);
addedLineCount++;
+ if (text.substr(index, ltrMarker.length) == ltrMarker) {
+ newTextDirs.push("ltr"); //$NON-NLS-0$
+ } else if (text.substr(index, rtlMarker.length) == rtlMarker) {
+ newTextDirs.push("rtl"); //$NON-NLS-0$
+ } else {
+ newTextDirs.push(""); //$NON-NLS-0$
+ }
+ }
+ if (text !== ltrMarker && text !== rtlMarker) {
+ var re1 = new RegExp(ltrMarker, "g"), re2 = new RegExp(rtlMarker, "g");
+ text = text.replace(re1, "").replace(re2, "");
+ addedCharCount = text.length;
}
var modelChangingEvent = {
@@ -582,16 +640,26 @@ define("orion/editor/textModel", ['orion/editor/eventTarget', 'orion/regex', 'or
if (newLineOffsets.length < limit) {
args = [startLine + 1, removedLineCount].concat(newLineOffsets);
Array.prototype.splice.apply(this._lineOffsets, args);
+ args = [startLine + 1, removedLineCount].concat(newTextDirs);
+ Array.prototype.splice.apply(this._textDirs, args);
} else {
index = startLine + 1;
this._lineOffsets.splice(index, removedLineCount);
for (var k = 0; k < newLineOffsets.length; k += limit) {
args = [index, 0].concat(newLineOffsets.slice(k, Math.min(newLineOffsets.length, k + limit)));
Array.prototype.splice.apply(this._lineOffsets, args);
+ args = [index, 0].concat(newTextDirs.slice(k, Math.min(newTextDirs.length, k + limit)));
+ Array.prototype.splice.apply(this._textDirs, args);
index += limit;
}
}
+ if (origText.indexOf(ltrMarker) == 0) {
+ this._textDirs[startLine] = "ltr"; //$NON-NLS-0$
+ } else if (origText.indexOf(rtlMarker) == 0) {
+ this._textDirs[startLine] = "rtl"; //$NON-NLS-0$
+ }
+
var offset = 0, chunk = 0, length;
while (chunk end) { return; }
if (ranges) {
for (var i = 0; i < ranges.length; i++) {
@@ -805,7 +806,7 @@ define("orion/editor/textView", [ //$NON-NLS-1$
styleStart = Math.max(start, styleStart);
styleEnd = Math.min(end, styleEnd);
if (start < styleStart) {
- this._createRange(text, start, styleStart, null, data);
+ this._createRange(text, start, styleStart, null, textDir, data);
}
if (!range.style || !range.style.unmergeable) {
while (i + 1 < ranges.length && ranges[i + 1].start - lineStart === styleEnd && compare(range.style, ranges[i + 1].style)) {
@@ -814,16 +815,16 @@ define("orion/editor/textView", [ //$NON-NLS-1$
i++;
}
}
- this._createRange(text, styleStart, styleEnd, range.style, data);
+ this._createRange(text, styleStart, styleEnd, range.style, textDir, data);
start = styleEnd;
}
}
}
if (start < end) {
- this._createRange(text, start, end, null, data);
+ this._createRange(text, start, end, null, textDir, data);
}
},
- _createRange: function(text, start, end, style, data) {
+ _createRange: function(text, start, end, style, textDir, data) {
if (start > end) { return; }
var tabSize = this.view._customTabSize, range;
var bidiStyle = {tagName:"span", bidi:true, style:{unicodeBidi:"embed", direction:"ltr"}};
@@ -833,9 +834,9 @@ define("orion/editor/textView", [ //$NON-NLS-1$
while (tabIndex !== -1 && tabIndex < end) {
if (start < tabIndex) {
range = {text: text.substring(start, tabIndex), style: style};
- range = bidiUtils.enforceTextDir(range);
+ range = bidiUtils.enforceTextDir(range, textDir);
data.ranges.push(range);
- if (bidiUtils.isBidiEnabled()) {
+ if (bidiUtils.isBidiEnabled() && !this.view._isMarkdown) {
data.ranges.push(bidiRange);
}
data.tabOffset += range.text.length;
@@ -849,7 +850,7 @@ define("orion/editor/textView", [ //$NON-NLS-1$
}
range = {text: spaces, style: style, ignoreChars: spacesCount - 1};
data.ranges.push(range);
- if (bidiUtils.isBidiEnabled()) {
+ if (bidiUtils.isBidiEnabled() && !this.view._isMarkdown) {
data.ranges.push(bidiRange);
}
data.tabOffset += range.text.length;
@@ -863,9 +864,9 @@ define("orion/editor/textView", [ //$NON-NLS-1$
}
if (start <= end) {
range = {text: text.substring(start, end), style: style};
- range = bidiUtils.enforceTextDir(range);
+ range = bidiUtils.enforceTextDir(range, textDir);
data.ranges.push(range);
- if (bidiUtils.isBidiEnabled()) {
+ if (bidiUtils.isBidiEnabled() && !this.view._isMarkdown) {
data.ranges.push(bidiRange);
}
data.tabOffset += range.text.length;
@@ -5338,6 +5339,27 @@ define("orion/editor/textView", [ //$NON-NLS-1$
this.setOptions({wrapMode: !this.getOptions("wrapMode")}); //$NON-NLS-1$
return true;
},
+ _doSetDirection: function (args) {
+ if (!this._isMarkdown) { return; }
+ var model = this._model;
+ var selections = this._getSelections();
+ var marker = ( args.type === "ltr" ? util.LtrMarker : util.RtlMarker );
+ var that = this;
+ selections.forEach(function(current) {
+ var startLineIndex = model.getLineAtOffset(current.start);
+ var endLineIndex = model.getLineAtOffset(current.end);
+ for (var i = startLineIndex; i<=endLineIndex; i++) {
+ var startLine = model.getLineStart(i);
+ var selection = [current];
+ selection[0].start = selection[0].end = startLine;
+ that._modifyContent({text: marker, selection: selection, _ignoreDOMSelection: true}, true);
+ selection[0].start = startLine;
+ selection[0].end = startLine + marker.length;
+ that._modifyContent({text: "", selection: selection, _ignoreDOMSelection: true}, true); //$NON-NLS-1$
+ }
+ });
+ return true;
+ },
/************************************ Internals ******************************************/
_autoScroll: function () {
@@ -5661,7 +5683,9 @@ define("orion/editor/textView", [ //$NON-NLS-1$
"toggleOverwriteMode": {defaultHandler: function(data) {return that._doOverwriteMode(merge(data,{}));}, actionDescription: {name: messages.toggleOverwriteMode}}, //$NON-NLS-1$
"toggleTabMode": {defaultHandler: function(data) {return that._doTabMode(merge(data,{}));}, actionDescription: {name: messages.toggleTabMode}}, //$NON-NLS-1$
- "toggleWrapMode": {defaultHandler: function(data) {return that._doWrapMode(merge(data,{}));}, actionDescription: {name: messages.toggleWrapMode}} //$NON-NLS-1$
+ "toggleWrapMode": {defaultHandler: function(data) {return that._doWrapMode(merge(data,{}));}, actionDescription: {name: messages.toggleWrapMode}}, //$NON-NLS-1$
+ "dirLTR": {defaultHandler: function(data) {return that._doSetDirection(merge(data,{type: "ltr"}));}, actionDescription: {name: messages.dirLTR}}, //$NON-NLS-1$
+ "dirRTL": {defaultHandler: function(data) {return that._doSetDirection(merge(data,{type: "rtl"}));}, actionDescription: {name: messages.dirRTL}}, //$NON-NLS-1$
};
},
_createRulerParent: function(doc, className) {
@@ -6496,6 +6520,7 @@ define("orion/editor/textView", [ //$NON-NLS-1$
this._dragOffset = -1;
this._isRangeRects = (!util.isIE || util.isIE >= 9) && typeof _parent.ownerDocument.createRange().getBoundingClientRect === "function"; //$NON-NLS-1$
this._isW3CEvents = _parent.addEventListener;
+ this._isMarkdown = (_parent.baseURI.indexOf("editor=orion.editor.markdown") != -1);
/* Auto scroll */
this._autoScrollX = null;
diff --git a/bundles/org.eclipse.orion.client.ui/web/orion/inputManager.js b/bundles/org.eclipse.orion.client.ui/web/orion/inputManager.js
index 693cd0b001..6a696ba825 100644
--- a/bundles/org.eclipse.orion.client.ui/web/orion/inputManager.js
+++ b/bundles/org.eclipse.orion.client.ui/web/orion/inputManager.js
@@ -20,8 +20,9 @@ define([
'orion/objects',
'orion/PageUtil',
'orion/editor/textModelFactory',
- 'orion/metrics'
-], function(messages, mNavigatorRenderer, i18nUtil, Deferred, EventTarget, objects, PageUtil, mTextModelFactory, mMetrics) {
+ 'orion/metrics',
+ 'orion/util'
+], function(messages, mNavigatorRenderer, i18nUtil, Deferred, EventTarget, objects, PageUtil, mTextModelFactory, mMetrics, util) {
function Idle(options){
this._document = options.document || document;
@@ -399,8 +400,9 @@ define([
function _save(that) {
editor.markClean();
- var contents = editor.getText();
+ var contents = editor.getModel().getTextForSave();
var data = contents;
+ var hasTextDir = (data.indexOf(util.LtrMarker) != -1 || data.indexOf(util.RtlMarker) != -1);
if (that._getSaveDiffsEnabled() && !that._errorSaving) {
var changes = that._getUnsavedChanges();
if (changes) {
@@ -408,7 +410,7 @@ define([
for (var i = 0; i < changes.length; i++) {
len += changes[i].text.length;
}
- if (contents.length > len) {
+ if (contents.length > len && !hasTextDir) {
data = {
diff: changes
};
diff --git a/bundles/org.eclipse.orion.client.ui/web/orion/markdownEditor.js b/bundles/org.eclipse.orion.client.ui/web/orion/markdownEditor.js
index 1b4ab3e94e..7e45745095 100644
--- a/bundles/org.eclipse.orion.client.ui/web/orion/markdownEditor.js
+++ b/bundles/org.eclipse.orion.client.ui/web/orion/markdownEditor.js
@@ -791,6 +791,25 @@ define([
this._computeAnnotations(annotationModel, baseModel, current, start, end, _add);
}.bind(this));
},
+ _enforceTextDir: function(block, tokens) {
+ var lineIndex = this.model.getLineAtOffset(block.start);
+ var LRE = "\u202A"; //$NON-NLS-0$
+ var RLE = "\u202B"; //$NON-NLS-0$
+ var textDir = this.model.getLineTextDir(lineIndex);
+ var charToAdd = "";
+ if (textDir === "ltr") {
+ charToAdd = LRE;
+ } else if (textDir === "rtl") {
+ charToAdd = RLE;
+ }
+ if (charToAdd.length > 0) {
+ tokens.forEach(function(current) {
+ if (current.text) {
+ current.text = charToAdd + current.text;
+ }
+ });
+ }
+ },
_generateHTML: function(rootElement, block) {
if (!block.startToken && !block.tokens) {
return; /* likely a block with only tokens for its parent */
@@ -806,6 +825,7 @@ define([
* removes each token from the array as it is processed
*/
var parseTokens = block.tokens.slice();
+ this._enforceTextDir(block, parseTokens);
parseTokens.links = this._linkDefs;
rootElement.innerHTML = marked.Parser.parse(parseTokens, markedOptions);
return;
@@ -818,6 +838,7 @@ define([
/* a container block (a list, list item or blockquote) */
parseTokens = [block.startToken, block.endToken];
+ this._enforceTextDir(block, parseTokens);
parseTokens.links = this._linkDefs;
rootElement.innerHTML = marked.Parser.parse(parseTokens, markedOptions);
@@ -827,6 +848,7 @@ define([
* context to marked) and the tokens being contributed from child blocks.
*/
parseTokens = [block.startToken].concat(parentTokens).concat(block.endToken);
+ this._enforceTextDir(block, parseTokens);
parseTokens.links = this._linkDefs;
var tempElement = document.createElement("div"); //$NON-NLS-0$
tempElement.innerHTML = marked.Parser.parse(parseTokens, markedOptions);