I've been working on a plugin to improve touch support for CM on phones and tablets, and I've just posted the code on Github: https://github.com/schanzer/CodeMirror
The first goal is to provide a selection interface (popup menu and selection corners), which is style-able to accommodate both iOS and Android. The second is to provide a style-able keyboard row, which sits on top of the normal touchscreen keyboard and provides access to commonly used characters that are not part of the default keyboard. This keyrow is also customizable, making it trivial to choose which keys you want. I can also call any JavaScript function (including those in CM), making it easy to add an undo or redo key to the keyboard.
Use of the plugin is simple: 1) include the lib/util/touch.js and lib/util/touch.css files 2) specify keys with a simple array of objects, each of which must contain a 'key' string (and optionally a 'fn' function) 2) after declaring your CM instance, just call editor.addKeyrow, passing in your key array.
Right now the plug-in targets CodeMirror 2, since our organization's software is stuck on v2 for the time being. My understanding is that it should work with minimal modification on v3, but I haven't had chance to look at the new DOM structure, so no guarantees there. I'd love to get feedback from people, and patches are especially welcome!
I've been wanting a better experience for touch users for my project (http://enjalot.com) and one thing I'm curious about since I haven't gone as deep as you have, is it possible to override the focus event to not bring up the OS's keyboard? I'd like to let the user touch around and select stuff without bringing up the keyboard under some cases. I don't know if this is part of CM, your plugin or the underlying native API, do you?
On Tuesday, October 9, 2012 11:52:29 AM UTC-7, Emmanuel Schanzer wrote:
> I've been working on a plugin to improve touch support for CM on phones > and tablets, and I've just posted the code on Github: > https://github.com/schanzer/CodeMirror
> The first goal is to provide a selection interface (popup menu and > selection corners), which is style-able to accommodate both iOS and > Android. The second is to provide a style-able keyboard row, which sits on > top of the normal touchscreen keyboard and provides access to commonly > used characters that are not part of the default keyboard. This keyrow is > also customizable, making it trivial to choose which keys you want. I can > also call any JavaScript function (including those in CM), making it easy > to add an undo or redo key to the keyboard.
> Use of the plugin is simple: > 1) include the lib/util/touch.js and lib/util/touch.css files > 2) specify keys with a simple array of objects, each of which must contain > a 'key' string (and optionally a 'fn' function) > 2) after declaring your CM instance, just call editor.addKeyrow, passing > in your key array.
> Right now the plug-in targets CodeMirror 2, since our organization's > software is stuck on v2 for the time being. My understanding is that it > should work with minimal modification on v3, but I haven't had chance to > look at the new DOM structure, so no guarantees there. I'd love to get > feedback from people, and patches are especially welcome!
Awesome! I don't have an iPad handy to test it, but I'm very glad
someone is working towards making CodeMirror behave properly on such
devices. Keep us updated.
> Update: several improvements, though it's becoming more iOS-specific as I
try to emulate more and more of the native features on iOS. Someone else could easily clone this and make an Android fork -- it's pretty easy to tell which parts are platform specific, and the code is in good, JSlinted-shape.
On iOS, text selection is done by having the user move selection corners, causing the selection to grow on contract. These corners are drawn on the text itself, meaning a finger placed on a corner will cover the cursor. To address this, Apple has a popup *magnifying glass*, which displays the area under the thumb as a zoomed-in region hovering just about the finger. It's incredibly slick, and it's been extremely difficult for me to copy.
I finally have a solution worked out: When a selection is made, I make a duplicate of the CM instance and DOM tree. This duplicate is then scaled up using CSS (faster than doing it in JS, and on iDevices it uses the GPU instead of the CPU for rendering). The clone is then hidden, and the magnifying glass is used as mask that "punches through" to the zoomed-in CM instance. The visual effect is a very good reproduction of what happens in iOS, and by calling cm.setSelection on both instances simultaneously I can even simulate the selection growing character-by-character in the "real" editor and in the magnifying glass.
My problem with the solution is that I have to clone the CM instance each time a selection is initiated, and I have to be very careful to copy every CM setting so the format and size of the clone matches perfectly. *Marijn, three questions for you:* 1) I currently use getTextArea to get the textarea from my CM instance, then fromTextArea to create the clone. Is there a way to also pass the clone the entireoptions object from the original instance? 2) I've been watching the refactoring of CM3, and the gradual shift towards event-based APIs. It looks like it might be possible to push this a little farther, to get a nearly-MVC style system in place. This would allow multiple CM "views" on the same code, and keep them in sync as the code is changed. Is this in the works already? If so, it would make my life enormously easy, since I could just keep a magnified view lying around in addition to the regular view. Since they'd always be in sync, I wouldn't need to work so hard to coordinate them, nor would I be constantly copying and then throwing away a huge DOM subtree. 3) I began this project as a CM extension, but that requires someone to call cm.addPopup(cm). It really shouldn't require anything to get started, provided the user has included the js file. Is there a way to have the code run automatically, thereby adding a bunch of nodes and events to the editor?
Thanks in advance for your help, and your amazing editor!
I really could use some other testers, since I only have an iPhone and that makes testing difficult. Is anyone out there able to help me out?
> 1) I currently use getTextArea to get the textarea from my CM instance, then
> fromTextArea to create the clone. Is there a way to also pass the clone the
> entireoptions object from the original instance?
Not really, though that could be added. What's more problematic is
that a set of options definitely do not reproduce the whole state of a
CodeMirror instance! There's text markers, folding, widgets, and so
on, that you won't be able to reproduce this way.
> 2) I've been watching the refactoring of CM3, and the gradual shift towards
> event-based APIs. It looks like it might be possible to push this a little
> farther, to get a nearly-MVC style system in place. This would allow
> multiple CM "views" on the same code, and keep them in sync as the code is
> changed.
That was the initial idea, but it doesn't work, because the data
structure for the code also holds a number of view-specific things,
such as line height, and splitting those out would be a major
reduction in efficiency. But I do plan, if I find the time or money,
to at some point build a thing where two documents are kept in sync.
If I add a way to clone a view, including options, markers, etcetera,
that would indeed help a lot for the thing you are building.
However, getting that to work would take quite some time, so, unless I
find a sponsor, it won't be happening anytime soon.
> 3) I began this project as a CM extension, but that requires someone to call
> cm.addPopup(cm). It really shouldn't require anything to get started,
> provided the user has included the js file. Is there a way to have the code
> run automatically, thereby adding a bunch of nodes and events to the editor?
The defineOption function in version 3 can be abused to do this. I've
been thinking about adding a way to add init hooks. It's probably
worthwhile.
> Not really, though that could be added. What's more problematic is > that a set of options definitely do not reproduce the whole state of a > CodeMirror instance! There's text markers, folding, widgets, and so > on, that you won't be able to reproduce this way.
Hrm -- I don't suppose there's wide appeal for a proper cm.clone() method, is there...? :)
> If I add a way to clone a view, including options, markers, etcetera,
that would indeed help a lot for the thing you are building.
> However, getting that to work would take quite some time, so, unless I > find a sponsor, it won't be happening anytime soon.
Understood. Bootstrap doesn't have a lot of money for things like this, but I would certainly contribute *something *out of pocket for something like this.
> The defineOption function in version 3 can be abused to do this. I've > been thinking about adding a way to add init hooks. It's probably > worthwhile.
That would be amazing! What can I do to be helpful here?
Sweet! It works like a charm. Just pushed to git, and updated the live demo. Hope to send you a pull request soon, after I (and hopefully others) have had a chance to bang on it a bit.
I tried it on my iPhone w/ Safari on iOS 6 and it works very well! The only thing I miss is a magnifying glass on cursor positioning, e.g. a single long tap so you can place the cursor exactly where you want it.
Interestingly, when using the Chrome app which uses WebKit just like Safari, a bunch of things failed like the positioning of the button bar and the magnifier not working.
On Tuesday, October 16, 2012 7:19:47 PM UTC+2, Emmanuel Schanzer wrote:
> Sweet! It works like a charm. Just pushed to git, and updated the live > demo. Hope to send you a pull request soon, after I (and hopefully others) > have had a chance to bang on it a bit.
> On Tuesday, October 16, 2012 10:03:24 AM UTC-4, Marijn Haverbeke wrote:
>> > That would be amazing! What can I do to be helpful here?
I'm surprised it's usable on the phone -- that's a much less forgiving platform than I was planning on!
It should be pretty trivial to bring up the magnifier during long-top. At the moment I'm trying to merge the code and DOM for the popup and magnifier, but once that's done I can start using it for cursor placement.
I haven't tested on Chrome yet -- that's going to be pretty much the last thing I do, since it's such a small target on iOS.
On Wednesday, October 17, 2012 4:36:00 AM UTC-4, Wout Mertens wrote:
> Awesome!
> I tried it on my iPhone w/ Safari on iOS 6 and it works very well! The > only thing I miss is a magnifying glass on cursor positioning, e.g. a > single long tap so you can place the cursor exactly where you want it.
> Interestingly, when using the Chrome app which uses WebKit just like > Safari, a bunch of things failed like the positioning of the button bar and > the magnifier not working.
First crack at the magnifying glass, which includes a lot of refactoring of code. It works, but there are a number of bugs relating to scrolling and focusing. At the moment, I'm [temporarily] hacking around the bug by showing you a selection in the magnifying glass, instead of the actual cursor. Check it out and tell me what you think?
*Marijn - * *is there any way to show the cursor on a non-focused CM?*Right now I am using setCursor() on the magnified CM instance, but it won't show unless I call focus(). Since iOS autoscrolls to a focused element, focus()ing on the magnified CM causes the window to shift, sending all my touch events haywire.
On Wednesday, October 17, 2012 4:36:00 AM UTC-4, Wout Mertens wrote:
> Awesome!
> I tried it on my iPhone w/ Safari on iOS 6 and it works very well! The > only thing I miss is a magnifying glass on cursor positioning, e.g. a > single long tap so you can place the cursor exactly where you want it.
> Interestingly, when using the Chrome app which uses WebKit just like > Safari, a bunch of things failed like the positioning of the button bar and > the magnifier not working.
> Wout.
> On Tuesday, October 16, 2012 7:19:47 PM UTC+2, Emmanuel Schanzer wrote:
>> Sweet! It works like a charm. Just pushed to git, and updated the live >> demo. Hope to send you a pull request soon, after I (and hopefully others) >> have had a chance to bang on it a bit.
>> On Tuesday, October 16, 2012 10:03:24 AM UTC-4, Marijn Haverbeke wrote:
>>> > That would be amazing! What can I do to be helpful here?
> Marijn - is there any way to show the cursor on a non-focused CM? Right now
> I am using setCursor() on the magnified CM instance, but it won't show
> unless I call focus(). Since iOS autoscrolls to a focused element,
> focus()ing on the magnified CM causes the window to shift, sending all my
> touch events haywire.
The hiding is done with CSS -- search codemirror.css for
CodeMirror-focused. Should be easy to override somehow.
*facepalm* Sorry to waste your time with that one.
Okay, fixed. The refactoring has things looking a bit messy (which I'll try to work on later this week), but the functionality is complete, the code is JSlinted, and it's ready to be tested. Marijn, what are some things you need to see happen before I send a pull request?
On Tuesday, October 23, 2012 10:16:49 AM UTC-4, Marijn Haverbeke wrote:
> > Marijn - is there any way to show the cursor on a non-focused CM? Right > now > > I am using setCursor() on the magnified CM instance, but it won't show > > unless I call focus(). Since iOS autoscrolls to a focused element, > > focus()ing on the magnified CM causes the window to shift, sending all > my > > touch events haywire.
> The hiding is done with CSS -- search codemirror.css for > CodeMirror-focused. Should be easy to override somehow.
During my refactoring, I've reached a point where I need an event handler to remove itself once it's executed. This looks easy to do in CM3: just have the function include an off() statement.
Unfortunately, I'm stuck in CM2 for the foreseeable future. Is there any way to do this without upgrading?
> Unfortunately, I'm stuck in CM2 for the foreseeable future. Is there any way
> to do this without upgrading?
instance.setOption("onChange", null)
(Assuming no other components are registering onChange (or
onCursorActivity, or whatever) handlers)
As for including it in the distribution -- I'm not sure. This seems
like it'd be big and fragile enough to warrant its own add-on project.
People tend to expect *me* to maintain the stuff in the distribution,
so I'm cautious about adding big or complicated components.
That doesn't seem to work on my end - let me give you some more context:
I'm trying to programmatically set event handlers on certain DOM nodes when a GUI element is visible, which then hide that element onTouchEnd (all done through CodeMirror.connect() ). Once touchEnd happens, the element should disappear -- but subsequent touchEnd events shouldn't trigger the same code.
I understand about including it -- I certainly don't want you having to maintain any of my code! I'd just like to get this out there to as many people as possible, once it's fully debugged.
On Wednesday, October 24, 2012 8:41:50 AM UTC-4, Marijn Haverbeke wrote:
> > Unfortunately, I'm stuck in CM2 for the foreseeable future. Is there any > way > > to do this without upgrading?
> instance.setOption("onChange", null)
> (Assuming no other components are registering onChange (or > onCursorActivity, or whatever) handlers)
> As for including it in the distribution -- I'm not sure. This seems > like it'd be big and fragile enough to warrant its own add-on project. > People tend to expect *me* to maintain the stuff in the distribution, > so I'm cautious about adding big or complicated components.
> That doesn't seem to work on my end - let me give you some more context:
Are you talking about DOM events? Version 2.x allows you to add a
fourth argument to connect, which causes it to return a closure that
can be used to unregister the handler.
On Wednesday, October 24, 2012 8:52:53 AM UTC-4, Marijn Haverbeke wrote:
> > That doesn't seem to work on my end - let me give you some more context:
> Are you talking about DOM events? Version 2.x allows you to add a > fourth argument to connect, which causes it to return a closure that > can be used to unregister the handler.
Fantastic -- I didn't realize that would be in scope! Thanks so much.
The only feature I have left to emulate is iOS's auto-scroll for setting the selection, which works pretty much the same as CodeMirror does for key and mouse events. It looks like there's some internal logic to do this for selection that's set by keys (shiftSelection) or mouse dragging, but nothing that's exposed externally. I could try to re-create this events programmatically and send them to CM via getScrollerElement().dispatchEvent(), but that's probably more brittle than just rewriting the same logic in the add-on.
I don't suppose there's a way to specify which end of the selection should be in view during setSelection(), is there? Internally, I see setCursor() being used in conjunction with moveV and moveH (which handle scrolling beautifully), but I haven't been able to figure out how that intersects with setSelection().
> I don't suppose there's a way to specify which end of the selection should
> be in view during setSelection(), is there? Internally, I see setCursor()
> being used in conjunction with moveV and moveH (which handle scrolling
> beautifully), but I haven't been able to figure out how that intersects with
> setSelection().
I've added a scrollIntoView method, which you can call without
arguments to get the behavior you want.
On Thursday, October 25, 2012 5:16:34 AM UTC-4, Marijn Haverbeke wrote:
> Hi Emmanuel,
> > I don't suppose there's a way to specify which end of the selection > should > > be in view during setSelection(), is there? Internally, I see > setCursor() > > being used in conjunction with moveV and moveH (which handle scrolling > > beautifully), but I haven't been able to figure out how that intersects > with > > setSelection().
> I've added a scrollIntoView method, which you can call without > arguments to get the behavior you want.
Updates: 1) cleaned up touch event handling, to properly detect tap, tap-and-hold, and double-tap (apple's built-in solutions don't mix well with regular touch events). 2) more responsive (as a result of #1) 3) Double-tapping a word now autoselects...just like native iOS textareas 4) Added key sound
Thanks to those of you who've emailed me with bug reports. It looks like I'm running into a lot of issues on iOS5, whereas 6 works pretty solidly. Please keep banging on the code - I want to make this as useable as possible!