IDEA-127205 Specify Editor Tab Width
authorVassiliy <vassiliy.kudryashov@jetbrains.com>
Fri, 1 Aug 2014 18:49:57 +0000 (22:49 +0400)
committerVassiliy <vassiliy.kudryashov@jetbrains.com>
Fri, 1 Aug 2014 18:50:46 +0000 (22:50 +0400)
platform/editor-ui-api/src/com/intellij/ide/ui/UISettings.java
platform/lang-impl/src/com/intellij/application/options/editor/EditorTabsConfigurable.form
platform/lang-impl/src/com/intellij/application/options/editor/EditorTabsConfigurable.java
platform/platform-api/src/com/intellij/ui/tabs/TabInfo.java
platform/platform-api/src/com/intellij/ui/tabs/impl/JBEditorTabs.java
platform/platform-api/src/com/intellij/ui/tabs/impl/TabLabel.java
platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorTabbedContainer.java
platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorWindow.java
platform/platform-resources-en/src/messages/ApplicationBundle.properties
platform/util/src/com/intellij/openapi/util/text/StringUtil.java
platform/util/testSrc/com/intellij/util/text/StringUtilTest.java

index cb0e1a298c9ce74a19d0250d587de33732e95302..f97e7636bda24d587df47ac8a987f92e075aa0b8 100644 (file)
@@ -72,6 +72,7 @@ public class UISettings implements PersistentStateComponent<UISettings>, Exporta
   public int RECENT_FILES_LIMIT = 50;
   public int CONSOLE_COMMAND_HISTORY_LIMIT = 300;
   public int EDITOR_TAB_LIMIT = 10;
+  public int EDITOR_TAB_TITLE_LIMIT = 30;
   public boolean ANIMATE_WINDOWS = true;
   public int ANIMATION_SPEED = 2000; // Pixels per second
   public boolean SHOW_TOOL_WINDOW_NUMBERS = true;
index 9872c7c624aab50e24446ec5813bb438a401fc10..db23ce17b2c31880ba57f4d9e770636bdfce22ff 100644 (file)
@@ -3,7 +3,7 @@
   <grid id="27dc6" binding="myRootPanel" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
     <margin top="0" left="0" bottom="0" right="0"/>
     <constraints>
-      <xy x="20" y="20" width="500" height="527"/>
+      <xy x="20" y="20" width="500" height="585"/>
     </constraints>
     <properties/>
     <border type="none"/>
           <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
         </constraints>
       </vspacer>
-      <grid id="eb8d0" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="10">
+      <grid id="eb8d0" layout-manager="GridLayoutManager" row-count="4" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="10">
         <margin top="0" left="0" bottom="0" right="0"/>
         <constraints>
           <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
         </clientProperties>
         <border type="none" title-resource-bundle="messages/ApplicationBundle" title-key="group.tab.closing.policy"/>
         <children>
-          <grid id="5a5a7" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+          <grid id="5a5a7" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
             <margin top="0" left="0" bottom="0" right="0"/>
             <constraints>
               <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
             <children>
               <component id="527a6" class="javax.swing.JLabel">
                 <constraints>
-                  <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+                  <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false">
+                    <preferred-size width="60" height="27"/>
+                  </grid>
                 </constraints>
                 <properties>
                   <text resource-bundle="messages/ApplicationBundle" key="editbox.tab.limit"/>
               <component id="16477" class="javax.swing.JTextField" binding="myEditorTabLimitField">
                 <constraints>
                   <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="0" indent="0" use-parent-layout="false">
-                    <preferred-size width="30" height="-1"/>
+                    <preferred-size width="30" height="27"/>
                   </grid>
                 </constraints>
                 <properties>
                   <text value="15"/>
                 </properties>
               </component>
+              <component id="2479d" class="javax.swing.JLabel">
+                <constraints>
+                  <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="4" fill="0" indent="0" use-parent-layout="false"/>
+                </constraints>
+                <properties>
+                  <text resource-bundle="messages/ApplicationBundle" key="editbox.tab.title.limit"/>
+                </properties>
+              </component>
+              <component id="3499c" class="javax.swing.JTextField" binding="myTabTitleLimitField">
+                <constraints>
+                  <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="0" indent="0" use-parent-layout="false">
+                    <preferred-size width="30" height="27"/>
+                  </grid>
+                </constraints>
+                <properties>
+                  <columns value="2"/>
+                  <text value="30"/>
+                </properties>
+              </component>
             </children>
           </grid>
           <grid id="9b723" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="2">
             <margin top="0" left="0" bottom="0" right="0"/>
             <constraints>
