From bb7a497ff0b0b98e265e5c86db985d630f4704d6 Mon Sep 17 00:00:00 2001 From: Kirill Likhodedov Date: Fri, 13 Aug 2010 17:28:19 +0400 Subject: [PATCH 1/8] [Mercurial] Optimized multiple addition and removals (based on a patch from Pepijn Van Eeckhoudt ). Added HgMockVcsHelper to be able to substitute dialogs during VCS operations, added 2 tests in HgDeleteTestCase using this helper. --- .../hg4idea/src/org/zmlx/hg4idea/HgUtil.java | 38 +++- .../src/org/zmlx/hg4idea/HgVFSListener.java | 152 +++++++++------ .../zmlx/hg4idea/command/HgAddCommand.java | 33 +++- .../zmlx/hg4idea/command/HgRemoveCommand.java | 34 +++- .../zmlx/hg4idea/test/HgAbstractTestCase.java | 21 ++- .../hg4idea/test/HgCollaborativeTestCase.java | 4 +- .../zmlx/hg4idea/test/HgDeleteTestCase.java | 50 +++++ .../zmlx/hg4idea/test/HgMockVcsHelper.java | 174 ++++++++++++++++++ .../hg4idea/test/HgSingleUserTestCase.java | 4 +- .../hg4idea/test/HgTestChangeListManager.java | 26 ++- .../zmlx/hg4idea/test/VcsHelperListener.java | 24 +++ 11 files changed, 459 insertions(+), 101 deletions(-) create mode 100644 plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgMockVcsHelper.java create mode 100644 plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/VcsHelperListener.java diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/HgUtil.java b/plugins/hg4idea/src/org/zmlx/hg4idea/HgUtil.java index 0536b9fa0bbc..669a03062a5d 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/HgUtil.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/HgUtil.java @@ -20,6 +20,7 @@ import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.GuiUtils; +import com.intellij.util.containers.HashMap; import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -28,10 +29,13 @@ import org.zmlx.hg4idea.command.HgWorkingCopyRevisionsCommand; import java.io.*; import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.Map; /** - * TODO JavaDoc. + * HgUtil is a collection of static utility methods for Mercurial. */ public abstract class HgUtil { @@ -225,14 +229,9 @@ public abstract class HgUtil { return vf; } - /** - * Gets the Mercurial root for the given file or throws a VcsException if non exists: - * the root should not only be in directory mappings, but also the .hg repository folder should exist. - * @see #getHgRootOrNull(com.intellij.openapi.project.Project, com.intellij.openapi.vcs.FilePath) - */ @NotNull - public static VirtualFile getHgRootOrThrow(Project project, VirtualFile vf) throws VcsException { - return getHgRootOrThrow(project, VcsUtil.getFilePath(vf.getPath())); + public static VirtualFile getHgRootOrThrow(Project project, VirtualFile file) throws VcsException { + return getHgRootOrThrow(project, VcsUtil.getFilePath(file.getPath())); } /** @@ -246,5 +245,26 @@ public abstract class HgUtil { public static boolean isMergeInProgress(@NotNull Project project, VirtualFile repository) { return new HgWorkingCopyRevisionsCommand(project).parents(repository).size() > 1; } - + /** + * Groups the given files by their Mercurial repositories and returns the map of relative paths to files for each repository. + * @param hgFiles files to be grouped. + * @return key is repository, values is the non-empty list of relative paths to files, which belong to this repository. + */ + @NotNull + public static Map> getRelativePathsByRepository(Collection hgFiles) { + final Map> map = new HashMap>(); + if (hgFiles == null) { + return map; + } + for(HgFile file : hgFiles) { + final VirtualFile repo = file.getRepo(); + List files = map.get(repo); + if (files == null) { + files = new ArrayList(); + map.put(repo, files); + } + files.add(file.getRelativePath()); + } + return map; + } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/HgVFSListener.java b/plugins/hg4idea/src/org/zmlx/hg4idea/HgVFSListener.java index 0c5141f6525d..7ad4704f9206 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/HgVFSListener.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/HgVFSListener.java @@ -15,6 +15,8 @@ */ package org.zmlx.hg4idea; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.*; import com.intellij.openapi.vcs.changes.ChangeListManagerImpl; @@ -22,6 +24,7 @@ import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.ui.VcsBackgroundTask; import com.intellij.vcsUtil.VcsUtil; +import org.jetbrains.annotations.NotNull; import org.zmlx.hg4idea.command.*; import java.util.*; @@ -82,24 +85,45 @@ public class HgVFSListener extends VcsVFSListener { } @Override - protected void performAdding(Collection addedFiles, final Map copyFromMap) { - (new VcsBackgroundTask(myProject, - HgVcsMessages.message("hg4idea.add.progress"), - VcsConfiguration.getInstance(myProject).getAddRemoveOption(), - addedFiles) { - protected void process(final VirtualFile file) throws VcsException { - if (file.isDirectory()) { - return; - } - final VirtualFile copyFrom = copyFromMap.get(file); - if (copyFrom != null) { - (new HgCopyCommand(myProject)).execute(new HgFile(myProject, copyFrom), new HgFile(myProject, file)); - } else { - (new HgAddCommand(myProject)).execute(new HgFile(myProject, file)); - } - dirtyScopeManager.fileDirty(file); - } + protected void performAdding(final Collection addedFiles, final Map copyFromMap) { + (new Task.ConditionalModal(myProject, + HgVcsMessages.message("hg4idea.add.progress"), + false, + VcsConfiguration.getInstance(myProject).getAddRemoveOption() ) { + @Override public void run(@NotNull ProgressIndicator aProgressIndicator) { + final ArrayList adds = new ArrayList(); + final HashMap copies = new HashMap(); // from -> to + // separate adds from copies + for (VirtualFile file : addedFiles) { + if (file.isDirectory()) { + continue; + } + + final VirtualFile copyFrom = copyFromMap.get(file); + if (copyFrom != null) { + copies.put(new HgFile(myProject, copyFrom), new HgFile(myProject, file)); + } else { + adds.add(new HgFile(myProject, file)); + } + } + + // add for all files at once + if (!adds.isEmpty()) { + new HgAddCommand(myProject).execute(adds); + } + + // copy needs to be run for each file separately + if (!copies.isEmpty()) { + for(Map.Entry copy : copies.entrySet()) { + new HgCopyCommand(myProject).execute(copy.getKey(), copy.getValue()); + } + } + + for (VirtualFile file : addedFiles) { + dirtyScopeManager.fileDirty(file); + } + } }).queue(); } @@ -118,6 +142,29 @@ public class HgVFSListener extends VcsVFSListener { return HgVcsMessages.message("hg4idea.remove.single.body"); } + @Override + protected VcsDeleteType needConfirmDeletion(VirtualFile file) { + //// newly added files (which were added to the repo but never committed) should be removed from the VCS, + //// but without user confirmation. + final FilePath filePath = VcsUtil.getFilePath(file.getPath()); + final VirtualFile repo = HgUtil.getHgRootOrNull(myProject, filePath); + if (repo == null) { + return super.needConfirmDeletion(file); + } + final HgFile hgFile = new HgFile(repo, filePath); + + final HgLogCommand logCommand = new HgLogCommand(myProject); + logCommand.setLogFile(true); + logCommand.setFollowCopies(false); + logCommand.setIncludeRemoved(true); + final List localRevisions = logCommand.execute(hgFile, -1, true); + // file is newly added, if it doesn't have a history or if the last history action was deleting this file. + if (localRevisions == null || localRevisions.isEmpty() || localRevisions.get(0).getDeletedFiles().contains(hgFile.getRelativePath())) { + return VcsDeleteType.SILENT; + } + return VcsDeleteType.CONFIRM; + } + protected void executeDelete() { final List filesToDelete = new ArrayList(myDeletedWithoutConfirmFiles); final List deletedFiles = new ArrayList(myDeletedFiles); @@ -125,33 +172,9 @@ public class HgVFSListener extends VcsVFSListener { myDeletedFiles.clear(); // skip unversioned files and files which are not under Mercurial - final List unversionedFilePaths = new ArrayList(); - for (VirtualFile vf : ChangeListManagerImpl.getInstanceImpl(myProject).getUnversionedFiles()) { - unversionedFilePaths.add(VcsUtil.getFilePath(vf.getPath())); - } - skipUnversionedAndNotUnderHg(unversionedFilePaths, filesToDelete); - skipUnversionedAndNotUnderHg(unversionedFilePaths, deletedFiles); - - // newly added files (which were added to the repo but never committed should be removed from the VCS, - // but without user confirmation. - for (Iterator it = deletedFiles.iterator(); it.hasNext(); ) { - final FilePath filePath = it.next(); - final HgLogCommand logCommand = new HgLogCommand(myProject); - logCommand.setLogFile(true); - logCommand.setFollowCopies(false); - logCommand.setIncludeRemoved(true); - final VirtualFile repo = HgUtil.getHgRootOrNull(myProject, filePath); - if (repo == null) { - continue; - } - final HgFile hgFile = new HgFile(repo, filePath); - final List localRevisions = logCommand.execute(hgFile, -1, true); - // file is newly added, if it doesn't have a history or if the last history action was deleting this file. - if (localRevisions == null || localRevisions.isEmpty() || localRevisions.get(0).getDeletedFiles().contains(hgFile.getRelativePath())) { - it.remove(); - filesToDelete.add(filePath); - } - } + final ChangeListManagerImpl changeListManager = ChangeListManagerImpl.getInstanceImpl(myProject); + skipUnversionedAndNotUnderHg(changeListManager, filesToDelete); + skipUnversionedAndNotUnderHg(changeListManager, deletedFiles); // confirm removal from the VCS if needed if (myRemoveOption.getValue() != VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY) { @@ -172,34 +195,43 @@ public class HgVFSListener extends VcsVFSListener { } /** - * Changes the given collection of files by filtering out unversioned files, - * files which are not under Mercurial repository, and - * newly added files (which were added to the repo, but never committed). - * @param unversionedFiles unversioned files retrieved from the ChangeListManager. - * Passing as a parameter not to transform List to List twice. + * Changes the given collection of files by filtering out unversioned files and + * files which are not under Mercurial repository. + * @param changeListManager instance of the ChangeListManagerImpl to retrieve unversioned files from it. * @param filesToFilter files to be filtered. */ - private void skipUnversionedAndNotUnderHg(Collection unversionedFiles, Collection filesToFilter) { + private void skipUnversionedAndNotUnderHg(ChangeListManagerImpl changeListManager, Collection filesToFilter) { for (Iterator iter = filesToFilter.iterator(); iter.hasNext(); ) { final FilePath filePath = iter.next(); - if (HgUtil.getHgRootOrNull(myProject, filePath) == null || unversionedFiles.contains(filePath)) { + if (HgUtil.getHgRootOrNull(myProject, filePath) == null || changeListManager.isUnversioned(filePath.getVirtualFile())) { iter.remove(); } } } @Override - protected void performDeletion(List filesToDelete) { - (new VcsBackgroundTask(myProject, + protected void performDeletion( final List filesToDelete) { + (new Task.ConditionalModal(myProject, HgVcsMessages.message("hg4idea.remove.progress"), - VcsConfiguration.getInstance(myProject).getAddRemoveOption(), - filesToDelete) { - protected void process(final FilePath file) throws VcsException { - if (file.isDirectory()) { - return; + false, + VcsConfiguration.getInstance(myProject).getAddRemoveOption()) { + @Override public void run( @NotNull ProgressIndicator aProgressIndicator ) { + final ArrayList deletes = new ArrayList(); + for (FilePath file : filesToDelete) { + if (file.isDirectory()) { + continue; + } + + deletes.add(new HgFile(VcsUtil.getVcsRootFor(myProject, file), file)); + } + + if (!deletes.isEmpty()) { + new HgRemoveCommand(myProject).execute(deletes); + } + + for (HgFile file : deletes) { + dirtyScopeManager.fileDirty(file.toFilePath()); } - (new HgRemoveCommand(myProject)).execute(new HgFile(VcsUtil.getVcsRootFor(myProject, file), file)); - dirtyScopeManager.fileDirty(file); } }).queue(); diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgAddCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgAddCommand.java index 41566988ceb5..680c7f129c87 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgAddCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgAddCommand.java @@ -13,22 +13,43 @@ package org.zmlx.hg4idea.command; import com.intellij.openapi.project.Project; -import org.zmlx.hg4idea.HgFile; +import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; +import org.zmlx.hg4idea.HgFile; +import org.zmlx.hg4idea.HgUtil; import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +/** + * A wrapper for 'hg add' command. + */ public class HgAddCommand { - private final Project project; + private final Project myProject; public HgAddCommand(Project project) { - this.project = project; + myProject = project; } - public void execute(@NotNull HgFile hgFile) { - HgCommandService.getInstance(project) - .execute(hgFile.getRepo(), "add", Arrays.asList(hgFile.getRelativePath())); + /** + * Adds given files to their Mercurial repositories. + * @param hgFiles files to be added. + */ + public void execute(@NotNull HgFile... hgFiles) { + execute(Arrays.asList(hgFiles)); + } + + /** + * Adds given files to their Mercurial repositories. + * @param hgFiles files to be added. + */ + public void execute(@NotNull Collection hgFiles) { + for(Map.Entry> entry : HgUtil.getRelativePathsByRepository(hgFiles).entrySet()) { + HgCommandService.getInstance(myProject).execute(entry.getKey(), "add", entry.getValue()); + } } } diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRemoveCommand.java b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRemoveCommand.java index b89b85bcdec2..9e08ac08a3aa 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRemoveCommand.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/command/HgRemoveCommand.java @@ -13,21 +13,45 @@ package org.zmlx.hg4idea.command; import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; import org.zmlx.hg4idea.HgFile; +import org.zmlx.hg4idea.HgUtil; import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +/** + * A wrapper for the 'hg remove' command. + */ public class HgRemoveCommand { - private final Project project; + private final Project myProject; public HgRemoveCommand(Project project) { - this.project = project; + myProject = project; } - public void execute(HgFile hgFile) { - HgCommandService.getInstance(project) - .execute(hgFile.getRepo(), "remove", Arrays.asList("--after", hgFile.getRelativePath())); + /** + * Removes given files from their Mercurial repositories. + * @param hgFiles files to be removed. + */ + public void execute(@NotNull HgFile... hgFiles) { + execute(Arrays.asList(hgFiles)); + } + + /** + * Removes given files from their Mercurial repositories. + * @param hgFiles files to be removed. + */ + public void execute(@NotNull Collection hgFiles) { + for( Map.Entry> entry : HgUtil.getRelativePathsByRepository(hgFiles).entrySet()) { + List filePaths = entry.getValue(); + filePaths.add(0, "--after"); + HgCommandService.getInstance(myProject).execute(entry.getKey(), "remove", filePaths); + } } } diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgAbstractTestCase.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgAbstractTestCase.java index 6e92e76262d6..799189d51715 100644 --- a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgAbstractTestCase.java +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgAbstractTestCase.java @@ -14,6 +14,7 @@ package org.zmlx.hg4idea.test; import com.intellij.execution.process.ProcessOutput; import com.intellij.openapi.application.PluginPathManager; +import com.intellij.openapi.vcs.AbstractVcsHelper; import com.intellij.openapi.vcs.VcsConfiguration; import com.intellij.openapi.vcs.VcsShowConfirmationOption; import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager; @@ -22,6 +23,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.testFramework.AbstractVcsTestCase; import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.Nullable; +import org.picocontainer.MutablePicoContainer; import org.testng.annotations.BeforeMethod; import org.zmlx.hg4idea.HgFile; import org.zmlx.hg4idea.HgVcs; @@ -64,14 +66,18 @@ public abstract class HgAbstractTestCase extends AbstractVcsTestCase { myTraceClient = true; } - protected void enableSilentOperation(final VcsConfiguration.StandardConfirmation op) { + protected void doActionSilently(final VcsConfiguration.StandardConfirmation op) { setStandardConfirmation(HgVcs.VCS_NAME, op, VcsShowConfirmationOption.Value.DO_ACTION_SILENTLY); } - protected void disableSilentOperation(final VcsConfiguration.StandardConfirmation op) { + protected void doNothingSilently(final VcsConfiguration.StandardConfirmation op) { setStandardConfirmation(HgVcs.VCS_NAME, op, VcsShowConfirmationOption.Value.DO_NOTHING_SILENTLY); } + protected void showConfirmation(final VcsConfiguration.StandardConfirmation op) { + setStandardConfirmation(HgVcs.VCS_NAME, op, VcsShowConfirmationOption.Value.SHOW_CONFIRMATION); + } + /** * Runs the hg command. * @@ -111,8 +117,17 @@ public abstract class HgAbstractTestCase extends AbstractVcsTestCase { } return new HgFile(myWorkingCopyDir, fileToInclude); } - + /** + * Registers HgMockVcsHelper as the AbstractVcsHelper. + */ + protected HgMockVcsHelper registerMockVcsHelper() { + final String key = "com.intellij.openapi.vcs.AbstractVcsHelper"; + final MutablePicoContainer picoContainer = (MutablePicoContainer) myProject.getPicoContainer(); + picoContainer.unregisterComponent(key); + picoContainer.registerComponentImplementation(key, HgMockVcsHelper.class); + return (HgMockVcsHelper) AbstractVcsHelper.getInstance(myProject); + } protected VirtualFile makeFile(File file) throws IOException { file.createNewFile(); diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgCollaborativeTestCase.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgCollaborativeTestCase.java index 3c82f4cddf76..d157fd45b874 100644 --- a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgCollaborativeTestCase.java +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgCollaborativeTestCase.java @@ -45,8 +45,8 @@ public class HgCollaborativeTestCase extends HgAbstractTestCase { activateVCS(HgVcs.VCS_NAME); myChangeListManager = new HgTestChangeListManager(myProject); - enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); - enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + doActionSilently(VcsConfiguration.StandardConfirmation.ADD); + doActionSilently(VcsConfiguration.StandardConfirmation.REMOVE); } } diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgDeleteTestCase.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgDeleteTestCase.java index 070cf7ea14e9..f75746effe60 100644 --- a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgDeleteTestCase.java +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgDeleteTestCase.java @@ -12,12 +12,15 @@ // limitations under the License. package org.zmlx.hg4idea.test; +import com.intellij.openapi.vcs.VcsConfiguration; import com.intellij.openapi.vfs.VirtualFile; import org.testng.Assert; import org.testng.annotations.Test; import java.io.File; +import static org.testng.Assert.fail; + public class HgDeleteTestCase extends HgSingleUserTestCase { @Test @@ -62,4 +65,51 @@ public class HgDeleteTestCase extends HgSingleUserTestCase { verify(runHgOnProjectRepo("status"), HgTestOutputParser.removed("com", "a.txt")); } + /** + * When deleting a file which was newly added to repository, this file shouldn't be prompted for removal from the repository. + * 1. Create a file and add it to the repository. + * 2. Remove the file from disk. + * 3. File shouldn't be prompted for removal from repository. + */ + @Test + public void testNewlyAddedFileShouldNotBePromptedForRemoval() throws Exception { + showConfirmation(VcsConfiguration.StandardConfirmation.REMOVE); + final VirtualFile vf = createFileInCommand("a.txt", null); + final HgMockVcsHelper helper = registerMockVcsHelper(); + helper.addListener(new VcsHelperListener() { + @Override + public void dialogInvoked() { + fail("No dialog should be invoked, because newly added file should be silently removed from the repository"); + } + }); + deleteFileInCommand(vf); + } + + /** + * A file is also considered to be newly added, if it has a history, but the last action was removal of that file. + * 1. Create a file, add it to the repository and commit. + * 2. Delete the file and commit it. + * 3. Create the file again and add it to the repository. + * 4. Delete the file. + * 5. File shouldn't be prompted for removal from repository. + */ + @Test + public void testJustDeletedAndThenAddedFileShouldNotBePromptedForRemoval() throws Exception { + VirtualFile vf = createFileInCommand("a.txt", null); + myChangeListManager.commitFiles(vf); + deleteFileInCommand(vf); + myChangeListManager.commitFiles(vf); + + showConfirmation(VcsConfiguration.StandardConfirmation.REMOVE); + vf = createFileInCommand("a.txt", null); + final HgMockVcsHelper helper = registerMockVcsHelper(); + helper.addListener(new VcsHelperListener() { + @Override + public void dialogInvoked() { + fail("No dialog should be invoked, because newly added file should be silently removed from the repository"); + } + }); + deleteFileInCommand(myProject, vf); + } + } diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgMockVcsHelper.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgMockVcsHelper.java new file mode 100644 index 000000000000..fff46294849d --- /dev/null +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgMockVcsHelper.java @@ -0,0 +1,174 @@ +/* + * Copyright 2000-2010 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. + */ +package org.zmlx.hg4idea.test; + +import com.intellij.ide.errorTreeView.HotfixData; +import com.intellij.openapi.vcs.*; +import com.intellij.openapi.vcs.annotate.AnnotationProvider; +import com.intellij.openapi.vcs.annotate.FileAnnotation; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vcs.history.VcsFileRevision; +import com.intellij.openapi.vcs.history.VcsHistoryProvider; +import com.intellij.openapi.vcs.merge.MergeProvider; +import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings; +import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.openapi.vfs.VirtualFile; +import gnu.trove.THashSet; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; +import java.io.File; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * Substitutes AbstractVcsHelperImpl for tests, where dialogs need to be tested. + * Currently it's just a stub implementation notifying listeners about action invoked (which would mean than a dialog would have been + * shown during normal execution). + * @author Kirill Likhodedov + */ +public class HgMockVcsHelper extends AbstractVcsHelper { + + private Collection myListeners = new THashSet(); + + @Override + public void showErrors(List abstractVcsExceptions, @NotNull String tabDisplayName) { + } + + @Override + public void showErrors(Map> exceptionGroups, @NotNull String tabDisplayName) { + } + + @Override + public List runTransactionRunnable(AbstractVcs vcs, TransactionRunnable runnable, Object vcsParameters) { + return null; + } + + @Override + public void showAnnotation(FileAnnotation annotation, VirtualFile file, AbstractVcs vcs) { + } + + @Override + public void showDifferences(VcsFileRevision cvsVersionOn, VcsFileRevision cvsVersionOn1, File file) { + } + + @Override + public void showChangesListBrowser(CommittedChangeList changelist, @Nls String title) { + } + + @Override + public void showChangesBrowser(List changelists) { + } + + @Override + public void showChangesBrowser(List changelists, @Nls String title) { + } + + @Override + public void showChangesBrowser(CommittedChangesProvider provider, + RepositoryLocation location, + @Nls String title, + @Nullable Component parent) { + } + + @Override + public void showWhatDiffersBrowser(@Nullable Component parent, Collection changes, @Nls String title) { + } + + @Override + public T chooseCommittedChangeList(CommittedChangesProvider provider, + RepositoryLocation location) { + return null; + } + + @Override + public void openCommittedChangesTab(AbstractVcs vcs, VirtualFile root, ChangeBrowserSettings settings, int maxCount, String title) { + } + + @Override + public void openCommittedChangesTab(CommittedChangesProvider provider, + RepositoryLocation location, + ChangeBrowserSettings settings, + int maxCount, + String title) { + } + + @NotNull + @Override + public List showMergeDialog(List files, MergeProvider provider) { + return null; + } + + @NotNull + @Override + public List showMergeDialog(List files) { + return null; + } + + @Override + public void showFileHistory(VcsHistoryProvider vcsHistoryProvider, FilePath path, AbstractVcs vcs, String repositoryPath) { + } + + @Override + public void showFileHistory(VcsHistoryProvider vcsHistoryProvider, + AnnotationProvider annotationProvider, + FilePath path, + String repositoryPath, + AbstractVcs vcs) { + } + + @Override + public void showRollbackChangesDialog(List changes) { + } + + @Nullable + @Override + public Collection selectFilesToProcess(List files, + String title, + @Nullable String prompt, + String singleFileTitle, + String singleFilePromptTemplate, + VcsShowConfirmationOption confirmationOption) { + notifyListeners(); + return null; + } + + @Nullable + @Override + public Collection selectFilePathsToProcess(List files, + String title, + @Nullable String prompt, + String singleFileTitle, + String singleFilePromptTemplate, + VcsShowConfirmationOption confirmationOption) { + notifyListeners(); + return null; + } + + public void addListener(VcsHelperListener listener) { + myListeners.add(listener); + } + + private void notifyListeners() { + for (VcsHelperListener listener : myListeners) { + listener.dialogInvoked(); + } + } + +} diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgSingleUserTestCase.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgSingleUserTestCase.java index f0247b2a6a91..acad6f4f3ff7 100644 --- a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgSingleUserTestCase.java +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgSingleUserTestCase.java @@ -42,8 +42,8 @@ public class HgSingleUserTestCase extends HgAbstractTestCase { activateVCS(HgVcs.VCS_NAME); myChangeListManager = new HgTestChangeListManager(myProject); - enableSilentOperation(VcsConfiguration.StandardConfirmation.ADD); - enableSilentOperation(VcsConfiguration.StandardConfirmation.REMOVE); + doActionSilently(VcsConfiguration.StandardConfirmation.ADD); + doActionSilently(VcsConfiguration.StandardConfirmation.REMOVE); } } \ No newline at end of file diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgTestChangeListManager.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgTestChangeListManager.java index 96bbc3ff97db..56134378c35c 100644 --- a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgTestChangeListManager.java +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/HgTestChangeListManager.java @@ -24,7 +24,6 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.containers.HashSet; import org.testng.Assert; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -50,6 +49,7 @@ public class HgTestChangeListManager { */ public void addUnversionedFilesToVcs(VirtualFile... files) { peer.addUnversionedFiles(peer.getDefaultChangeList(), Arrays.asList(files)); + ensureUpToDate(); } /** @@ -59,7 +59,7 @@ public class HgTestChangeListManager { * @param files Files to be checked. */ public void checkFilesAreInList(boolean only, VirtualFile... files) { - peer.ensureUpToDate(false); + ensureUpToDate(); final Collection changes = peer.getDefaultChangeList().getChanges(); if (only) { @@ -78,6 +78,7 @@ public class HgTestChangeListManager { * Commits all changes of the given files. */ public void commitFiles(VirtualFile... files) { + ensureUpToDate(); final List changes = new ArrayList(files.length); for (VirtualFile f : files) { changes.addAll(peer.getChangesIn(f)); @@ -86,20 +87,17 @@ public class HgTestChangeListManager { assertNotNull(list); list.setComment("A comment to a commit"); Assert.assertTrue(peer.commitChangesSynchronouslyWithResult(list, changes)); + ensureUpToDate(); } - public void removeFiles(final VirtualFile file) { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - try { - file.delete(this); - } - catch (IOException e) { - e.printStackTrace(); - } - } - }); + /** + * Ensures the ChangelistManager is up to date. + * It is called after each operation in the HgTestChangeListManager. + */ + public void ensureUpToDate() { + if (!ApplicationManager.getApplication().isDispatchThread()) { // for dispatch thread no need to force update. + peer.ensureUpToDate(false); + } } } diff --git a/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/VcsHelperListener.java b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/VcsHelperListener.java new file mode 100644 index 000000000000..1ed9ee540ca8 --- /dev/null +++ b/plugins/hg4idea/testSrc/org/zmlx/hg4idea/test/VcsHelperListener.java @@ -0,0 +1,24 @@ +/* + * Copyright 2000-2010 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. + */ +package org.zmlx.hg4idea.test; + +/** + * Listens to events in HgMockVcsHelper. + * @author Kirill Likhodedov + */ +public interface VcsHelperListener { + void dialogInvoked(); +} From 088f8aed709f2135df59fc3b7c8f6928084bc0d5 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Wed, 1 Sep 2010 11:40:14 +0400 Subject: [PATCH 2/8] Typos --- .../openapi/vfs/impl/local/LocalFileSystemImpl.java | 8 ++++---- .../intellij/openapi/vfs/newvfs/RefreshSessionImpl.java | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemImpl.java index fc25cc78b99f..10c098936fe0 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemImpl.java @@ -229,14 +229,14 @@ public final class LocalFileSystemImpl extends LocalFileSystemBase implements Ap } } - public void markSuspicousFilesDirty(List files) { + public void markSuspiciousFilesDirty(List files) { storeRefreshStatusToFiles(); if (myWatcher.isOperational()) { for (String root : myWatcher.getManualWatchRoots()) { - final VirtualFile suspicousRoot = findFileByPathIfCached(root); - if (suspicousRoot != null) { - ((NewVirtualFile)suspicousRoot).markDirtyRecursively(); + final VirtualFile suspiciousRoot = findFileByPathIfCached(root); + if (suspiciousRoot != null) { + ((NewVirtualFile)suspiciousRoot).markDirtyRecursively(); } } } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshSessionImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshSessionImpl.java index 51960ce66391..9bc2b617623e 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshSessionImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshSessionImpl.java @@ -45,9 +45,8 @@ public class RefreshSessionImpl extends RefreshSession { private final Semaphore mySemaphore = new Semaphore(); private volatile boolean iHaveEventsToFire; - public RefreshSessionImpl(final boolean isAsync, boolean reqursively, - final Runnable finishRunnable) { - myIsRecursive = reqursively; + public RefreshSessionImpl(final boolean isAsync, final boolean recursively, final Runnable finishRunnable) { + myIsRecursive = recursively; myFinishRunnable = finishRunnable; myIsAsync = isAsync; } @@ -83,7 +82,7 @@ public class RefreshSessionImpl extends RefreshSession { boolean hasEventsToFire = myFinishRunnable != null || !myEvents.isEmpty(); if (!workQueue.isEmpty()) { - ((LocalFileSystemImpl)LocalFileSystem.getInstance()).markSuspicousFilesDirty(workQueue); + ((LocalFileSystemImpl)LocalFileSystem.getInstance()).markSuspiciousFilesDirty(workQueue); for (VirtualFile file : workQueue) { final NewVirtualFile nvf = (NewVirtualFile)file; From d818597e213cd6a81bcc8943eaa604235b7d020c Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Wed, 1 Sep 2010 13:53:42 +0400 Subject: [PATCH 3/8] New Java parser - process comments and whitespaces on parsing --- .../lang/java/parser/DeclarationParser.java | 38 ++++---- .../intellij/lang/java/parser/FileParser.java | 52 +++++++--- .../intellij/lang/java/parser/JavaParser.java | 5 +- .../lang/java/parser/JavaParserUtil.java | 81 +++++++++++++++- .../lang/java/parser/StatementParser.java | 96 +++++++++---------- .../psi/impl/source/parsing/ParseUtil.java | 4 - .../lang/java/parser/JavaParsingTestCase.java | 11 +-- .../lang/WhitespacesAndCommentsProcessor.java | 13 ++- .../intellij/lang/impl/PsiBuilderImpl.java | 54 +++++++---- .../intellij/lang/LightPsiBuilderTest.java | 4 +- .../openapi/util/text/StringUtil.java | 9 ++ 11 files changed, 245 insertions(+), 122 deletions(-) diff --git a/java/java-impl/src/com/intellij/lang/java/parser/DeclarationParser.java b/java/java-impl/src/com/intellij/lang/java/parser/DeclarationParser.java index 0bd4cf719fd7..35c5d379cefd 100644 --- a/java/java-impl/src/com/intellij/lang/java/parser/DeclarationParser.java +++ b/java/java-impl/src/com/intellij/lang/java/parser/DeclarationParser.java @@ -116,7 +116,7 @@ public class DeclarationParser { } } - declaration.done(JavaElementType.CLASS); + done(declaration, JavaElementType.CLASS); return declaration; } @@ -162,10 +162,10 @@ public class DeclarationParser { if (builder.getTokenType() == JavaTokenType.LBRACE) { final PsiBuilder.Marker constantInit = builder.mark(); parseClassBodyWithBraces(builder, false, false); - constantInit.done(JavaElementType.ENUM_CONSTANT_INITIALIZER); + done(constantInit, JavaElementType.ENUM_CONSTANT_INITIALIZER); } - constant.done(JavaElementType.ENUM_CONSTANT); + done(constant, JavaElementType.ENUM_CONSTANT); return constant; } else { @@ -309,7 +309,7 @@ public class DeclarationParser { final PsiBuilder.Marker error = typeParams.precede(); error.errorBefore(JavaErrorMessages.message("unexpected.token"), codeBlock); } - declaration.done(JavaElementType.CLASS_INITIALIZER); + done(declaration, JavaElementType.CLASS_INITIALIZER); return declaration; } else { @@ -391,7 +391,7 @@ public class DeclarationParser { } } - modList.done(JavaElementType.MODIFIER_LIST); + done(modList, JavaElementType.MODIFIER_LIST); return Pair.create(modList, isEmpty); } @@ -405,7 +405,7 @@ public class DeclarationParser { final PsiBuilder.Marker receiver = builder.mark(); final PsiBuilder.Marker annotations = parseAnnotations(builder); if (annotations != null) { - receiver.done(JavaElementType.METHOD_RECEIVER); + done(receiver, JavaElementType.METHOD_RECEIVER); } else { receiver.drop(); @@ -441,7 +441,7 @@ public class DeclarationParser { } } - declaration.done(anno ? JavaElementType.ANNOTATION_METHOD : JavaElementType.METHOD); + done(declaration, anno ? JavaElementType.ANNOTATION_METHOD : JavaElementType.METHOD); return declaration; } @@ -528,7 +528,7 @@ public class DeclarationParser { invalidElements.error(errorMessage); } - paramList.done(JavaElementType.PARAMETER_LIST); + done(paramList, JavaElementType.PARAMETER_LIST); return paramList; } @@ -552,7 +552,7 @@ public class DeclarationParser { if (expect(builder, JavaTokenType.IDENTIFIER)) { eatBrackets(builder); - param.done(JavaElementType.PARAMETER); + done(param, JavaElementType.PARAMETER); return param; } else { @@ -603,7 +603,7 @@ public class DeclarationParser { } if (builder.getTokenType() != JavaTokenType.COMMA) break; - variable.done(varType); + done(variable, varType); builder.advanceLexer(); if (builder.getTokenType() != JavaTokenType.IDENTIFIER) { @@ -642,7 +642,7 @@ public class DeclarationParser { } if (openMarker) { - variable.done(varType); + done(variable, varType); } return declaration; @@ -683,7 +683,7 @@ public class DeclarationParser { parseAnnotationParameterList(builder); - anno.done(JavaElementType.ANNOTATION); + done(anno, JavaElementType.ANNOTATION); return anno; } @@ -692,12 +692,12 @@ public class DeclarationParser { PsiBuilder.Marker list = builder.mark(); if (!expect(builder, JavaTokenType.LPARENTH)) { - list.done(JavaElementType.ANNOTATION_PARAMETER_LIST); + done(list, JavaElementType.ANNOTATION_PARAMETER_LIST); return list; } if (expect(builder, JavaTokenType.RPARENTH)) { - list.done(JavaElementType.ANNOTATION_PARAMETER_LIST); + done(list, JavaElementType.ANNOTATION_PARAMETER_LIST); return list; } @@ -739,7 +739,7 @@ public class DeclarationParser { } } - list.done(JavaElementType.ANNOTATION_PARAMETER_LIST); + done(list, JavaElementType.ANNOTATION_PARAMETER_LIST); return list; } @@ -749,7 +749,7 @@ public class DeclarationParser { if (mayBeSimple) { parseAnnotationValue(builder); if (builder.getTokenType() != JavaTokenType.EQ) { - pair.done(JavaElementType.NAME_VALUE_PAIR); + done(pair, JavaElementType.NAME_VALUE_PAIR); return false; } @@ -763,7 +763,7 @@ public class DeclarationParser { parseAnnotationValue(builder); - pair.done(JavaElementType.NAME_VALUE_PAIR); + done(pair, JavaElementType.NAME_VALUE_PAIR); return hasName; } @@ -798,7 +798,7 @@ public class DeclarationParser { builder.advanceLexer(); if (expect(builder, JavaTokenType.RBRACE)) { - annoArray.done(JavaElementType.ANNOTATION_ARRAY_INITIALIZER); + done(annoArray, JavaElementType.ANNOTATION_ARRAY_INITIALIZER); return annoArray; } @@ -819,7 +819,7 @@ public class DeclarationParser { } } - annoArray.done(JavaElementType.ANNOTATION_ARRAY_INITIALIZER); + done(annoArray, JavaElementType.ANNOTATION_ARRAY_INITIALIZER); if (unclosed) { annoArray.setCustomEdgeProcessors(null, GREEDY_RIGHT_EDGE_PROCESSOR); } diff --git a/java/java-impl/src/com/intellij/lang/java/parser/FileParser.java b/java/java-impl/src/com/intellij/lang/java/parser/FileParser.java index f55fde673840..329ae1f7ed09 100644 --- a/java/java-impl/src/com/intellij/lang/java/parser/FileParser.java +++ b/java/java-impl/src/com/intellij/lang/java/parser/FileParser.java @@ -27,38 +27,61 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import static com.intellij.lang.PsiBuilderUtil.expect; +import static com.intellij.lang.java.parser.JavaParserUtil.done; import static com.intellij.lang.java.parser.JavaParserUtil.semicolon; public class FileParser { + public interface TopLevelDeclarationParser { + @Nullable PsiBuilder.Marker parse(PsiBuilder builder); + } + private static final TokenSet IMPORT_LIST_STOPPER_SET = TokenSet.orSet( ElementType.MODIFIER_BIT_SET, TokenSet.create(JavaTokenType.CLASS_KEYWORD, JavaTokenType.INTERFACE_KEYWORD, JavaTokenType.ENUM_KEYWORD, JavaTokenType.AT)); + private static final TopLevelDeclarationParser TOP_LEVEL_DECLARATION_PARSER = new TopLevelDeclarationParser() { + public PsiBuilder.Marker parse(final PsiBuilder builder) { + return DeclarationParser.parse(builder, DeclarationParser.Context.FILE); + } + }; + private FileParser() { } public static void parse(final PsiBuilder builder) { + parseFile(builder, IMPORT_LIST_STOPPER_SET, TOP_LEVEL_DECLARATION_PARSER, JavaErrorMessages.message("expected.class.or.interface")); + } + + public static void parseFile(final PsiBuilder builder, final TokenSet importListStoppers, + final TopLevelDeclarationParser declarationParser, final String errorMessage) { parsePackageStatement(builder); - parseImportList(builder); + final Pair impListInfo = parseImportList(builder, importListStoppers); + Boolean firstDeclarationOk = null; + PsiBuilder.Marker firstDeclaration = null; PsiBuilder.Marker invalidElements = null; while (!builder.eof()) { if (builder.getTokenType() == JavaTokenType.SEMICOLON) { if (invalidElements != null) { - invalidElements.error(JavaErrorMessages.message("expected.class.or.interface")); + invalidElements.error(errorMessage); invalidElements = null; } builder.advanceLexer(); + if (firstDeclarationOk == null) firstDeclarationOk = false; continue; } - final PsiBuilder.Marker declaration = DeclarationParser.parse(builder, DeclarationParser.Context.FILE); + final PsiBuilder.Marker declaration = declarationParser.parse(builder); if (declaration != null) { if (invalidElements != null) { - invalidElements.errorBefore(JavaErrorMessages.message("expected.class.or.interface"), declaration); + invalidElements.errorBefore(errorMessage, declaration); invalidElements = null; } + if (firstDeclarationOk == null) { + firstDeclarationOk = true; + firstDeclaration = declaration; + } continue; } @@ -66,10 +89,16 @@ public class FileParser { invalidElements = builder.mark(); } builder.advanceLexer(); + if (firstDeclarationOk == null) firstDeclarationOk = false; } if (invalidElements != null) { - invalidElements.error(JavaErrorMessages.message("expected.class.or.interface")); + invalidElements.error(errorMessage); + } + + if (impListInfo.second && firstDeclarationOk == Boolean.TRUE) { + impListInfo.first.setCustomEdgeProcessors(JavaParserUtil.PRECEDING_COMMENT_BINDER, null); // pass comments behind fake import list + firstDeclaration.setCustomEdgeProcessors(JavaParserUtil.SPECIAL_PRECEDING_COMMENT_BINDER, null); } } @@ -80,7 +109,7 @@ public class FileParser { if (!expect(builder, JavaTokenType.PACKAGE_KEYWORD)) { final PsiBuilder.Marker modList = builder.mark(); DeclarationParser.parseAnnotations(builder); - modList.done(JavaElementType.MODIFIER_LIST); + done(modList, JavaElementType.MODIFIER_LIST); if (!expect(builder, JavaTokenType.PACKAGE_KEYWORD)) { statement.rollbackTo(); return null; @@ -95,15 +124,10 @@ public class FileParser { semicolon(builder); - statement.done(JavaElementType.PACKAGE_STATEMENT); + done(statement, JavaElementType.PACKAGE_STATEMENT); return statement; } - @NotNull - private static Pair parseImportList(final PsiBuilder builder) { - return parseImportList(builder, IMPORT_LIST_STOPPER_SET); - } - @NotNull public static Pair parseImportList(final PsiBuilder builder, final TokenSet stoppers) { final PsiBuilder.Marker list = builder.mark(); @@ -134,7 +158,7 @@ public class FileParser { } } - list.done(JavaElementType.IMPORT_LIST); + done(list, JavaElementType.IMPORT_LIST); return Pair.create(list, isEmpty); } @@ -153,7 +177,7 @@ public class FileParser { semicolon(builder); } - statement.done(type); + done(statement, type); return statement; } } diff --git a/java/java-impl/src/com/intellij/lang/java/parser/JavaParser.java b/java/java-impl/src/com/intellij/lang/java/parser/JavaParser.java index 3bd61235b27d..dbe1ab0f5121 100644 --- a/java/java-impl/src/com/intellij/lang/java/parser/JavaParser.java +++ b/java/java-impl/src/com/intellij/lang/java/parser/JavaParser.java @@ -19,7 +19,6 @@ import com.intellij.lang.ASTNode; import com.intellij.lang.PsiBuilder; import com.intellij.lang.PsiParser; import com.intellij.pom.java.LanguageLevel; -import com.intellij.psi.impl.source.parsing.ParseUtil; import com.intellij.psi.tree.IElementType; import org.jetbrains.annotations.NotNull; @@ -41,8 +40,6 @@ public class JavaParser implements PsiParser { FileParser.parse(builder); root.done(rootType); - final ASTNode rootNode = builder.getTreeBuilt(); - ParseUtil.bindComments(rootNode); - return rootNode; + return builder.getTreeBuilt(); } } diff --git a/java/java-impl/src/com/intellij/lang/java/parser/JavaParserUtil.java b/java/java-impl/src/com/intellij/lang/java/parser/JavaParserUtil.java index e4120a033c54..d4c66df0241a 100644 --- a/java/java-impl/src/com/intellij/lang/java/parser/JavaParserUtil.java +++ b/java/java-impl/src/com/intellij/lang/java/parser/JavaParserUtil.java @@ -18,8 +18,12 @@ package com.intellij.lang.java.parser; import com.intellij.codeInsight.daemon.JavaErrorMessages; import com.intellij.lang.*; import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.JavaTokenType; +import com.intellij.psi.impl.source.tree.ElementType; +import com.intellij.psi.impl.source.tree.JavaDocElementType; +import com.intellij.psi.impl.source.tree.JavaElementType; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import com.intellij.util.diff.FlyweightCapableTreeStructure; @@ -34,11 +38,77 @@ public class JavaParserUtil { private static final Key LANG_LEVEL_KEY = Key.create("JavaParserUtil.LanguageLevel"); public static final WhitespacesAndCommentsProcessor GREEDY_RIGHT_EDGE_PROCESSOR = new WhitespacesAndCommentsProcessor() { - public int process(final List tokens) { + public int process(final List tokens, final boolean atStreamEdge, final TokenTextGetter getter) { return tokens.size(); } }; + private static class PrecedingWhitespacesAndCommentsProcessor implements WhitespacesAndCommentsProcessor { + private final boolean myAfterEmptyImport; + + public PrecedingWhitespacesAndCommentsProcessor(final boolean afterImport) { + this.myAfterEmptyImport = afterImport; + } + + public int process(final List tokens, final boolean atStreamEdge, final TokenTextGetter + getter) { + if (tokens.size() == 0) return 0; + + // 1. bind doc comment + for (int idx = tokens.size() - 1; idx >= 0; idx--) { + if (tokens.get(idx) == JavaDocElementType.DOC_COMMENT) return idx; + } + + // 2. bind plain comments + int result = tokens.size(); + for (int idx = tokens.size() - 1; idx >= 0; idx--) { + final IElementType tokenType = tokens.get(idx); + if (ElementType.JAVA_WHITESPACE_BIT_SET.contains(tokenType)) { + if (StringUtil.getLineBreakCount(getter.get(idx)) > 1) break; + } + else if (ElementType.JAVA_PLAIN_COMMENT_BIT_SET.contains(tokenType)) { + if (atStreamEdge || + (idx == 0 && myAfterEmptyImport) || + (idx > 0 && ElementType.JAVA_WHITESPACE_BIT_SET.contains(tokens.get(idx - 1)) && StringUtil.containsLineBreak(getter.get(idx - 1)))) { + result = idx; + } + } + else break; + } + + return result; + } + } + + private static class TrailingWhitespacesAndCommentsProcessor implements WhitespacesAndCommentsProcessor { + public int process(final List tokens, final boolean atStreamEdge, final TokenTextGetter getter) { + if (tokens.size() == 0) return 0; + + int result = 0; + for (int idx = 0; idx < tokens.size(); idx++) { + final IElementType tokenType = tokens.get(idx); + if (ElementType.JAVA_WHITESPACE_BIT_SET.contains(tokenType)) { + if (StringUtil.containsLineBreak(getter.get(idx))) break; + } + else if (ElementType.JAVA_PLAIN_COMMENT_BIT_SET.contains(tokenType)) { + result = idx + 1; + } + else break; + } + + return result; + } + } + + private static final TokenSet PRECEDING_COMMENT_SET = ElementType.FULL_MEMBER_BIT_SET; + private static final TokenSet TRAILING_COMMENT_SET = TokenSet.orSet( + TokenSet.create(JavaElementType.PACKAGE_STATEMENT), + ElementType.IMPORT_STATEMENT_BASE_BIT_SET, ElementType.FULL_MEMBER_BIT_SET, ElementType.JAVA_STATEMENT_BIT_SET); + + public static final WhitespacesAndCommentsProcessor PRECEDING_COMMENT_BINDER = new PrecedingWhitespacesAndCommentsProcessor(false); + public static final WhitespacesAndCommentsProcessor SPECIAL_PRECEDING_COMMENT_BINDER = new PrecedingWhitespacesAndCommentsProcessor(true); + public static final WhitespacesAndCommentsProcessor TRAILING_COMMENT_BINDER = new TrailingWhitespacesAndCommentsProcessor(); + private JavaParserUtil() { } public static void setLanguageLevel(final PsiBuilder builder, final LanguageLevel level) { @@ -57,12 +127,19 @@ public class JavaParserUtil { return level; } + public static void done(final PsiBuilder.Marker marker, final IElementType type) { + marker.done(type); + final WhitespacesAndCommentsProcessor left = PRECEDING_COMMENT_SET.contains(type) ? PRECEDING_COMMENT_BINDER : null; + final WhitespacesAndCommentsProcessor right = TRAILING_COMMENT_SET.contains(type) ? TRAILING_COMMENT_BINDER : null; + marker.setCustomEdgeProcessors(left, right); + } + @Nullable public static IElementType exprType(@Nullable final PsiBuilder.Marker marker) { return marker != null ? ((LighterASTNode)marker).getTokenType() : null; } - // used instead of PsiBuilder.error() as it drops all but first subsequent error messages + // used instead of PsiBuilder.error() as it keeps all subsequent error messages public static void error(final PsiBuilder builder, final String message) { builder.mark().error(message); } diff --git a/java/java-impl/src/com/intellij/lang/java/parser/StatementParser.java b/java/java-impl/src/com/intellij/lang/java/parser/StatementParser.java index 7d592df0e92e..b0cc604745a8 100644 --- a/java/java-impl/src/com/intellij/lang/java/parser/StatementParser.java +++ b/java/java-impl/src/com/intellij/lang/java/parser/StatementParser.java @@ -118,7 +118,7 @@ public class StatementParser { final boolean greedyBlock = !expectOrError(builder, JavaTokenType.RBRACE, JavaErrorMessages.message("expected.rbrace")); - codeBlock.done(JavaElementType.CODE_BLOCK); + done(codeBlock, JavaElementType.CODE_BLOCK); if (greedyBlock) { codeBlock.setCustomEdgeProcessors(null, GREEDY_RIGHT_EDGE_PROCESSOR); } @@ -214,7 +214,7 @@ public class StatementParser { else if (tokenType == JavaTokenType.SEMICOLON) { final PsiBuilder.Marker empty = builder.mark(); builder.advanceLexer(); - empty.done(JavaElementType.EMPTY_STATEMENT); + done(empty, JavaElementType.EMPTY_STATEMENT); return empty; } else if (tokenType == JavaTokenType.IDENTIFIER || tokenType == JavaTokenType.AT) { @@ -230,7 +230,7 @@ public class StatementParser { ReferenceParser.parseType(builder, false, false); error(builder, JavaErrorMessages.message("expected.identifier")); } - declStatement.done(JavaElementType.DECLARATION_STATEMENT); + done(declStatement, JavaElementType.DECLARATION_STATEMENT); return declStatement; } } @@ -255,15 +255,15 @@ public class StatementParser { } if (count > 1) { pos.drop(); - list.done(JavaElementType.EXPRESSION_LIST); + done(list, JavaElementType.EXPRESSION_LIST); semicolon(builder); - statement.done(JavaElementType.EXPRESSION_LIST_STATEMENT); + done(statement, JavaElementType.EXPRESSION_LIST_STATEMENT); return statement; } if (exprType(expr) != JavaElementType.REFERENCE_EXPRESSION) { drop(list, pos); semicolon(builder); - statement.done(JavaElementType.EXPRESSION_STATEMENT); + done(statement, JavaElementType.EXPRESSION_STATEMENT); return statement; } pos.rollbackTo(); @@ -275,7 +275,7 @@ public class StatementParser { final PsiBuilder.Marker decl = DeclarationParser.parse(builder, DeclarationParser.Context.CODE_BLOCK); if (decl != null) { final PsiBuilder.Marker statement = decl.precede(); - statement.done(JavaElementType.DECLARATION_STATEMENT); + done(statement, JavaElementType.DECLARATION_STATEMENT); return statement; } @@ -283,7 +283,7 @@ public class StatementParser { final PsiBuilder.Marker statement = builder.mark(); advance(builder, 2); parseStatement(builder); - statement.done(JavaElementType.LABELED_STATEMENT); + done(statement, JavaElementType.LABELED_STATEMENT); return statement; } @@ -291,7 +291,7 @@ public class StatementParser { final PsiBuilder.Marker statement = builder.mark(); ExpressionParser.parse(builder); semicolon(builder); - statement.done(JavaElementType.EXPRESSION_STATEMENT); + done(statement, JavaElementType.EXPRESSION_STATEMENT); return statement; } @@ -311,19 +311,19 @@ public class StatementParser { builder.advanceLexer(); if (!parseExpressionInParenth(builder)) { - statement.done(JavaElementType.IF_STATEMENT); + done(statement, JavaElementType.IF_STATEMENT); return statement; } final PsiBuilder.Marker thenStatement = parseStatement(builder); if (thenStatement == null) { error(builder, JavaErrorMessages.message("expected.statement")); - statement.done(JavaElementType.IF_STATEMENT); + done(statement, JavaElementType.IF_STATEMENT); return statement; } if (!expect(builder, JavaTokenType.ELSE_KEYWORD)) { - statement.done(JavaElementType.IF_STATEMENT); + done(statement, JavaElementType.IF_STATEMENT); return statement; } @@ -332,7 +332,7 @@ public class StatementParser { error(builder, JavaErrorMessages.message("expected.statement")); } - statement.done(JavaElementType.IF_STATEMENT); + done(statement, JavaElementType.IF_STATEMENT); return statement; } @@ -342,7 +342,7 @@ public class StatementParser { builder.advanceLexer(); if (!parseExpressionInParenth(builder)) { - statement.done(JavaElementType.WHILE_STATEMENT); + done(statement, JavaElementType.WHILE_STATEMENT); return statement; } @@ -351,7 +351,7 @@ public class StatementParser { error(builder, JavaErrorMessages.message("expected.statement")); } - statement.done(JavaElementType.WHILE_STATEMENT); + done(statement, JavaElementType.WHILE_STATEMENT); return statement; } @@ -362,7 +362,7 @@ public class StatementParser { if (!expect(builder, JavaTokenType.LPARENTH)) { error(builder, JavaErrorMessages.message("expected.lparen")); - statement.done(JavaElementType.FOR_STATEMENT); + done(statement, JavaElementType.FOR_STATEMENT); return statement; } @@ -384,7 +384,7 @@ public class StatementParser { if (init == null){ error(builder, JavaErrorMessages.message("expected.statement")); if (!expect(builder, JavaTokenType.RPARENTH)) { - statement.done(JavaElementType.FOR_STATEMENT); + done(statement, JavaElementType.FOR_STATEMENT); return statement; } } @@ -393,7 +393,7 @@ public class StatementParser { if (!expect(builder, JavaTokenType.SEMICOLON)) { error(builder, JavaErrorMessages.message("expected.semicolon")); if (!expect(builder, JavaTokenType.RPARENTH)) { - statement.done(JavaElementType.FOR_STATEMENT); + done(statement, JavaElementType.FOR_STATEMENT); return statement; } } @@ -401,7 +401,7 @@ public class StatementParser { parseExpressionOrExpressionList(builder); if (!expect(builder, JavaTokenType.RPARENTH)) { error(builder, JavaErrorMessages.message("expected.rparen")); - statement.done(JavaElementType.FOR_STATEMENT); + done(statement, JavaElementType.FOR_STATEMENT); return statement; } } @@ -412,7 +412,7 @@ public class StatementParser { error(builder, JavaErrorMessages.message("expected.statement")); } - statement.done(JavaElementType.FOR_STATEMENT); + done(statement, JavaElementType.FOR_STATEMENT); return statement; } @@ -423,7 +423,7 @@ public class StatementParser { final PsiBuilder.Marker expressionStatement; if (builder.getTokenType() != JavaTokenType.COMMA) { expressionStatement = expr.precede(); - expressionStatement.done(JavaElementType.EXPRESSION_STATEMENT); + done(expressionStatement, JavaElementType.EXPRESSION_STATEMENT); } else { final PsiBuilder.Marker expressionList = expr.precede(); @@ -438,8 +438,8 @@ public class StatementParser { } while (builder.getTokenType() == JavaTokenType.COMMA); - expressionList.done(JavaElementType.EXPRESSION_LIST); - expressionStatement.done(JavaElementType.EXPRESSION_LIST_STATEMENT); + done(expressionList, JavaElementType.EXPRESSION_LIST); + done(expressionStatement, JavaElementType.EXPRESSION_LIST_STATEMENT); } } @@ -462,7 +462,7 @@ public class StatementParser { error(builder, JavaErrorMessages.message("expected.rparen")); } - statement.done(JavaElementType.FOREACH_STATEMENT); + done(statement, JavaElementType.FOREACH_STATEMENT); return statement; } @@ -474,13 +474,13 @@ public class StatementParser { final PsiBuilder.Marker body = parseStatement(builder); if (body == null) { error(builder, JavaErrorMessages.message("expected.statement")); - statement.done(JavaElementType.DO_WHILE_STATEMENT); + done(statement, JavaElementType.DO_WHILE_STATEMENT); return statement; } if (!expect(builder, JavaTokenType.WHILE_KEYWORD)) { error(builder, JavaErrorMessages.message("expected.while")); - statement.done(JavaElementType.DO_WHILE_STATEMENT); + done(statement, JavaElementType.DO_WHILE_STATEMENT); return statement; } @@ -488,7 +488,7 @@ public class StatementParser { semicolon(builder); } - statement.done(JavaElementType.DO_WHILE_STATEMENT); + done(statement, JavaElementType.DO_WHILE_STATEMENT); return statement; } @@ -498,7 +498,7 @@ public class StatementParser { builder.advanceLexer(); if (!parseExpressionInParenth(builder)) { - statement.done(JavaElementType.SWITCH_STATEMENT); + done(statement, JavaElementType.SWITCH_STATEMENT); return statement; } @@ -507,7 +507,7 @@ public class StatementParser { error(builder, JavaErrorMessages.message("expected.lbrace")); } - statement.done(JavaElementType.SWITCH_STATEMENT); + done(statement, JavaElementType.SWITCH_STATEMENT); return statement; } @@ -527,7 +527,7 @@ public class StatementParser { expectOrError(builder, JavaTokenType.COLON, JavaErrorMessages.message("expected.colon")); - statement.done(JavaElementType.SWITCH_LABEL_STATEMENT); + done(statement, JavaElementType.SWITCH_LABEL_STATEMENT); return statement; } @@ -537,7 +537,7 @@ public class StatementParser { builder.advanceLexer(); expect(builder, JavaTokenType.IDENTIFIER); semicolon(builder); - statement.done(JavaElementType.BREAK_STATEMENT); + done(statement, JavaElementType.BREAK_STATEMENT); return statement; } @@ -547,7 +547,7 @@ public class StatementParser { builder.advanceLexer(); expect(builder, JavaTokenType.IDENTIFIER); semicolon(builder); - statement.done(JavaElementType.CONTINUE_STATEMENT); + done(statement, JavaElementType.CONTINUE_STATEMENT); return statement; } @@ -559,7 +559,7 @@ public class StatementParser { ExpressionParser.parse(builder); semicolon(builder); - statement.done(JavaElementType.RETURN_STATEMENT); + done(statement, JavaElementType.RETURN_STATEMENT); return statement; } @@ -571,12 +571,12 @@ public class StatementParser { final PsiBuilder.Marker expr = ExpressionParser.parse(builder); if (expr == null) { error(builder, JavaErrorMessages.message("expected.expression")); - statement.done(JavaElementType.THROW_STATEMENT); + done(statement, JavaElementType.THROW_STATEMENT); return statement; } semicolon(builder); - statement.done(JavaElementType.THROW_STATEMENT); + done(statement, JavaElementType.THROW_STATEMENT); return statement; } @@ -586,7 +586,7 @@ public class StatementParser { builder.advanceLexer(); if (!parseExpressionInParenth(builder)) { - statement.done(JavaElementType.SYNCHRONIZED_STATEMENT); + done(statement, JavaElementType.SYNCHRONIZED_STATEMENT); return statement; } @@ -595,7 +595,7 @@ public class StatementParser { error(builder, JavaErrorMessages.message("expected.lbrace")); } - statement.done(JavaElementType.SYNCHRONIZED_STATEMENT); + done(statement, JavaElementType.SYNCHRONIZED_STATEMENT); return statement; } @@ -607,13 +607,13 @@ public class StatementParser { final PsiBuilder.Marker tryBlock = parseCodeBlock(builder); if (tryBlock == null) { error(builder, JavaErrorMessages.message("expected.lbrace")); - statement.done(JavaElementType.TRY_STATEMENT); + done(statement, JavaElementType.TRY_STATEMENT); return statement; } if (!TRY_CLOSERS_SET.contains(builder.getTokenType())) { error(builder, JavaErrorMessages.message("expected.catch.or.finally")); - statement.done(JavaElementType.TRY_STATEMENT); + done(statement, JavaElementType.TRY_STATEMENT); return statement; } @@ -628,7 +628,7 @@ public class StatementParser { } } - statement.done(JavaElementType.TRY_STATEMENT); + done(statement, JavaElementType.TRY_STATEMENT); return statement; } @@ -638,7 +638,7 @@ public class StatementParser { if (!expect(builder, JavaTokenType.LPARENTH)) { error(builder, JavaErrorMessages.message("expected.lparen")); - section.done(JavaElementType.CATCH_SECTION); + done(section, JavaElementType.CATCH_SECTION); return false; } @@ -649,18 +649,18 @@ public class StatementParser { if (!expect(builder, JavaTokenType.RPARENTH)) { error(builder, JavaErrorMessages.message("expected.rparen")); - section.done(JavaElementType.CATCH_SECTION); + done(section, JavaElementType.CATCH_SECTION); return false; } final PsiBuilder.Marker body = parseCodeBlock(builder); if (body == null) { error(builder, JavaErrorMessages.message("expected.lbrace")); - section.done(JavaElementType.CATCH_SECTION); + done(section, JavaElementType.CATCH_SECTION); return false; } - section.done(JavaElementType.CATCH_SECTION); + done(section, JavaElementType.CATCH_SECTION); return true; } @@ -672,7 +672,7 @@ public class StatementParser { final PsiBuilder.Marker expr = ExpressionParser.parse(builder); if (expr == null) { error(builder, JavaErrorMessages.message("expected.boolean.expression")); - statement.done(JavaElementType.ASSERT_STATEMENT); + done(statement, JavaElementType.ASSERT_STATEMENT); return statement; } @@ -680,13 +680,13 @@ public class StatementParser { final PsiBuilder.Marker expr2 = ExpressionParser.parse(builder); if (expr2 == null) { error(builder, JavaErrorMessages.message("expected.expression")); - statement.done(JavaElementType.ASSERT_STATEMENT); + done(statement, JavaElementType.ASSERT_STATEMENT); return statement; } } semicolon(builder); - statement.done(JavaElementType.ASSERT_STATEMENT); + done(statement, JavaElementType.ASSERT_STATEMENT); return statement; } @@ -694,7 +694,7 @@ public class StatementParser { private static PsiBuilder.Marker parseBlockStatement(final PsiBuilder builder) { final PsiBuilder.Marker statement = builder.mark(); parseCodeBlock(builder); - statement.done(JavaElementType.BLOCK_STATEMENT); + done(statement, JavaElementType.BLOCK_STATEMENT); return statement; } diff --git a/java/java-impl/src/com/intellij/psi/impl/source/parsing/ParseUtil.java b/java/java-impl/src/com/intellij/psi/impl/source/parsing/ParseUtil.java index 3809e11a8245..72d1a07d4010 100644 --- a/java/java-impl/src/com/intellij/psi/impl/source/parsing/ParseUtil.java +++ b/java/java-impl/src/com/intellij/psi/impl/source/parsing/ParseUtil.java @@ -52,10 +52,6 @@ public class ParseUtil extends ParseUtilBase { inserter.invoke(); } - public static void bindComments(final ASTNode root) { - JavaMissingTokenInserter.bindComments(root); - } - private static class JavaMissingTokenInserter extends MissingTokenInserter { public JavaMissingTokenInserter(final CompositeElement root, final Lexer lexer, diff --git a/java/java-tests/testSrc/com/intellij/lang/java/parser/JavaParsingTestCase.java b/java/java-tests/testSrc/com/intellij/lang/java/parser/JavaParsingTestCase.java index be6205d0a724..06be1084123e 100644 --- a/java/java-tests/testSrc/com/intellij/lang/java/parser/JavaParsingTestCase.java +++ b/java/java-tests/testSrc/com/intellij/lang/java/parser/JavaParsingTestCase.java @@ -23,13 +23,9 @@ import com.intellij.openapi.fileTypes.StdFileTypes; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.LanguageLevelProjectExtension; import com.intellij.pom.java.LanguageLevel; -import com.intellij.psi.FileViewProvider; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiManager; -import com.intellij.psi.SingleRootFileViewProvider; +import com.intellij.psi.*; import com.intellij.psi.impl.JavaPsiFacadeEx; import com.intellij.psi.impl.source.PsiJavaFileImpl; -import com.intellij.psi.impl.source.parsing.ParseUtil; import com.intellij.psi.impl.source.tree.FileElement; import com.intellij.psi.tree.IFileElementType; import com.intellij.testFramework.IdeaTestCase; @@ -97,7 +93,6 @@ public abstract class JavaParsingTestCase extends ParsingTestCase { root.done(this); final ASTNode rootNode = builder.getTreeBuilt(); - ParseUtil.bindComments(rootNode); return rootNode.getFirstChildNode(); } }; @@ -116,7 +111,9 @@ public abstract class JavaParsingTestCase extends ParsingTestCase { } private static PsiBuilder createBuilder(final ASTNode chameleon) { - final Project project = chameleon.getPsi().getProject(); + final PsiElement psi = chameleon.getPsi(); + assert psi != null : chameleon; + final Project project = psi.getProject(); final PsiBuilderFactory factory = PsiBuilderFactory.getInstance(); final PsiBuilder builder = factory.createBuilder(project, chameleon, chameleon.getElementType().getLanguage(), chameleon.getChars()); diff --git a/platform/lang-api/src/com/intellij/lang/WhitespacesAndCommentsProcessor.java b/platform/lang-api/src/com/intellij/lang/WhitespacesAndCommentsProcessor.java index 31a79b7b0f79..d9830d2992d7 100644 --- a/platform/lang-api/src/com/intellij/lang/WhitespacesAndCommentsProcessor.java +++ b/platform/lang-api/src/com/intellij/lang/WhitespacesAndCommentsProcessor.java @@ -27,6 +27,13 @@ import java.util.List; * @see com.intellij.lang.PsiBuilder.Marker#setCustomEdgeProcessors(WhitespacesAndCommentsProcessor, WhitespacesAndCommentsProcessor) */ public interface WhitespacesAndCommentsProcessor { + /** + * Provides an ability for the processor to get a text of any of given tokens. + */ + interface TokenTextGetter { + CharSequence get(int i); + } + /** *

Analyzes whitespace and comment tokens at element's edge and returns element's edge position relative to these tokens. * Value returned by left edge processor will be used as a pointer to a first token of element. @@ -41,7 +48,9 @@ public interface WhitespacesAndCommentsProcessor { * whitespace or comment after element's end. * * @param tokens sequence of whitespace and comment tokens at the element's edge. - * @return position of element's edge relative to given tokens + * @param atStreamEdge true if sequence of tokens is located at the beginning or the end of token stream. + * @param getter token text getter. + * @return position of element's edge relative to given tokens. */ - int process(List tokens); + int process(List tokens, boolean atStreamEdge, TokenTextGetter getter); } diff --git a/platform/lang-impl/src/com/intellij/lang/impl/PsiBuilderImpl.java b/platform/lang-impl/src/com/intellij/lang/impl/PsiBuilderImpl.java index de5fb9cb590a..260e5ce3e2b6 100644 --- a/platform/lang-impl/src/com/intellij/lang/impl/PsiBuilderImpl.java +++ b/platform/lang-impl/src/com/intellij/lang/impl/PsiBuilderImpl.java @@ -40,7 +40,10 @@ import com.intellij.psi.tree.*; import com.intellij.util.CharTable; import com.intellij.util.IncorrectOperationException; import com.intellij.util.ThreeState; -import com.intellij.util.containers.*; +import com.intellij.util.containers.CollectionFactory; +import com.intellij.util.containers.Convertor; +import com.intellij.util.containers.LimitedPool; +import com.intellij.util.containers.Stack; import com.intellij.util.diff.DiffTree; import com.intellij.util.diff.DiffTreeChangeBuilder; import com.intellij.util.diff.FlyweightCapableTreeStructure; @@ -104,13 +107,13 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder { }); private static final WhitespacesAndCommentsProcessor DEFAULT_LEFT_EDGE_PROCESSOR = new WhitespacesAndCommentsProcessor() { - public int process(final List tokens) { + public int process(final List tokens, final boolean atStreamEdge, final TokenTextGetter getter) { return tokens.size(); } }; private static final WhitespacesAndCommentsProcessor DEFAULT_RIGHT_EDGE_PROCESSOR = new WhitespacesAndCommentsProcessor() { - public int process(final List tokens) { + public int process(final List tokens, final boolean atStreamEdge, final TokenTextGetter getter) { return 0; } }; @@ -827,23 +830,7 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder { markTokenTypeChecked(); - for (int i = 1; i < myProduction.size() - 1; i++) { - final ProductionMarker item = myProduction.get(i); - - if (item instanceof StartMarker && ((StartMarker)item).myDoneMarker == null) { - LOG.error(UNBALANCED_MESSAGE); - } - - final int prevProductionLexIndex = myProduction.get(i - 1).myLexemeIndex; - int wsStartIndex = item.myLexemeIndex; - while (wsStartIndex > prevProductionLexIndex && whitespaceOrComment(myLexTypes[wsStartIndex - 1])) wsStartIndex--; - - int wsEndIndex = item.myLexemeIndex; - while (wsEndIndex < myLexemeCount && whitespaceOrComment(myLexTypes[wsEndIndex])) wsEndIndex++; - - final List wsTokens = CollectionFactory.arrayList(myLexTypes, wsStartIndex, wsEndIndex); - item.myLexemeIndex = wsStartIndex + item.myEdgeProcessor.process(wsTokens); - } + balanceWhiteSpaces(); rootMarker.firstChild = rootMarker.lastChild = rootMarker.next = null; StartMarker curNode = rootMarker; @@ -891,6 +878,33 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder { return rootMarker; } + private void balanceWhiteSpaces() { + for (int i = 1; i < myProduction.size() - 1; i++) { + final ProductionMarker item = myProduction.get(i); + + if (item instanceof StartMarker && ((StartMarker)item).myDoneMarker == null) { + LOG.error(UNBALANCED_MESSAGE); + } + + final int prevProductionLexIndex = myProduction.get(i - 1).myLexemeIndex; + int idx = item.myLexemeIndex; + while (idx > prevProductionLexIndex && whitespaceOrComment(myLexTypes[idx - 1])) idx--; + final int wsStartIndex = idx; + + int wsEndIndex = item.myLexemeIndex; + while (wsEndIndex < myLexemeCount && whitespaceOrComment(myLexTypes[wsEndIndex])) wsEndIndex++; + + final List wsTokens = CollectionFactory.arrayList(myLexTypes, wsStartIndex, wsEndIndex); + final boolean atEnd = (wsStartIndex == 0 || wsEndIndex == myLexemeCount); + final WhitespacesAndCommentsProcessor.TokenTextGetter getter = new WhitespacesAndCommentsProcessor.TokenTextGetter() { + public CharSequence get(final int i) { + return myText.subSequence(myLexStarts[wsStartIndex + i], myLexStarts[wsStartIndex + i + 1]); + } + }; + item.myLexemeIndex = wsStartIndex + item.myEdgeProcessor.process(wsTokens, atEnd, getter); + } + } + private void bind(CompositeElement ast, StartMarker marker) { bind(ast, marker, marker.myLexemeIndex); } diff --git a/platform/lang-impl/testSrc/com/intellij/lang/LightPsiBuilderTest.java b/platform/lang-impl/testSrc/com/intellij/lang/LightPsiBuilderTest.java index 40ab0e96a1ac..eab175bb58cb 100644 --- a/platform/lang-impl/testSrc/com/intellij/lang/LightPsiBuilderTest.java +++ b/platform/lang-impl/testSrc/com/intellij/lang/LightPsiBuilderTest.java @@ -291,14 +291,14 @@ public class LightPsiBuilderTest { @Test public void testCustomEdgeProcessors() throws Exception { final WhitespacesAndCommentsProcessor leftEdgeProcessor = new WhitespacesAndCommentsProcessor() { - public int process(List tokens) { + public int process(List tokens, boolean atStreamEdge, TokenTextGetter getter) { int pos = tokens.size() - 1; while (tokens.get(pos) != COMMENT && pos > 0) pos--; return pos; } }; final WhitespacesAndCommentsProcessor rightEdgeProcessor = new WhitespacesAndCommentsProcessor() { - public int process(List tokens) { + public int process(List tokens, boolean atStreamEdge, TokenTextGetter getter) { int pos = 0; while (tokens.get(pos) != COMMENT && pos < tokens.size()-1) pos++; return pos + 1; diff --git a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java index e48478b57f57..c3ca694a25c5 100644 --- a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java +++ b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java @@ -305,6 +305,7 @@ public class StringUtil { } else if (c == '\r') { if (i + 1 < text.length() && text.charAt(i + 1) == '\n') { + //noinspection AssignmentToForLoopParameter i++; count++; } @@ -316,6 +317,14 @@ public class StringUtil { return count; } + public static boolean containsLineBreak(@NotNull CharSequence text) { + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (c == '\n' || c == '\r') return true; + } + return false; + } + public static int lineColToOffset(@NotNull CharSequence text, int line, int col) { int curLine = 0; int offset = 0; From 899f5ed67fd71cc48692dee2af1457d3d9b9cf7c Mon Sep 17 00:00:00 2001 From: Kirill Likhodedov Date: Wed, 1 Sep 2010 14:39:21 +0400 Subject: [PATCH 4/8] [Mercurial] Fixed NPE (EA-211618): find hg root manually if project is null. --- plugins/hg4idea/src/org/zmlx/hg4idea/HgUtil.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/hg4idea/src/org/zmlx/hg4idea/HgUtil.java b/plugins/hg4idea/src/org/zmlx/hg4idea/HgUtil.java index 669a03062a5d..7cd0fe8c7402 100644 --- a/plugins/hg4idea/src/org/zmlx/hg4idea/HgUtil.java +++ b/plugins/hg4idea/src/org/zmlx/hg4idea/HgUtil.java @@ -201,6 +201,9 @@ public abstract class HgUtil { */ @Nullable public static VirtualFile getHgRootOrNull(Project project, FilePath filePath) { + if (project == null) { + return getNearestHgRoot(VcsUtil.getVirtualFile(filePath.getPath())); + } return getNearestHgRoot(VcsUtil.getVcsRootFor(project, filePath)); } From a29fc79f0444a901d90b9e8d92d691717ac52843 Mon Sep 17 00:00:00 2001 From: Dmitry Avdeev Date: Wed, 1 Sep 2010 15:12:18 +0400 Subject: [PATCH 5/8] test fixed --- .../refactoring/actions/BasePlatformRefactoringAction.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/platform/lang-impl/src/com/intellij/refactoring/actions/BasePlatformRefactoringAction.java b/platform/lang-impl/src/com/intellij/refactoring/actions/BasePlatformRefactoringAction.java index 578a9ef74ce5..8cc7d326723a 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/actions/BasePlatformRefactoringAction.java +++ b/platform/lang-impl/src/com/intellij/refactoring/actions/BasePlatformRefactoringAction.java @@ -62,7 +62,9 @@ public abstract class BasePlatformRefactoringAction extends BaseRefactoringActio protected final RefactoringActionHandler getHandler(DataContext dataContext) { final Language[] languages = LangDataKeys.CONTEXT_LANGUAGES.getData(dataContext); if (languages != null) { - PsiElement elementAtCaret = getElementAtCaret(LangDataKeys.EDITOR.getData(dataContext), LangDataKeys.PSI_FILE.getData(dataContext)); + Editor editor = LangDataKeys.EDITOR.getData(dataContext); + PsiFile file = LangDataKeys.PSI_FILE.getData(dataContext); + PsiElement elementAtCaret = editor == null || file == null ? null : getElementAtCaret(editor, file); for (Language language : languages) { RefactoringActionHandler handler = getHandler(language, elementAtCaret); if (handler != null) { From 5a28fcc09c64dbf9c8874464abfbdd7a082504dc Mon Sep 17 00:00:00 2001 From: "Maxim.Mossienko" Date: Wed, 1 Sep 2010 15:28:59 +0400 Subject: [PATCH 6/8] fix NPE --- .../vcs-impl/src/com/intellij/openapi/vcs/changes/VcsGuess.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VcsGuess.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VcsGuess.java index 0c55528aeb8b..0fd65ed54031 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VcsGuess.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VcsGuess.java @@ -41,7 +41,7 @@ public class VcsGuess { @Nullable public AbstractVcs getVcsForDirty(final VirtualFile file) { - if (!file.isInLocalFileSystem()) { + if (!file.isInLocalFileSystem() || myExcludedFileIndex == null) { return null; } if (myExcludedFileIndex.isInContent(file) || isFileInBaseDir(file) || From de57d873bade16a6f4ae4830b5adcfa8b3d8a5fb Mon Sep 17 00:00:00 2001 From: nik Date: Wed, 1 Sep 2010 14:50:36 +0400 Subject: [PATCH 7/8] http proxy configurable reworked --- .../util/net/HTTPProxySettingsDialog.java | 2 +- .../util/net/HTTPProxySettingsPanel.java | 33 ++++- .../intellij/util/net/HttpConfigurable.java | 119 +++++------------- .../src/META-INF/PlatformExtensions.xml | 2 +- 4 files changed, 66 insertions(+), 90 deletions(-) diff --git a/platform/platform-api/src/com/intellij/util/net/HTTPProxySettingsDialog.java b/platform/platform-api/src/com/intellij/util/net/HTTPProxySettingsDialog.java index 1d823738e5ca..e4dc41ada40c 100644 --- a/platform/platform-api/src/com/intellij/util/net/HTTPProxySettingsDialog.java +++ b/platform/platform-api/src/com/intellij/util/net/HTTPProxySettingsDialog.java @@ -55,7 +55,7 @@ public class HTTPProxySettingsDialog extends DialogWrapper { } protected JComponent createCenterPanel() { - return panel.getComponent(); + return panel.createComponent(); } protected Action[] createActions() { diff --git a/platform/platform-api/src/com/intellij/util/net/HTTPProxySettingsPanel.java b/platform/platform-api/src/com/intellij/util/net/HTTPProxySettingsPanel.java index 482732475de5..09d9a6eb8838 100644 --- a/platform/platform-api/src/com/intellij/util/net/HTTPProxySettingsPanel.java +++ b/platform/platform-api/src/com/intellij/util/net/HTTPProxySettingsPanel.java @@ -15,7 +15,10 @@ */ package com.intellij.util.net; +import com.intellij.openapi.options.SearchableConfigurable; import com.intellij.openapi.util.Comparing; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; import javax.swing.*; import javax.swing.event.DocumentListener; @@ -30,7 +33,7 @@ import java.awt.event.ActionListener; * Time: 3:52:47 PM * To change this template use Options | File Templates. */ -public class HTTPProxySettingsPanel{ +public class HTTPProxySettingsPanel implements SearchableConfigurable { private JPanel myMainPanel; private JTextField myProxyLoginTextField; @@ -134,10 +137,32 @@ public class HTTPProxySettingsPanel{ myRememberProxyPasswordCheckBox.setEnabled(enabled); } - public JComponent getComponent() { + public JComponent createComponent() { return myMainPanel; } + @NotNull + public String getId() { + return getHelpTopic(); + } + + public Runnable enableSearch(final String option) { + return null; + } + + @Nls + public String getDisplayName() { + return "HTTP Proxy"; + } + + public Icon getIcon() { + return null; + } + + public String getHelpTopic() { + return "http.proxy"; + } + public void addActionListener(final ActionListener actionListener) { myProxyLoginTextField.addActionListener(actionListener); DocumentListener docListener = new DocumentListener() { @@ -161,4 +186,8 @@ public class HTTPProxySettingsPanel{ myRememberProxyPasswordCheckBox.addActionListener(actionListener); } + + @Override + public void disposeUIResources() { + } } diff --git a/platform/platform-api/src/com/intellij/util/net/HttpConfigurable.java b/platform/platform-api/src/com/intellij/util/net/HttpConfigurable.java index 228a3a306424..09edbf519d1d 100644 --- a/platform/platform-api/src/com/intellij/util/net/HttpConfigurable.java +++ b/platform/platform-api/src/com/intellij/util/net/HttpConfigurable.java @@ -15,18 +15,14 @@ */ package com.intellij.util.net; -import com.intellij.openapi.components.ApplicationComponent; +import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.options.ConfigurationException; -import com.intellij.openapi.options.SearchableConfigurable; -import com.intellij.openapi.util.DefaultJDOMExternalizer; -import com.intellij.openapi.util.InvalidDataException; -import com.intellij.openapi.util.JDOMExternalizable; -import com.intellij.openapi.util.WriteExternalException; +import com.intellij.openapi.components.State; +import com.intellij.openapi.components.Storage; +import com.intellij.openapi.options.ShowSettingsUtil; import com.intellij.ui.GuiUtils; +import com.intellij.util.xmlb.XmlSerializerUtil; import org.apache.commons.codec.binary.Base64; -import org.jdom.Element; -import org.jetbrains.annotations.Nls; import javax.swing.*; import java.io.IOException; @@ -39,7 +35,13 @@ import java.net.*; * Time: 3:58:23 PM * To change this template use Options | File Templates. */ -public class HttpConfigurable implements JDOMExternalizable, ApplicationComponent, SearchableConfigurable { +@State( + name = "HttpConfigurable", + storages = { + @Storage(id = "other", file = "$APP_CONFIG$/other.xml") + } +) +public class HttpConfigurable implements PersistentStateComponent { public boolean USE_HTTP_PROXY = false; public String PROXY_HOST = ""; public int PROXY_PORT = 80; @@ -49,39 +51,33 @@ public class HttpConfigurable implements JDOMExternalizable, ApplicationComponen public String PROXY_PASSWORD_CRYPT = ""; public boolean KEEP_PROXY_PASSWORD = false; - private HTTPProxySettingsPanel myPanel; - public static HttpConfigurable getInstance() { return ServiceManager.getService(HttpConfigurable.class); } - public void readExternal(Element element) throws InvalidDataException { - DefaultJDOMExternalizer.readExternal(this, element); - if (!KEEP_PROXY_PASSWORD) + public static boolean editConfigurable(final JComponent parent) { + return ShowSettingsUtil.getInstance().editConfigurable(parent, new HTTPProxySettingsPanel(getInstance())); + } + + @Override + public HttpConfigurable getState() { + final HttpConfigurable state = new HttpConfigurable(); + XmlSerializerUtil.copyBean(this, state); + if (!KEEP_PROXY_PASSWORD) { + state.PROXY_PASSWORD_CRYPT = ""; + } + return state; + } + + @Override + public void loadState(HttpConfigurable state) { + XmlSerializerUtil.copyBean(state, this); + if (!KEEP_PROXY_PASSWORD) { PROXY_PASSWORD_CRYPT = ""; + } } - public void writeExternal(Element element) throws WriteExternalException { - String proxyPassword = PROXY_PASSWORD_CRYPT; - if (!KEEP_PROXY_PASSWORD) - PROXY_PASSWORD_CRYPT = ""; - - DefaultJDOMExternalizer.writeExternal(this, element); - - PROXY_PASSWORD_CRYPT = proxyPassword; - } - - public String getComponentName() { - return "HttpConfigurable"; - } - - public void initComponent() { - } - - public void disposeComponent() { - } - - public String getPlainProxyPassword () { + public String getPlainProxyPassword() { return new String(new Base64().decode(PROXY_PASSWORD_CRYPT.getBytes())); } @@ -90,8 +86,7 @@ public class HttpConfigurable implements JDOMExternalizable, ApplicationComponen } public PasswordAuthentication getPromptedAuthentication(final String host, final String prompt) { - if (PROXY_AUTHENTICATION && - ! KEEP_PROXY_PASSWORD) { + if (PROXY_AUTHENTICATION && !KEEP_PROXY_PASSWORD) { Runnable runnable = new Runnable() { public void run() { AuthenticationDialog dlg = new AuthenticationDialog(host, prompt); @@ -158,52 +153,4 @@ public class HttpConfigurable implements JDOMExternalizable, ApplicationComponen Authenticator.setDefault(null); } } - - public String getId() { - return getHelpTopic(); - } - - public Runnable enableSearch(final String option) { - return null; - } - - @Nls - public String getDisplayName() { - return "HTTP Proxy"; - } - - public Icon getIcon() { - return null; - } - - public String getHelpTopic() { - return "http.proxy"; - } - - public JComponent createComponent() { - if (myPanel == null) { - myPanel = new HTTPProxySettingsPanel(getInstance()); - } - return myPanel.getComponent(); - } - - public boolean isModified() { - return myPanel != null && myPanel.isModified(); - } - - public void apply() throws ConfigurationException { - if (myPanel != null) { - myPanel.apply(); - } - } - - public void reset() { - if (myPanel != null) { - myPanel.reset(); - } - } - - public void disposeUIResources() { - myPanel = null; - } } diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index 2d9040cb40b9..19cec3aebef6 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -176,7 +176,7 @@ - + From 0ae67ff03e396d3924d9cbaa62879489784215d3 Mon Sep 17 00:00:00 2001 From: nik Date: Wed, 1 Sep 2010 15:31:44 +0400 Subject: [PATCH 8/8] reverted reverting reverted change --- resources/src/META-INF/IdeaPlugin.xml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 7590d08ae782..89603d037086 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -404,10 +404,14 @@ + + @@ -644,9 +648,9 @@ - + - +