Sublime Forum

Everything you (n)ever wanted to know about indentation in ST3

#14

Hi Remi,

thanks for the remark, I will certainly take a look into your vhdl package.

I started working on an indentation tool that will allow the user (or language package) to define some opening and closing patterns (supporting multiline pattern) between which the code will be indented. It should easily handle situations like instantiation since the closing semicolon of the port map can close (in terms of indentation region) the port map itself as well as the instantiation. Thus also the problem of switch/case statements can be solved.

At first it’s going to be an “offline” tool like yours to unindent and reindent the complete code. But I’ll try to get the automatic indentation on a newline out of it as well, shouldn’t be too hard… :wink:

I’ll keep you updated if anything usefull comes out.

0 Likes

#15

It’s actually a in-editor function and works better in-editor. I did create a standalone beautifier wrapper that uses the same method, but since it doesn’t have access to scope information, the alignment routine gets slightly more stupid. It was mainly for debug though because it’s easier to go through a huge file of stdout logging than try to parse through the ST console.

Anyway, good luck! Always interested in work done for the language.

0 Likes

#16

What about simply indenting based on scope nesting?

  • Mark an indent whenever a meta scope begins and an outdent whenever one ends.
  • Collapse paired series of repeating in/outdents.
  • Use these in/outdent marks to determine the correct indent level of the beginning of each line.
0 Likes

#17

Well I was under the impression (from the discussion at the top of the page) that the auto indent was based on those Textmate Preferences flags. These do rely on the lexical scope though, which is why I thought it might be interesting to put them in with that (if desired). But honestly I’ve not looked into the auto-indent functions though I need to probably for the next feature stage and try to make sure there’s something sane in place. Currently there’s no auto-indent information for VHDL that I was able to notice, so it’ll just return to the point of last indent after a CR/LF.

For my own beautifier, I do a lot of pre-process work (removing whitespace, aligning assignment operators, etc.) on a list of the lines in the file and I rapidly lose connection with the original lexically scoped source.

0 Likes

#18

I’m trying to figure out a better indentation syntax for Erlang. Is it possible to de-indent a line based on the previous line? In Erlang, function syntax looks like so:

foo(A, B) ->
    line1,
    line2
    line3.

bar() ->
    line1,
    line2.

