codemirror / codemirror5

In-browser code editor (version 5, legacy)
http://codemirror.net/5/
MIT License
26.82k stars 4.97k forks source link

Capslock on iOS #3403

Open D-32 opened 9 years ago

D-32 commented 9 years ago

When focusing onto an empty line on an iOS device inside WKWebview or Safari the keyboard switches to all caps mode. This can be reproduced easily in the example on http://codemirror.net. Just tap somewhere next to the last line, the cursor will focus as seen on the attached screenshot and the keyboard goes into all caps mode.

img_0048

marijnh commented 9 years ago

It goes into 'capitalize the next letter' mode, which is what mobile keyboards tend to do when at the start of what looks like a sentence. I don't think there is a way for CodeMirror to suppress this, but if you do know of one, tell me.

D-32 commented 9 years ago

That would make sense, but it stays in caps lock. So if I type A the next character is still upper case. Which is annoying, as the user manually has to get out of that mode. This surely isn't an intended behavior.

marijnh commented 9 years ago

That is indeed odd. I'll see if I can figure out what is happening the next time I have my iPad in my hand.

D-32 commented 9 years ago

Thanks :)

boucher commented 9 years ago

Seems related to this bug filed against WebKit: https://bugs.webkit.org/show_bug.cgi?id=148503

boucher commented 9 years ago

Switching to inputStyle: "textarea" resolves this issue, but produces the issue where the native cursor is displayed in addition to the codemirror cursor. I might be willing to live with that by disabling the codemirror cursor, but perhaps there are other problems I haven't discovered yet since textarea is not the default on mobile?

edit: at least one thing that stops working correctly in mobile with "textarea" mode is text selection.

denisoby commented 8 years ago

I've met same issue. The problem is that if e.preventDefault is called - then iOS will skip internal callbacks and will NOT switch case to lower.

For CM4 I've made a workaround for CM keypress event like this

    onKeyPress: function(e) {
      if (capitalizationHack.isPreventDefaultNeeded(e)){ // if uppercase -> not needed
        e.preventDefault();
      }
      operation(....)
    },

And after this - we have two events of adding letter. The extra letter should be removed

  if (cm.capitalizationHack.isHackEnabled()) {
    CodeMirror.on(cm.doc, 'change', function (instance, change) {
      var capHack = this.editor.capitalizationHack;
        if (capHack.isDuplicateChange() && (change.text.length == 1)) {
          console.log("Line BF: " + cm.getLine(change.from.line));

          var realFrom = {
            line: change.from.line,
            ch: change.from.ch - 1
          };
          var realTo = {
            line: change.from.line,
            ch: change.from.ch + 1
          };
          cm.replaceRange(change.text[0], realFrom, realTo);

          console.log("Line AF: " + cm.getLine(change.from.line));
     }
    }.bind(this));
  }

This is definitely ugly hack. But first seems to be working ok for user. I hope that CM authors will think how to process this situation in a more beautiful way.

If necessary - I can provide full code of workaround. Most likely in CM5 - the situation is the same.

@boucher @marijnh @D-32 FYI

sassywebgal commented 8 years ago

@denis-aes Would it be possible for you to provide the full code workaround for this issue? This has become a very problematic bug for a current project and we're attempting to patch. Any further code snippets/file would be much appreciated.

denisoby commented 8 years ago

@sassywebgal it has changed a little. We removed preventing. But now always remove duplicate symbols:

Main logic in codemirror.js

var isSafariOrWebview = /(iPhone|iPod|iPad).*AppleWebKit/i.test(navigator.userAgent);

  if (_isCapsHackEnabled) {
    var capitalizationHack = (new function capitalizationHack() {
      var changeNum = 0;
      var eventPressed;

      this.isHackEnabled = function () {return _isCapsHackEnabled;};

      this.processKeyUp = function () {
        changeNum = 0;
        eventPressed = false;
      };

      this.isDuplicateChange = function () {
        if (eventPressed) {
          changeNum++;
          return changeNum == 2;
        }
      };
    }());
  }
  else{
    var capitalizationHack = {
      isHackEnabled: function () {return _isCapsHackEnabled;},
      processKeyUp: function () {},
      isDuplicateChange: function () {},
      isPreventDefaultNeeded: function () {return true}
    }
  }

Also you should fix onKeyUp:

  function onKeyUp(e) {
    if (e.keyCode == 16) this.doc.sel.shift = false;
    capitalizationHack.processKeyUp(e); //this line added
    signalDOMEvent(this, e);
  }