-              <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+              <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
             </constraints>
             <properties/>
             <border type="none"/>
           <grid id="611cc" layout-manager="GridLayoutManager" row-count="4" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="2">
             <margin top="10" left="0" bottom="0" right="0"/>
             <constraints>
-              <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+              <grid row="3" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
             </constraints>
             <properties/>
             <border type="none"/>
index 4e1de817255aad07898f440efb530899058458dc..8db2a882bfc7938e9a195b3646e9781e7bc5d4e2 100644 (file)
@@ -45,6 +45,7 @@ public class EditorTabsConfigurable implements EditorOptionsProvider {
   private JCheckBox myShowCloseButtonOnCheckBox;
   private JCheckBox myShowDirectoryInTabCheckBox;
   private JRadioButton myActivateRightNeighbouringTabRadioButton;
+  private JTextField myTabTitleLimitField;
 
   public EditorTabsConfigurable() {
     myEditorTabPlacement.setModel(new DefaultComboBoxModel(new Object[]{
@@ -119,6 +120,7 @@ public class EditorTabsConfigurable implements EditorOptionsProvider {
     myHideKnownExtensions.setSelected(uiSettings.HIDE_KNOWN_EXTENSION_IN_TABS);
     myShowDirectoryInTabCheckBox.setSelected(uiSettings.SHOW_DIRECTORY_FOR_NON_UNIQUE_FILENAMES);
     myEditorTabLimitField.setText(Integer.toString(uiSettings.EDITOR_TAB_LIMIT));
+    myTabTitleLimitField.setText(Integer.toString(uiSettings.EDITOR_TAB_TITLE_LIMIT));
     myShowCloseButtonOnCheckBox.setSelected(uiSettings.SHOW_CLOSE_BUTTON);
 
     if (uiSettings.CLOSE_NON_MODIFIED_FILES_FIRST) {
@@ -171,14 +173,29 @@ public class EditorTabsConfigurable implements EditorOptionsProvider {
     uiSettings.ACTIVATE_RIGHT_EDITOR_ON_CLOSE = myActivateRightNeighbouringTabRadioButton.isSelected();
 
     String temp = myEditorTabLimitField.getText();
-    if(temp.trim().length() > 0){
+    if (temp.trim().length() > 0) {
       try {
         int newEditorTabLimit = Integer.parseInt(temp);
-        if(newEditorTabLimit>0&&newEditorTabLimit!=uiSettings.EDITOR_TAB_LIMIT){
-          uiSettings.EDITOR_TAB_LIMIT=newEditorTabLimit;
+        if (newEditorTabLimit > 0 && newEditorTabLimit != uiSettings.EDITOR_TAB_LIMIT) {
+          uiSettings.EDITOR_TAB_LIMIT = newEditorTabLimit;
           uiSettingsChanged = true;
         }
-      }catch (NumberFormatException ignored){}
+      }
+      catch (NumberFormatException ignored) {
+      }
+    }
+    temp = myTabTitleLimitField.getText();
+    if (temp.trim().length() > 0) {
+      try {
+        int newTabTitleLimit = Integer.parseInt(temp);
+        newTabTitleLimit = Math.max(10, Math.min(100, newTabTitleLimit));
+        if (newTabTitleLimit != uiSettings.EDITOR_TAB_TITLE_LIMIT){
+          uiSettings.EDITOR_TAB_TITLE_LIMIT = newTabTitleLimit;
+          uiSettingsChanged = true;
+        }
+      }
+      catch (NumberFormatException ignored) {
+      }
     }
     if(uiSettingsChanged){
       uiSettings.fireUISettingsChanged();
@@ -191,6 +208,7 @@ public class EditorTabsConfigurable implements EditorOptionsProvider {
     boolean isModified = isModified(myCbModifiedTabsMarkedWithAsterisk, uiSettings.MARK_MODIFIED_TABS_WITH_ASTERISK);
     isModified |= isModified(myShowTabsTooltipsCheckBox, uiSettings.SHOW_TABS_TOOLTIPS);
     isModified |= isModified(myEditorTabLimitField, uiSettings.EDITOR_TAB_LIMIT);
+    isModified |= isModified(myTabTitleLimitField, uiSettings.EDITOR_TAB_TITLE_LIMIT);
     int tabPlacement = ((Integer)myEditorTabPlacement.getSelectedItem()).intValue();
     isModified |= tabPlacement != uiSettings.EDITOR_TAB_PLACEMENT;
     isModified |= myHideKnownExtensions.isSelected() != uiSettings.HIDE_KNOWN_EXTENSION_IN_TABS;
index d1dfa976e479d977220495f4c82fb8fbdf094788..c8c944c86e4aed263f06983d98a85bc6e82ba918 100644 (file)
@@ -92,6 +92,7 @@ public final class TabInfo implements Queryable, PlaceProvider<String> {
    * out of its container. (IDEA-61536)
    */
   private WeakReference<TabInfo> myPreviousSelection = new WeakReference<TabInfo>(null);
+  private boolean myTitleShortened;
 
   public TabInfo(final JComponent component) {
     myComponent = component;
@@ -392,6 +393,14 @@ public final class TabInfo implements Queryable, PlaceProvider<String> {
     return myPreviousSelection.get();
   }
 
+  public boolean isTitleShortened() {
+    return myTitleShortened;
+  }
+
+  public void setTitleIsShortened(boolean titleIsShortened) {
+    myTitleShortened = titleIsShortened;
+  }
+
   public interface DragOutDelegate {
 
     void dragOutStarted(MouseEvent mouseEvent, TabInfo info);
index 8e5844fd46847f31115be935443a4cb0885a747f..b08f6c9489a567d45c48bb920f4c532331b38d7e 100644 (file)
@@ -44,6 +44,7 @@ import java.util.List;
  */
 public class JBEditorTabs extends JBTabsImpl {
   public static final String TABS_ALPHABETICAL_KEY = "tabs.alphabetical";
+  static final String TABS_CHORTEN_TITLE_IF_NEED = "tabs.shorten.title.if.need";
   private JBEditorTabsPainter myDarkPainter = new DarculaEditorTabsPainter();
   private JBEditorTabsPainter myDefaultPainter = new DefaultEditorTabsPainter();
 
@@ -60,6 +61,13 @@ public class JBEditorTabs extends JBTabsImpl {
     return super.createSingleRowLayout();
   }
 
+  @Override
+  protected TabLabel createTabLabel(TabInfo info) {
+    TabLabel label = super.createTabLabel(info);
+    label.putClientProperty(TABS_CHORTEN_TITLE_IF_NEED, Boolean.TRUE);
+    return label;
+  }
+
   @Override
   public boolean isEditorTabs() {
     return true;
index 21c36d5dd66a58a9b6e6da10eb4abbbdc4b40490..b85fbcad0f4788a27cb0008b13a29375f7838b7c 100644 (file)
@@ -22,6 +22,7 @@ import com.intellij.openapi.actionSystem.ActionPlaces;
 import com.intellij.openapi.actionSystem.DefaultActionGroup;
 import com.intellij.openapi.util.Pass;
 import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.ui.*;
 import com.intellij.ui.components.panels.Wrapper;
 import com.intellij.ui.tabs.JBTabsPosition;
@@ -317,6 +318,16 @@ public class TabLabel extends JPanel {
 
 
   public void setText(final SimpleColoredText text) {
+    myInfo.setTitleIsShortened(false);
+    if (text != null && text.getTexts().size() == 1 && Boolean.TRUE == getClientProperty(JBEditorTabs.TABS_CHORTEN_TITLE_IF_NEED)) {
+      String title = text.getTexts().get(0);
+      if (title.length() > UISettings.getInstance().EDITOR_TAB_TITLE_LIMIT) {
+        SimpleTextAttributes attributes = text.getAttributes().get(0);
+        text.clear();
+        text.append(StringUtil.getShortened(title, UISettings.getInstance().EDITOR_TAB_TITLE_LIMIT), attributes);
+        myInfo.setTitleIsShortened(true);
+      }
+    }
     myLabel.change(new Runnable() {
       public void run() {
         myLabel.clear();
index c2353d5db20714b82c986b9cfc199f8426070420..03b03faffe03ccc84b6f18baeaa7a6bfe72c9606 100644 (file)
@@ -277,6 +277,8 @@ public final class EditorTabbedContainer implements Disposable, CloseAction.Clos
     myTabs.getTabAt(index).setTooltipText(text);
   }
 
+  public boolean isTitleShortened(int index) { return myTabs.getTabAt(index).isTitleShortened(); }
+
   public void setBackgroundColorAt(final int index, final Color color) {
     myTabs.getTabAt(index).setTabColor(color);
   }
index 14b2afe16345aa07044808b099f9554dd2879bd7..06a3966ade1d04ce15b1dcbe3161fe507425427c 100644 (file)
@@ -464,6 +464,10 @@ public class EditorWindow {
     }
   }
 
+  private boolean isTitleShortenedAt(int index) {
+    return myTabbedPane != null && myTabbedPane.isTitleShortened(index);
+  }
+
   private void setBackgroundColorAt(final int index, final Color color) {
     if (myTabbedPane != null) {
       myTabbedPane.setBackgroundColorAt(index, color);
@@ -908,7 +912,9 @@ public class EditorWindow {
     final int index = findEditorIndex(findFileComposite(file));
     if (index != -1) {
       setTitleAt(index, EditorTabbedContainer.calcTabTitle(getManager().getProject(), file));
-      setToolTipTextAt(index, UISettings.getInstance().SHOW_TABS_TOOLTIPS ? getManager().getFileTooltipText(file) : null);
+      setToolTipTextAt(index, UISettings.getInstance().SHOW_TABS_TOOLTIPS || isTitleShortenedAt(index)
+                              ? getManager().getFileTooltipText(file)
+                              : null);
     }
   }
 
index bbd737afedf15c803090428262a3acf60eefb2d9..5d539357ca3d284ab2850462bfdfe9d364666e07 100644 (file)
@@ -360,6 +360,7 @@ label.collapse.by.default=Collapse by default:
 checkbox.show.code.folding.outline=Show code folding outline
 group.tab.appearance=Tab Appearance
 editbox.tab.limit=Tab limit:
+editbox.tab.title.limit=Tab title limit:
 combobox.editor.tab.placement=Placement:
 checkbox.editor.tabs.in.single.row=Show tabs in single row
 checkbox.editor.tabs.show.close.button=Show "close" button on editor tabs
index 3556f1cce3bde29f8ec08dcc2058a6f209f86425..61ec6edb41cdd23b3ce4b03ab176410058efbb89 100644 (file)
@@ -2658,6 +2658,56 @@ public class StringUtil extends StringUtilRt {
     return s.startsWith(smallPart.toLowerCase()) && bigPart.toLowerCase().startsWith(s);
   }
 
+  public static String getShortened(String s, int maxWidth) {
+    int length = s.length();
+    if (isEmpty(s) || length <= maxWidth) return s;
+    ArrayList<String> words = new ArrayList<String>();
+
+    StringBuilder builder = new StringBuilder();
+    for (int i = 0; i < length; i++) {
+      char ch = s.charAt(i);
+
+      if (i == length - 1) {
+        builder.append(ch);
+        words.add(builder.toString());
+        builder.delete(0, builder.length());
+        continue;
+      }
+
+      if (i > 0 && (ch == '/' || ch == '.' || Character.isUpperCase(ch))) {
+        words.add(builder.toString());
+        builder.delete(0, builder.length());
+      }
+      builder.append(ch);
+    }
+
+    int removedLength = 0;
+
+    String toPaste = "...";
+    int index;
+    while (true) {
+      index = Math.max(0, words.size() / 2 - 1);
+      String aWord = words.get(index);
+      words.remove(index);
+      if (words.size() < 2) {
+        int toCut = length - removedLength - maxWidth + 3;
+        int pos = (aWord.length() - toCut) / 2;
+        toPaste = aWord.substring(0, pos) + "..." + aWord.substring(pos+toCut);
+        break;
+      }
+      removedLength += aWord.length();
+      if (length - removedLength <= maxWidth - 3) {
+        break;
+      }
+    }
+    for (int i = 0; i < words.size(); i++) {
+      String word = words.get(i);
+      if (i == index || words.size() == 1) builder.append(toPaste);
+      builder.append(word);
+    }
+    return builder.toString().replaceAll("\\.{4,}", "...");
+  }
+
   /**
    * Expirable CharSequence. Very useful to control external library execution time,
    * i.e. when java.util.regex.Pattern match goes out of control.
index 2f31dc4f4e61449221d380037be9b9962d50a1ce..e1d28d247d725024147f4c03acdaf34b6e458d6e 100644 (file)
@@ -195,4 +195,15 @@ public class StringUtilTest extends TestCase {
     assertEquals(Arrays.asList("\n", "\r\n", "\n", "\r\n", "\r", "\r", "aa\r", "bb\r\n", "cc\n", "\r", "dd\n", "\n", "\r\n", "\r"),
                  Arrays.asList(StringUtil.splitByLinesKeepSeparators("\n\r\n\n\r\n\r\raa\rbb\r\ncc\n\rdd\n\n\r\n\r")));
   }
+
+  public void testShortened() {
+    String[] names = {"AVeryVeeryLongClassName.java", "com.test.SomeJAVAClassName.java", "strangelowercaseclassname.java"};
+    for (String name : names) {
+      for (int i = name.length() + 1; i > 15; i--) {
+        String shortened = StringUtil.getShortened(name, i);
+        assertTrue(shortened.length() <= i);
+        assertTrue(!shortened.contains("...."));
+      }
+    }
+  }
 }