A function is a header, a list of statements separated by commas (,) and concluded with a dot (`.). I want the de-indent to happen on the line after the dot, otherwise Sublime Text indents the next function at the same level as the body of the previous function. Is this possible at all?

1 Like

#19

Great question!

I’ve been experimenting, and unfortunately, I can’t see a way to do it. I had the idea that it would be possible, but not purely from the .tmPreferences metadata file - I thought it might work if the syntax definition were to scope the line after the dot, so that it could be targeted specially and use different rules for that line than for the rest of the Erlang syntax (and those rules would just always tell ST to de-indent the line). My attempt:

--- Shipped Packages/Erlang/Erlang.sublime-syntax   2017-10-10 16:52:54
+++ Packages/Erlang/Erlang.sublime-syntax   2017-10-24 16:27:15
@@ -239,11 +239,20 @@
         1: entity.name.function.definition.erlang
       push:
         - meta_scope: meta.function.erlang
         - match: \.
           scope: punctuation.terminator.function.erlang
-          pop: true
+          set:
+            - meta_scope: test
+            - match: \s*$\n?
+              scope: meta.after-function-terminator.erlang
+              set:
+                - match: ^\s*
+                  scope: meta.unindent-me.erlang
+                  pop: true
+            - match: ''
+              pop: true
         - match: ^\s*+([a-z][a-zA-Z\d@_]*+)\s*+(?=\()
           captures:
             1: entity.name.function.erlang
         - match: (?=\()
           push:
--- Shipped Packages/Erlang/Indentation Rules.tmPreferences 2017-04-12 09:40:46
+++ Packages/Erlang/Indentation Rules.tmPreferences 2017-10-24 16:10:40
@@ -8,9 +8,9 @@
    <key>settings</key>
    <dict>
        <key>decreaseIndentPattern</key>
        <string>^\s*\b(end)\b</string>
        <key>increaseIndentPattern</key>
-       <string><![CDATA[^[^%]*(\b(if|case|receive|after|fun|try|catch|begin|query)\b(?!.*\b(end)\b.*))|(->(\s*%.*)?$)]]></string>
+       <string><![CDATA[^[^%]*(\b(if|case|receive|after|fun|try|catch|begin|query)\b(?!.*\bend\b)|->(\s*%.*)?$)]]></string>
    </dict>
 </dict>
 </plist>

New file: Packages/Erlang/Indentation Rules - Unindent After Function Dot.tmPreferences:

<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
	<key>name</key>
	<string>Indentation Rules</string>
	<key>scope</key>
	<string>source.erlang meta.unindent-me.erlang</string>
	<key>settings</key>
	<dict>
		<key>decreaseIndentPattern</key>
		<string>.</string>
		<!-- <key>unIndentedLinePattern</key>
		<string>.</string> -->
		<key>increaseIndentPattern</key>
		<string></string>
	</dict>
</dict>
</plist>

But, because the line between the end of the function and the next function is blank, it seems to be ignored by the indentation engine when calculating the indentation for the next function (bar) in your example. Not having a blank line between the two (assuming its even valid Erlang syntax) means that ST has no way to add the new “unindent me” scope, so that’s not a solution/workaround either.

I think the best one can do at the moment is create a macro so that when you press Enter after the . that marks the end of a function, it would automatically perform a left_delete after the newline is added, to unindent one level. But this of course doesn’t help for formatting existing (parts of) documents.

1 Like

Writting an Identation plugin for ST3 for Python
#20

Thanks for such an in depth investigation!

This is my conclusion as well.

Working with the indentation system in Sublime, I can only conclude that it is very limited. I continuously wanted all kinds of variants of the existing options, such as “decreaseIndentPreviousLinePattern”, “increaseIndentCurrentLinePattern” etc.

I’m not sure if it is feasible, but something based on the scopes defined in the actual syntax file would be great. Having to work with very cumbersome and limited regex options inside an XML settings file is far from ideal.

4 Likes

#21

I’ve experimented with just such a system. You’d want to do something like:

  1. Specify which scopes should increase nesting levels. (i.e. meta.*)
  2. Determine the raw nesting depth of each line. (There are some tricky cases here with punctuation.)
  3. Take the list of line depths and remove redundant multiple indentation.
  4. Reindent each line accordingly, ignoring some lines (e.g. lines scoped string.*).

This should work for languages with comprehensive meta scopes. It wouldn’t work for languages that don’t have those meta scopes, probably including a lot of languages with old tmLanguage syntaxes and definitely including most indentation-based languages.

1 Like

#22

Really useful article, thanks! I raised an issue for the problem you already mentioned “lines that don’t conform to the tab stop size” - perhaps you could link it?

I also raised github issues 2029 and 2030 for new problems “Reindent forces indent on line that should have none” and “Indendation wrong for CSS close brace with comment” (I didn’t insert direct links as it seems that new users are only allowed two links in a post!)

0 Likes

#23

I have some more observations on comment indentation after testing with PHP and Java.

  1. After fixing Indentation Rules - Comments.tmPreferences as you suggested I also had to edit the language specific file Indentation Rules Annex.tmPreferences. I removed // from unIndentedLinePattern which allows single-line comments to be re-indented.

  2. In PHP and Java, the re-indent command does not corrupt DocBlock comments. The fix comes from the same setting as in item 1, which excludes lines with ’ *’ and ’ */’ from indenting.

This combination works pretty well and I would recommend it for all languages:

  • preserveIndent false for comments
  • unIndentedLinePattern excludes single line comments and start of block comments
  • unIndentedLinePattern includes continuation and end of block comments
0 Likes

#24

done :slight_smile:

(I’ve also mentioned this at https://github.com/sublimehq/Packages/issues/1156#issuecomment-340426582 -) The problem with excluding single line comments from the unIndentedLinePattern is that this would break:

<?php
if (true)
    // I'm a comment after the if statement that should be indented, and I shouldn't cause the next code line to be unindented
    var_dump(false);

so I guess its up to users to choose what’s best for them while ST is still constrained to the TextMate indentation system

0 Likes

#25

I can un-indent using shift+tab.
Is there any shortcut to un-dent selected content completely with one key? If not, how can I create such shortcut?

0 Likes

Is there any shortcut to un-dent selected content completely?
#26

Hi Keith, do you think it’s possible to make indentation of docblocks work? For example:

/**
 *
 *
 *
 */

The body has an space before the asterisk, and when I reindent the file, it removes that space:

/**
*
*
*
*/
0 Likes

#27

Hi,

I’m not at a computer right now to check, but from memory, I think that it is unfortunately impossible with the TextMate indentation engine that ST uses. Setting preserveIndent to true in the .tmPreferences file should stop ST removing the space, but then it won’t correctly indent the comments to line up with the surrounding code iirc. I can’t think of any way to fix it, even giving the space a special scope won’t make any difference in all the scenarios I can imagine, but is probably something that should be done anyway for future use.

0 Likes

#28

Is it possible to execute a python function after the reindent command? I am not sure how to intercept sublime events.

0 Likes

#29

You can use an EventListener to tell when a command is either about to run or just finished running. An example would be:

import sublime
import sublime_plugin

class AfterIndentListener(sublime_plugin.EventListener):
    def on_post_text_command(self, view, command, args):
        if command == "reindent":
            print("reindent text command was just executed")

A regular EventListener is always active; you can also use a ViewEventListener as well, in which case the listener can be selectively applied only to a specific view based on it’s settings, such as the syntax that’s in use.

More info on all of the events you can monitor and how they work can be found in the API docs.

1 Like

#30

Is bracketIndentNextLinePattern only used when smart_indent is turned on?

0 Likes

#31

Hi: Is there a feature where I can set the desired indentation pattern for a block of text (e.g. chapter 1), then use a “paste special” feature like in excel to format the remaining text (e.g. chapter 2 to 40)

for example, for indentation
Section 1
[tab]Chapter One: Fractions. , 18
[tab tab]Equivalent fractions…,18
[tab tab]Adding and subtracting ,18

Currently, I am indenting each child block manually e.g. highlighting the child rows and clicking command ] on mac. With many chapters this takes a bit of effort.

then I have a similar question but to resolve space between text and page number (often many variations ) to have text[comma]page number
Section 1
[tab]Chapter One: Fractions,18
[tab tab]Equivalent fractions,18
[tab tab]Adding and subtracting,18

apologises if this not the correct place to post this question. I am very new to sublime and this forum.

0 Likes

#32

this thread mainly deals with the indentation engine which is based on syntax scopes, so if you want to make use of it, you would first need to work out how to distinguish (using regular expressions) which text should be indented at level 1, which at level 2 etc. Then you could just run a Reindent command (as opposed to paste special) to get everything indented nicely.

But this only deals with leading indentation, not spaces which are not at the beginning of lines. So if you have a desire to pretty-print/format your text, likely a custom Python plugin or external tool would be the way to go.

0 Likes

#33

A very quick question: How to set bracket indentation behavior in ST3?

0 Likes