Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setting for using spaces instead of tabs #988

Closed
Gozala opened this issue Nov 21, 2012 · 35 comments
Closed

Setting for using spaces instead of tabs #988

Gozala opened this issue Nov 21, 2012 · 35 comments

Comments

@Gozala
Copy link
Contributor

Gozala commented Nov 21, 2012

I may be missing something but I'm not able to detect a way to configure codemirror such that it would use spaces for indentation when a tabs button is used. Maybe there is a way to do it via custom keys but even so it's common enough case (since all editors have such option) to have a dedicated option for that.

@marijnh
Copy link
Member

marijnh commented Nov 21, 2012

Rebind the Tab key to something like function(cm){cm.replaceSelection(" ");}, it's trivially easy -- http://codemirror.net/doc/manual.html#keymaps

@marijnh marijnh closed this as completed Nov 21, 2012
@Gozala
Copy link
Contributor Author

Gozala commented Nov 21, 2012

Do you mind having an option like indentWithSpaces or alike to do:

function(cm){ cm.replaceSelection(Array(cm.getOption('tabSize')).join(" "); }

Also to be honest I expected indentWithTabs: false to do exactly that

@marijnh
Copy link
Member

marijnh commented Nov 21, 2012

No. I went down the road of building in N indentation models in CM version 1. It was a mess and still didn't cover everything people wanted. Just use the building blocks provided to do what you want to do.

espadrine added a commit to espadrine/CodeMirror that referenced this issue Mar 3, 2013
Issues codemirror#988, codemirror#742, codemirror#612, etc. show that CodeMirror users tend to:

1. Not find how to make the Tab key insert spaces instead of a tab,
2. Miss how to use extraKeys to easily solve the issue.

Adding an example helps them find a solution immediately.
marijnh pushed a commit that referenced this issue Mar 4, 2013
Issues #988, #742, #612, etc. show that CodeMirror users tend to:

1. Not find how to make the Tab key insert spaces instead of a tab,
2. Miss how to use extraKeys to easily solve the issue.

Adding an example helps them find a solution immediately.
@elygre
Copy link

elygre commented Mar 14, 2013

When using the "cm.replaceSelection"-solution, it is no longer possible to indent code by marking multiple lines and pressing "Tab". Is there a workaround or code sample that shows how to keep that (very useful) functionality, while still enabling insertion to be spaces instead of a tab?

@espadrine
Copy link
Contributor

@elygre Sure there is! A bit of fiddling with the API:

function betterTab(cm) {
  if (cm.somethingSelected()) {
    cm.indentSelection("add");
  } else {
    cm.replaceSelection(cm.getOption("indentWithTabs")? "\t":
      Array(cm.getOption("indentUnit") + 1).join(" "), "end", "+input");
  }
}

CodeMirror.fromTextArea(document.getElementById("the-editor"), {
  extraKeys: { Tab: betterTab }
});

@elygre
Copy link

elygre commented Mar 14, 2013

Brilliant. This should probably go in the documentation, instead of the version that doesn't handle this scenario.

Thanks.

@Enygma2002
Copy link

Thumbs up for including that in the docs. I`ve lost some time looking for the way to do it right. A simpler, more user friendly, method wouldn't hurt either.

@briangonzalez
Copy link

@espadrine

You're a lifesaver, sir. Thank you!

@anodos
Copy link

anodos commented Nov 1, 2013

Spaces are mandated at our organization. Tabs are not consistent enough across editors and terminals and we got tired of having to reconfigure every editor and terminal in order to display source correctly. It's pretty annoying that CodeMirror doesn't have a simple way to do this. The method in the docs is incomplete, and @espadrine provides a partial solution above.
I say partial because all the solutions given so far have a bug: say your indent is set to four spaces. If you are in column 2 then pressing TAB should add enough spaces to take you to column 4, but instead TAB always inserts 4 spaces no matter what column you are on.
So now we have to "patch" this manual solution. Making the "use the existing tools approach" even more complex and error prone.
Is it really that difficult for CodeMirror to include this option out of the box? It seems to me that CodeMirror has had no difficulty solving much more difficult problems than this.

@marijnh
Copy link
Member

marijnh commented Nov 3, 2013

@anodos I do not believe there is a "bug" in the above solutions, it is just that they implement something that is not what you want. Since you didn't bother to explain what exactly it is that you want, I can't really comment on how to do it. Maybe binding to the "indentAuto" command is what you are trying to do?

@anodos
Copy link

anodos commented Nov 3, 2013

I did bother to explain exactly what I want, so I will quote myself, "If you are in column 2 then pressing TAB should add enough spaces to take you to column 4, but instead [with the examples given so far] TAB always inserts 4 spaces no matter what column you are on."
Since this wasn't clear enough, I'll translate a little. Tabs don't blindly advance you 4 spaces (in this example), they always take you to the column that is either the next tab stop or the next multiple of the tab size. In all the examples given so far 'indentUnit' # of spaces are inserted no matter what column your cursor is currently on. If I am positioned on column 2, then I expect two spaces to be inserted to take me to the next multiple of 4. If I am on column 4 then I expect four spaces to be inserted to take me to the next multiple of 4. And so on.
I have several other code editors and all of them support using spaces instead of tabs and all of them simulate tabs with spaces by inserting just enough spaces to take you from your current position to the next tab stop. This is standard behavior for code editors when using spaces instead of tabs.

@marijnh
Copy link
Member

marijnh commented Nov 3, 2013

  1. No, that sentence did not describe exactly what you were trying to do.
  2. I've never used a code editor where this is the default behavior of tab.
  3. But you could easily implement it in CodeMirror, if you'd take a moment to read the docs.
  4. (And no, I'm not going to do it for you. I might have, if your tone didn't ooze entitlement the way it does.)

@anodos
Copy link

anodos commented Nov 3, 2013

I think what we must have here is a failure to communicate. You are sensing a tone of entitlement, so I can see you are misunderstanding me, since I meant no such tone. It is also very possible at this point that you won't try to understand what I am saying. But I will give it one last go.
Since my attempt at explaining isn't working, maybe an actual working demo will do the trick.
I have handy IntelliJ IDEA, Visual Studio, WebStorm, Eclipse, and some other editors. They all have options to use spaces instead of tabs, and all work as I've described. As a working demo I'll using WebStorm, since I have it open right now.
WebStorm has a checkbox on its "Code Style -> JavaScript" "Settings" panel for "Use tab character". By unchecking "Use tab character" WebStorm will use spaces instead of tabs. You can set up a tab size and indent size right below the check box. For this demo, set the tab and indent sizes to 4 and make sure "Use tab character" is unchecked. Now, go into a blank .js file and press tab. You'll see it has inserted 4 spaces. Type something that is two characters long, say, "Hi". Now press tab again. WebStorm will insert 2 spaces, which now positions your cursor on column 9. If you type a single character and press tab, WebStorm will insert 3 spaces.
This demo should show what I'm trying to communicate, while also proving that at least one popular editor works this way.. though the others also have the same option and work in the same way.
Oh, and I'm not asking anyone to do this for me... I've implemented it myself, and am willing to share the code with anyone who is interested.

@marijnh
Copy link
Member

marijnh commented Nov 3, 2013

I understand what you wanted (the previous comment made it clear). I'm just challenging the notion that this is an obvious default/built-in behavior. I've had at least ten different whitespace/tab key/indentation set-ups being pushed onto the system as 'the obviously right way', and at this point I've given up providing something that works for everyone, and my response to such things is "use the API, customize it to your heart's content"

@elygre
Copy link

elygre commented Nov 4, 2013

I encountered this issue quite a while ago, and found / was told about the customization I required (see further above).

Now, if this is a frequent enough request that people push many different variations, perhaps a good solution could be an easier accessible documentation of those? For example, on http://codemirror.net/doc/manual.html, a new heading below "Keymaps" called "Tabs" or somethings, with a listing of various ways of doing this? If the heading was created, I could surely provide text for at least the one we're currently using, including pros & cons and the actual code. We might perhaps even provide examples for each of them various ways.

@lyrixx
Copy link

lyrixx commented Mar 8, 2014

Hello.

Every time I use Code Mirro, I had to set this:

            extraKeys: {
                Tab: function(cm) {
                    var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
                    cm.replaceSelection(spaces, "end", "+input");
                }
            }

Could we have an option to do that ?

I know you don't want, but really, it's not easy for new comers (like me :p ). More over this feature is really needed when editing YAML files.

If you still don't want, Can we add this snippet to the documentation ?

@marijnh
Copy link
Member

marijnh commented Mar 9, 2014

This snippet is in the docs (http://codemirror.net/doc/manual.html#keymaps). Key bindings aren't options in CodeMirror. Are the CodeMirror-using sites you use not under your control, that you can't simply add this once and for all? A bookmarklet might also be a solution.

@lyrixx
Copy link

lyrixx commented Mar 9, 2014

Oups, The last time I used code mirror (~1 year ago), the snippet was not in the docs ;)

Thanks.

@danielearwicker
Copy link

If the requirement is to emulate the behaviour of Visual Studio (with Tabs: Insert Spaces) or emacs with "indent-tabs-mode nil", or countless other editors, then the idea is to make it look like a tab has been inserted, so the cursor moves to the next tab stop.

Therefore the count has to be reduced by the modulo of the current cursor position with the whole count. The example on this page (and in the documentation) doesn't do that. You would need something like this:

extraKeys:{
    Tab: function (cm) {
        if (cm.doc.somethingSelected()) {
            return CodeMirror.Pass;
        }
        var spacesPerTab = cm.getOption("indentUnit");
        var spacesToInsert = spacesPerTab - (cm.doc.getCursor("start").ch % spacesPerTab);    
        var spaces = Array(spacesToInsert + 1).join(" ");
        cm.replaceSelection(spaces, "end", "+input");
    }
}

@marijnh
Copy link
Member

marijnh commented Mar 14, 2014

         var spacesToInsert = spacesPerTab - (cm.doc.sel.from.ch % spacesPerTab);    

(You don't want to randomly reach into undocumented internals like this -- use getCursor instead. This will already break in the upcoming 4.0 release.)

@danielearwicker
Copy link

Thanks - I edited my previous comment accordingly. Also added the check for somethingSelected() to avoid interfering with the built-in (very standard) behaviour for indenting a block of selected text with Tab, or un-indenting it with Shift+Tab.

Ironically, that built-in handling of a selection does a fine job of inserting spaces! It seems strange that in the default configuration, the end user will get a mixture of tabs and spaces depending on whether they hit the tab key with or without a range selected.

marijnh added a commit that referenced this issue Mar 24, 2014
@grssam
Copy link

grssam commented Apr 19, 2014

Um... This is my tab method:

    cm.addKeyMap({
        Tab: (cm) => {
          if (cm.somethingSelected()) {
            cm.indentSelection("add");
            return;
          }

          if (this.config.indentWithTabs)
            cm.replaceSelection("\t", "end", "+input");
          else
            cm.execCommand("insertSoftTab");
        },
        "Shift-Tab": (cm) => {
          cm.indentSelection("subtract");
        }
      });

When this.config.indentWithTabs is true, Hard tabs are inserted correctly, but when its false, nothing is inserted. The cursor does not move at all. What is the correct way to use the insertSoftTab command ?

@marijnh
Copy link
Member

marijnh commented Apr 22, 2014

The version you are using probably does not have the insertSoftTab command yet. I'll release 4.1 later today, which includes this command.

asolove added a commit to asolove/Eve that referenced this issue Dec 6, 2016
…ion.

Before this, CodeMirror was storing literal tabs. They align in the IDE because it has tab size set to 2, but are inconsistent when saved to disk or viewed in another editor. The issue is discussed and my solution comes from this CodeMirror thread: codemirror/codemirror5#988
YtvwlD added a commit to YtvwlD/dojo that referenced this issue Jan 24, 2017
0fd1dab replaced tabs with four spaces on compilation but the tabs remaind in the code.
This commit uses codemirror/codemirror5#988 (comment) to insert
four spaces by pressing tab and preserves the features to indent a code block and to un-indent.
@amrondonp
Copy link

I'm having a little trouble in a use case I want to support. I want to let the users paste their code or upload the code from a file. I have been thinking that is not very efficient to replace the tabs in the code string whenever it changes. I would like to do this without changing the source code of codemirror. any ideas?

@marijnh
Copy link
Member

marijnh commented Jun 24, 2017

You can use the beforeChange event to inspect and modify changes before they are applied.

@chriscalo
Copy link

chriscalo commented May 26, 2018

In case this is useful for anyone else, my favorite solution:

extraKeys: {
  Tab: (cm) => cm.execCommand("indentMore"),
  "Shift-Tab": (cm) => cm.execCommand("indentLess"),
},

It couldn't be simpler, and it has the following behavior, which I think is 👌:

  • If text on one or more lines is selected, all lines get indented (or outdented) with either tabs or spaces according to the indentWithTabs boolean option, and the selection range is maintained as expected
  • If there's no selection, no matter where the cursor is—even in the middle of a word—that line gets indented (or outdented) (because who actually needs to insert tabs anywhere in code except for leading indentation?)
  • If the cursor is somewhere in the leading whitespace of a line, one more level of indentation is added and the cursor is moved to the end of the new leading whitespace, before any non-whitespace content starts

Now the only thing left to figure out is how to delete 2 spaces at a time instead of just one…

@danielearwicker
Copy link

danielearwicker commented May 26, 2018

@chriscalo Indenting the current line with no selection would probably annoy a lot of people. It’s very common to line up “table-like” declarations by hitting the tab key at end or middle of the line.

pplx added a commit to ilscipio/scipio-erp that referenced this issue Jun 8, 2018
@haoranyu
Copy link
Contributor

haoranyu commented Nov 5, 2019

If having the setting:

        indentWithTabs    : false,
        smartIndent       : true,

Then the following setting might be good for a better experience of using the tab key.

extraKeys: {
  Tab: (cm) => {
    if (cm.getMode().name === 'null') {
      cm.execCommand('insertTab');
    } else {
      if (cm.somethingSelected()) {
        cm.execCommand('indentMore');
      } else {
        cm.execCommand('insertSoftTab');
      }
    }
  },
  'Shift-Tab': (cm) => cm.execCommand('indentLess')
}

@windy
Copy link

windy commented Nov 21, 2019

If having the setting:

        indentWithTabs    : false,
        smartIndent       : true,

Then the following setting might be good for a better experience of using the tab key.

extraKeys: {
  Tab: (cm) => {
    if (cm.getMode().name === 'null') {
      cm.execCommand('insertTab');
    } else {
      if (cm.somethingSelected()) {
        cm.execCommand('indentMore');
      } else {
        cm.execCommand('insertSoftTab');
      }
    }
  },
  Backspace: (cm) => {
    if (!cm.somethingSelected()) {
      let cursorsPos = cm.listSelections().map((selection) => selection.anchor);
      let indentUnit = cm.options.indentUnit;
      let shouldDelChar = false;
      for (let cursorIndex in cursorsPos) {
        let cursorPos = cursorsPos[cursorIndex];
        let indentation = cm.getStateAfter(cursorPos.line).indented;
        if (!(indentation !== 0 &&
           cursorPos.ch <= indentation &&
           cursorPos.ch % indentUnit === 0)) {
          shouldDelChar = true;
        }
      }
      if (!shouldDelChar) {
        cm.execCommand('indentLess');
      } else {
        cm.execCommand('delCharBefore');
      }
    } else {
      cm.execCommand('delCharBefore');
    }
  },
  'Shift-Tab': (cm) => cm.execCommand('indentLess')
}

I love your solution

@MuhammadSawalhy
Copy link

MuhammadSawalhy commented Jun 10, 2021

Your way of getting the indentation didn't work for me, cm.getStateAfter(cursorPos.line).indented, so I had to write a polyfill for it.

let cursorPos = cursorsPos[cursorIndex];
let lineContent = cm.doc.getLine(cursorPos.line);
let indentation = lineContent.match(/^\s+/)?.[0].length ?? 0;

@haacked
Copy link

haacked commented Aug 17, 2021

The solution by @windy here works great, but has one minor bug.

If you indent the a blank line, then move the cursor to the beginning of the line and try to backspace, nothing happens. That's because in the conditional:

if (!(indentation !== 0 &&
   cursorPos.ch <= indentation &&
   cursorPos.ch % indentUnit === 0)) {
  shouldDelChar = true;
}

We don't meet the conditional because indentation is greater than 0 and cursorPos.ch == 0 which means when you mod it against indentUnit it returns 0.

We found several situations where backspace didn't work at the beginning of the line.

if (!(indentation !== 0 &&
    cursorPos.ch > 0 &&
    cursorPos.ch <= indentation &&
    cursorPos.ch % indentUnit === 0)) {
    shouldDelChar = true;
}

Making this change seems to fix the issue. Note I added cursorPos.ch > 0 to this conditional.

jakobwesthoff added a commit to jakobwesthoff/the_raytracer_challenge_repl that referenced this issue Jan 20, 2022
Our YAML parser does not like tabs as indentdation marker. Therefore
CodeMirror is now configured to ident with 2 spaces per identation unit.
Futhermore the Tab and Shift Tab behaviour has been overwritten to use
spaces "inteligently" instead of tabs.

Information about this procedure was taken from here: codemirror/codemirror5#988 (comment)
potomak added a commit to potomak/crystal that referenced this issue Jul 9, 2022
Update CodeMirror options as suggested by
codemirror/codemirror5#988 (comment)
in order to indent code using spaces instead of tabs.

Tested by running playground (`./bin/crystal play`) and updating some
code:

https://user-images.githubusercontent.com/153842/178105140-4aad0486-a937-4686-8904-5e9c0a87145b.mov
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests