Bug 73104 - [format] indentation amount tied to tab size
Summary: [format] indentation amount tied to tab size
Status: VERIFIED FIXED
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 3.0   Edit
Hardware: All All
: P3 enhancement (vote)
Target Milestone: 3.1 M6   Edit
Assignee: Olivier Thomann CLA
QA Contact:
URL:
Whiteboard:
Keywords:
: 81776 82090 87055 89823 (view as bug list)
Depends on:
Blocks:
 
Reported: 2004-09-01 17:38 EDT by Per Bothner CLA
Modified: 2005-04-06 06:42 EDT (History)
9 users (show)

See Also:


Attachments
jdt-core_mixed-indents.diff (15.73 KB, text/plain)
2005-01-10 16:23 EST, Tom Hofmann CLA
no flags Details
jdt-core-tests-model_mixed-indents.diff (1.33 KB, text/plain)
2005-01-10 16:24 EST, Tom Hofmann CLA
no flags Details
jdt-ui_mixed-indents.diff (11.01 KB, text/plain)
2005-01-10 16:27 EST, Tom Hofmann CLA
no flags Details
My formatting options (25.52 KB, text/plain)
2005-02-10 10:31 EST, Philipe Mulet CLA
no flags Details
Modified javadoc. Apply on HEAD (1.66 KB, patch)
2005-03-10 14:53 EST, Olivier Thomann CLA
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Per Bothner CLA 2004-09-01 17:38:11 EDT
The Code Formatter has an option (Indentation -> Use tab character) to use tabs
for indentation, but it appears to assume a tab always has the same width as the
indentation amount, which the same preference panel calls 'Tab size'.

It specifically ignores the Preferences -> Java -> Editor -> Appearance ->
Displayed tab width setting.

In Unix and GNU environments it is traditional to use tab essentially for
compression, with the assumption that tabs are always 8 characters.  It is
common to indent code by 2, 3, or 4 columns, while also using tabs to correspond
to 8 spaces.  One might argue whether a such convention is a good idea where
most programmers use IDEs and editors following different conventions; however
the fact is that a lot of code follows these "Unix/GNU" conventions.  

This is perhaps less an issue for Java (though my "Kawa" code does follow this
convention, for now), but it is probably a bigger issue for C/C++, and hence for
CDT.

I'm calling this an "enhancement" because it's not quite clear what the right
behavior should be.  You need a little bit of UI-design.  The simplest would be
to rename "Tab size" by "Indentation columns", and then if Use tab character is
set use the Displayed tab width setting to compress initial spaces.

The Emacs editor by default supports the descripted "Unix/GNU" standard; see
http://www.gnu.org/software/emacs/manual/html_node/Display-Custom.html
Comment 1 Dani Megert CLA 2004-09-02 02:49:04 EDT
>It specifically ignores the Preferences -> Java -> Editor -> Appearance ->
>Displayed tab width setting.
Of course. This is UI code which defines how a tab is rendered. It has nothing
to do with how the file gets changed during formatting.

Moving to J Core to comment.
Comment 2 Per Bothner CLA 2004-09-02 03:22:21 EDT
> Of course. This is UI code which defines how a tab is rendered. It has nothing
> to do with how the file gets changed during formatting.

Right - but should it?  Or rather: perhaps the code formatter needs two
parameters: "indentation amount in columns", and "if using tabs, number of
initial columsn to represent by a tab".  If so, should the latter default to or
be tied to the "Displayed tab width" setting?

It's hard to think of a use case where you'd want these to be different.
On the other hand, is the current behaviour (where "indentation amount in
columns" and "if using tabs, number of initial columsn to represent by a tab"
are tied to each other, regardless of the "Displayed tab width" setting)
useful/sensible?
Comment 3 Tom Hofmann CLA 2005-01-10 16:23:49 EST
Created attachment 17059 [details]
jdt-core_mixed-indents.diff

