如何让 CodeMirror 与 TextArea 同步,以便两者的光标位置、选择和数据保持相同?
我将 CodeMirror 与 MobWrite 结合使用。 CodeMirror 仅使用 textArea 来读取输入,而 MobWrite 可以处理给定 TextArea 上的选择等,问题是让 CodeMirror 与 TextArea 同步。
最佳答案
扩展了 ModWrite 代码以支持 CodeMirror & 它工作起来就像一个魅力。
它也可以在 GitHub 上找到.详细信息 CodeMirror Group .
下面为所有可能走这条路的人转储的代码:
// CODEMIRROR INPUTS : Aped from Neil's code
/*
Set a 'id' property for your codeMirror obj
NoteToSelf: mobwrite.shareCodeMirrorObj.prototype.captureCursor_ - implement hasFocus
import linech2n func
import this code after you import MobWrite.
*/
/**
* Constructor of shared object representing a CodeMirror obj
*/
mobwrite.shareCodeMirrorObj = function(cmObj) {
// Call our prototype's constructor.
if(!"id" in cmObj) cmObj.id="TEMP";
mobwrite.shareObj.apply(this, [cmObj.id]);
this.element = cmObj;
};
// The textarea shared object's parent is a shareObj.
mobwrite.shareCodeMirrorObj.prototype = new mobwrite.shareObj('');
/**
* Retrieve the user's text.
* @return {string} Plaintext content.
*/
mobwrite.shareCodeMirrorObj.prototype.getClientText = function() {
var text = mobwrite.shareCodeMirrorObj.normalizeLinebreaks_(this.element.getValue());
return text;
};
/**
* Set the user's text.
* @param {string} text New text
*/
mobwrite.shareCodeMirrorObj.prototype.setClientText = function(text) {
this.element.setValue(text);
this.fireChange(this.element.getInputField());
};
/**
* Modify the user's plaintext by applying a series of patches against it.
* @param {Array.<patch_obj>} patches Array of Patch objects.
*/
mobwrite.shareCodeMirrorObj.prototype.patchClientText = function(patches) {
// Set some constants which tweak the matching behaviour.
// Maximum distance to search from expected location.
this.dmp.Match_Distance = 1000;
// At what point is no match declared (0.0 = perfection, 1.0 = very loose)
this.dmp.Match_Threshold = 0.6;
var oldClientText = this.getClientText();
var cursor = this.captureCursor_();
// Pack the cursor offsets into an array to be adjusted.
// See http://neil.fraser.name/writing/cursor/
var offsets = [];
if (cursor) {
offsets[0] = cursor.startOffset;
if ('endOffset' in cursor) {
offsets[1] = cursor.endOffset;
}
}
var newClientText = this.patch_apply_(patches, oldClientText, offsets);
// Set the new text only if there is a change to be made.
if (oldClientText != newClientText) {
this.setClientText(newClientText);
if (cursor) {
// Unpack the offset array.
cursor.startOffset = offsets[0];
if (offsets.length > 1) {
cursor.endOffset = offsets[1];
if (cursor.startOffset >= cursor.endOffset) {
cursor.collapsed = true;
}
}
this.restoreCursor_(cursor);
}
}
};
/**
* Merge a set of patches onto the text. Return a patched text.
* @param {Array.<patch_obj>} patches Array of patch objects.
* @param {string} text Old text.
* @param {Array.<number>} offsets Offset indices to adjust.
* @return {string} New text.
*/
mobwrite.shareCodeMirrorObj.prototype.patch_apply_ =
function(patches, text, offsets) {
if (patches.length == 0) {
return text;
}
// Deep copy the patches so that no changes are made to originals.
patches = this.dmp.patch_deepCopy(patches);
var nullPadding = this.dmp.patch_addPadding(patches);
text = nullPadding + text + nullPadding;
this.dmp.patch_splitMax(patches);
// delta keeps track of the offset between the expected and actual location
// of the previous patch. If there are patches expected at positions 10 and
// 20, but the first patch was found at 12, delta is 2 and the second patch
// has an effective expected position of 22.
var delta = 0;
for (var x = 0; x < patches.length; x++) {
var expected_loc = patches[x].start2 + delta;
var text1 = this.dmp.diff_text1(patches[x].diffs);
var start_loc;
var end_loc = -1;
if (text1.length > this.dmp.Match_MaxBits) {
// patch_splitMax will only provide an oversized pattern in the case of
// a monster delete.
start_loc = this.dmp.match_main(text,
text1.substring(0, this.dmp.Match_MaxBits), expected_loc);
if (start_loc != -1) {
end_loc = this.dmp.match_main(text,
text1.substring(text1.length - this.dmp.Match_MaxBits),
expected_loc + text1.length - this.dmp.Match_MaxBits);
if (end_loc == -1 || start_loc >= end_loc) {
// Can't find valid trailing context. Drop this patch.
start_loc = -1;
}
}
} else {
start_loc = this.dmp.match_main(text, text1, expected_loc);
}
if (start_loc == -1) {
// No match found. :(
if (mobwrite.debug) {
window.console.warn('Patch failed: ' + patches[x]);
}
// Subtract the delta for this failed patch from subsequent patches.
delta -= patches[x].length2 - patches[x].length1;
} else {
// Found a match. :)
if (mobwrite.debug) {
window.console.info('Patch OK.');
}
delta = start_loc - expected_loc;
var text2;
if (end_loc == -1) {
text2 = text.substring(start_loc, start_loc + text1.length);
} else {
text2 = text.substring(start_loc, end_loc + this.dmp.Match_MaxBits);
}
// Run a diff to get a framework of equivalent indices.
var diffs = this.dmp.diff_main(text1, text2, false);
if (text1.length > this.dmp.Match_MaxBits &&
this.dmp.diff_levenshtein(diffs) / text1.length >
this.dmp.Patch_DeleteThreshold) {
// The end points match, but the content is unacceptably bad.
if (mobwrite.debug) {
window.console.warn('Patch contents mismatch: ' + patches[x]);
}
} else {
var index1 = 0;
var index2;
for (var y = 0; y < patches[x].diffs.length; y++) {
var mod = patches[x].diffs[y];
if (mod[0] !== DIFF_EQUAL) {
index2 = this.dmp.diff_xIndex(diffs, index1);
}
if (mod[0] === DIFF_INSERT) { // Insertion
text = text.substring(0, start_loc + index2) + mod[1] +
text.substring(start_loc + index2);
for (var i = 0; i < offsets.length; i++) {
if (offsets[i] + nullPadding.length > start_loc + index2) {
offsets[i] += mod[1].length;
}
}
} else if (mod[0] === DIFF_DELETE) { // Deletion
var del_start = start_loc + index2;
var del_end = start_loc + this.dmp.diff_xIndex(diffs,
index1 + mod[1].length);
text = text.substring(0, del_start) + text.substring(del_end);
for (var i = 0; i < offsets.length; i++) {
if (offsets[i] + nullPadding.length > del_start) {
if (offsets[i] + nullPadding.length < del_end) {
offsets[i] = del_start - nullPadding.length;
} else {
offsets[i] -= del_end - del_start;
}
}
}
}
if (mod[0] !== DIFF_DELETE) {
index1 += mod[1].length;
}
}
}
}
}
// Strip the padding off.
text = text.substring(nullPadding.length, text.length - nullPadding.length);
return text;
};
/**
* Record information regarding the current cursor.
* @return {Object?} Context information of the cursor.
* @private
*/
mobwrite.shareCodeMirrorObj.prototype.captureCursor_ = function() {
this.element.focus();//change to hasFocus()?Pass:return null;
var padLength = this.dmp.Match_MaxBits / 2; // Normally 16.
var text = this.element.getValue();
var cursor = {};
var selectionStart = linech2n(this.element, this.element.getCursor(true));
var selectionEnd = linech2n(this.element, this.element.getCursor(false));
cursor.startPrefix = text.substring(selectionStart - padLength, selectionStart);
cursor.startSuffix = text.substring(selectionStart, selectionStart + padLength);
cursor.startOffset = selectionStart;
cursor.collapsed = (selectionStart == selectionEnd);
if (!cursor.collapsed) {
cursor.endPrefix = text.substring(selectionEnd - padLength, selectionEnd);
cursor.endSuffix = text.substring(selectionEnd, selectionEnd + padLength);
cursor.endOffset = selectionEnd;
}
// Record scrollbar locations
if ('scrollTop' in this.element.getScrollerElement()) {
scroller = this.element.getScrollerElement();
cursor.scrollTop = scroller.scrollTop / scroller.scrollHeight;
cursor.scrollLeft = scroller.scrollLeft / scroller.scrollWidth;
}
// alert(cursor.startPrefix + '|' + cursor.startSuffix + ' ' +
// cursor.startOffset + '\n' + cursor.endPrefix + '|' +
// cursor.endSuffix + ' ' + cursor.endOffset + '\n' +
// cursor.scrollTop + ' x ' + cursor.scrollLeft);
return cursor;
};
/**
* Attempt to restore the cursor's location.
* @param {Object} cursor Context information of the cursor.
* @private
*/
mobwrite.shareCodeMirrorObj.prototype.restoreCursor_ = function(cursor) {
// Set some constants which tweak the matching behaviour.
// Maximum distance to search from expected location.
this.dmp.Match_Distance = 1000;
// At what point is no match declared (0.0 = perfection, 1.0 = very loose)
this.dmp.Match_Threshold = 0.9;
var padLength = this.dmp.Match_MaxBits / 2; // Normally 16.
var newText = this.element.getValue();
// Find the start of the selection in the new text.
var pattern1 = cursor.startPrefix + cursor.startSuffix;
var pattern2, diff;
var cursorStartPoint = this.dmp.match_main(newText, pattern1,
cursor.startOffset - padLength);
if (cursorStartPoint !== null) {
pattern2 = newText.substring(cursorStartPoint,
cursorStartPoint + pattern1.length);
//alert(pattern1 + '\nvs\n' + pattern2);
// Run a diff to get a framework of equivalent indicies.
diff = this.dmp.diff_main(pattern1, pattern2, false);
cursorStartPoint += this.dmp.diff_xIndex(diff, cursor.startPrefix.length);
}
var cursorEndPoint = null;
if (!cursor.collapsed) {
// Find the end of the selection in the new text.
pattern1 = cursor.endPrefix + cursor.endSuffix;
cursorEndPoint = this.dmp.match_main(newText, pattern1,
cursor.endOffset - padLength);
if (cursorEndPoint !== null) {
pattern2 = newText.substring(cursorEndPoint,
cursorEndPoint + pattern1.length);
//alert(pattern1 + '\nvs\n' + pattern2);
// Run a diff to get a framework of equivalent indicies.
diff = this.dmp.diff_main(pattern1, pattern2, false);
cursorEndPoint += this.dmp.diff_xIndex(diff, cursor.endPrefix.length);
}
}
// Deal with loose ends
if (cursorStartPoint === null && cursorEndPoint !== null) {
// Lost the start point of the selection, but we have the end point.
// Collapse to end point.
cursorStartPoint = cursorEndPoint;
} else if (cursorStartPoint === null && cursorEndPoint === null) {
// Lost both start and end points.
// Jump to the offset of start.
cursorStartPoint = cursor.startOffset;
}
if (cursorEndPoint === null) {
// End not known, collapse to start.
cursorEndPoint = cursorStartPoint;
}
// Restore selection.
this.element.setSelection(n2linech(this.element, cursorStartPoint), n2linech(this.element, cursorEndPoint));
// Restore scrollbar locations
if ('scrollTop' in cursor) {
this.element.getScrollerElement().scrollTop = cursor.scrollTop * this.element.getScrollerElement().scrollHeight;
this.element.getScrollerElement().scrollLeft = cursor.scrollLeft * this.element.getScrollerElement().scrollWidth;
}
};
/**
* Ensure that all linebreaks are LF
* @param {string} text Text with unknown line breaks
* @return {string} Text with normalized linebreaks
* @private
*/
mobwrite.shareCodeMirrorObj.normalizeLinebreaks_ = function(text) {
return text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
};
/**
* Handler to accept CodeMirror Objs
* @param {*} cmObj CodeMirror Object.
* @return {Object?} A sharing object or null.
*/
mobwrite.shareCodeMirrorObj.shareHandler = function(cmObj) {
if ('lineCount' in cmObj) {
return new mobwrite.shareCodeMirrorObj(cmObj);
}
return null;
};
// Register this shareHandler with MobWrite.
mobwrite.shareHandlers.push(mobwrite.shareCodeMirrorObj.shareHandler);
//functions for converting b/n index on the data string & the {line, ch} obj of codeMirror
function linech2n(ed, linech) {
var line = linech.line;
var ch = linech.ch;
var n = line + ch; //for the \n s & chars in the line
for(i=0;i<line;i++) {
n += (ed.getLine(i)).length;//for the chars in all preceeding lines
}
return n;
}
function n2linech(ed, n) {
var line=0, ch=0, index=0;
for(i=0;i<ed.lineCount();i++) {
len = (ed.getLine(i)).length;
if(n < index+len) {
//alert(len+","+index+","+(n-index));
line = i;
ch = n-index;
return {line:line, ch:ch};
}
len++;//for \n char
index += len;
}
return {line:line, ch:ch};
}
关于javascript - 让 CodeMirror 跟随一个 TextArea,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7499541/
使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta
我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何
我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>
如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象
关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?
我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案
我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b
我想让一个yaml对象引用另一个,如下所示:intro:"Hello,dearuser."registration:$introThanksforregistering!new_message:$introYouhaveanewmessage!上面的语法只是它如何工作的一个例子(这也是它在thiscpanmodule中的工作方式。)我正在使用标准的rubyyaml解析器。这可能吗? 最佳答案 一些yaml对象确实引用了其他对象:irb>require'yaml'#=>trueirb>str="hello"#=>"hello"ir
我的问题的一个例子是体育游戏。一场体育比赛有两支球队,一支主队和一支客队。我的事件记录模型如下:classTeam"Team"has_one:away_team,:class_name=>"Team"end我希望能够通过游戏访问一个团队,例如:Game.find(1).home_team但我收到一个单元化常量错误:Game::team。谁能告诉我我做错了什么?谢谢, 最佳答案 如果Gamehas_one:team那么Rails假设您的teams表有一个game_id列。不过,您想要的是games表有一个team_id列,在这种情况下
我在一个静态网站上工作(因此没有真正的服务器支持),我想在另一个网站中包含一个小的细长片段,可能会向它传递一个变量。这可能吗?在rails中很容易,虽然是render方法,但我不知道如何在slim上做(显然load方法不适用于slim)。 最佳答案 Slim包含Include插件,允许在编译时直接在模板文件中包含其他文件:require'slim/include'includepartial_name文档可在此处获得:https://github.com/slim-template/slim/blob/master/doc/incl