And in client code, where you create CodeMirror instance, do:

    CodeMirror.on(cm.doc, 'beforeChange', function(instance, e) {

        if (cm.capitalizationHack && cm.capitalizationHack.isHackEnabled()) {
            var capHack = this.editor.capitalizationHack;

            if (capHack.isDuplicateChange() && (e.text.length == 1)) {
                e.update({
                    ch: e.from.ch - 1,
                    line: e.from.line
                },{
                    ch: e.from.ch,
                    line: e.from.line
                },'');
            }
        }
    });

Try it. I hope will help.

facundomedica commented 8 years ago

@denis-aes do you have any CodeMirror.js for me to take a look? I can't get it to work. Thanks!

louisatome commented 7 years ago

I have the same problem, i didn't find how to make the @denis-aes's patch work but I find another ugly way by re-focus the editor after the user type the first character on a line :

editor.on('change', function(instance, change) {
  var line = editor.getLine(change.from.line);
  if(line.length === 1 && change.text.length === 1 && (/^[A-Z]$/).test(change.text[0])){
    var focused = $(':focus');
    focused.blur();
    focused.focus();
  }
});
marijnh commented 7 years ago

See also this ProseMirror issue for context. There we ended up half-fixing it by letting native edit events go through, and patching up the DOM after the fact. We might also eventually want to do something like that in CodeMirror, but I'm not sure when I'll have time for it. Also, this still won't solve the problem of the keyboard case being stuck when programatically moving the cursor. And the blur/focus hack is too slow to apply it every time it'd be needed.

In the end, this is a really stupid bug in Mobile Safari, and because Apple doesn't appear to have an open bug tracker for stuff like this, it's rather hard to figure out whether they are aware of the issue and intend to look into it. If you have an Apple account and some time on your hands, firing more bug reports their way might help: https://developer.apple.com/bug-reporting/ (the precise issue, which can easily be reproduced outside of CodeMirror, is that if you change the DOM selection with JavaScript, as opposed to the user doing it directly, that doesn't cause the keyboard state -- such as default case -- to update)

firasdib commented 6 years ago

This seems fixed now?

seidtgeist commented 6 years ago

Does someone who participated in this thread have a stable solution that works with the latest version of CodeMirror and that they can recommend?

adrianheine commented 6 years ago

We are working on a rewrite (CodeMirror 6) that will probably address this issue, and we are currently raising money for this work: See the announcement for more information about the rewrite and a demo.

Note that CodeMirror 6 is by no means stable or usable in production, yet. It's highly unlikely that we pick up this issue for CodeMirror 5, though.

marijnh commented 6 years ago

Note that, unless this was fixed in the meantime, Mobile Safari is pretty awful about syncing up the automatic capitalization with programmatic selection updates—it tends to leave that in the state it was at the last user-driven cursor change or user input. So this'll work much better in version 6 than it does now (tapping or typing gives the expected result), but if yours scripts move the cursor somewhere, there might still be flakiness.

Jetski5822 commented 6 years ago

Yeah - been using this recently, and the ios experience is not great

adrianheine commented 6 years ago

Just to clarify: You tried out the CodeMirror 6 demo and found issues under iOS?

Jetski5822 commented 6 years ago

Darn no - version 5. Is the dsl of 5 wildly different in 6?

adrianheine commented 6 years ago

CodeMirror 6 is a complete rewrite, and it's currently under development. You can have a look at the announcement with embedded demo, and I would like to know how it works in iOS :)

kimbtech commented 6 years ago

Just checked the Demo on my iPhone and it seems to work fine.

seidtgeist commented 6 years ago

For the maintainers, might it make sense to have a label and/or title/description blurb to mark issues that are addressed by version 6 to improve clarity?

Edit: Just noticed the “Fixed in rewrite (hopefully)” milestone 🙏

ashvin777 commented 5 years ago

[SOLVED]

I have fixed this issue in one of my project. We just need to set autocapitalize to off for the input field which is used by codemirror. Here is how it can be done -

let editor = CodeMirror($el, { options });
let $input = editor.getInputField();
$input.setAttribute('autocapitalize', 'off');

If you want to turn off auto correct-

$input.setAttribute('autocorrect', 'off');

Enjoy !! 🥂

marijnh commented 5 years ago

Oh, wow, CodeMirror was setting these to "false", which is apparently ignored because the attribute expects "off" (contrary to spellcheck or contenteditable, which do use "false" 🙄).

Attached patch fixes this.

jackycute commented 5 years ago

I think this issue can be closed as well? https://github.com/codemirror/CodeMirror/issues/4865

bushev commented 3 years ago

Hey guys, how to apply this fix to version 6?

marijnh commented 3 years ago

An extension like EditorView.contentAttributes.of({autocapitalize: "off", autocorrect: "off"}) should work I think.