The patches implements mixed settings for tab length and indentation size.

==========

The only changes to the formatter itself are in Scribe and Alignment.

Alignment.java:
This is straight forward - all I did is replace its own tab-length computation
by delegation to Scribe.

Scribe.java:
- added Scribe.indentationSize, which tells the amount of space-equivalents per
indentation unit.
- Scribe.indentationLevel is always space-equivalent based (e.g. if useTab &&
tabSize==2 && column==(4+1), then indentationLevel == 4, not 2*tab). Most
changes are due to all the places I got rid of tabSize conversions.
- Scribe.printIndentationIfNecessary() does some trickery finding out whether
it
should add a tab or not. It uses getNextIndentationLevel.
- added Scribe.snapToTab - if this is true, all indentations are made to align
with tab stops. It is set to true if tabs are used and the tabsize and
indentation size are equal. To be honest, this is really to be compatible with
the regression tests, which I guess are not alway 100% consistent when aligning
parameters.
- Scribe.getNextIndentationLevel computes the next tab stop considering
snapToTab

==========

RIPPLES:

DefaultCodeFormatterConstants.java

There are quite some clients of
DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE. Most clients use either one
of the two previous meanings of the option. Some need the visual tab length
(e.g. the editors that perform tab conversion and inform StyledText about the
tab length), others the indentation size. I assume that the latter are the vast
majority. Especially the clients that use TAB_SIZE together with TAB_CHAR to
compute the indentation are definitely interested in the indentation size, not
the tabulator length. This accounts for most of our test cases.

I therefore let FORMATTER_TAB_SIZE mean FORMATTER_INDENTATION_SIZE (which
replaces the deprecated FORMATTER_TAB_SIZE), and introduce the new option
FORMATTER_TAB_LENGTH for the visual equivalent of one tab.

DefaultCodeFormatterOptions.java
- added the 'indentation_size' option
- modified set/get accordingly
- for compatibility, if the options passed to set() do not contain a value for
FORMATTER_TAB_LENGTH, the indentation_size value is taken
- modified default and javaconvention settings accordingly (java conventions
are really with tab=8,indent=4, but I left that to not break the tests)

CodeFormatter.java
- added legacy conversion

FormatterRegressionTests.java
- this is covered by the compatibility code
- except for one place where the tab_size is explicitely set to 3, I set the
indentation_size to 3

There are more ripples where they are not so easy to resolve. In JDT Core, I
mainly see org.eclipse.jdt.internal.core.dom.rewrite.Indents.java

There are a couple of ripples in JDT-UI land, but these should be handleable.
There may be some malfunctions for users that start to use mixed indentation
settings, before everything is fully in place, but most of it should work right
away.
Comment 4 Tom Hofmann CLA 2005-01-10 16:24:24 EST
Created attachment 17060 [details]
jdt-core-tests-model_mixed-indents.diff
Comment 5 Tom Hofmann CLA 2005-01-10 16:27:58 EST
Created attachment 17061 [details]
jdt-ui_mixed-indents.diff

JDT-UI first cut of changes.
Comment 6 Per Bothner CLA 2005-01-10 16:38:01 EST
Great!  Looking forward to trying this once it's stable.
Comment 7 Philipe Mulet CLA 2005-01-11 04:13:54 EST
Olivier - pls look into these proposed changes.
Comment 8 Olivier Thomann CLA 2005-01-11 21:35:47 EST
I will review these patches.
Comment 9 Olivier Thomann CLA 2005-01-12 17:30:24 EST
With the patches, I end up with a mix of tabs and spaces.
Need to find out what is wrong.
Comment 10 Dani Megert CLA 2005-01-13 04:20:33 EST
Olivier, I'm not 100% sure but I think that's the idea and that's also the Java
coding conventions default. AFAIK you find some hints and examples there.
Comment 11 Tom Hofmann CLA 2005-01-17 06:02:37 EST
As Dani has said, this is the entire point of the patch.

