diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/ManifestFileUtil.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ManifestFileUtil.java index c884d4d8c599..dced5f8bd21a 100644 --- a/java/compiler/impl/src/com/intellij/packaging/impl/elements/ManifestFileUtil.java +++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ManifestFileUtil.java @@ -134,7 +134,7 @@ public class ManifestFileUtil { } } - public static void updateManifest(VirtualFile file, ManifestFileConfiguration configuration) { + public static void updateManifest(VirtualFile file, ManifestFileConfiguration configuration, final boolean replaceValues) { final Manifest manifest = readManifest(file); final Attributes mainAttributes = manifest.getMainAttributes(); @@ -142,17 +142,34 @@ public class ManifestFileUtil { if (mainClass != null) { mainAttributes.put(Attributes.Name.MAIN_CLASS, mainClass); } - else { + else if (replaceValues) { mainAttributes.remove(Attributes.Name.MAIN_CLASS); } final List classpath = configuration.getClasspath(); if (classpath != null && !classpath.isEmpty()) { - mainAttributes.put(Attributes.Name.CLASS_PATH, StringUtil.join(classpath, " ")); + List updatedClasspath; + if (replaceValues) { + updatedClasspath = classpath; + } + else { + updatedClasspath = new ArrayList(); + final String oldClasspath = (String)mainAttributes.get(Attributes.Name.CLASS_PATH); + if (!StringUtil.isEmpty(oldClasspath)) { + updatedClasspath.addAll(StringUtil.split(oldClasspath, " ")); + } + for (String path : classpath) { + if (!updatedClasspath.contains(path)) { + updatedClasspath.add(path); + } + } + } + mainAttributes.put(Attributes.Name.CLASS_PATH, StringUtil.join(updatedClasspath, " ")); } - else { + else if (replaceValues) { mainAttributes.remove(Attributes.Name.CLASS_PATH); } + ManifestBuilder.setVersionAttribute(mainAttributes); try { diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/ManifestFilesInfo.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/ManifestFilesInfo.java index 861826b822aa..f937d26b2975 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/ManifestFilesInfo.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/ManifestFilesInfo.java @@ -67,7 +67,7 @@ public class ManifestFilesInfo { } } - ManifestFileUtil.updateManifest(file, configuration); + ManifestFileUtil.updateManifest(file, configuration, true); } } diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/actions/PutSourceItemIntoParentAndLinkViaManifestAction.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/actions/PutSourceItemIntoParentAndLinkViaManifestAction.java index 773a6b9bd630..4ec6bb455633 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/actions/PutSourceItemIntoParentAndLinkViaManifestAction.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/artifacts/sourceItems/actions/PutSourceItemIntoParentAndLinkViaManifestAction.java @@ -56,12 +56,15 @@ public class PutSourceItemIntoParentAndLinkViaManifestAction extends PutIntoDefa } boolean enable = parentInfo != null; + boolean isProvideElements = false; for (PackagingSourceItem item : mySourceItemsTree.getSelectedItems()) { + isProvideElements |= item.isProvideElements(); if (!item.getKindOfProducedElements().containsJarFiles()) { enable = false; break; } } + enable &= isProvideElements; presentation.setVisible(enable); presentation.setEnabled(enable); } diff --git a/java/java-impl/src/com/intellij/unscramble/UnscrambleDialog.java b/java/java-impl/src/com/intellij/unscramble/UnscrambleDialog.java index 9393d80d452c..a6830073b60e 100644 --- a/java/java-impl/src/com/intellij/unscramble/UnscrambleDialog.java +++ b/java/java-impl/src/com/intellij/unscramble/UnscrambleDialog.java @@ -256,7 +256,10 @@ public class UnscrambleDialog extends DialogWrapper{ static String normalizeText(@NonNls String text) { StringBuilder builder = new StringBuilder(text.length()); + + text = text.replaceAll("(\\S[ \\t\\x0B\\f\\r]+)(at\\s+)", "$1\n$2"); String[] lines = text.split("\n"); + boolean first = true; boolean inAuxInfo = false; for (String line : lines) { diff --git a/platform/lang-api/src/com/intellij/psi/util/ReferenceSetBase.java b/platform/lang-api/src/com/intellij/psi/util/ReferenceSetBase.java index c02e87a758b7..7b767f76c9ef 100644 --- a/platform/lang-api/src/com/intellij/psi/util/ReferenceSetBase.java +++ b/platform/lang-api/src/com/intellij/psi/util/ReferenceSetBase.java @@ -17,11 +17,14 @@ package com.intellij.psi.util; import com.intellij.openapi.util.TextRange; -import com.intellij.psi.*; +import com.intellij.psi.ElementManipulators; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiReference; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -63,8 +66,7 @@ public abstract class ReferenceSetBase { do { next = findNextSeparator(str, current); final TextRange range = new TextRange(offset + current + 1, offset + (next > 0 ? next : str.length())); - final T ref = createReference(range, index++); - references.add(ref); + references.addAll(createReferences(range, index ++)); } while ((current = next) >= 0); return references; @@ -76,8 +78,16 @@ public abstract class ReferenceSetBase { return next; } - @NotNull - protected abstract T createReference(final TextRange range, final int index); + @Nullable + protected T createReference(final TextRange range, final int index) { + return null; + } + + protected List createReferences(final TextRange range, final int index) { + T reference = createReference(range, index); + + return reference == null? Collections.emptyList() : Collections.singletonList(reference); + } public PsiElement getElement() { return myElement; diff --git a/platform/lang-impl/src/com/intellij/ide/bookmarks/actions/BookmarksAction.java b/platform/lang-impl/src/com/intellij/ide/bookmarks/actions/BookmarksAction.java index 751616aedf00..c91f60bd87db 100644 --- a/platform/lang-impl/src/com/intellij/ide/bookmarks/actions/BookmarksAction.java +++ b/platform/lang-impl/src/com/intellij/ide/bookmarks/actions/BookmarksAction.java @@ -50,6 +50,7 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.ui.*; import com.intellij.util.Alarm; +import com.intellij.util.Function; import org.jetbrains.annotations.Nullable; import javax.swing.*; @@ -206,15 +207,9 @@ public class BookmarksAction extends AnAction implements DumbAware { list.clearSelection(); } - new ListSpeedSearch(list) { - @Override - protected String getElementText(Object element) { - return ((ItemWrapper)element).speedSearchText(); - } - }; - list.setCellRenderer(new ItemRenderer(project)); + JPanel footerPanel = new JPanel(new BorderLayout()) { @Override protected void paintComponent(Graphics g) { @@ -248,7 +243,12 @@ public class BookmarksAction extends AnAction implements DumbAware { setSouthComponent(footerPanel). setEastComponent(previewPanel). setItemChoosenCallback(runnable). - createPopup(); + setItemsNamer(new Function() { + public String fun(Object o) { + return ((ItemWrapper)o).speedSearchText(); + } + }).createPopup(); + editDescriptionAction.setPopup(popup); popup.showCenteredInCurrentWindow(project); } diff --git a/platform/lang-impl/src/com/intellij/openapi/module/impl/ProjectLoadingErrorsNotifierImpl.java b/platform/lang-impl/src/com/intellij/openapi/module/impl/ProjectLoadingErrorsNotifierImpl.java index 6553e4c9a1f4..7ce18ccb7fc2 100644 --- a/platform/lang-impl/src/com/intellij/openapi/module/impl/ProjectLoadingErrorsNotifierImpl.java +++ b/platform/lang-impl/src/com/intellij/openapi/module/impl/ProjectLoadingErrorsNotifierImpl.java @@ -86,7 +86,7 @@ public class ProjectLoadingErrorsNotifierImpl extends ProjectLoadingErrorsNotifi } final String invalidElements = getInvalidElementsString(descriptions); - final String errorText = ProjectBundle.message("error.message.configuration.cannot.load") + " " + invalidElements + " Fix"; + final String errorText = ProjectBundle.message("error.message.configuration.cannot.load") + " " + invalidElements + " Details..."; Notifications.Bus.notify(new Notification("Project Loading Error", "Error Loading Project", errorText, NotificationType.ERROR, new NotificationListener() { diff --git a/platform/platform-api/src/com/intellij/openapi/ui/popup/PopupChooserBuilder.java b/platform/platform-api/src/com/intellij/openapi/ui/popup/PopupChooserBuilder.java index 66f941ab8eff..322b1f295abf 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/popup/PopupChooserBuilder.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/popup/PopupChooserBuilder.java @@ -22,7 +22,9 @@ import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Pair; import com.intellij.ui.InplaceButton; import com.intellij.ui.ListScrollingUtil; +import com.intellij.ui.speedSearch.ListWithFilter; import com.intellij.ui.treeStructure.treetable.TreeTable; +import com.intellij.util.Function; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.tree.TreeUtil; import org.jetbrains.annotations.Nls; @@ -68,6 +70,8 @@ public class PopupChooserBuilder { private Component mySettingsButtons; private boolean myAutoselectOnMouseMove = true; + private Function myItemsNamer = null; + public PopupChooserBuilder(@NotNull JList list) { myChooserComponent = list; } @@ -153,11 +157,16 @@ public class PopupChooserBuilder { myAutoselectOnMouseMove = doAutoSelect; return this; } + + public PopupChooserBuilder setItemsNamer(Function namer) { + myItemsNamer = namer; + return this; + } @NotNull public JBPopup createPopup() { if (myChooserComponent instanceof JList) { - myChooserComponent = new MyListWrapper((JList)myChooserComponent); + myChooserComponent = ListWithFilter.wrap((JList)myChooserComponent, new MyListWrapper((JList)myChooserComponent), myItemsNamer); } JPanel contentPane = new JPanel(new BorderLayout()); @@ -168,8 +177,8 @@ public class PopupChooserBuilder { contentPane.add(label, BorderLayout.NORTH); } - if (myChooserComponent instanceof MyListWrapper) { - JList list = ((MyListWrapper)myChooserComponent).myList; + if (myChooserComponent instanceof ListWithFilter) { + JList list = ((ListWithFilter)myChooserComponent).getList(); if (list.getSelectedIndex() == -1 && myAutoselect) { list.setSelectedIndex(0); } @@ -193,8 +202,8 @@ public class PopupChooserBuilder { } final JScrollPane scrollPane; - if (myChooserComponent instanceof MyListWrapper) { - scrollPane = (MyListWrapper)myChooserComponent; + if (myChooserComponent instanceof ListWithFilter) { + scrollPane = ((ListWithFilter)myChooserComponent).getScrollPane(); } else if (myChooserComponent instanceof JTable) { scrollPane = createScrollPane((JTable)myChooserComponent); @@ -207,7 +216,13 @@ public class PopupChooserBuilder { } scrollPane.getViewport().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - contentPane.add(scrollPane, BorderLayout.CENTER); + + if (myChooserComponent instanceof ListWithFilter) { + contentPane.add(myChooserComponent, BorderLayout.CENTER); + } + else { + contentPane.add(scrollPane, BorderLayout.CENTER); + } if (mySouthComponent != null) { contentPane.add(mySouthComponent, BorderLayout.SOUTH); @@ -224,7 +239,9 @@ public class PopupChooserBuilder { builder.setDimensionServiceKey(null, myDimensionServiceKey, false).setRequestFocus(myRequestFocus).setResizable(myForceResizable) .setMovable(myForceMovable).setTitle(myForceMovable ? myTitle : null).setCancelCallback(myCancelCallback).setAlpha(myAlpha) - .setFocusOwners(myFocusOwners).setCancelKeyEnabled(myCancelKeyEnabled).setAdText(myAd).setKeyboardActions(myKeyboardActions); + .setFocusOwners(myFocusOwners).setCancelKeyEnabled(myCancelKeyEnabled && !(myChooserComponent instanceof ListWithFilter)). + setAdText(myAd).setKeyboardActions(myKeyboardActions); + if (myCommandButton != null) { builder.setCommandButton(myCommandButton); } @@ -252,6 +269,9 @@ public class PopupChooserBuilder { private void registerClosePopupKeyboardAction(final KeyStroke keyStroke, final boolean shouldPerformAction) { myChooserComponent.registerKeyboardAction(new AbstractAction() { public void actionPerformed(ActionEvent e) { + if (!shouldPerformAction && myChooserComponent instanceof ListWithFilter) { + if (((ListWithFilter)myChooserComponent).resetFilter()) return; + } closePopup(shouldPerformAction, null); } }, keyStroke, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); diff --git a/platform/platform-api/src/com/intellij/ui/speedSearch/FilteringListModel.java b/platform/platform-api/src/com/intellij/ui/speedSearch/FilteringListModel.java new file mode 100644 index 000000000000..65a4b8de91bd --- /dev/null +++ b/platform/platform-api/src/com/intellij/ui/speedSearch/FilteringListModel.java @@ -0,0 +1,85 @@ +/* + * Copyright 2000-2009 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @author max + */ +package com.intellij.ui.speedSearch; + +import com.intellij.openapi.util.Condition; + +import javax.swing.*; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; + +/** + * @author max + */ +public class FilteringListModel extends DefaultListModel { + private final JList myList; + private final ListModel myOriginalModel; + private Condition myCondition = null; + + private final ListDataListener myListDataListener = new ListDataListener() { + public void contentsChanged(ListDataEvent e) { + refilter(); + } + + public void intervalAdded(ListDataEvent e) { + refilter(); + } + + public void intervalRemoved(ListDataEvent e) { + refilter(); + } + }; + + protected FilteringListModel(JList list) { + myList = list; + myOriginalModel = list.getModel(); + myOriginalModel.addListDataListener(myListDataListener); + refilter(); + + list.setModel(this); + } + + public void dispose() { + myOriginalModel.removeListDataListener(myListDataListener); + } + + public void setFilter(Condition condition) { + myCondition = condition; + refilter(); + } + + public void refilter() { + removeAllElements(); + for (int i = 0; i < myOriginalModel.getSize(); i++) { + final T elt = (T)myOriginalModel.getElementAt(i); + if (passElement(elt)) { + addToFiltered(elt); + } + } + } + + protected void addToFiltered(T elt) { + addElement(elt); + } + + private boolean passElement(T element) { + return myCondition == null || myCondition.value(element); + } +} diff --git a/platform/platform-api/src/com/intellij/ui/speedSearch/ListWithFilter.java b/platform/platform-api/src/com/intellij/ui/speedSearch/ListWithFilter.java new file mode 100644 index 000000000000..92db1c060fcc --- /dev/null +++ b/platform/platform-api/src/com/intellij/ui/speedSearch/ListWithFilter.java @@ -0,0 +1,137 @@ +/* + * Copyright 2000-2009 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @author max + */ +package com.intellij.ui.speedSearch; + +import com.intellij.openapi.util.Condition; +import com.intellij.ui.LightColors; +import com.intellij.util.Function; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; + +public class ListWithFilter extends JPanel { + private final JList myList; + private final JTextField mySpeedSearchPatternField; + private final NameFilteringListModel myModel; + private final JScrollPane myScroller; + private final MySpeedSearch mySpeedSearch; + + public static JComponent wrap(JList list) { + return wrap(list, new JScrollPane(list), new Function() { + public String fun(Object o) { + return o.toString(); + } + }); + } + + public static JComponent wrap(JList list, JScrollPane scroller, Function namer) { + return new ListWithFilter(list, scroller, namer); + } + + private ListWithFilter(JList list, JScrollPane scroller, Function namer) { + super(new BorderLayout()); + + myList = list; + myScroller = scroller; + + mySpeedSearchPatternField = new JTextField(); + mySpeedSearchPatternField.setFocusable(false); + mySpeedSearchPatternField.setVisible(false); + + add(mySpeedSearchPatternField, BorderLayout.NORTH); + add(myScroller, BorderLayout.CENTER); + + mySpeedSearch = new MySpeedSearch(); + mySpeedSearch.setEnabled(namer != null); + + myList.addKeyListener(new KeyAdapter() { + public void keyPressed(final KeyEvent e) { + mySpeedSearch.process(e); + } + }); + + myModel = new NameFilteringListModel(myList, namer, new Condition() { + public boolean value(String s) { + return mySpeedSearch.shouldBeShowing(s); + } + }, mySpeedSearch); + + setBackground(list.getBackground()); + setFocusable(true); + } + + public boolean resetFilter() { + boolean hadPattern = mySpeedSearch.isHoldingFilter(); + mySpeedSearch.reset(); + return hadPattern; + } + + private class MySpeedSearch extends SpeedSearch { + boolean searchFieldShown = false; + + protected void update() { + mySpeedSearchPatternField.setBackground(new JTextField().getBackground()); + onSpeedSearchPatternChanged(); + mySpeedSearchPatternField.setText(getFilter()); + if (isHoldingFilter() && !searchFieldShown) { + mySpeedSearchPatternField.setVisible(true); + searchFieldShown = true; + revalidate(); + } + else if (!isHoldingFilter() && searchFieldShown) { + mySpeedSearchPatternField.setVisible(false); + searchFieldShown = false; + revalidate(); + } + } + } + + protected void onSpeedSearchPatternChanged() { + myModel.refilter(); + if (myModel.getSize() > 0) { + int fullMatchIndex = myModel.getClosestMatchIndex(); + if (fullMatchIndex != -1) { + myList.setSelectedIndex(fullMatchIndex); + } + + if (myModel.getSize() <= myList.getSelectedIndex() || !myModel.contains(myList.getSelectedValue())) { + myList.setSelectedIndex(0); + } + } + else { + mySpeedSearchPatternField.setBackground(LightColors.RED); + } + } + + public JList getList() { + return myList; + } + + public JScrollPane getScrollPane() { + return myScroller; + } + + @Override + public void requestFocus() { + myList.requestFocus(); + } +} diff --git a/platform/platform-api/src/com/intellij/ui/speedSearch/NameFilteringListModel.java b/platform/platform-api/src/com/intellij/ui/speedSearch/NameFilteringListModel.java new file mode 100644 index 000000000000..e1d5221ded56 --- /dev/null +++ b/platform/platform-api/src/com/intellij/ui/speedSearch/NameFilteringListModel.java @@ -0,0 +1,73 @@ +/* + * Copyright 2000-2009 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @author max + */ +package com.intellij.ui.speedSearch; + +import com.intellij.openapi.util.Condition; +import com.intellij.util.Function; + +import javax.swing.*; + +public class NameFilteringListModel extends FilteringListModel { + private final Function myNamer; + + private int myFullMatchIndex = -1; + private int myStartsWithIndex = -1; + private final SpeedSearch mySpeedSearch; + + public NameFilteringListModel(JList list, final Function namer, final Condition filter, + SpeedSearch speedSearch) { + super(list); + mySpeedSearch = speedSearch; + setFilter(namer != null ? new Condition() { + public boolean value(T t) { + return filter.value(namer.fun(t)); + } + } : null); + myNamer = namer; + } + + @Override + protected void addToFiltered(T elt) { + super.addToFiltered(elt); + + String filterString = mySpeedSearch.getFilter().toUpperCase(); + String candidateString = myNamer.fun(elt).toUpperCase(); + int index = size() - 1; + + if (myFullMatchIndex == -1 && filterString.equals(candidateString)) { + myFullMatchIndex = index; + } + + if (myStartsWithIndex == -1 && candidateString.startsWith(filterString)) { + myStartsWithIndex = index; + } + } + + @Override + public void refilter() { + myFullMatchIndex = -1; + myStartsWithIndex = -1; + super.refilter(); + } + + public int getClosestMatchIndex() { + return myFullMatchIndex != -1 ? myFullMatchIndex : myStartsWithIndex; + } +} diff --git a/platform/platform-impl/src/com/intellij/ide/actions/BaseShowRecentFilesAction.java b/platform/platform-impl/src/com/intellij/ide/actions/BaseShowRecentFilesAction.java index 723282ecf885..38c23a62bf5a 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/BaseShowRecentFilesAction.java +++ b/platform/platform-impl/src/com/intellij/ide/actions/BaseShowRecentFilesAction.java @@ -32,9 +32,9 @@ import com.intellij.openapi.vcs.FileStatusManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.ColoredListCellRenderer; import com.intellij.ui.LightColors; -import com.intellij.ui.ListSpeedSearch; import com.intellij.ui.SimpleTextAttributes; import com.intellij.util.ArrayUtil; +import com.intellij.util.Function; import com.intellij.util.IconUtil; import javax.swing.*; @@ -163,7 +163,7 @@ public abstract class BaseShowRecentFilesAction extends AnAction implements Dumb if (list.getModel().getSize() == 0) { list.clearSelection(); } - new MyListSpeedSearch(list); + list.setCellRenderer(new RecentFilesRenderer(project)); /* @@ -189,12 +189,17 @@ public abstract class BaseShowRecentFilesAction extends AnAction implements Dumb footerPanel.add(pathLabel); new PopupChooserBuilder(list). - setTitle(getTitle()). - setMovable(true). - setSouthComponent(footerPanel). - setItemChoosenCallback(runnable). - addAdditionalChooseKeystroke(getAdditionalSelectKeystroke()). - createPopup().showCenteredInCurrentWindow(project); + setTitle(getTitle()). + setMovable(true). + setSouthComponent(footerPanel). + setItemChoosenCallback(runnable). + addAdditionalChooseKeystroke(getAdditionalSelectKeystroke()). + setItemsNamer(new Function() { + public String fun(Object o) { + return o instanceof VirtualFile ? ((VirtualFile)o).getName() : ""; + } + }). + createPopup().showCenteredInCurrentWindow(project); } protected abstract String getTitle(); @@ -234,14 +239,4 @@ public abstract class BaseShowRecentFilesAction extends AnAction implements Dumb } } } - - private static class MyListSpeedSearch extends ListSpeedSearch { - public MyListSpeedSearch(JList list) { - super(list); - } - - protected String getElementText(Object element) { - return element instanceof VirtualFile ? ((VirtualFile)element).getName() : null; - } - } } diff --git a/plugins/git4idea/src/git4idea/GitContentRevision.java b/plugins/git4idea/src/git4idea/GitContentRevision.java index 4eeed34c70e3..e7cbb2a0ef92 100644 --- a/plugins/git4idea/src/git4idea/GitContentRevision.java +++ b/plugins/git4idea/src/git4idea/GitContentRevision.java @@ -17,6 +17,7 @@ package git4idea; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.FilePathImpl; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.ContentRevision; import com.intellij.openapi.vcs.changes.CurrentContentRevision; @@ -124,4 +125,14 @@ public class GitContentRevision implements ContentRevision { return CurrentContentRevision.create(file); } } + + public static ContentRevision createRevision(final VirtualFile file, final VcsRevisionNumber revisionNumber, final Project project) throws VcsException { + final FilePathImpl filePath = new FilePathImpl(file); + if (revisionNumber != null) { + return new GitContentRevision(filePath, (GitRevisionNumber)revisionNumber, project); + } + else { + return CurrentContentRevision.create(filePath); + } + } } diff --git a/plugins/git4idea/src/git4idea/annotate/GitFileAnnotation.java b/plugins/git4idea/src/git4idea/annotate/GitFileAnnotation.java index f3acc9783037..8dd5078bb87d 100644 --- a/plugins/git4idea/src/git4idea/annotate/GitFileAnnotation.java +++ b/plugins/git4idea/src/git4idea/annotate/GitFileAnnotation.java @@ -17,6 +17,9 @@ package git4idea.annotate; import com.intellij.openapi.editor.EditorGutterAction; import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.FileStatus; +import com.intellij.openapi.vcs.FileStatusListener; +import com.intellij.openapi.vcs.FileStatusManager; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.annotate.*; import com.intellij.openapi.vcs.history.VcsFileRevision; @@ -71,6 +74,9 @@ public class GitFileAnnotation implements FileAnnotation { * listener for file system events */ private final VirtualFileAdapter myFileListener; + + private final MyFileStatusListener myFileStatusListener; + /** * the virtual file for which annotations are generated */ @@ -134,9 +140,12 @@ public class GitFileAnnotation implements FileAnnotation { } }; VirtualFileManager.getInstance().addVirtualFileListener(myFileListener); + myFileStatusListener = new MyFileStatusListener(); + FileStatusManager.getInstance(myProject).addFileStatusListener(myFileStatusListener); } else { myFileListener = null; + myFileStatusListener = null; } } @@ -178,6 +187,7 @@ public class GitFileAnnotation implements FileAnnotation { public void dispose() { if (myMonitorFlag) { VirtualFileManager.getInstance().removeVirtualFileListener(myFileListener); + FileStatusManager.getInstance(myProject).removeFileStatusListener(myFileStatusListener); } } @@ -364,4 +374,23 @@ public class GitFileAnnotation implements FileAnnotation { return myAuthor; } } + + private class MyFileStatusListener implements FileStatusListener { + public void fileStatusesChanged() { + checkAndFire(); + } + + public void fileStatusChanged(@NotNull VirtualFile virtualFile) { + if (myFile.equals(virtualFile)) { + checkAndFire(); + } + } + + private void checkAndFire() { + // for the case of commit changes... remove annotation gutter + if (FileStatus.NOT_CHANGED.equals(FileStatusManager.getInstance(myProject).getStatus(myFile))) { + fireAnnotationChanged(); + } + } + } } diff --git a/plugins/git4idea/src/git4idea/changes/GitChangeProvider.java b/plugins/git4idea/src/git4idea/changes/GitChangeProvider.java index 2aa76be7ff06..d51ead92934e 100644 --- a/plugins/git4idea/src/git4idea/changes/GitChangeProvider.java +++ b/plugins/git4idea/src/git4idea/changes/GitChangeProvider.java @@ -15,17 +15,21 @@ */ package git4idea.changes; +import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; -import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.*; import com.intellij.openapi.vcs.changes.*; import com.intellij.openapi.vfs.VirtualFile; +import git4idea.GitContentRevision; +import git4idea.GitRevisionNumber; import git4idea.GitUtil; import git4idea.GitVcs; import org.jetbrains.annotations.NotNull; import java.util.Collection; import java.util.List; +import java.util.Set; /** * Git repository change provider @@ -53,14 +57,62 @@ public class GitChangeProvider implements ChangeProvider { final ProgressIndicator progress, final ChangeListManagerGate addGate) throws VcsException { Collection roots = GitUtil.gitRootsForPaths(dirtyScope.getAffectedContentRoots()); + + final MyNonChangedHolder holder = new MyNonChangedHolder(myProject, dirtyScope.getDirtyFilesNoExpand()); + for (VirtualFile root : roots) { ChangeCollector c = new ChangeCollector(myProject, dirtyScope, root); - for (Change file : c.changes()) { + final Collection changes = c.changes(); + holder.changed(changes); + for (Change file : changes) { builder.processChange(file, GitVcs.getKey()); } for (VirtualFile f : c.unversioned()) { builder.processUnversionedFile(f); } + holder.feedBuilder(builder); + } + } + + private static class MyNonChangedHolder { + private final Project myProject; + private final Set myDirty; + + private MyNonChangedHolder(final Project project, final Set dirty) { + myProject = project; + myDirty = dirty; + } + + public void changed(final Collection changes) { + for (Change change : changes) { + final FilePath beforePath = ChangesUtil.getBeforePath(change); + if (beforePath != null) { + myDirty.remove(beforePath); + } + final FilePath afterPath = ChangesUtil.getBeforePath(change); + if (afterPath != null) { + myDirty.remove(afterPath); + } + } + } + + public void feedBuilder(final ChangelistBuilder builder) throws VcsException { + final VcsKey gitKey = GitVcs.getKey(); + final ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myProject); + final FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance(); + for (FilePath filePath : myDirty) { + final VirtualFile vf = filePath.getVirtualFile(); + if (vf != null) { + if (fileDocumentManager.isFileModifiedAndDocumentUnsaved(vf)) { + final VirtualFile root = vcsManager.getVcsRootFor(vf); + if (root != null) { + final GitRevisionNumber beforeRevisionNumber = GitChangeUtils.loadRevision(myProject, root, "HEAD"); + builder.processChange(new Change(GitContentRevision.createRevision(vf, beforeRevisionNumber, myProject), + GitContentRevision.createRevision(vf, null, myProject), FileStatus.MODIFIED), gitKey); + } + } + } + } } } @@ -68,7 +120,7 @@ public class GitChangeProvider implements ChangeProvider { * {@inheritDoc} */ public boolean isModifiedDocumentTrackingRequired() { - return false; + return true; } /** diff --git a/plugins/git4idea/src/git4idea/checkin/GitConvertFilesDialog.form b/plugins/git4idea/src/git4idea/checkin/GitConvertFilesDialog.form index fe2f20272c16..d1fd50825e7f 100644 --- a/plugins/git4idea/src/git4idea/checkin/GitConvertFilesDialog.form +++ b/plugins/git4idea/src/git4idea/checkin/GitConvertFilesDialog.form @@ -1,6 +1,6 @@
- + @@ -34,21 +34,13 @@ - + - - - - - - - - diff --git a/plugins/git4idea/src/git4idea/checkin/GitConvertFilesDialog.java b/plugins/git4idea/src/git4idea/checkin/GitConvertFilesDialog.java index 2c70b61edff6..dde324c18b17 100644 --- a/plugins/git4idea/src/git4idea/checkin/GitConvertFilesDialog.java +++ b/plugins/git4idea/src/git4idea/checkin/GitConvertFilesDialog.java @@ -19,6 +19,7 @@ import com.intellij.codeStyle.CodeStyleFacade; import com.intellij.openapi.fileEditor.impl.LoadTextUtil; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.util.Ref; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vfs.LocalFileSystem; @@ -29,15 +30,16 @@ import com.intellij.util.containers.HashMap; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.tree.TreeUtil; import git4idea.GitUtil; +import git4idea.GitVcs; import git4idea.commands.GitHandler; import git4idea.commands.GitSimpleHandler; import git4idea.commands.StringScanner; import git4idea.config.GitVcsSettings; +import git4idea.config.GitVersion; import git4idea.i18n.GitBundle; import javax.swing.*; import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; @@ -47,6 +49,14 @@ import java.util.*; * This dialog allows converting the specified files before committing them. */ public class GitConvertFilesDialog extends DialogWrapper { + /** + * The version when option --stdin was added + */ + private static final GitVersion CHECK_ATTR_STDIN_SUPPORTED = new GitVersion(1, 6, 1, 0); + /** + * Do not convert exit code + */ + public static final int DO_NOT_CONVERT = NEXT_USER_EXIT_CODE; /** * The checkbox used to indicate that dialog should not be shown */ @@ -55,10 +65,6 @@ public class GitConvertFilesDialog extends DialogWrapper { * The root panel of the dialog */ private JPanel myRootPanel; - /** - * The checkbox that disables conversion of files - */ - private JCheckBox myDoNotConvertFilesCheckBox; /** * The tree of files to convert */ @@ -73,7 +79,7 @@ public class GitConvertFilesDialog extends DialogWrapper { * * @param project the project to which this dialog is related */ - GitConvertFilesDialog(Project project, GitVcsSettings settings, Map> filesToShow) { + GitConvertFilesDialog(Project project, Map> filesToShow) { super(project, true); ArrayList roots = new ArrayList(filesToShow.keySet()); Collections.sort(roots, GitUtil.VIRTUAL_FILE_COMPARATOR); @@ -86,33 +92,18 @@ public class GitConvertFilesDialog extends DialogWrapper { vcsRoot.add(new CheckedTreeNode(file)); } } - myDoNotConvertFilesCheckBox.setSelected(settings.LINE_SEPARATORS_CONVERSION == GitVcsSettings.ConversionPolicy.NONE); - updateFields(); TreeUtil.expandAll(myFilesToConvert); - myDoNotConvertFilesCheckBox.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - updateFields(); - } - }); setTitle(GitBundle.getString("crlf.convert.title")); + setOKButtonText(GitBundle.getString("crlf.convert.convert")); init(); } - /** - * Update fields basing on selection state + * {@inheritDoc} */ - private void updateFields() { - if (myDoNotConvertFilesCheckBox.isSelected()) { - myRootNode.setChecked(false); - myFilesToConvert.setEnabled(false); - setOKButtonText(GitBundle.getString("crlf.convert.leave")); - } - else { - myFilesToConvert.setEnabled(true); - myRootNode.setChecked(true); - setOKButtonText(GitBundle.getString("crlf.convert.convert")); - } + @Override + protected Action[] createActions() { + return new Action[]{getOKAction(), new DoNotConvertAction(), getCancelAction()}; } @@ -199,27 +190,19 @@ public class GitConvertFilesDialog extends DialogWrapper { if (files.isEmpty()) { return true; } + final Ref selectedFiles = new Ref(); UIUtil.invokeAndWaitIfNeeded(new Runnable() { public void run() { - GitConvertFilesDialog d = new GitConvertFilesDialog(project, settings, files); + GitConvertFilesDialog d = new GitConvertFilesDialog(project, files); d.show(); if (d.isOK()) { settings.LINE_SEPARATORS_CONVERSION_ASK = d.myDoNotShowCheckBox.isSelected(); - if (d.myDoNotConvertFilesCheckBox.isSelected()) { - settings.LINE_SEPARATORS_CONVERSION = GitVcsSettings.ConversionPolicy.NONE; - } - else { - settings.LINE_SEPARATORS_CONVERSION = GitVcsSettings.ConversionPolicy.PROJECT_LINE_SEPARATORS; - for (VirtualFile f : d.myFilesToConvert.getCheckedNodes(VirtualFile.class, null)) { - try { - LoadTextUtil.changeLineSeparator(project, d, f, nl); - } - catch (IOException e) { - //noinspection ThrowableInstanceNeverThrown - exceptions.add(new VcsException("Failed to change line separators for the file: " + f.getPresentableUrl(), e)); - } - } - } + settings.LINE_SEPARATORS_CONVERSION = GitVcsSettings.ConversionPolicy.PROJECT_LINE_SEPARATORS; + selectedFiles.set(d.myFilesToConvert.getCheckedNodes(VirtualFile.class, null)); + } + else if (d.getExitCode() == DO_NOT_CONVERT) { + settings.LINE_SEPARATORS_CONVERSION_ASK = d.myDoNotShowCheckBox.isSelected(); + settings.LINE_SEPARATORS_CONVERSION = GitVcsSettings.ConversionPolicy.NONE; } else { //noinspection ThrowableInstanceNeverThrown @@ -227,6 +210,17 @@ public class GitConvertFilesDialog extends DialogWrapper { } } }); + if (selectedFiles.get() != null) { + for (VirtualFile f : selectedFiles.get()) { + try { + LoadTextUtil.changeLineSeparator(project, GitConvertFilesDialog.class.getName(), f, nl); + } + catch (IOException e) { + //noinspection ThrowableInstanceNeverThrown + exceptions.add(new VcsException("Failed to change line separators for the file: " + f.getPresentableUrl(), e)); + } + } + } } } catch (VcsException e) { @@ -243,10 +237,14 @@ public class GitConvertFilesDialog extends DialogWrapper { * @throws VcsException if there is problem with running git */ private static void ignoreFilesWithCrlfUnset(Project project, Map> files) throws VcsException { + boolean stdin = GitVcs.getInstance(project).version().isLessOrEqual(CHECK_ATTR_STDIN_SUPPORTED); for (final Map.Entry> e : files.entrySet()) { final VirtualFile r = e.getKey(); GitSimpleHandler h = new GitSimpleHandler(project, r, GitHandler.CHECK_ATTR); - h.addParameters("--stdin", "-z", "crlf"); + if (stdin) { + h.addParameters("--stdin", "-z"); + } + h.addParameters("crlf"); h.setSilent(true); h.setNoSSH(true); final HashMap filesToCheck = new HashMap(); @@ -254,33 +252,39 @@ public class GitConvertFilesDialog extends DialogWrapper { for (VirtualFile file : fileSet) { filesToCheck.put(GitUtil.relativePath(r, file), file); } - h.setInputProcessor(new Processor() { - public boolean process(OutputStream outputStream) { - try { - OutputStreamWriter out = new OutputStreamWriter(outputStream, GitUtil.UTF8_CHARSET); + if (stdin) { + h.setInputProcessor(new Processor() { + public boolean process(OutputStream outputStream) { try { - for (String file : filesToCheck.keySet()) { - out.write(file); - out.write("\u0000"); + OutputStreamWriter out = new OutputStreamWriter(outputStream, GitUtil.UTF8_CHARSET); + try { + for (String file : filesToCheck.keySet()) { + out.write(file); + out.write("\u0000"); + } + } + finally { + out.close(); } } - finally { - out.close(); + catch (IOException ex) { + try { + outputStream.close(); + } + catch (IOException ioe) { + // ignore exception + } } + return true; } - catch (IOException ex) { - try { - outputStream.close(); - } - catch (IOException ioe) { - // ignore exception - } - } - return true; - } - }); + }); + } + else { + h.endOptions(); + h.addRelativeFiles(filesToCheck.values()); + } StringScanner output = new StringScanner(h.run()); - String unsetIndicator = ": crlf unset"; + String unsetIndicator = ": crlf: unset"; while (output.hasMoreData()) { String l = output.line(); if (l.endsWith(unsetIndicator)) { @@ -290,6 +294,35 @@ public class GitConvertFilesDialog extends DialogWrapper { } } + /** + * Action used to indicate that no conversion should be performed + */ + class DoNotConvertAction extends AbstractAction { + private static final long serialVersionUID = 1931383640152023206L; + + /** + * The constructor + */ + DoNotConvertAction() { + putValue(NAME, GitBundle.getString("crlf.convert.leave")); + putValue(DEFAULT_ACTION, Boolean.FALSE); + } + + /** + * {@inheritDoc} + */ + public void actionPerformed(ActionEvent e) { + if (myPerformAction) return; + try { + myPerformAction = true; + close(DO_NOT_CONVERT); + } + finally { + myPerformAction = false; + } + } + } + /** * The cell renderer for the tree @@ -330,11 +363,12 @@ public class GitConvertFilesDialog extends DialogWrapper { /** * Render unknown node - * @param r a renderer to use + * + * @param r a renderer to use * @param value the unknown value */ private static void renderUnknown(ColoredTreeCellRenderer r, Object value) { - r.append("UNSUPPORTED NODE TYPE: "+(value == null?"null":value.getClass().getName()), SimpleTextAttributes.ERROR_ATTRIBUTES); + r.append("UNSUPPORTED NODE TYPE: " + (value == null ? "null" : value.getClass().getName()), SimpleTextAttributes.ERROR_ATTRIBUTES); } } } diff --git a/plugins/git4idea/src/git4idea/config/GitVcsPanel.java b/plugins/git4idea/src/git4idea/config/GitVcsPanel.java index b0852aa013b9..91596f9a0068 100644 --- a/plugins/git4idea/src/git4idea/config/GitVcsPanel.java +++ b/plugins/git4idea/src/git4idea/config/GitVcsPanel.java @@ -92,6 +92,7 @@ public class GitVcsPanel { mySSHExecutableComboBox.addItem(IDEA_SSH); mySSHExecutableComboBox.addItem(NATIVE_SSH); mySSHExecutableComboBox.setSelectedItem(GitVcsSettings.isDefaultIdeaSsh() ? IDEA_SSH : NATIVE_SSH); + myAskBeforeConversionsCheckBox.setSelected(mySettings.LINE_SEPARATORS_CONVERSION_ASK); myTestButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { testConnection(); @@ -175,7 +176,8 @@ public class GitVcsPanel { public boolean isModified(@NotNull GitVcsSettings settings) { return !settings.GIT_EXECUTABLE.equals(myGitField.getText()) || (settings.isIdeaSsh() != IDEA_SSH.equals(mySSHExecutableComboBox.getSelectedItem())) || - !crlfPolicyItem(settings).equals(myConvertTextFilesComboBox.getSelectedItem()); + !crlfPolicyItem(settings).equals(myConvertTextFilesComboBox.getSelectedItem()) || + settings.LINE_SEPARATORS_CONVERSION_ASK != myAskBeforeConversionsCheckBox.isSelected(); } /** @@ -188,13 +190,16 @@ public class GitVcsPanel { settings.setIdeaSsh(IDEA_SSH.equals(mySSHExecutableComboBox.getSelectedItem())); Object policyItem = myConvertTextFilesComboBox.getSelectedItem(); GitVcsSettings.ConversionPolicy conversionPolicy; - if(CRLF_DO_NOT_CONVERT.equals(policyItem)) { + if (CRLF_DO_NOT_CONVERT.equals(policyItem)) { conversionPolicy = GitVcsSettings.ConversionPolicy.NONE; - } else if (CRLF_CONVERT_TO_PROJECT.equals(policyItem)) { + } + else if (CRLF_CONVERT_TO_PROJECT.equals(policyItem)) { conversionPolicy = GitVcsSettings.ConversionPolicy.PROJECT_LINE_SEPARATORS; - } else { - throw new IllegalStateException("Unknown selected CRLF policy: "+policyItem); + } + else { + throw new IllegalStateException("Unknown selected CRLF policy: " + policyItem); } settings.LINE_SEPARATORS_CONVERSION = conversionPolicy; + settings.LINE_SEPARATORS_CONVERSION_ASK = myAskBeforeConversionsCheckBox.isSelected(); } }