Some people (don't count me in) like to use TAB as a character-saver on their
files, where a TAB replaces any run of eight (or whathever tab-length is)
SPACEs. Any indentation that is not a multiple of tab-length gets filled up with
SPACEs.

Unfortunately, this is the java coding standard...

Example (indentation is 4, tab-length is 8): A single indentation will be 4
SPACE (.), double indentation 1 TAB (->-), triple 1 TAB 4 SPACE etc:

class A {
....void method() {
---->---if (true)
---->---....return;
....}
}
Comment 12 Ulrik Restorp CLA 2005-01-18 03:11:09 EST
*** Bug 81776 has been marked as a duplicate of this bug. ***
Comment 13 Art Pope CLA 2005-01-20 14:13:29 EST
The Sun JDK is among the existing code that assumes that a TAB is
8 characters. Thus, unless one reformats the JDK source, or chooses
to indent one's own Java code by 8 spaces, the JDK classes appear
incorrectly formatted when viewed in Eclipse 3.1 M4.
Comment 14 Olivier Thomann CLA 2005-02-08 13:54:31 EST
This should be a new mode to format code. It should not replace the existing tab
formatting. it would be the default mode in java conventions settings. But I
think it should still be possible to format using only tabs.
I will try to integrate the patch into HEAD contents.
Comment 15 Tom Hofmann CLA 2005-02-08 14:02:00 EST
(In reply to comment #14)
> This should be a new mode to format code. It should not replace the existing tab
> formatting. it would be the default mode in java conventions settings. But I
> think it should still be possible to format using only tabs.

Agreed - I think this is the case as long as the indentation size and tab size
are kept equal, as then there will be no odd spaces to add.

Comment 16 Olivier Thomann CLA 2005-02-08 14:06:29 EST
(In reply to comment #15)
Yes, but it should also be possible to use only tabs, even if the indentation
size and the tab size are different.
I have several changes in the code formatter since the patch has been done. I am
trying to integrate everything.
Comment 17 Per Bothner CLA 2005-02-08 14:34:35 EST
(In reply to comment #16)
Do you mean an option to "ignore display tab width" when indenting,
and set the "indentation amount" to "1 tab" rather than "N columns"?

The question I see is the user interface.  If you can add such an option without
complicting the UI then I guess it's harmless.  But it doesn't add any extra
functionality, since people can achieve the same effect by setting the display
tab width to the same as the indentation width and selecting "use tabs".  If you
want "indentation amount" to be "1 tab" it would be silly (i.e. confusing) to
set set the tab width and indentation width different, and if they're the same
you'd automatically indent only using tabs.  Having an extra explicit option
would be redundant and hence confusing.

Perhaps you could lay out the UI like:

Indentation between levels:
* a tab (currently displayed as T columns)
* [I] columns
  * using spaces only
  * using tabs (currently T columns) and spaces

Here I is a fillable field.  T should ideally be a link to the appropriate
preference pane for display tab width.

Alternatively:

Identation between levels: [I]
* use spaces only
* use tabs (currently T columns) and spaces
* use tabs only [warning: doesn't match current tab width T]

The [warning] would only appear when I != T.
The 2nd choice might be grayed out when I == T.
Comment 18 Olivier Thomann CLA 2005-02-08 15:15:09 EST
I integrated the patch, but I found issue with Scribe.snapToTab. If tab_length
== indentation_size, the behavior should be similar to what we have now and this
is not the case.
I need to fix it before I can release.
Comment 19 Tom Hofmann CLA 2005-02-09 04:00:46 EST
Oh, I think know I understand your comment 14 and comment 16! Your concern is to
avoid mixed indentations and you want the user to be able to say so. I think
that the problem only arises for deep-alignment cases where parameter lists,
field names etc. are aligned with a certain marker (an opening parenthesis for
example):

From FormatterRegressionTests#114:

format:
--
public void somehd(String argument1, String argument2,String argument3) {}
--

expects (note the tab after the opening parenthesis):
--
public void somehd(>String argument1,\n
-->|-->|-->|-->|-->|String argument2,\n
-->|-->|-->|-->|-->|String argument3) {\n
}
--

This is what you would expect in tab-only mode. If tab-only mode is off, you
would want to get
--
public void somehd(String argument1,\n
-->|-->|-->|-->|...String argument2,\n
-->|-->|-->|-->|...String argument3) {\n
}
--

The question to ask the user is whether the deep-alignment should be made to
snap to an indentation level, or should use the odd indentation of the parenthesis? 

To get what you want, you can essentially make Scribe.snapToTabs a user
preference instead of deriving it from the tabSize and indentationSize settings.
This would avoid getting mixed indents as long as (indentationSize % tabSize ==
0) holds. If this condition is false, there is no way to avoid mixed indents anyway.

Also, Scribe.getNextIndentationLevel is wrong in my patch - it should round to
the next *indentationSize*, not the next *tabStop*. It should read:

---
public int getNextIndentationLevel(int someColumn) {
    int indent= someColumn - 1;
    if (indent == 0) return this.indentationLevel;
    if (snapToTabs) {
        int rem= indent % this.indentationSize;
        int addition= rem == 0 ? 0 : this.indentationSize - rem; // round to
superior
        return indent + addition;
    } else {
        return indent;
    }
}	
---


One thing to consider in this discussion is also bug 49896 which requests to
*only use spaces* when doing deep-alignment...
Comment 20 Olivier Thomann CLA 2005-02-09 11:43:54 EST
I added a snapToTabs option, but I think this should be enabled only if the
formatter is using tabs.
The bug 49896 would be fixed by using tabs without enabling snapToTabs, I believe.

Do you think this could solve this issue? When can we synchronize to release
this code? I'd like to try it in a nightly build first. I don't want to break an
integration build.
Comment 21 Tom Hofmann CLA 2005-02-09 13:35:17 EST
(In reply to comment #20)
> I added a snapToTabs option, but I think this should be enabled only if the
> formatter is using tabs.

I agree it doesn't make much sense otherwise.

> The bug 49896 would be fixed by using tabs without enabling snapToTabs, I believe.

I don't think so - the bug requests that any deep alignment is done using
spaces. Any standard indents (including double indentation for continuations)
would continue to use tabs (if so configured). Everything that you call
INDENT_ON_COLUMN would use spaces only. This way, everyone can choose the basic
indent he likes, but aligning parameters still works.

class X {
-->|int method(int param1,
-->|...........int param2) {
-->|-->|return param1 + param2;
-->|}
}

But that is a separate issue really...

> When can we synchronize to release
> this code? I'd like to try it in a nightly build first. I don't want to break an
> integration build.

I see - plus the next I-Build is really a milestone... I think we should try as
soon as possible.

+ I have patches for jdt-ui/-text. 
+ As long as the user does not change the tab setting to something different
than the indentation setting, everything will be as it is now, so I don't think
the risk is unreasonable high.
+ If we find a serious problem, we can always disable the new option in the UI
so users can't set the indentation size to something other than tab size.

So, from our standpoint (checked with Dani & Dirk): go for it if you can add it
tonight or tomorrow. This way, we can at least check it out on friday and monday.
Comment 22 Olivier Thomann CLA 2005-02-09 14:31:00 EST
Changes in:
dom/org/eclipse/jdt/internal/core/dom/rewrite/ASTRewriteFormatter.java
formatter/org/eclipse/jdt/core/formatter/DefaultCodeFormatterConstants.java
formatter/org/eclipse/jdt/internal/formatter/DefaultCodeFormatterOptions.java
formatter/org/eclipse/jdt/internal/formatter/Scribe.java
formatter/org/eclipse/jdt/internal/formatter/align/Alignment.java
formatter/org/eclipse/jdt/internal/formatter/old/CodeFormatter.java
model/org/eclipse/jdt/internal/core/CreateTypeMemberOperation.java
model/org/eclipse/jdt/internal/core/JavaCorePreferenceInitializer.java

src/org/eclipse/jdt/core/tests/formatter/FormatterRegressionTests.java
src/org/eclipse/jdt/core/tests/rewrite/describing/ASTRewritingTest.java
src/org/eclipse/jdt/core/tests/rewrite/modifying/ASTRewritingModifyingTest.java
workspace/Formatter/test458/A_out.java

The code will be released shortly to let you adapt the UI and Text tomorrow.
Comment 23 Olivier Thomann CLA 2005-02-09 15:48:23 EST
Fixed and released in HEAD.
Regression tests updated.
Comment 24 Tom Hofmann CLA 2005-02-10 05:22:05 EST
cool, thanks!
Comment 25 Philipe Mulet CLA 2005-02-10 10:24:30 EST
Using this version of the formatter, I am seeing strange behavior with a tab
size of 4. Statements are no longer properly indented; when manually setting tab
size to 8, it looks fine. 
Comment 26 Philipe Mulet CLA 2005-02-10 10:31:38 EST
Created attachment 17818 [details]
My formatting options
Comment 27 Tom Hofmann CLA 2005-02-10 10:41:57 EST
Philippe, the nightly build does not yet contain the JDT UI code that does the
formatter profile conversion - I only released it this morning. I will attach a
jdt-ui preview that should work for you.

This will fix all clients that use a custom formatter profile.

We're also discussing changing the default setting for tab-length in the
JavaConventionSettings back to 4 so existing clients always see the same
behavior before and after the formatter change. 
Comment 28 Olivier Thomann CLA 2005-02-10 12:52:16 EST
We will revert this fix for now. Too close to M5 and we have some compatibility
issues with 3.0 clients.
This will be worked on immediately after M5.
Comment 29 Tom Hofmann CLA 2005-02-11 11:42:31 EST
*** Bug 82090 has been marked as a duplicate of this bug. ***
Comment 30 Tom Hofmann CLA 2005-03-03 14:02:49 EST
*** Bug 87055 has been marked as a duplicate of this bug. ***
Comment 31 Olivier Thomann CLA 2005-03-10 14:38:05 EST
In order to fix this we propose the following changes:

We would like to add a new value for the FORMATTER_TAB_CHAR option.

 	/**
	 * <pre>
	 * FORMATTER / Option to specify the tabulation size
	 *     - option id:         "org.eclipse.jdt.core.formatter.tabulation.size"
	 *     - possible values:   { TAB, SPACE }
	 *     - default:           TAB
	 * </pre>
	 * @see JavaCore#TAB
	 * @see JavaCore#SPACE
	 * @since 3.0
	 */
	public static final String FORMATTER_TAB_CHAR = JavaCore.PLUGIN_ID +
".formatter.tabulation.char"; //$NON-NLS-1$

This is an API breaking change, because the API doesn't specify that other
values can be defined. We can also claim that it doesn't explicitely say that
the only possible values are SPACE and TAB.
However the source compatibility is not preserved if someone is using our option.
if (myValue.equals(JavaCore.TAB)) {
   ... 
} else {
  // this is implicitely SPACE
   ...
}

But with a third value this could be wrong.
The code would need to be changed for:
if (myValue.equals(JavaCore.TAB)) {
   ... 
} else if (myValue.equals(JavaCore.SPACE)) {
  // this is implicitely SPACE
   ...
}

So it is trivial to explain how to port existing code once a new value is added.

The only client of this API right now is JDT/UI. I don't believe there is
anybody else using these APIs.
With this change, the existing UI would not be broken because they explicitely
set a value to be either TAB or SPACE.

Jim, let me know if I missed anything.
Comment 32 Jim des Rivieres CLA 2005-03-10 14:46:12 EST
Olivier, Please include the modified Javadoc spec for FORMATTER_TAB_CHAR that 
mentions the new third value and explains more values may be added in future.
Comment 33 Olivier Thomann CLA 2005-03-10 14:52:19 EST
Here is the modified javadoc:
	/**
	 * <pre>
	 * FORMATTER / Option to specify the tabulation size
	 *     - option id:         "org.eclipse.jdt.core.formatter.tabulation.char"
	 *     - possible values:   { TAB, SPACE, MIXED }
	 *     - default:           TAB
	 * </pre>
	 * More values may be added in the future.
	 * 
	 * @see JavaCore#TAB
	 * @see JavaCore#SPACE
	 * @see #MIXED
	 * @since 3.0
	 */

I will attached a patch with the modifications.
Comment 34 Olivier Thomann CLA 2005-03-10 14:53:09 EST
Created attachment 18667 [details]
Modified javadoc. Apply on HEAD
Comment 35 Jim des Rivieres CLA 2005-03-23 12:57:05 EST
Request forwarded to PMC. Risks seem very low. Please proceed for 3.1M6 under 
assumption that PMC will approve. Be sure to add an entry in Incompatibilities 
section of the 3.1M6 Migration Guide (in 
the /org.eclipse.platform.doc.isv/porting/3.1/incompatibilities.html). And let 
me know if you'd like me to polish your draft.
Comment 36 Olivier Thomann CLA 2005-03-28 17:19:11 EST
Fixed and released in HEAD.
Regression test added in FormatterRegressionTests.test549/550/551/552/555.

The following API has been added:
org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants.FORMATTER_INDENTATION_SIZE

This is used to set the size of one indentation in term of spaces. It is used
only if the
org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR
value is org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants.MIXED.

Tom,

Could you please review the changes?
Comment 37 Tom Hofmann CLA 2005-03-29 07:08:40 EST
Looking good.
Comment 38 Per Bothner CLA 2005-03-30 11:10:35 EST
Is this fix supposed to be in: eclipse-SDK-I20050329-2000-linux-gtk.tar.gz?
I assume not - I don't see any difference.
I've got Preferences -> Text Editors -> Displayed tab width = 8,
That seems to have no effect; instead it uses the
Preferences -> Java -> Formatter -> Indentation:
tab size to *override* the display tab width.
Also, I don't see any change in the UI.
Comment 39 Olivier Thomann CLA 2005-03-30 11:13:39 EST
As far as I know, this is only at the core level, not the ui level yet.
Comment 40 Tom Hofmann CLA 2005-03-30 11:22:43 EST
UI support will not make it into M6, as there are a couple of places that need
adaption.
Comment 41 Per Bothner CLA 2005-03-30 11:34:17 EST
But it has been fixed in HEAD?  Should I try the next nightly build (rather than
a stream integration build) to try this out?

Tom Eicher wrote there are "acouple of places that need adaption" which to me
implies there are still some issues.
So perhaps marking this as "resolved fixed" was premature?
Comment 42 Olivier Thomann CLA 2005-03-30 11:38:43 EST
It is resolved and fixed from the JDT/Core stand point. This is the component
attached to this PR. This doesn't mean the JDT/UI is in line with what core
provides.
We needed a new API to fix it and it had to be done for M6. JDT/UI can however
adapt after M6 since they don't need to add a new API.
Comment 43 Tom Hofmann CLA 2005-03-30 11:42:25 EST
I filed bug 89593 to track the UI side of it and added you to the cc list.
Comment 44 Olivier Thomann CLA 2005-03-30 19:15:04 EST
API is in place and regression tests passed.
Verified in 20050330-0500
Comment 45 Tom Hofmann CLA 2005-04-06 06:42:37 EDT
*** Bug 89823 has been marked as a duplicate of this bug. ***