diff --git a/bin/WinLauncher/WinLauncher.exe b/bin/WinLauncher/WinLauncher.exe index c40b7a0f1352..3596a30377f4 100644 Binary files a/bin/WinLauncher/WinLauncher.exe and b/bin/WinLauncher/WinLauncher.exe differ diff --git a/bin/WinLauncher/WinLauncher64.exe b/bin/WinLauncher/WinLauncher64.exe index 1c305dfbe73b..c4b0a9b56068 100644 Binary files a/bin/WinLauncher/WinLauncher64.exe and b/bin/WinLauncher/WinLauncher64.exe differ diff --git a/java/debugger/impl/src/com/intellij/debugger/jdi/MethodBytecodeUtil.java b/java/debugger/impl/src/com/intellij/debugger/jdi/MethodBytecodeUtil.java index f5ac8a8104fc..36c7bb99d8f9 100644 --- a/java/debugger/impl/src/com/intellij/debugger/jdi/MethodBytecodeUtil.java +++ b/java/debugger/impl/src/com/intellij/debugger/jdi/MethodBytecodeUtil.java @@ -289,17 +289,19 @@ public class MethodBytecodeUtil { List res = new ArrayList<>(); for (Map.Entry> entry : byMethod.entrySet()) { - res.addAll(removeMethodSameLineLocations(entry.getKey(), entry.getValue())); + res.addAll(removeMethodSameLineLocations(entry.getKey(), (List)entry.getValue())); } return res; } - private static Collection removeMethodSameLineLocations(@NotNull Method method, @NotNull Collection locations) { - if (locations.size() < 2) { + private static Collection removeMethodSameLineLocations(@NotNull Method method, @NotNull List locations) { + int locationsSize = locations.size(); + if (locationsSize < 2) { return locations; } + //noinspection ConstantConditions int lineNumber = ContainerUtil.getFirstItem(locations).lineNumber(); - List mask = new ArrayList<>(); + List mask = new ArrayList<>(locationsSize); visit(method, new MethodVisitor(Opcodes.API_VERSION) { boolean myNewBlock = true; @Override @@ -322,8 +324,10 @@ public class MethodBytecodeUtil { myNewBlock = true; } }, true); - if (mask.size() == locations.size()) { - List res = new ArrayList<>(); + + if (mask.size() == locationsSize) { + locations.sort(Comparator.comparing(Location::codeIndex)); + List res = new ArrayList<>(locationsSize); int pos = 0; for (Location location : locations) { if (mask.get(pos++)) { diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddExceptionToExistingCatchFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddExceptionToExistingCatchFix.java index e8bbf73f8026..000a3781c41e 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddExceptionToExistingCatchFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddExceptionToExistingCatchFix.java @@ -137,7 +137,8 @@ public class AddExceptionToExistingCatchFix extends PsiElementBaseIntentionActio if (unhandledExceptions.isEmpty()) return null; boolean containsInCatchOrFinally = containsInCatchOrFinally(element); List tryStatements = - PsiTreeUtil.collectParentsOfType(element, PsiTryStatement.class, PsiLambdaExpression.class, PsiClass.class); + PsiTreeUtil.collectParents(element, PsiTryStatement.class, el -> + el instanceof PsiLambdaExpression || el instanceof PsiClass && !(el instanceof PsiAnonymousClass)); if (containsInCatchOrFinally) { tryStatements.remove(0); } diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/SortContentAction.java b/java/java-impl/src/com/intellij/codeInsight/intention/impl/SortContentAction.java index 3df133ee6e19..5449b419e0b1 100644 --- a/java/java-impl/src/com/intellij/codeInsight/intention/impl/SortContentAction.java +++ b/java/java-impl/src/com/intellij/codeInsight/intention/impl/SortContentAction.java @@ -14,6 +14,7 @@ import com.intellij.util.IncorrectOperationException; import com.intellij.util.SmartList; import com.siyeh.ig.psiutils.ExpressionUtils; import gnu.trove.TIntArrayList; +import one.util.streamex.StreamEx; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -821,6 +822,15 @@ public class SortContentAction extends PsiElementBaseIntentionAction { PsiElement lBrace = aClass.getLBrace(); PsiElement rBrace = aClass.getRBrace(); if (lBrace == null || rBrace == null) return; + + //PsiEnumConstant holds comments inside, we need codegen to know about this comments to place \n correctly + for (SortableEntry entry : sortableList.myEntries) { + List comments = StreamEx.ofTree(entry.myElement, el -> StreamEx.of(el.getChildren())).select(PsiComment.class).toList(); + for (PsiComment comment : comments) { + entry.myBeforeSeparator.add((PsiComment)comment.copy()); + comment.delete(); + } + } StringBuilder sb = new StringBuilder(); sortableList.generate(sb); PsiElement elementToPreserve = lastElement.getNextSibling(); diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiJavaFileBaseImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiJavaFileBaseImpl.java index 0ccc3be41edb..5e97247a95e0 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiJavaFileBaseImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/PsiJavaFileBaseImpl.java @@ -40,7 +40,6 @@ import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.*; import com.intellij.util.IncorrectOperationException; import com.intellij.util.Processor; -import java.util.HashSet; import com.intellij.util.containers.MostlySingularMultiMap; import com.intellij.util.indexing.IndexingDataKeys; import org.jetbrains.annotations.NotNull; @@ -48,6 +47,7 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.List; public abstract class PsiJavaFileBaseImpl extends PsiFileImpl implements PsiJavaFile { @@ -137,8 +137,8 @@ public abstract class PsiJavaFileBaseImpl extends PsiFileImpl implements PsiJava if (stub != null) { PsiImportList[] nodes = stub.getChildrenByType(JavaStubElementTypes.IMPORT_LIST, PsiImportList.ARRAY_FACTORY); if (nodes.length == 1) return nodes[0]; - if (nodes.length == 0) return null; - reportStubAstMismatch(stub + "; " + stub.getChildrenStubs(), getStubTree()); + assert nodes.length == 0; + return null; } ASTNode node = calcTreeElement().findChildByType(JavaElementType.IMPORT_LIST); diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addExceptionToExistingCatch/afterInAnnonimousClass.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addExceptionToExistingCatch/afterInAnnonimousClass.java new file mode 100644 index 000000000000..e1c94f5728ab --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addExceptionToExistingCatch/afterInAnnonimousClass.java @@ -0,0 +1,18 @@ +// "Add exception to existing catch clause" "true" +import java.io.FileInputStream; +import java.io.FileNotFoundException; + +class Test { + void m() { + try { + new Runnable() { + @Override + public void run() { } + + InputStream in = new FileInputStream(""); + }; + } catch (RuntimeException | FileNotFoundException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addExceptionToExistingCatch/beforeInAnnonimousClass.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addExceptionToExistingCatch/beforeInAnnonimousClass.java new file mode 100644 index 000000000000..6f57aecfe6e1 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addExceptionToExistingCatch/beforeInAnnonimousClass.java @@ -0,0 +1,17 @@ +// "Add exception to existing catch clause" "true" +import java.io.FileInputStream; + +class Test { + void m() { + try { + new Runnable() { + @Override + public void run() { } + + InputStream in = new FileInputStream(""); + }; + } catch (RuntimeException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addExceptionToExistingCatch/beforeInLocalClass.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addExceptionToExistingCatch/beforeInLocalClass.java new file mode 100644 index 000000000000..e72a780e217c --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/addExceptionToExistingCatch/beforeInLocalClass.java @@ -0,0 +1,17 @@ +// "Add exception to existing catch clause" "false" +import java.io.FileInputStream; + +class Test { + void m() { + try { + class A implements Runnable { + @Override + public void run() { } + + InputStream in = new FileInputStream(""); + }; + } catch (RuntimeException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/sortContent/afterEnumComments.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/sortContent/afterEnumComments.java new file mode 100644 index 000000000000..7831bf6ccaf2 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/sortContent/afterEnumComments.java @@ -0,0 +1,13 @@ +// "Sort content" "true" + +public enum OSType { + LINUX// linux + , + + MAC//mac + , + WIN//after win +// win + + ; +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/sortContent/beforeEnumComments.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/sortContent/beforeEnumComments.java new file mode 100644 index 000000000000..2639bf0ed280 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/sortContent/beforeEnumComments.java @@ -0,0 +1,9 @@ +// "Sort content" "true" + +public enum OSType { + WIN// win + //after win + , LINUX // linux + ,MAC //mac + ; +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/psi/StubAstSwitchTest.groovy b/java/java-tests/testSrc/com/intellij/java/psi/StubAstSwitchTest.groovy index 0c7d2fdc5519..af7a14b8c969 100644 --- a/java/java-tests/testSrc/com/intellij/java/psi/StubAstSwitchTest.groovy +++ b/java/java-tests/testSrc/com/intellij/java/psi/StubAstSwitchTest.groovy @@ -34,15 +34,13 @@ import com.intellij.psi.stubs.StubTree import com.intellij.psi.util.PsiTreeUtil import com.intellij.reference.SoftReference import com.intellij.testFramework.LeakHunter -import com.intellij.testFramework.PlatformTestUtil import com.intellij.testFramework.SkipSlowTestLocally import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase import com.intellij.util.ref.GCUtil -import groovy.transform.CompileStatic import java.util.concurrent.Callable import java.util.concurrent.CountDownLatch -import java.util.concurrent.Future +import java.util.concurrent.Future /** * @author peter */ @@ -236,7 +234,7 @@ class B { LeakHunter.checkLeak(file, StubTree) { candidate -> candidate.root.psi == file } } - void "test node is not deeply parsed when loaded in green stub presence"() { + void "test node has same PSI when loaded in green stub presence"() { PsiFileImpl file = (PsiFileImpl)myFixture.addFileToProject("a.java", "class A{}") def stubTree = file.stubTree PsiClass psiClass = ((PsiJavaFile)file).classes[0] @@ -244,7 +242,7 @@ class B { GCUtil.tryGcSoftlyReachableObjects() assert stubTree.is(file.greenStubTree) - assert !file.node.parsed + assert file.node.lastChildNode.psi.is(psiClass) } void "test load stub from non-file PSI after AST is unloaded"() { @@ -258,26 +256,22 @@ class B { assert ((PsiClassImpl) cls).stub } - void "test load PSI via stub when AST is gc-ed but PSI exists that has never known stub"() { + void "test load PSI via stub when AST is gc-ed but PSI exists that was loaded via AST but knows its stub index"() { PsiJavaFileImpl file = (PsiJavaFileImpl)myFixture.addFileToProject("a.java", "class A{}") def cls = file.lastChild assert cls instanceof PsiClass GCUtil.tryGcSoftlyReachableObjects() - assert !file.treeElement - - assert cls == myFixture.findClass('A') - } - - void "test load PSI via stub when AST is gc-ed and PSI remains that never knew stub"() { - PsiJavaFileImpl file = (PsiJavaFileImpl)myFixture.addFileToProject("a.java", "class A{}") - def cls = file.lastChild - assert cls instanceof PsiClass + assert file.treeElement // we still hold a strong reference to AST + assert cls == myFixture.findClass('A') + + // now we know stub index and can GC AST GCUtil.tryGcSoftlyReachableObjects() assert !file.treeElement assert cls == myFixture.findClass('A') + assert !file.treeElement } void "test bind stubs to AST after AST has been loaded and gc-ed"() { @@ -335,30 +329,27 @@ class B { assert stubTree.plainList.find { it.stubType == JavaStubElementTypes.ANONYMOUS_CLASS } } - @CompileStatic - void "test getStub performance with cached PSI"() { - def text = "class Foo { " + "void bar(int a, int b, int c, int d, int e) { int x = null; }\n" * 1000 + "}" - def file = myFixture.addFileToProject "a.java", text - - PsiMethod[] methods = ((PsiJavaFile) file).classes[0].methods - def params = methods.collect { PsiMethod method -> method.parameterList.parameters } - def literal = file.findElementAt(text.indexOf('null')).parent as PsiLiteralExpression // the only cached PSI without stubIndex + void "test stub index is cleared on AST change"() { + def clazz = myFixture.addClass("class Foo { int a; }") + def field = clazz.fields[0] + def file = clazz.containingFile as PsiFileImpl + WriteCommandAction.runWriteCommandAction(project, { + file.viewProvider.document.insertString(0, ' ') + PsiDocumentManager.getInstance(project).commitAllDocuments() + }) + + assert file.calcStubTree() + WriteCommandAction.runWriteCommandAction(project, { + file.viewProvider.document.insertString(file.text.indexOf('int'), 'void foo();') + PsiDocumentManager.getInstance(project).commitAllDocuments() + }) + GCUtil.tryGcSoftlyReachableObjects() - def fileImpl = (PsiFileImpl)file - assert !fileImpl.treeElement - assert !fileImpl.stub + assert file.calcStubTree() - PlatformTestUtil.startPerformanceTest('getStub performance', 100, { - 10_000.times { - if (fileImpl.stub != null) { - throw new IllegalStateException("has stub") - } - } - }).assertTiming() - - assert params - assert literal + assert field.valid + assert field.name == 'a' } } diff --git a/json/src/com/jetbrains/jsonSchema/JsonSchemaMappingsView.java b/json/src/com/jetbrains/jsonSchema/JsonSchemaMappingsView.java index b6d1d39f6f47..fd166c06b72c 100644 --- a/json/src/com/jetbrains/jsonSchema/JsonSchemaMappingsView.java +++ b/json/src/com/jetbrains/jsonSchema/JsonSchemaMappingsView.java @@ -38,8 +38,11 @@ import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; import java.io.File; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; @@ -50,6 +53,7 @@ import static com.jetbrains.jsonSchema.JsonSchemaConfigurable.isHttpPath; */ public class JsonSchemaMappingsView implements Disposable { private static final String ADD_SCHEMA_MAPPING = "settings.json.schema.add.mapping"; + private static final String EDIT_SCHEMA_MAPPING = "settings.json.schema.edit.mapping"; private final Runnable myTreeUpdater; private TableView myTableView; private JComponent myComponent; @@ -70,23 +74,20 @@ public class JsonSchemaMappingsView implements Disposable { myTableView = new TableView<>(); myTableView.getTableHeader().setVisible(false); final ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myTableView); - decorator.setRemoveAction(new AnActionButtonRunnable() { + final MyEditActionButtonRunnableImpl editAction = new MyEditActionButtonRunnableImpl(project); + decorator.setRemoveAction(new MyRemoveActionButtonRunnable()) + .setAddAction(new MyAddActionButtonRunnable(project)) + .setEditAction(editAction) + .disableUpDownActions(); + + myTableView.addMouseListener(new MouseAdapter() { @Override - public void run(AnActionButton button) { - final int[] rows = myTableView.getSelectedRows(); - if (rows != null && rows.length > 0) { - int cnt = 0; - for (int row : rows) { - myTableView.getListTableModel().removeRow(row - cnt); - ++cnt; - } - myTableView.getListTableModel().fireTableDataChanged(); - myTreeUpdater.run(); + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + editAction.execute(); } } - }) - .setAddAction(new MyAddActionButtonRunnable(project)) - .disableUpDownActions(); + }); mySchemaField = new TextFieldWithBrowseButton(); SwingHelper.installFileCompletionAndBrowseDialog(myProject, mySchemaField, JsonBundle.message("json.schema.add.schema.chooser.title"), @@ -180,21 +181,30 @@ public class JsonSchemaMappingsView implements Disposable { return myComponent; } - private class MyAddActionButtonRunnable implements AnActionButtonRunnable { + private abstract class MyAddOrEditActionButtonRunnableBase implements AnActionButtonRunnable { private final Project myProject; - public MyAddActionButtonRunnable(Project project) { + public MyAddOrEditActionButtonRunnableBase(Project project) { myProject = project; } @Override - public void run(AnActionButton button) { + public abstract void run(AnActionButton button); + + protected void doRun(@Nullable UserDefinedJsonSchemaConfiguration.Item currentItem, int selectedRow) { + assert currentItem == null || selectedRow != -1; final JBPanel panel = new JBPanel(new GridBagLayout()); final GridBag bag = new GridBag(); final JBTextField patternField = new JBTextField(); final TextFieldWithBrowseButton directoryField = new TextFieldWithBrowseButton(); + if (currentItem != null && currentItem.directory) { + directoryField.setText(currentItem.path); + } final TextFieldWithBrowseButton fileField = new TextFieldWithBrowseButton(); + if (currentItem != null && !currentItem.directory && !currentItem.pattern) { + fileField.setText(currentItem.path); + } bag.setDefaultAnchor(GridBagConstraints.NORTHWEST); final JBRadioButton radioPattern = new JBRadioButton("Filename pattern:"); @@ -218,17 +228,28 @@ public class JsonSchemaMappingsView implements Disposable { group.add(radioDirectory); group.add(radioFile); - radioDirectory.setSelected(true); + if (currentItem == null) { + radioDirectory.setSelected(true); + } + else { + if (currentItem.pattern) radioPattern.setSelected(true); + else if (currentItem.directory) radioDirectory.setSelected(true); + else radioFile.setSelected(true); + } patternField.setMinimumSize(new Dimension(JBUI.scale(200), UIUtil.getInformationIcon().getIconHeight())); patternField.getEmptyText().setText("Example: *.config.json"); + if (currentItem != null && currentItem.pattern) { + patternField.setText(currentItem.path); + } final DialogBuilder builder = new DialogBuilder(); - builder.setTitle("Add JSON Schema Mapping"); + String addOrEdit = currentItem == null ? "Add" : "Edit"; + builder.setTitle(addOrEdit + " JSON Schema Mapping"); builder.setNorthPanel(panel); builder.setPreferredFocusComponent(directoryField); builder.setDimensionServiceKey("com.jetbrains.jsonSchema.JsonSchemaMappingsView#add"); - builder.setHelpId(ADD_SCHEMA_MAPPING); + builder.setHelpId(currentItem == null ? ADD_SCHEMA_MAPPING : EDIT_SCHEMA_MAPPING); final Getter textGetter = () -> { if (radioPattern.isSelected()) { @@ -279,13 +300,66 @@ public class JsonSchemaMappingsView implements Disposable { final String pattern = textGetter.get(); final UserDefinedJsonSchemaConfiguration.Item item = new UserDefinedJsonSchemaConfiguration.Item(pattern, radioPattern.isSelected(), radioDirectory.isSelected()); - myTableView.getListTableModel().addRow(item); + if (currentItem != null) { + myTableView.getListTableModel().removeRow(selectedRow); + myTableView.getListTableModel().insertRow(selectedRow, item); + myTableView.setSelection(Collections.singleton(item)); + } + else { + myTableView.getListTableModel().addRow(item); + } myTreeUpdater.run(); } Disposer.dispose(alarm); } } + private class MyAddActionButtonRunnable extends MyAddOrEditActionButtonRunnableBase { + public MyAddActionButtonRunnable(Project project) { + super(project); + } + + @Override + public void run(AnActionButton button) { + doRun(null, -1); + } + } + + private class MyEditActionButtonRunnableImpl extends MyAddOrEditActionButtonRunnableBase { + public MyEditActionButtonRunnableImpl(Project project) { + super(project); + } + + @Override + public void run(AnActionButton button) { + execute(); + } + + public void execute() { + int selectedRow = myTableView.getSelectedRow(); + if (selectedRow == -1) return; + UserDefinedJsonSchemaConfiguration.Item item = myTableView.getListTableModel().getItem(selectedRow); + if (item == null) return; + doRun(item, selectedRow); + } + } + + private class MyRemoveActionButtonRunnable implements AnActionButtonRunnable { + @Override + public void run(AnActionButton button) { + final int[] rows = myTableView.getSelectedRows(); + if (rows != null && rows.length > 0) { + int cnt = 0; + for (int row : rows) { + myTableView.getListTableModel().removeRow(row - cnt); + ++cnt; + } + myTableView.getListTableModel().fireTableDataChanged(); + myTreeUpdater.run(); + } + } + } + private static String getRelativePath(@NotNull Project project, @NotNull String text) { text = text.trim(); if (project.isDefault() || project.getBasePath() == null) return text; diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java index d0dcce6997dd..90fef6f27e4e 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaAnnotatorChecker.java @@ -457,6 +457,9 @@ class JsonSchemaAnnotatorChecker { if (!Boolean.TRUE.equals(schema.getAdditionalItemsAllowed())) { error("Additional items are not allowed", arrayValue.getDelegate()); } + else if (schema.getAdditionalItemsSchema() != null) { + checkObjectBySchemaRecordErrors(schema.getAdditionalItemsSchema(), arrayValue); + } } } } diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaObject.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaObject.java index 35309b44feff..643b2252651c 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaObject.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaObject.java @@ -9,6 +9,7 @@ import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -28,6 +29,8 @@ import java.util.stream.Collectors; public class JsonSchemaObject { @NonNls public static final String DEFINITIONS = "definitions"; @NonNls public static final String PROPERTIES = "properties"; + @NonNls public static final String ITEMS = "items"; + @NonNls public static final String ADDITIONAL_ITEMS = "additionalItems"; @NonNls public static final String X_INTELLIJ_HTML_DESCRIPTION = "x-intellij-html-description"; @NotNull private final JsonObject myJsonObject; @Nullable private Map myDefinitionsMap; @@ -641,12 +644,42 @@ public class JsonSchemaObject { current = current.getProperties().get(parts.get(++i)); continue; } - + if (ITEMS.equals(part)) { + if (i == (parts.size() - 1)) { + current = current.getItemsSchema(); + } + else { + //noinspection AssignmentToForLoopParameter + Integer next = tryParseInt(parts.get(++i)); + List itemsSchemaList = current.getItemsSchemaList(); + if (itemsSchemaList != null && next != null && next < itemsSchemaList.size()) { + current = itemsSchemaList.get(next); + } + } + continue; + } + if (ADDITIONAL_ITEMS.equals(part)) { + if (i == (parts.size() - 1)) { + current = current.getAdditionalItemsSchema(); + } + continue; + } + current = current.getDefinitionsMap() == null ? null : current.getDefinitionsMap().get(part); } return current; } + @Nullable + private static Integer tryParseInt(String s) { + try { + return Integer.parseInt(s); + } + catch (Exception __) { + return null; + } + } + @Override public boolean equals(@Nullable Object o) { if (this == o) return true; diff --git a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaReader.java b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaReader.java index cd2bd5f4cdfa..72d8e2c613cb 100644 --- a/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaReader.java +++ b/json/src/com/jetbrains/jsonSchema/impl/JsonSchemaReader.java @@ -155,8 +155,8 @@ public class JsonSchemaReader { READERS_MAP.put("pattern", (element, object, queue) -> { if (element instanceof JsonStringLiteral) object.setPattern(StringUtil.unquoteString(element.getText())); }); - READERS_MAP.put("additionalItems", createAdditionalItems()); - READERS_MAP.put("items", createItems()); + READERS_MAP.put(JsonSchemaObject.ADDITIONAL_ITEMS, createAdditionalItems()); + READERS_MAP.put(JsonSchemaObject.ITEMS, createItems()); READERS_MAP.put("contains", createContains()); READERS_MAP.put("maxItems", (element, object, queue) -> { if (element instanceof JsonNumberLiteral) object.setMaxItems((int)((JsonNumberLiteral)element).getValue()); diff --git a/json/src/com/jetbrains/jsonSchema/remote/JsonSchemaCatalogManager.java b/json/src/com/jetbrains/jsonSchema/remote/JsonSchemaCatalogManager.java index 6285e2216960..8c6ffe8ee285 100644 --- a/json/src/com/jetbrains/jsonSchema/remote/JsonSchemaCatalogManager.java +++ b/json/src/com/jetbrains/jsonSchema/remote/JsonSchemaCatalogManager.java @@ -25,7 +25,7 @@ public class JsonSchemaCatalogManager { @NotNull private final ConcurrentMap myResolvedMappings = ContainerUtil.newConcurrentMap(); private static final String NO_CACHE = "$_$_WS_NO_CACHE_$_$"; private static final String EMPTY = "$_$_WS_EMPTY_$_$"; - private static final AtomicBoolean myIsEnabled = new AtomicBoolean(!ApplicationManager.getApplication().isUnitTestMode()); + private static final AtomicBoolean myIsEnabled = new AtomicBoolean(true); public JsonSchemaCatalogManager(@NotNull Project project) { myProject = project; @@ -37,6 +37,7 @@ public class JsonSchemaCatalogManager { } public void startUpdates() { + if (ApplicationManager.getApplication().isUnitTestMode()) return; RemoteFileManager instance = RemoteFileManager.getInstance(); instance.addRemoteContentProvider(myRemoteContentProvider); myCatalog = JsonFileResolver.urlToFile(DEFAULT_CATALOG); @@ -44,6 +45,7 @@ public class JsonSchemaCatalogManager { @Nullable public VirtualFile getSchemaFileForFile(@NotNull VirtualFile file) { + if (ApplicationManager.getApplication().isUnitTestMode()) return null; if (!myIsEnabled.get()) return null; String name = file.getName(); diff --git a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java index e8f915d04896..dc254502917a 100644 --- a/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java +++ b/json/tests/test/com/jetbrains/jsonSchema/JsonSchemaHighlightingTest.java @@ -546,6 +546,78 @@ public class JsonSchemaHighlightingTest extends DaemonAnalyzerTestCase { doTest(schema, "{\"withFormat\": \"localhost\"}"); } + public void testArrayItemReference() throws Exception { + @Language("JSON") final String schema = "{\n" + + " \"items\": [\n" + + " {\n" + + " \"type\": \"integer\"\n" + + " },\n" + + " {\n" + + " \"$ref\": \"#/items/0\"\n" + + " }\n" + + " ]\n" + + "}"; + doTest(schema, "[1, 2]"); + doTest(schema, "[1, \"foo\"]"); + } + + public void testArrayReference() throws Exception { + @Language("JSON") final String schema = "{\n" + + " \"definitions\": {\n" + + " \"options\": {\n" + + " \"type\": \"array\",\n" + + " \"items\": {\n" + + " \"type\": \"number\"\n" + + " }\n" + + " }\n" + + " },\n" + + " \"items\":{\n" + + " \"$ref\": \"#/definitions/options/items\"\n" + + " }\n" + + " \n" + + "}"; + doTest(schema, "[2, 3 ,4]"); + doTest(schema, "[2, \"3\"]"); + } + + public void testSelfArrayReferenceDoesNotThrowSOE() throws Exception { + @Language("JSON") final String schema = "{\n" + + " \"items\": [\n" + + " {\n" + + " \"$ref\": \"#/items/0\"\n" + + " }\n" + + " ]\n" + + "}"; + doTest(schema, "[]"); + } + + public void testValidateAdditionalItems() throws Exception { + @Language("JSON") final String schema = "{\n" + + " \"definitions\": {\n" + + " \"options\": {\n" + + " \"type\": \"array\",\n" + + " \"items\": {\n" + + " \"type\": \"number\"\n" + + " }\n" + + " }\n" + + " },\n" + + " \"items\": [\n" + + " {\n" + + " \"type\": \"boolean\"\n" + + " },\n" + + " {\n" + + " \"type\": \"boolean\"\n" + + " }\n" + + " ],\n" + + " \"additionalItems\": {\n" + + " \"$ref\": \"#/definitions/options/items\"\n" + + " }\n" + + "}"; + doTest(schema, "[true, true]"); + doTest(schema, "[true, true, 1, 2, 3]"); + doTest(schema, "[true, true, 1, \"2\"]"); + } + public static String rootObjectRedefinedSchema() { return "{\n" + " \"$schema\": \"http://json-schema.org/draft-04/schema#\",\n" + diff --git a/native/WinLauncher/WinLauncher/WinLauncher.cpp b/native/WinLauncher/WinLauncher/WinLauncher.cpp index 476a3dc75415..a58ce368ff91 100644 --- a/native/WinLauncher/WinLauncher/WinLauncher.cpp +++ b/native/WinLauncher/WinLauncher/WinLauncher.cpp @@ -674,8 +674,7 @@ bool CreateJVM() buf << "Failed to create JVM: error code " << result << ".\n"; buf << "JVM Path: " << jvmPath << "\n"; - buf << "If you already have a " BITS_STR " JDK installed, define a JAVA_HOME variable in \n"; - buf << "Computer > System Properties > System Settings > Environment Variables."; + buf << "Most likely the cause of the problem is incorrect settings in the VM options file."; std::string error = LoadStdString(IDS_ERROR_LAUNCHING_APP); MessageBoxA(NULL, buf.str().c_str(), error.c_str(), MB_OK); } diff --git a/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/BuildTasksImpl.groovy b/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/BuildTasksImpl.groovy index 75bab3b8c6d7..90bbfb43ab4b 100644 --- a/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/BuildTasksImpl.groovy +++ b/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/BuildTasksImpl.groovy @@ -48,6 +48,7 @@ class BuildTasksImpl extends BuildTasks { } buildContext.notifyArtifactBuilt(targetFile) } + logFreeDiskSpace("after building sources archive") } @Override @@ -299,7 +300,9 @@ idea.fatal.error.notification=disabled copyDependenciesFile() def patchedApplicationInfo = patchApplicationInfo() + logFreeDiskSpace("before compilation") def distributionJARsBuilder = compileModulesForDistribution(patchedApplicationInfo) + logFreeDiskSpace("after compilation") def mavenArtifacts = buildContext.productProperties.mavenArtifacts if (mavenArtifacts.forIdeModules || !mavenArtifacts.additionalModules.isEmpty()) { buildContext.executeStep("Generate Maven artifacts", BuildOptions.MAVEN_ARTIFACTS_STEP) { @@ -354,8 +357,14 @@ idea.fatal.error.notification=disabled } } } + logFreeDiskSpace("after building distributions") } + private void logFreeDiskSpace(String phase) { + CompilationContextImpl.logFreeDiskSpace(buildContext.messages, buildContext.paths.buildOutputRoot, phase) + } + + private def copyDependenciesFile() { if (buildContext.gradle.forceRun('Preparing dependencies file', 'dependenciesFile')) { def outputFile = "$buildContext.paths.artifacts/dependencies.txt" diff --git a/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/CompilationContextImpl.groovy b/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/CompilationContextImpl.groovy index ec233782bbd1..9fed638528f3 100644 --- a/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/CompilationContextImpl.groovy +++ b/platform/build-scripts/groovy/org/jetbrains/intellij/build/impl/CompilationContextImpl.groovy @@ -16,6 +16,7 @@ package org.jetbrains.intellij.build.impl import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.util.text.StringUtil import com.intellij.util.PathUtilRt import com.intellij.util.SystemProperties import groovy.transform.CompileDynamic @@ -73,6 +74,7 @@ class CompilationContextImpl implements CompilationContext { } def dependenciesProjectDir = new File(communityHome, 'build/dependencies') + logFreeDiskSpace(messages, projectHome, "before dwonloading dependencies") GradleRunner gradle = new GradleRunner(dependenciesProjectDir, messages, SystemProperties.getJavaHome()) if (!options.isInDevelopmentMode) { setupCompilationDependencies(gradle) @@ -369,6 +371,11 @@ class CompilationContextImpl implements CompilationContext { private static String toCanonicalPath(String path) { FileUtil.toSystemIndependentName(new File(path).canonicalPath) } + + static void logFreeDiskSpace(BuildMessages buildMessages, String directoryPath, String phase) { + def dir = new File(directoryPath) + buildMessages.debug("Free disk space $phase: ${StringUtil.formatFileSize(dir.freeSpace)} (on disk containing $dir)") + } } class BuildPathsImpl extends BuildPaths { diff --git a/platform/core-api/src/com/intellij/psi/stubs/StubBase.java b/platform/core-api/src/com/intellij/psi/stubs/StubBase.java index 14453b447f15..2f2f7a7fc586 100644 --- a/platform/core-api/src/com/intellij/psi/stubs/StubBase.java +++ b/platform/core-api/src/com/intellij/psi/stubs/StubBase.java @@ -51,7 +51,8 @@ public abstract class StubBase extends ObjectStubBase List collectParentsOfType(PsiElement element, Class parent, Class... stopClasses) { + public static List collectParents(PsiElement element, Class parent, Predicate stopCondition) { element = element.getParent(); List parents = new SmartList<>(); while (element != null) { - if (instanceOf(element, stopClasses)) break; + if (stopCondition.test(element)) break; if (parent.isInstance(element)) { parents.add(parent.cast(element)); } diff --git a/platform/core-impl/src/com/intellij/extapi/psi/StubBasedPsiElementBase.java b/platform/core-impl/src/com/intellij/extapi/psi/StubBasedPsiElementBase.java index eb91ea950877..fc23b904d501 100644 --- a/platform/core-impl/src/com/intellij/extapi/psi/StubBasedPsiElementBase.java +++ b/platform/core-impl/src/com/intellij/extapi/psi/StubBasedPsiElementBase.java @@ -84,7 +84,6 @@ public class StubBasedPsiElementBase extends ASTDelegateP public static final Key CREATION_TRACE = Key.create("CREATION_TRACE"); public static final boolean ourTraceStubAstBinding = "true".equals(System.getProperty("trace.stub.ast.binding", "false")); private volatile SubstrateRef mySubstrateRef; - private volatile int myStubIndex = -1; private final IElementType myElementType; public StubBasedPsiElementBase(@NotNull T stub, @NotNull IStubElementType nodeType) { @@ -238,29 +237,6 @@ public class StubBasedPsiElementBase extends ASTDelegateP */ public final void setSubstrateRef(@NotNull SubstrateRef substrateRef) { mySubstrateRef = substrateRef; - myStubIndex = -1; - } - - /** - * Don't invoke this method, it's public for implementation reasons. - */ - public final void setStubIndex(int stubIndex) { - myStubIndex = stubIndex; - } - - /** - * Don't invoke this method, it's public for implementation reasons. - */ - public int getStubIndex() { - return myStubIndex; - } - - /** - * Don't invoke this method, it's public for implementation reasons. - */ - @NotNull - public final SubstrateRef getSubstrateRef() { - return mySubstrateRef; } @NotNull @@ -383,7 +359,7 @@ public class StubBasedPsiElementBase extends ASTDelegateP public T getStub() { ProgressIndicatorProvider.checkCanceled(); // Hope, this is called often //noinspection unchecked - return (T)mySubstrateRef.getStub(myStubIndex); + return (T)mySubstrateRef.getStub(); } /** @@ -395,7 +371,7 @@ public class StubBasedPsiElementBase extends ASTDelegateP public final T getGreenStub() { ProgressIndicatorProvider.checkCanceled(); // Hope, this is called often //noinspection unchecked - return (T)mySubstrateRef.getGreenStub(myStubIndex); + return (T)mySubstrateRef.getGreenStub(); } /** diff --git a/platform/core-impl/src/com/intellij/psi/impl/source/AstPathPsiMap.java b/platform/core-impl/src/com/intellij/psi/impl/source/AstPathPsiMap.java deleted file mode 100644 index 87e300b5a03b..000000000000 --- a/platform/core-impl/src/com/intellij/psi/impl/source/AstPathPsiMap.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2000-2016 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 com.intellij.psi.impl.source; - -import com.intellij.extapi.psi.StubBasedPsiElementBase; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.LowMemoryWatcher; -import com.intellij.openapi.util.UserDataHolderEx; -import com.intellij.psi.impl.DebugUtil; -import com.intellij.psi.impl.source.tree.AstPath; -import com.intellij.psi.impl.source.tree.CompositeElement; -import com.intellij.reference.SoftReference; -import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.lang.ref.Reference; -import java.lang.ref.ReferenceQueue; -import java.lang.ref.WeakReference; -import java.util.Objects; -import java.util.concurrent.ConcurrentMap; -import java.util.stream.Stream; - -/** - * A weak cache for all instantiated stub-based PSI to allow {@link CompositeElement#getPsi()} return it when AST is reloaded.

- * - * All methods should be called under an external lock (view provider's PsiLock), except for - * ({@link #getCachedPsi(AstPath)} which can be called without lock. - * - * @author peter - */ -class AstPathPsiMap { - /** - * Not using ConcurrentWeakValueMap because we need to clean each of them up separately, when ASTs in thousands of files are created and gc-ed. - * So we have a per-project single shared reference queue {@link #myQueue} for that. - * Otherwise the files end up retaining lots of maps with all-gc-ed stuff inside, but the maps are still very large. - */ - private final ConcurrentMap myMap = ContainerUtil.newConcurrentMap(); - private volatile Boolean myHasUnbindableCachedPsi = null; - - private static final Key STUB_PSI_REFS = Key.create("STUB_PSI_REFS"); - private final MyReferenceQueue myQueue; - - AstPathPsiMap(@NotNull Project project) { - MyReferenceQueue queue = project.getUserData(STUB_PSI_REFS); - myQueue = queue != null ? queue : ((UserDataHolderEx)project).putUserDataIfAbsent(STUB_PSI_REFS, new MyReferenceQueue(project)); - } - - void invalidatePsi() { - myQueue.cleanupStaleReferences(); - getAllCachedPsi().forEach(psi -> { - DebugUtil.onInvalidated(psi); - psi.setSubstrateRef(SubstrateRef.createInvalidRef(psi)); - }); - myMap.clear(); - myHasUnbindableCachedPsi = false; - } - - void switchToStrongRefs() { - myQueue.cleanupStaleReferences(); - getAllCachedPsi().forEach(psi -> { - CompositeElement node = (CompositeElement)psi.getNode(); - node.setPsi(psi); - psi.setSubstrateRef(SubstrateRef.createAstStrongRef(node)); - }); - myMap.clear(); - myHasUnbindableCachedPsi = false; - } - - @Nullable - StubBasedPsiElementBase getCachedPsi(@NotNull AstPath ref) { - return SoftReference.dereference(myMap.get(ref)); - } - - @NotNull - StubBasedPsiElementBase cachePsi(@NotNull AstPath key, @NotNull StubBasedPsiElementBase psi) { - myQueue.cleanupStaleReferences(); - // ensure PSI will use AST path before making it available to other threads - // otherwise another thread could invoke StubRef.getNode and fail since file's AST isn't set yet - psi.setSubstrateRef(key); - myMap.put(key, new MyReference(psi, key, myQueue)); - clearStubIndexCache(); - return psi; - } - - Stream> getAllCachedPsi() { - myQueue.cleanupStaleReferences(); - if (myMap.isEmpty()) return Stream.empty(); - - return myMap.values().stream().map(Reference::get).filter(Objects::nonNull); - } - - boolean hasUnbindableCachedPsi() { - Boolean answer = myHasUnbindableCachedPsi; - if (answer == null) { - myHasUnbindableCachedPsi = answer = calcHasUnbindableCachedPsi(); - } - return answer; - } - - private boolean calcHasUnbindableCachedPsi() { - myQueue.cleanupStaleReferences(); - if (myMap.isEmpty()) return false; - - return getAllCachedPsi().anyMatch(e -> e.getStubIndex() < 0); - } - - void clearStubIndexCache() { - myHasUnbindableCachedPsi = null; - } - - private static class MyReference extends WeakReference> { - final AstPath pathRef; - - MyReference(StubBasedPsiElementBase psi, AstPath ref, ReferenceQueue> queue) { - super(psi, queue); - pathRef = ref; - } - } - - private static class MyReferenceQueue extends ReferenceQueue> { - MyReferenceQueue(Project project) { - LowMemoryWatcher.register(() -> cleanupStaleReferences(), project); - } - - void cleanupStaleReferences() { - while (true) { - MyReference reference = (MyReference)poll(); - if (reference == null) break; - - AstPath key = reference.pathRef; - AstPathPsiMap refToPsi = key.getContainingFile().getRefToPsi(); - refToPsi.myMap.remove(key, reference); - refToPsi.clearStubIndexCache(); - } - } - - } -} diff --git a/platform/core-impl/src/com/intellij/psi/impl/source/FileTrees.java b/platform/core-impl/src/com/intellij/psi/impl/source/FileTrees.java index 031d99778975..6bf2b1d06ebe 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/source/FileTrees.java +++ b/platform/core-impl/src/com/intellij/psi/impl/source/FileTrees.java @@ -15,32 +15,48 @@ */ package com.intellij.psi.impl.source; +import com.intellij.extapi.psi.StubBasedPsiElementBase; +import com.intellij.lang.ASTNode; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Getter; +import com.intellij.psi.PsiElement; +import com.intellij.psi.impl.DebugUtil; +import com.intellij.psi.impl.source.tree.CompositeElement; import com.intellij.psi.impl.source.tree.FileElement; -import com.intellij.psi.stubs.PsiFileStubImpl; -import com.intellij.psi.stubs.StubTree; +import com.intellij.psi.stubs.*; import com.intellij.reference.SoftReference; -import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.lang.ref.Reference; -import java.util.Set; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; /** * @author peter */ final class FileTrees { + private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.FileTrees"); + private static final int firstNonFilePsiIndex = 1; + private final PsiFileImpl myFile; private final Reference myStub; private final Getter myTreeElementPointer; // SoftReference/WeakReference to ASTNode or a strong reference to a tree if the file is a DummyHolder - final boolean astLoaded; - final boolean useStrongRefs; + + /** Keeps references to all alive stubbed PSI (using {@link SpineRef}) to ensure PSI identity is preserved after AST/stubs are gc-ed and reloaded */ + @Nullable private final List> myRefToPsi; - private FileTrees(@Nullable Reference stub, @Nullable Getter ast, boolean astLoaded, boolean useStrongRefs) { + private FileTrees(@NotNull PsiFileImpl file, + @Nullable Reference stub, + @Nullable Getter ast, + @Nullable List> refToPsi) { + this.myFile = file; this.myStub = stub; this.myTreeElementPointer = ast; - this.astLoaded = astLoaded; - this.useStrongRefs = useStrongRefs; + this.myRefToPsi = refToPsi; } @Nullable @@ -54,7 +70,41 @@ final class FileTrees { } FileTrees switchToStrongRefs() { - return new FileTrees(myStub, myTreeElementPointer, astLoaded, true); + if (myRefToPsi == null) return this; + + getAllCachedPsi(myRefToPsi).forEach(psi -> { + ASTNode node = psi.getNode(); + LOG.assertTrue(node.getPsi() == psi); + psi.setSubstrateRef(SubstrateRef.createAstStrongRef(node)); + }); + + return new FileTrees(myFile, myStub, myTreeElementPointer, null); + } + + private static Stream getAllCachedPsi(@NotNull List> refToPsi) { + return refToPsi.stream().map(SoftReference::dereference).filter(Objects::nonNull); + } + + boolean useSpineRefs() { + return myRefToPsi != null; + } + + FileTrees switchToSpineRefs(@NotNull StubbedSpine srcSpine) { + List> refToPsi = myRefToPsi; + if (refToPsi == null) refToPsi = new ArrayList<>(Collections.nCopies(srcSpine.getStubCount(), null)); + + for (int i = firstNonFilePsiIndex; i < refToPsi.size(); i++) { + StubBasedPsiElementBase psi = (StubBasedPsiElementBase)srcSpine.getStubPsi(i); + assert psi != null; + psi.setSubstrateRef(new SpineRef(myFile, i)); + StubBasedPsiElementBase existing = SoftReference.dereference(refToPsi.get(i)); + if (existing != null) { + assert existing == psi : "Duplicate PSI found"; + } else { + refToPsi.set(i, new WeakReference<>(psi)); + } + } + return new FileTrees(myFile, myStub, myTreeElementPointer, refToPsi); } FileTrees clearStub(@NotNull String reason) { @@ -62,29 +112,104 @@ final class FileTrees { if (stubHolder != null) { ((PsiFileStubImpl)stubHolder.getRoot()).clearPsi(reason); } - return new FileTrees(null, myTreeElementPointer, astLoaded, useStrongRefs); + + if (myRefToPsi != null) { + DebugUtil.performPsiModification("clearStub", () -> getAllCachedPsi(myRefToPsi).forEach(psi -> { + DebugUtil.onInvalidated(psi); + psi.setSubstrateRef(SubstrateRef.createInvalidRef(psi)); + })); + } + + return new FileTrees(myFile, null, myTreeElementPointer, null); } FileTrees withAst(@NotNull Getter ast) { - return new FileTrees(myStub, ast, true, useStrongRefs); + return new FileTrees(myFile, myStub, ast, myRefToPsi).reconcilePsi(derefStub(), ast.get(), true); } - FileTrees withExclusiveStub(@NotNull StubTree stub, Set allRoots) { - if (derefTreeElement() != null || useStrongRefs) { - throw new RuntimeException(toString() + "; roots=" + allRoots + "; root trees=" + ContainerUtil.map(allRoots, PsiFileImpl::getFileTrees)); - } - return new FileTrees(new SoftReference<>(stub), null, false, false); - } - - FileTrees withGreenStub(@NotNull StubTree stub, @NotNull PsiFileImpl file) { - if (derefTreeElement() == null || !astLoaded) { - throw new RuntimeException("No AST in file " + file + " of " + file.getClass() + "; " + this); - } - return new FileTrees(new SoftReference<>(stub), myTreeElementPointer, true, useStrongRefs); + FileTrees withStub(@NotNull StubTree stub, @Nullable FileElement ast) { + assert derefTreeElement() == ast; + return new FileTrees(myFile, new SoftReference<>(stub), myTreeElementPointer, myRefToPsi) + .reconcilePsi(stub, ast, false); } static FileTrees noStub(@Nullable FileElement ast, @NotNull PsiFileImpl file) { - return new FileTrees(null, ast, ast != null, file instanceof DummyHolder); + return new FileTrees(file, null, ast, null); + } + + /** + * Ensures {@link #myRefToPsi}, stubs and AST all have the same PSI at corresponding indices. + * In case several sources already have PSI (e.g. created during AST parsing), overwrites them with the "correct" one, + * which is taken from {@link #myRefToPsi} if exists, otherwise from either stubs or AST depending on {@code takePsiFromStubs}. + */ + private FileTrees reconcilePsi(@Nullable StubTree stubTree, @Nullable FileElement astRoot, boolean takePsiFromStubs) { + assert stubTree != null || astRoot != null; + + if ((stubTree == null || astRoot == null) && (myRefToPsi == null || !getAllCachedPsi(myRefToPsi).findFirst().isPresent())) { + // there's only one source of PSI, nothing to reconcile + return new FileTrees(myFile, myStub, myTreeElementPointer, null); + } + + List> stubList = stubTree == null ? null : stubTree.getPlainList(); + List nodeList = astRoot == null ? null : astRoot.getStubbedSpine().getSpineNodes(); + + try { + return DebugUtil.performPsiModification("reconcilePsi", () -> { + if (myRefToPsi != null) { + assert myRefToPsi.size() == (stubList != null ? stubList.size() : nodeList.size()) : "Cached PSI count doesn't match actual one"; + bindSubstratesToCachedPsi(stubList, nodeList); + } + + if (stubList != null && nodeList != null) { + assert stubList.size() == nodeList.size() : "Stub count doesn't match stubbed node length"; + + StubbedSpine srcSpine = takePsiFromStubs ? stubTree.getSpine() : astRoot.getStubbedSpine(); + FileTrees result = switchToSpineRefs(srcSpine); + bindStubsWithAst(srcSpine, stubList, nodeList, takePsiFromStubs); + return result; + } + return this; + }); + } + catch (Throwable e) { + LOG.error(e); + myFile.onContentReload(); + myFile.rebuildStub(); + throw StubTreeLoader.getInstance().stubTreeAndIndexDoNotMatch(e.getMessage(), stubTree, myFile); + } + } + + private void bindSubstratesToCachedPsi(List> stubList, List nodeList) { + assert myRefToPsi != null; + for (int i = firstNonFilePsiIndex; i < myRefToPsi.size(); i++) { + StubBasedPsiElementBase cachedPsi = SoftReference.dereference(myRefToPsi.get(i)); + if (cachedPsi != null) { + if (stubList != null) { + //noinspection unchecked + ((StubBase)stubList.get(i)).setPsi(cachedPsi); + } + if (nodeList != null) { + nodeList.get(i).setPsi(cachedPsi); + } + } + } + } + + private static void bindStubsWithAst(StubbedSpine srcSpine, List> stubList, List nodeList, boolean takePsiFromStubs) { + for (int i = firstNonFilePsiIndex; i < stubList.size(); i++) { + StubElement stub = stubList.get(i); + CompositeElement node = nodeList.get(i); + assert stub.getStubType() == node.getElementType() : "Stub type mismatch"; + + PsiElement psi = srcSpine.getStubPsi(i); + assert psi != null; + if (takePsiFromStubs) { + node.setPsi(psi); + } else { + //noinspection unchecked + ((StubBase)stub).setPsi(psi); + } + } } @Override @@ -92,8 +217,7 @@ final class FileTrees { return "FileTrees{" + "stub=" + (myStub == null ? "noRef" : derefStub()) + ", AST=" + (myTreeElementPointer == null ? "noRef" : derefTreeElement()) + - ", astLoaded=" + astLoaded + - ", useStrongRefs=" + useStrongRefs + + ", useSpineRefs=" + useSpineRefs() + '}' ; } } diff --git a/platform/core-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java b/platform/core-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java index 9d2507511d12..eedee4bc13f0 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java +++ b/platform/core-impl/src/com/intellij/psi/impl/source/PsiFileImpl.java @@ -2,7 +2,6 @@ package com.intellij.psi.impl.source; -import com.intellij.extapi.psi.StubBasedPsiElementBase; import com.intellij.ide.util.PsiNavigationSupport; import com.intellij.lang.*; import com.intellij.navigation.ItemPresentation; @@ -14,8 +13,10 @@ import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Queryable; -import com.intellij.openapi.util.Factory; -import com.intellij.openapi.util.*; +import com.intellij.openapi.util.Getter; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileWithId; @@ -40,7 +41,6 @@ import com.intellij.util.Function; import com.intellij.util.IncorrectOperationException; import com.intellij.util.PatchedWeakReference; import com.intellij.util.concurrency.AtomicFieldUpdater; -import com.intellij.util.containers.ContainerUtil; import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -66,8 +66,6 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF private volatile FileTrees myTrees = FileTrees.noStub(null, this); private boolean myInvalidated; @SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized") - private AstPathPsiMap myRefToPsi; - private final ThreadLocal myFileElementBeingLoaded = new ThreadLocal<>(); protected final PsiManagerEx myManager; public static final Key BUILDING_STUB = new Key<>("Don't use stubs mark!"); private final PsiLock myPsiLock; @@ -80,7 +78,6 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF protected PsiFileImpl(@NotNull FileViewProvider provider ) { myManager = (PsiManagerEx)provider.getManager(); myViewProvider = provider; - myRefToPsi = new AstPathPsiMap(getProject()); myPsiLock = ((AbstractFileViewProvider) provider).getFilePsiLock(); } @@ -179,43 +176,16 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF } } - FileElement treeElement = createFileElement(viewProvider.getContents()); - treeElement.setPsi(this); - - myFileElementBeingLoaded.set(treeElement); - try { - while (true) { - FileTrees trees = myTrees; - List> bindings = calcStubAstBindings(treeElement, trees); - - FileElement savedTree = ensureTreeElement(viewProvider, treeElement, trees, bindings); - if (savedTree != null) { - return savedTree; - } - } - } - finally { - myFileElementBeingLoaded.remove(); - } - } - - @Nullable - private FileElement ensureTreeElement(@NotNull FileViewProvider viewProvider, - @NotNull FileElement treeElement, - @NotNull FileTrees trees, - @NotNull List> bindings) { synchronized (myPsiLock) { - FileElement existing = derefTreeElement(); - if (existing != null) { - return existing; + FileElement treeElement = derefTreeElement(); + if (treeElement != null) { + return treeElement; } - if (trees != myTrees) { - return null; // try again - } + treeElement = createFileElement(viewProvider.getContents()); + treeElement.setPsi(this); - switchFromStubToAst(bindings, trees); - updateTrees(trees.withAst(createTreeElementPointer(treeElement))); + updateTrees(myTrees.withAst(createTreeElementPointer(treeElement))); if (LOG.isDebugEnabled() && viewProvider.isPhysical()) { LOG.debug("Loaded text for file " + viewProvider.getVirtualFile().getPresentableUrl()); @@ -229,49 +199,15 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF @Override public StubbedSpine getStubbedSpine() { StubTree tree = getGreenStubTree(); - return tree != null ? tree.getSpine() : calcTreeElement().getStubbedSpine(); - } - - private void switchFromStubToAst(List> bindings, FileTrees trees) { - if (!bindings.isEmpty() && trees.useStrongRefs) { - List psiStrings = ContainerUtil.map(bindings, pair -> pair.first.getClass().getName()); - LOG.error(this + " of " + getClass() + "; " + psiStrings); - } - - for (int i = 0; i < bindings.size(); i++) { - Pair pair = bindings.get(i); - StubBasedPsiElementBase psi = pair.first; - AstPath path = pair.second; - path.getNode().setPsi(psi); - myRefToPsi.cachePsi(path, psi); - psi.setStubIndex(i + 1); - } - myRefToPsi.clearStubIndexCache(); - } - - private List> calcStubAstBindings(@NotNull FileElement root, FileTrees trees) { - final StubTree stubTree = trees.derefStub(); - if (stubTree == null || trees.astLoaded) { // don't bind green stub to AST: the PSI should already be cached in myRefToPsi - return Collections.emptyList(); - } - - try { - List> result = TreeUtil.calcStubAstBindings(stubTree, root); + if (tree != null) return tree.getSpine(); + + AstSpine astSpine = calcTreeElement().getStubbedSpine(); + if (!myTrees.useSpineRefs()) { synchronized (myPsiLock) { - return ContainerUtil.map(result, pair -> { - StubElement stub = pair.first; - PsiElement psi = stub.getPsi(); - assert psi != null : "Stub " + stub + " (" + stub.getClass() + ") has returned null PSI"; - AstPath path = AstPath.getNodePath((CompositeElement)pair.second); - assert path != null : "Null path"; - return Pair.create((StubBasedPsiElementBase)psi, path); - }); + updateTrees(myTrees.switchToSpineRefs(astSpine)); } } - catch (TreeUtil.StubBindingException e) { - reportStubAstMismatch(e.getMessage(), stubTree); - return Collections.emptyList(); - } + return astSpine; } @Nullable @@ -281,15 +217,6 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF return type instanceof IStubFileElementType ? (IStubFileElementType)type : null; } - void reportStubAstMismatch(String message, StubTree stubTree) { - rebuildStub(); - synchronized (myPsiLock) { - updateTrees(myTrees.clearStub(STUB_PSI_MISMATCH)); - } - - throw StubTreeLoader.getInstance().stubTreeAndIndexDoNotMatch(message, stubTree, this); - } - @NotNull protected FileElement createFileElement(CharSequence docText) { final FileElement treeElement; @@ -396,12 +323,13 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF PsiFileImpl clone = BlockSupportImpl.getFileCopy(this, providerCopy); copyCopyableDataTo(clone); - clone.myRefToPsi = new AstPathPsiMap(getProject()); if (getTreeElement() != null) { // not set by provider in clone final FileElement treeClone = (FileElement)calcTreeElement().clone(); clone.setTreeElementPointer(treeClone); // should not use setTreeElement here because cloned file still have VirtualFile (SCR17963) treeClone.setPsi(clone); + } else { + clone.setTreeElementPointer(null); } if (viewProvider.isEventSystemEnabled()) { @@ -583,8 +511,6 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF DebugUtil.performPsiModification("onContentReload", () -> { synchronized (myPsiLock) { - myRefToPsi.invalidatePsi(); - FileElement treeElement = derefTreeElement(); if (treeElement != null) { treeElement.detachFromFile(); @@ -628,13 +554,12 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF public StubTree getStubTree() { assertReadAccessAllowed(); - if (myTrees.astLoaded && !mayReloadStub()) return null; - if (Boolean.TRUE.equals(getUserData(BUILDING_STUB))) return null; + if (getTreeElement() != null) return null; final StubTree derefd = derefStub(); if (derefd != null) return derefd; - if (getElementTypeForStubBuilder() == null) return null; + if (Boolean.TRUE.equals(getUserData(BUILDING_STUB)) || getElementTypeForStubBuilder() == null) return null; final VirtualFile vFile = getVirtualFile(); if (!(vFile instanceof VirtualFileWithId) || !vFile.isValid()) return null; @@ -645,7 +570,7 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF final List> roots = StubTreeBuilder.getStubbedRoots(viewProvider); synchronized (myPsiLock) { - if (!mayLoadExclusiveStub()) return null; + if (getTreeElement() != null) return null; final StubTree derefdOnLock = derefStub(); if (derefdOnLock != null) return derefdOnLock; @@ -664,66 +589,31 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF return null; } - // first, set all references from stubs to existing PSI (in AST or AstPathPsiMap) - Map bindings = prepareAllStubTrees(roots, stubRoots); - StubTree result = bindings.get(this); - assert result != null : "Current file not in root list: " + roots + ", vp=" + viewProvider; - - // now stubs can be safely published - for (PsiFileImpl eachPsiRoot : bindings.keySet()) { - FileTrees trees = eachPsiRoot.myTrees; - StubTree stub = bindings.get(eachPsiRoot); - FileElement ast = trees.derefTreeElement(); - eachPsiRoot.updateTrees(ast == null ? trees.withExclusiveStub(stub, bindings.keySet()) : trees.withGreenStub(stub, eachPsiRoot)); + StubTree result = null; + for (int i = 0; i < roots.size(); i++) { + PsiFileImpl eachPsiRoot = (PsiFileImpl)roots.get(i).second; + if (eachPsiRoot.derefStub() == null) { + StubTree stubTree = eachPsiRoot.setStubTree(stubRoots[i]); + if (eachPsiRoot == this) { + result = stubTree; + } + } } + + assert result != null : "Current file not in root list: " + roots + ", vp=" + viewProvider; return result; } } - private boolean mayLoadExclusiveStub() { - return getTreeElement() == null && !hasUnbindableCachedPsi(); - } - - private static Map prepareAllStubTrees(List> roots, PsiFileStub[] rootStubs) { - Map bindings = ContainerUtil.newIdentityHashMap(); - for (int i = 0; i < roots.size(); i++) { - PsiFileImpl eachPsiRoot = (PsiFileImpl)roots.get(i).second; - //noinspection unchecked - ((StubBase)rootStubs[i]).setPsi(eachPsiRoot); - StubTree stubTree = new StubTree(rootStubs[i]); - FileElement fileElement = eachPsiRoot.getTreeElement(); - stubTree.setDebugInfo("created in getStubTree(), with AST = " + (fileElement != null)); - if (fileElement != null) { - // Set references from these stubs to AST, because: - // Stub index might call getStubTree on main PSI file, but then use getPlainListFromAllRoots and return stubs from another file. - // Even if that file already has AST, stub.getPsi() should be the same as in AST - TreeUtil.bindStubsToTree(stubTree, fileElement); - eachPsiRoot.myRefToPsi.clearStubIndexCache(); - bindings.put(eachPsiRoot, stubTree); - } else if (eachPsiRoot.derefStub() == null && eachPsiRoot.mayLoadExclusiveStub()) { - eachPsiRoot.bindStubsToCachedPsi(stubTree); - bindings.put(eachPsiRoot, stubTree); - } - } - return bindings; - } - - private boolean mayReloadStub() { - if (getTreeElement() != null || useStrongRefs()) { - return false; - } - StubTreeLoader loader = StubTreeLoader.getInstance(); - if (loader != null && loader.isStubReloadingProhibited()) { - return false; - } - return !hasUnbindableCachedPsi(); - } - - private boolean hasUnbindableCachedPsi() { - synchronized (myPsiLock) { - return ContainerUtil.exists(myViewProvider.getAllFiles(), - file -> file instanceof PsiFileImpl && ((PsiFileImpl)file).myRefToPsi.hasUnbindableCachedPsi()); - } + @NotNull + private StubTree setStubTree(PsiFileStub root) { + //noinspection unchecked + ((StubBase)root).setPsi(this); + StubTree stubTree = new StubTree(root); + FileElement fileElement = getTreeElement(); + stubTree.setDebugInfo("created in getStubTree(), with AST = " + (fileElement != null)); + updateTrees(myTrees.withStub(stubTree, fileElement)); + return stubTree; } @Nullable @@ -738,23 +628,8 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF } } - FileTrees getFileTrees() { - return myTrees; - } - - private void bindStubsToCachedPsi(StubTree stubTree) { - myRefToPsi.getAllCachedPsi().forEach(psi -> { - int index = psi.getStubIndex(); - if (index >= 0) { - //noinspection unchecked - ((StubBase)stubTree.getPlainList().get(index)).setPsi(psi); - } - }); - } - protected PsiFileImpl cloneImpl(FileElement treeElementClone) { PsiFileImpl clone = (PsiFileImpl)super.clone(); - clone.myRefToPsi = new AstPathPsiMap(getProject()); clone.setTreeElementPointer(treeElementClone); // should not use setTreeElement here because cloned file still have VirtualFile (SCR17963) treeElementClone.setPsi(clone); return clone; @@ -791,14 +666,8 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF @NotNull public final FileElement calcTreeElement() { - // Attempt to find (loaded) tree element without taking lock first. FileElement treeElement = getTreeElement(); - if (treeElement != null) return treeElement; - - treeElement = myFileElementBeingLoaded.get(); - if (treeElement != null) return treeElement; - - return loadTreeElement(); + return treeElement != null ? treeElement : loadTreeElement(); } @Override @@ -1026,7 +895,6 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF if (tree != null) { return tree; } - assert myFileElementBeingLoaded.get() == null : "non-empty thread-local"; FileElement fileElement = calcTreeElement(); synchronized (myPsiLock) { tree = derefStub(); @@ -1052,23 +920,14 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF tree = new StubTree((PsiFileStub)currentStubTree); tree.setDebugInfo("created in calcStubTree"); - try { - TreeUtil.bindStubsToTree(tree, fileElement); - myRefToPsi.clearStubIndexCache(); - } - catch (TreeUtil.StubBindingException e) { - rebuildStub(); - throw new RuntimeException("Stub and PSI element type mismatch in " + getName(), e); - } - - updateTrees(myTrees.withGreenStub(tree, this)); + updateTrees(myTrees.withStub(tree, fileElement)); } return tree; } } - private void rebuildStub() { + final void rebuildStub() { ApplicationManager.getApplication().invokeLater(() -> { if (!myManager.isDisposed()) { myManager.dropPsiCaches(); @@ -1104,13 +963,10 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF public final void beforeAstChange() { checkWritable(); - if (!useStrongRefs()) { - synchronized (myPsiLock) { - for (PsiFile root : myViewProvider.getAllFiles()) { - if (root instanceof PsiFileImpl) { - ((PsiFileImpl)root).switchToStrongRefs(); - } - } + synchronized (myPsiLock) { + FileTrees updated = myTrees.switchToStrongRefs(); + if (updated != myTrees) { + updateTrees(updated); } } } @@ -1123,42 +979,4 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF CheckUtil.checkWritable(this); } } - - private void switchToStrongRefs() { - FileElement node = calcTreeElement(); - updateTrees(myTrees.switchToStrongRefs()); - myRefToPsi.switchToStrongRefs(); - AstPath.invalidatePaths(node); - } - - @Nullable - public StubBasedPsiElementBase obtainPsi(@NotNull AstPath path, @NotNull Factory> creator) { - if (useStrongRefs()) { - return null; - } - - StubBasedPsiElementBase psi = myRefToPsi.getCachedPsi(path); - if (psi != null) return psi; - - synchronized (myPsiLock) { - if (useStrongRefs()) { - return null; - } - - psi = myRefToPsi.getCachedPsi(path); - return psi != null ? psi : myRefToPsi.cachePsi(path, creator.create()); - } - } - - final AstPathPsiMap getRefToPsi() { - return myRefToPsi; - } - - public final boolean useStrongRefs() { - return myTrees.useStrongRefs; - } - - public boolean mayCacheAst() { - return myFileElementBeingLoaded.get() == null; - } } diff --git a/platform/core-impl/src/com/intellij/psi/impl/source/SpineRef.java b/platform/core-impl/src/com/intellij/psi/impl/source/SpineRef.java new file mode 100644 index 000000000000..f16d6b4f859f --- /dev/null +++ b/platform/core-impl/src/com/intellij/psi/impl/source/SpineRef.java @@ -0,0 +1,53 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.psi.impl.source; + +import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiFile; +import com.intellij.psi.stubs.Stub; +import com.intellij.psi.stubs.StubTree; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author peter + */ +class SpineRef extends SubstrateRef { + private final PsiFileImpl myFile; + private final int myIndex; + + SpineRef(@NotNull PsiFileImpl file, int index) { + myFile = file; + myIndex = index; + } + + @NotNull + @Override + public ASTNode getNode() { + return myFile.calcTreeElement().getStubbedSpine().getSpineNodes().get(myIndex); + } + + @Nullable + @Override + public Stub getStub() { + StubTree tree = myFile.getStubTree(); + return tree == null ? null : tree.getPlainList().get(myIndex); + } + + @Nullable + @Override + public Stub getGreenStub() { + StubTree tree = myFile.getGreenStubTree(); + return tree == null ? null : tree.getPlainList().get(myIndex); + } + + @Override + public boolean isValid() { + return myFile.isValid(); + } + + @NotNull + @Override + public PsiFile getContainingFile() { + return myFile; + } +} diff --git a/platform/core-impl/src/com/intellij/psi/impl/source/SubstrateRef.java b/platform/core-impl/src/com/intellij/psi/impl/source/SubstrateRef.java index 45491ba69508..f862caa7d683 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/source/SubstrateRef.java +++ b/platform/core-impl/src/com/intellij/psi/impl/source/SubstrateRef.java @@ -42,13 +42,13 @@ public abstract class SubstrateRef { public abstract ASTNode getNode(); @Nullable - public Stub getStub(int stubIndex) { + public Stub getStub() { return null; } @Nullable - public Stub getGreenStub(int index) { - return getStub(index); + public Stub getGreenStub() { + return getStub(); } public abstract boolean isValid(); @@ -119,7 +119,7 @@ public abstract class SubstrateRef { @NotNull @Override - public Stub getStub(int stubIndex) { + public Stub getStub() { return myStub; } diff --git a/platform/core-impl/src/com/intellij/psi/impl/source/tree/AstPath.java b/platform/core-impl/src/com/intellij/psi/impl/source/tree/AstPath.java deleted file mode 100644 index b91a3f305117..000000000000 --- a/platform/core-impl/src/com/intellij/psi/impl/source/tree/AstPath.java +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Copyright 2000-2016 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 com.intellij.psi.impl.source.tree; - -import com.intellij.extapi.psi.StubBasedPsiElementBase; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.vfs.VirtualFileWithId; -import com.intellij.psi.PsiElement; -import com.intellij.psi.impl.source.PsiFileImpl; -import com.intellij.psi.impl.source.SubstrateRef; -import com.intellij.psi.stubs.IStubElementType; -import com.intellij.psi.stubs.Stub; -import com.intellij.psi.stubs.StubTree; -import com.intellij.reference.SoftReference; -import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.lang.ref.WeakReference; -import java.util.List; - -/** - * A lightweight object representing a chain of node indices (among all lazy-parseable and stub-based elements) - * allowing to restore a specific node after it's been garbage-collected and recreated. - * - * @author peter - */ -public abstract class AstPath extends SubstrateRef { - @SuppressWarnings("ConstantForZeroLengthArrayAllocation") // need a different instance to distinguish removed children for diagnostics - private static final CompositeElement[] REMOVED_PATH_CHILDREN = new CompositeElement[0]; - private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.tree.AstPath"); - private static final Key PATH_CHILDREN = Key.create("PATH_CHILDREN"); - private static final Key NODE_PATH = Key.create("NODE_PATH"); - - @NotNull - public abstract PsiFileImpl getContainingFile(); - - @NotNull - public abstract CompositeElement getNode(); - - @Override - public boolean isValid() { - return getContainingFile().isValid(); - } - - protected abstract int getDepth(); - - @Nullable - @Override - public Stub getStub(int stubIndex) { - if (stubIndex < 0) return null; - - StubTree stubTree = getFileStubTree(); - return stubTree == null ? null : stubTree.getPlainList().get(stubIndex); - } - - @Nullable - protected abstract StubTree getFileStubTree(); - - @Nullable - @Override - public Stub getGreenStub(int stubIndex) { - if (stubIndex < 0) return null; - - StubTree stubTree = getContainingFile().getGreenStubTree(); - return stubTree == null ? null : stubTree.getPlainList().get(stubIndex); - } - - @Nullable - public static AstPath getNodePath(@NotNull CompositeElement node) { - if (node instanceof FileElement) { - PsiElement psi = node.getCachedPsi(); - if (!(psi instanceof PsiFileImpl)) return null; - - PsiFileImpl file = (PsiFileImpl)psi; - if (!(file.getVirtualFile() instanceof VirtualFileWithId) || file.getElementTypeForStubBuilder() == null) { - return null; - } - return new RootPath(file); - } - - return node.getUserData(NODE_PATH); - } - - static void cacheNodePaths(@NotNull LazyParseableElement parent, @Nullable TreeElement child, @Nullable AstPath parentPath) { - if (parentPath == null) { - return; - } - - final int depth = parentPath.getDepth() + 1; - - final List children = ContainerUtil.newArrayList(); - while (child != null) { - child.acceptTree(new RecursiveTreeElementWalkingVisitor(false) { - @Override - public void visitComposite(CompositeElement composite) { - boolean lazy = composite instanceof LazyParseableElement; - if (lazy || composite.getElementType() instanceof IStubElementType) { - int index = children.size(); - composite.putUserData(NODE_PATH, depth % 4 == 0 ? new MilestoneChildPath(parentPath, index, depth) : new ChildPath(parentPath, index)); - children.add(composite); - } - - if (!lazy) { - super.visitComposite(composite); - } - } - }); - child = child.getTreeNext(); - } - - parent.putUserData(PATH_CHILDREN, children.isEmpty() ? CompositeElement.EMPTY_ARRAY : children.toArray(CompositeElement.EMPTY_ARRAY)); - - for (CompositeElement each : children) { - if (each instanceof LazyParseableElement && ((LazyParseableElement)each).isParsed()) { - cacheNodePaths((LazyParseableElement)each, each.getFirstChildNode(), getNodePath(each)); - } - } - } - - public static void invalidatePaths(@NotNull LazyParseableElement scope) { - CompositeElement[] children = scope.getUserData(PATH_CHILDREN); - if (children == null) return; - - scope.putUserData(PATH_CHILDREN, REMOVED_PATH_CHILDREN); - for (CompositeElement child : children) { - child.putUserData(NODE_PATH, null); - assertConsistency(child.getCachedPsi()); - if (child instanceof LazyParseableElement) { - invalidatePaths((LazyParseableElement)child); - } - } - } - - private static void assertConsistency(PsiElement cachedPsi) { - if (cachedPsi instanceof StubBasedPsiElementBase && - ((StubBasedPsiElementBase)cachedPsi).getSubstrateRef() instanceof AstPath) { - LOG.error("Expected strong reference at " + cachedPsi + - " of " + cachedPsi.getClass() + - " and " + ((StubBasedPsiElementBase)cachedPsi).getElementType()); - } - } - - private static class ChildPath extends AstPath { - private final AstPath myParent; - private final int myIndex; - - ChildPath(@NotNull AstPath parent, int index) { - myParent = parent; - myIndex = index; - } - - @NotNull - @Override - public PsiFileImpl getContainingFile() { - return myParent.getContainingFile(); - } - - @NotNull - @Override - public CompositeElement getNode() { - CompositeElement parentNode = myParent.getNode(); - //noinspection ResultOfMethodCallIgnored - parentNode.getFirstChildNode(); // expand chameleons, populate PATH_CHILDREN array - CompositeElement[] children = parentNode.getUserData(PATH_CHILDREN); - boolean removed = children == REMOVED_PATH_CHILDREN; - if (children == null || removed) { - throw reportMissingChildren(parentNode, removed); - } - if (myIndex >= children.length) { - throw new AssertionError(myIndex + " >= " + children.length + "; " + parentNode + " of " + parentNode.getClass()); - } - return children[myIndex]; - } - - private AssertionError reportMissingChildren(CompositeElement parentNode, boolean removed) { - String message = "No path children in " + parentNode + " of " + parentNode.getClass() + "; removed=" + removed; - - PsiFileImpl file = getContainingFile(); - message += "\n file: " + file + " of " + file.getClass() + "; physical=" + file.isPhysical() + "; useStrongRefs=" + file.useStrongRefs(); - - FileElement fileElement = file.getTreeElement(); - message += "\n ast=" + fileElement; - if (fileElement != null) { - CompositeElement[] rootChildren = fileElement.getUserData(PATH_CHILDREN); - message += "; root.children=" + (rootChildren == REMOVED_PATH_CHILDREN ? "removed" : rootChildren == null ? "null" : "nonNull"); - } - return new AssertionError(message); - } - - @Override - protected int getDepth() { - return 1 + myParent.getDepth(); - } - - @Override - protected StubTree getFileStubTree() { - return myParent.getFileStubTree(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ChildPath)) return false; - - ChildPath path = (ChildPath)o; - return myIndex == path.myIndex && myParent.equals(path.myParent); - } - - @Override - public int hashCode() { - return 31 * myParent.hashCode() + myIndex; - } - } - - private static class MilestoneChildPath extends ChildPath { - private final int myDepth; - private final PsiFileImpl myFile; - private volatile WeakReference myNode; - - MilestoneChildPath(@NotNull AstPath parent, int index, int depth) { - super(parent, index); - myDepth = depth; - myFile = parent.getContainingFile(); - } - - @NotNull - @Override - public CompositeElement getNode() { - CompositeElement node = SoftReference.dereference(myNode); - if (node == null) { - node = super.getNode(); - if (myFile.mayCacheAst()) { - myNode = new WeakReference<>(node); - } - } - return node; - } - - @Override - protected StubTree getFileStubTree() { - return SoftReference.dereference(myNode) == null ? myFile.getStubTree() : null; - } - - @NotNull - @Override - public PsiFileImpl getContainingFile() { - return myFile; - } - - @Override - protected int getDepth() { - return myDepth; - } - } - - private static class RootPath extends AstPath { - private final PsiFileImpl myFile; - - RootPath(@NotNull PsiFileImpl file) { - myFile = file; - } - - @Override - public boolean equals(Object o) { - return this == o || o instanceof RootPath && myFile.equals(((RootPath)o).myFile); - } - - @Override - public int hashCode() { - return myFile.hashCode(); - } - - @NotNull - @Override - public PsiFileImpl getContainingFile() { - return myFile; - } - - @NotNull - @Override - public CompositeElement getNode() { - return myFile.calcTreeElement(); - } - - @Override - protected int getDepth() { - return 0; - } - - @Override - protected StubTree getFileStubTree() { - return myFile.getStubTree(); - } - } - -} diff --git a/platform/core-impl/src/com/intellij/psi/impl/source/tree/AstSpine.java b/platform/core-impl/src/com/intellij/psi/impl/source/tree/AstSpine.java index c93d08e4e92f..f6e1107abbbc 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/source/tree/AstSpine.java +++ b/platform/core-impl/src/com/intellij/psi/impl/source/tree/AstSpine.java @@ -57,7 +57,8 @@ public class AstSpine implements StubbedSpine { return index >= myNodes.size() ? null : myNodes.get(index).getElementType(); } - List getNodes() { + @NotNull + public List getSpineNodes() { return myNodes; } } diff --git a/platform/core-impl/src/com/intellij/psi/impl/source/tree/CompositeElement.java b/platform/core-impl/src/com/intellij/psi/impl/source/tree/CompositeElement.java index 46158a8fa2de..b205c1c3aa34 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/source/tree/CompositeElement.java +++ b/platform/core-impl/src/com/intellij/psi/impl/source/tree/CompositeElement.java @@ -18,7 +18,6 @@ package com.intellij.psi.impl.source.tree; import com.intellij.diagnostic.ThreadDumper; import com.intellij.extapi.psi.ASTDelegatePsiElement; -import com.intellij.extapi.psi.StubBasedPsiElementBase; import com.intellij.lang.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; @@ -29,9 +28,10 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.impl.DebugUtil; import com.intellij.psi.impl.FreeThreadedFileViewProvider; -import com.intellij.psi.impl.source.*; +import com.intellij.psi.impl.source.DummyHolder; +import com.intellij.psi.impl.source.DummyHolderFactory; +import com.intellij.psi.impl.source.SourceTreeToPsiMap; import com.intellij.psi.impl.source.codeStyle.CodeEditUtil; -import com.intellij.psi.stubs.IStubElementType; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import com.intellij.psi.util.PsiUtilCore; @@ -723,21 +723,10 @@ public class CompositeElement extends TreeElement { PsiElement wrapper = myWrapper; if (wrapper != null) return wrapper; - wrapper = obtainStubBasedPsi(); - if (wrapper == null) wrapper = createPsiNoLock(); + wrapper = createPsiNoLock(); return ourPsiUpdater.compareAndSet(this, null, wrapper) ? wrapper : ObjectUtils.assertNotNull(myWrapper); } - /** - * If AST has been gced and recreated, but someone still holds a reference to a PSI, then {@link #getPsi()} should return the very same PSI object. - * So we try to find that PSI in file's {@link AstPathPsiMap}. - */ - @Nullable - private PsiElement obtainStubBasedPsi() { - AstPath path = getElementType() instanceof IStubElementType ? AstPath.getNodePath(this) : null; - return path == null ? null : path.getContainingFile().obtainPsi(path, () -> (StubBasedPsiElementBase)createPsiNoLock()); - } - @Override public T getPsi(@NotNull Class clazz) { return LeafElement.getPsi(clazz, getPsi(), LOG); @@ -755,6 +744,10 @@ public class CompositeElement extends TreeElement { } public void setPsi(@NotNull PsiElement psi) { + PsiElement prev = myWrapper; + if (prev != null && prev != psi) { + DebugUtil.onInvalidated(prev); + } myWrapper = psi; } @@ -769,13 +762,6 @@ public class CompositeElement extends TreeElement { } public void rawAddChildrenWithoutNotifications(@NotNull TreeElement first) { - if (DebugUtil.DO_EXPENSIVE_CHECKS && !(this instanceof LazyParseableElement)) { - PsiFileImpl file = getCachedFile(this); - if (file != null && !file.useStrongRefs()) { - throw new AssertionError("Attempt to modify PSI in a file with weakly-referenced AST. Possible cause: missing PomTransaction."); - } - } - final TreeElement last = getLastChildNode(); if (last == null){ TreeElement chainLast = rawSetParents(first, this); diff --git a/platform/core-impl/src/com/intellij/psi/impl/source/tree/FileElement.java b/platform/core-impl/src/com/intellij/psi/impl/source/tree/FileElement.java index 60988897c194..311da5c0179f 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/source/tree/FileElement.java +++ b/platform/core-impl/src/com/intellij/psi/impl/source/tree/FileElement.java @@ -18,6 +18,7 @@ package com.intellij.psi.impl.source.tree; import com.intellij.lang.*; import com.intellij.openapi.util.Getter; +import com.intellij.openapi.util.RecursionManager; import com.intellij.psi.PsiElement; import com.intellij.psi.StubBuilder; import com.intellij.psi.impl.PsiManagerEx; @@ -27,12 +28,9 @@ import com.intellij.psi.stubs.IStubElementType; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.ILightStubFileElementType; import com.intellij.psi.tree.IStubFileElementType; -import com.intellij.reference.SoftReference; import com.intellij.util.CharTable; import org.jetbrains.annotations.NotNull; -import java.lang.ref.Reference; -import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; @@ -40,7 +38,7 @@ public class FileElement extends LazyParseableElement implements FileASTNode, Ge public static final FileElement[] EMPTY_ARRAY = new FileElement[0]; private volatile CharTable myCharTable = new CharTableImpl(); private volatile boolean myDetached; - private volatile Reference myStubbedSpine; + private volatile AstSpine myStubbedSpine; @Override protected PsiElement createPsiNoLock() { @@ -108,13 +106,17 @@ public class FileElement extends LazyParseableElement implements FileASTNode, Ge @NotNull public final AstSpine getStubbedSpine() { - AstSpine result = SoftReference.dereference(myStubbedSpine); + AstSpine result = myStubbedSpine; if (result == null) { - IStubFileElementType type = ((PsiFileImpl)getPsi()).getElementTypeForStubBuilder(); + PsiFileImpl file = (PsiFileImpl)getPsi(); + IStubFileElementType type = file.getElementTypeForStubBuilder(); if (type == null) return AstSpine.EMPTY_SPINE; - result = new AstSpine(calcStubbedDescendants(type.getBuilder())); - myStubbedSpine = getManager().isBatchFilesProcessingMode() ? new WeakReference<>(result) : new SoftReference<>(result); + result = RecursionManager.doPreventingRecursion(file, false, () -> new AstSpine(calcStubbedDescendants(type.getBuilder()))); + if (result == null) { + throw new StackOverflowError("Endless recursion prevented"); + } + myStubbedSpine = result; } return result; } diff --git a/platform/core-impl/src/com/intellij/psi/impl/source/tree/LazyParseableElement.java b/platform/core-impl/src/com/intellij/psi/impl/source/tree/LazyParseableElement.java index af2fa02c2df1..bf0501167bdc 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/source/tree/LazyParseableElement.java +++ b/platform/core-impl/src/com/intellij/psi/impl/source/tree/LazyParseableElement.java @@ -19,7 +19,6 @@ */ package com.intellij.psi.impl.source.tree; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Attachment; import com.intellij.openapi.diagnostic.LogUtil; import com.intellij.openapi.diagnostic.Logger; @@ -200,7 +199,7 @@ public class LazyParseableElement extends CompositeElement { } if (parsedNode != null) { - setChildren(parsedNode, AstPath.getNodePath(this)); + setChildren(parsedNode); } myParsed = true; @@ -220,10 +219,9 @@ public class LazyParseableElement extends CompositeElement { } } - private void setChildren(@NotNull TreeElement parsedNode, @Nullable AstPath thisPath) { + private void setChildren(@NotNull TreeElement parsedNode) { ProgressManager.getInstance().executeNonCancelableSection(() -> { try { - AstPath.cacheNodePaths(this, parsedNode, thisPath); TreeElement last = rawSetParents(parsedNode, this); super.setFirstChildNode(parsedNode); super.setLastChildNode(last); diff --git a/platform/core-impl/src/com/intellij/psi/impl/source/tree/TreeUtil.java b/platform/core-impl/src/com/intellij/psi/impl/source/tree/TreeUtil.java index f25af12cd404..c9acf84d4484 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/source/tree/TreeUtil.java +++ b/platform/core-impl/src/com/intellij/psi/impl/source/tree/TreeUtil.java @@ -16,21 +16,14 @@ package com.intellij.psi.impl.source.tree; -import com.intellij.extapi.psi.StubBasedPsiElementBase; import com.intellij.lang.ASTNode; import com.intellij.lexer.Lexer; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.util.Couple; import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiComment; -import com.intellij.psi.PsiFile; import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.impl.DebugUtil; -import com.intellij.psi.stubs.StubBase; -import com.intellij.psi.stubs.StubElement; -import com.intellij.psi.stubs.StubTree; import com.intellij.psi.templateLanguages.OuterLanguageElement; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.IStrongWhitespaceHolderElementType; @@ -38,7 +31,9 @@ import com.intellij.psi.tree.TokenSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; public class TreeUtil { @@ -441,45 +436,6 @@ public class TreeUtil { boolean isStrongElementOnRisingSlope = true; } - public static class StubBindingException extends RuntimeException { - StubBindingException(String message) { - super(message); - } - } - - public static void bindStubsToTree(@NotNull StubTree stubTree, @NotNull FileElement tree) throws StubBindingException { - List> bindings = calcStubAstBindings(stubTree, tree); - - for (int i = 0; i < bindings.size(); i++) { - Pair pair = bindings.get(i); - StubBasedPsiElementBase psi = (StubBasedPsiElementBase)pair.second.getPsi(); - //noinspection unchecked - pair.first.setPsi(psi); - psi.setStubIndex(i + 1); - } - } - - @NotNull - public static List> calcStubAstBindings(@NotNull StubTree stubTree, @NotNull FileElement tree) throws StubBindingException { - PsiFile file = (PsiFile)tree.getPsi(); - List nodes = tree.getStubbedSpine().getNodes(); - List> stubs = stubTree.getPlainList(); - if (stubs.size() != nodes.size()) { - throw new StubBindingException("Stub list in " + file.getName() + " length differs from PSI"); - } - - List> bindings = new ArrayList<>(); - for (int i = 1; i < stubs.size(); i++) { // start from 1 to skip file root stub - StubBase stub = (StubBase)stubs.get(i); - CompositeElement node = nodes.get(i); - if (stub.getStubType() != node.getElementType()) { - throw new StubBindingException("stub:" + stub + ", AST:" + node.getElementType()); - } - bindings.add(Pair.create(stub, node)); - } - return bindings; - } - @Nullable public static ASTNode skipWhitespaceAndComments(final ASTNode node, boolean forward) { return skipWhitespaceCommentsAndTokens(node, TokenSet.EMPTY, forward); diff --git a/platform/core-impl/src/com/intellij/psi/stubs/StubTreeLoader.java b/platform/core-impl/src/com/intellij/psi/stubs/StubTreeLoader.java index 81c764262246..8196d8352c28 100644 --- a/platform/core-impl/src/com/intellij/psi/stubs/StubTreeLoader.java +++ b/platform/core-impl/src/com/intellij/psi/stubs/StubTreeLoader.java @@ -51,10 +51,6 @@ public abstract class StubTreeLoader { @Nullable public abstract ObjectStubTree readFromVFile(Project project, final VirtualFile vFile); - public boolean isStubReloadingProhibited() { - return false; - } - public abstract void rebuildStubTree(VirtualFile virtualFile); public abstract boolean canHaveStub(VirtualFile file); diff --git a/platform/diff-impl/src/com/intellij/openapi/vcs/ex/LineStatusMarkerPopupRenderer.java b/platform/diff-impl/src/com/intellij/openapi/vcs/ex/LineStatusMarkerPopupRenderer.java index 48e0b0eb5a56..024f5fe111ba 100644 --- a/platform/diff-impl/src/com/intellij/openapi/vcs/ex/LineStatusMarkerPopupRenderer.java +++ b/platform/diff-impl/src/com/intellij/openapi/vcs/ex/LineStatusMarkerPopupRenderer.java @@ -97,7 +97,10 @@ public abstract class LineStatusMarkerPopupRenderer extends LineStatusMarkerRend } @Nullable - protected JComponent createAdditionalInfoPanel(@NotNull Editor editor, @NotNull Range range, @Nullable Point mousePosition) { + protected JComponent createAdditionalInfoPanel(@NotNull Editor editor, + @NotNull Range range, + @Nullable Point mousePosition, + @NotNull Disposable disposable) { return null; } @@ -142,7 +145,7 @@ public abstract class LineStatusMarkerPopupRenderer extends LineStatusMarkerRend toolbar.updateActionsImmediately(); // we need valid ActionToolbar.getPreferredSize() to calc size of popup toolbar.setReservePlaceAutoPopupIcon(false); - JComponent additionalInfoPanel = createAdditionalInfoPanel(editor, range, mousePosition); + JComponent additionalInfoPanel = createAdditionalInfoPanel(editor, range, mousePosition, disposable); PopupPanel popupPanel = new PopupPanel(editor, toolbar, editorComponent, additionalInfoPanel); diff --git a/platform/editor-ui-api/src/com/intellij/ide/projectView/TreeStructureProvider.java b/platform/editor-ui-api/src/com/intellij/ide/projectView/TreeStructureProvider.java index 5d13dc167f6c..98ae81917bb8 100644 --- a/platform/editor-ui-api/src/com/intellij/ide/projectView/TreeStructureProvider.java +++ b/platform/editor-ui-api/src/com/intellij/ide/projectView/TreeStructureProvider.java @@ -53,7 +53,7 @@ public interface TreeStructureProvider { * @see com.intellij.openapi.actionSystem.DataProvider */ @Nullable - default Object getData(Collection selected, String dataName) { + default Object getData(@NotNull Collection selected, String dataName) { return null; } } diff --git a/platform/editor-ui-ex/src/com/intellij/ide/todo/TodoConfiguration.java b/platform/editor-ui-ex/src/com/intellij/ide/todo/TodoConfiguration.java index 4218fe0601c6..dd84905cd542 100644 --- a/platform/editor-ui-ex/src/com/intellij/ide/todo/TodoConfiguration.java +++ b/platform/editor-ui-ex/src/com/intellij/ide/todo/TodoConfiguration.java @@ -64,8 +64,11 @@ public class TodoConfiguration implements PersistentStateComponent { buildIndexPatterns(); } + /** + * Returns the list of default TO_DO patterns. Can be customized in other IDEs (and is customized in Rider). + */ @NotNull - private static TodoPattern[] getDefaultPatterns() { + protected TodoPattern[] getDefaultPatterns() { //noinspection SpellCheckingInspection return new TodoPattern[]{ new TodoPattern("\\btodo\\b.*", TodoAttributesUtil.createDefault(), false), diff --git a/platform/lang-api/src/com/intellij/execution/runners/ExecutionUtil.java b/platform/lang-api/src/com/intellij/execution/runners/ExecutionUtil.java index 1fd4ffa96dda..c0197e0a53c4 100644 --- a/platform/lang-api/src/com/intellij/execution/runners/ExecutionUtil.java +++ b/platform/lang-api/src/com/intellij/execution/runners/ExecutionUtil.java @@ -229,7 +229,12 @@ public class ExecutionUtil { return getLiveIndicator(base, 13, 13); } + @SuppressWarnings("UseJBColor") public static Icon getLiveIndicator(@Nullable final Icon base, int emptyIconWidth, int emptyIconHeight) { + return getIndicator(base, emptyIconWidth, emptyIconHeight, Color.GREEN); + } + + public static Icon getIndicator(@Nullable final Icon base, int emptyIconWidth, int emptyIconHeight, Color color) { return new LayeredIcon(base, new Icon() { @SuppressWarnings("UseJBColor") @Override @@ -238,7 +243,7 @@ public class ExecutionUtil { Graphics2D g2d = (Graphics2D)g.create(); try { GraphicsUtil.setupAAPainting(g2d); - g2d.setColor(Color.GREEN); + g2d.setColor(color); Ellipse2D.Double shape = new Ellipse2D.Double(x + getIconWidth() - iSize, y + getIconHeight() - iSize, iSize, iSize); g2d.fill(shape); diff --git a/platform/lang-impl/src/com/intellij/application/options/editor/EditorSmartKeysConfigurable.form b/platform/lang-impl/src/com/intellij/application/options/editor/EditorSmartKeysConfigurable.form index 309450650b52..a458b1bdb4c7 100644 --- a/platform/lang-impl/src/com/intellij/application/options/editor/EditorSmartKeysConfigurable.form +++ b/platform/lang-impl/src/com/intellij/application/options/editor/EditorSmartKeysConfigurable.form @@ -1,6 +1,6 @@

- + @@ -10,7 +10,7 @@ - + @@ -60,7 +60,7 @@ - + @@ -88,7 +88,7 @@ - + @@ -114,7 +114,7 @@ - + @@ -146,7 +146,7 @@ - + @@ -184,7 +184,7 @@ - + @@ -196,6 +196,15 @@ + + + + + + + + + diff --git a/platform/lang-impl/src/com/intellij/application/options/editor/EditorSmartKeysConfigurable.java b/platform/lang-impl/src/com/intellij/application/options/editor/EditorSmartKeysConfigurable.java index 6b61d4a78666..d5e031be92f5 100644 --- a/platform/lang-impl/src/com/intellij/application/options/editor/EditorSmartKeysConfigurable.java +++ b/platform/lang-impl/src/com/intellij/application/options/editor/EditorSmartKeysConfigurable.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2016 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. - */ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.application.options.editor; @@ -71,6 +57,7 @@ public class EditorSmartKeysConfigurable extends CompositeConfigurable, C public boolean ADD_MEMBER_IMPORTS_ON_THE_FLY = true; public boolean JSP_ADD_UNAMBIGIOUS_IMPORTS_ON_THE_FLY; + public boolean TAB_EXITS_BRACKETS_AND_QUOTES; + /** * Names of classes and packages excluded from (Java) auto-import and completion. These are only IDE-specific settings * and don't take project-specific settings into account. diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/PassExecutorService.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/PassExecutorService.java index 1e300fc9dcc2..de0a661d4c25 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/PassExecutorService.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/PassExecutorService.java @@ -479,7 +479,7 @@ class PassExecutorService implements Disposable { @NotNull final AtomicInteger threadsToStartCountdown, @NotNull Runnable callbackOnApplied) { ApplicationManager.getApplication().invokeLater((DumbAwareRunnable)() -> { - if (isDisposed() || myProject.isDisposed()) { + if (isDisposed() || myProject.isDisposed() || !fileEditor.isValid()) { updateProgress.cancel(); } if (updateProgress.isCanceled()) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/BraceOrQuoteOutAction.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/BraceOrQuoteOutAction.java new file mode 100644 index 000000000000..8803e80f4760 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/BraceOrQuoteOutAction.java @@ -0,0 +1,71 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.codeInsight.editorActions; + +import com.intellij.codeInsight.CodeInsightSettings; +import com.intellij.codeInsight.highlighting.BraceMatcher; +import com.intellij.codeInsight.highlighting.BraceMatchingUtil; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.editor.Caret; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.actionSystem.EditorAction; +import com.intellij.openapi.editor.actionSystem.EditorActionHandler; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.editor.highlighter.HighlighterIterator; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiUtilBase; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class BraceOrQuoteOutAction extends EditorAction { + public BraceOrQuoteOutAction() { + super(new Handler()); + } + + private static class Handler extends EditorActionHandler { + private Handler() { + super(true); + } + + @Override + protected boolean isEnabledForCaret(@NotNull Editor editor, @NotNull Caret caret, DataContext dataContext) { + return getCaretShift(editor, caret) != 0; + } + + @Override + protected void doExecute(@NotNull Editor editor, @Nullable Caret caret, DataContext dataContext) { + assert caret != null; + int caretShift = getCaretShift(editor, caret); + if (caretShift != 0) { + caret.moveToOffset(caret.getOffset() + caretShift); + } + } + + private static int getCaretShift(@NotNull Editor editor, @NotNull Caret caret) { + if (!CodeInsightSettings.getInstance().TAB_EXITS_BRACKETS_AND_QUOTES || !(editor instanceof EditorEx)) return 0; + + Project project = editor.getProject(); + if (project == null) return 0; + PsiFile psiFile = PsiUtilBase.getPsiFileInEditor(editor, project); + if (psiFile == null) return 0; + FileType fileType = TypedHandler.getFileType(psiFile, editor); + + int caretOffset = caret.getOffset(); + HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(caretOffset); + + BraceMatcher braceMatcher = BraceMatchingUtil.getBraceMatcher(fileType, iterator); + if (caretOffset == iterator.getStart() && + braceMatcher.isRBraceToken(iterator, editor.getDocument().getImmutableCharSequence(), fileType)) { + return iterator.getEnd() - caretOffset; + } + else { + QuoteHandler quoteHandler = TypedHandler.getQuoteHandler(psiFile, editor); + if (quoteHandler != null && quoteHandler.isClosingQuote(iterator, caretOffset)) { + return 1; + } + } + return 0; + } + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TabActionsPromoter.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TabActionsPromoter.java new file mode 100644 index 000000000000..2b668dd8a7d6 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TabActionsPromoter.java @@ -0,0 +1,24 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.codeInsight.editorActions; + +import com.intellij.openapi.actionSystem.ActionPromoter; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.editor.actions.TabAction; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +public class TabActionsPromoter implements ActionPromoter { + @Override + public List promote(List actions, DataContext context) { + List newList = new ArrayList<>(actions); + newList.sort(Comparator.comparingInt(action -> { + if (action instanceof BraceOrQuoteOutAction) return 0; + else if (action instanceof TabAction) return 1; + else return -1; + })); + return newList; + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java index 6d6238817dac..4ac3eb262618 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TypedHandler.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2017 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. - */ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.codeInsight.editorActions; @@ -101,7 +87,7 @@ public class TypedHandler extends TypedActionHandlerBase { } @NotNull - private static FileType getFileType(@NotNull PsiFile file, @NotNull Editor editor) { + static FileType getFileType(@NotNull PsiFile file, @NotNull Editor editor) { FileType fileType = file.getFileType(); Language language = PsiUtilBase.getLanguageInEditor(editor, file.getProject()); if (language != null && language != PlainTextLanguage.INSTANCE) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByLineCommentHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByLineCommentHandler.java index ee6517d9d009..9f54c79003b8 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByLineCommentHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByLineCommentHandler.java @@ -27,8 +27,6 @@ import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; -import com.intellij.psi.codeStyle.CodeStyleSettings; -import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.psi.impl.source.tree.injected.InjectedCaret; import com.intellij.psi.util.PsiUtilBase; @@ -46,14 +44,12 @@ import java.util.List; import java.util.Map; public class CommentByLineCommentHandler extends MultiCaretCodeInsightActionHandler { - private Project myProject; private final List myBlocks = new ArrayList<>(); @Override // first pass - adjacent carets are grouped into blocks public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull Caret caret, @NotNull PsiFile file) { - myProject = project; file = file.getViewProvider().getPsi(file.getViewProvider().getBaseLanguage()); PsiElement context = InjectedLanguageManager.getInstance(file.getProject()).getInjectionHost(file); @@ -135,8 +131,6 @@ public class CommentByLineCommentHandler extends MultiCaretCodeInsightActionHand public void postInvoke() { FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.comment.line"); - CodeStyleSettings codeStyleSettings = CodeStyleSettingsManager.getSettings(myProject); - // second pass - determining whether we need to comment or to uncomment boolean allLinesCommented = true; for (Block block : myBlocks) { @@ -158,7 +152,7 @@ public class CommentByLineCommentHandler extends MultiCaretCodeInsightActionHand block.blockSuitableCommenter = getBlockSuitableCommenter(psiFile, offset, endOffset); Language lineStartLanguage = getLineStartLanguage(block.editor, psiFile, startLine); - CommonCodeStyleSettings languageSettings = codeStyleSettings.getCommonSettings(lineStartLanguage); + CommonCodeStyleSettings languageSettings = CodeStyle.getLanguageSettings(psiFile, lineStartLanguage); block.commentWithIndent = !languageSettings.LINE_COMMENT_AT_FIRST_COLUMN; block.addSpace = languageSettings.LINE_COMMENT_ADD_SPACE; diff --git a/platform/lang-impl/src/com/intellij/codeInspection/actions/SilentCodeCleanupAction.java b/platform/lang-impl/src/com/intellij/codeInspection/actions/SilentCodeCleanupAction.java index 1454dc3acff8..af10ffee881a 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/actions/SilentCodeCleanupAction.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/actions/SilentCodeCleanupAction.java @@ -52,6 +52,10 @@ public class SilentCodeCleanupAction extends AnAction { runInspections(project, analysisScope); } + @SuppressWarnings("WeakerAccess") + @Nullable + protected Runnable getPostRunnable() { return null; } + @SuppressWarnings("WeakerAccess") protected void runInspections(@NotNull Project project, @NotNull AnalysisScope scope) { InspectionProfile profile = getProfileForSilentCleanup(project); @@ -60,7 +64,7 @@ public class SilentCodeCleanupAction extends AnAction { } InspectionManager managerEx = InspectionManager.getInstance(project); GlobalInspectionContextBase globalContext = (GlobalInspectionContextBase) managerEx.createNewGlobalContext(false); - globalContext.codeCleanup(scope, profile, getTemplatePresentation().getText(), null, false); + globalContext.codeCleanup(scope, profile, getTemplatePresentation().getText(), getPostRunnable(), false); } @SuppressWarnings("WeakerAccess") diff --git a/platform/lang-impl/src/com/intellij/ide/actions/SearchEverywhereAction.java b/platform/lang-impl/src/com/intellij/ide/actions/SearchEverywhereAction.java index b7aa8e9e6f48..4a53907e931d 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/SearchEverywhereAction.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/SearchEverywhereAction.java @@ -672,7 +672,7 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA UIManager.getColor("SearchEverywhere.shortcutForeground") : foregroundColor; StringBuilder cbText = new StringBuilder(""); - cbText.append(IdeBundle.message("checkbox.include.non.project.items")); + cbText.append(ProjectUICustomization.replaceProjectConceptName(IdeBundle.message("checkbox.include.non.project.items"))); cbText.append(" "); if (!UIUtil.isUnderWin10LookAndFeel()) cbText.append(""); cbText.append("").append(getShortcut()).append(""); diff --git a/platform/lang-impl/src/com/intellij/ide/fileTemplates/FileTemplateUtil.java b/platform/lang-impl/src/com/intellij/ide/fileTemplates/FileTemplateUtil.java index 1ba53923ecfd..4754ca04d3c5 100644 --- a/platform/lang-impl/src/com/intellij/ide/fileTemplates/FileTemplateUtil.java +++ b/platform/lang-impl/src/com/intellij/ide/fileTemplates/FileTemplateUtil.java @@ -15,6 +15,7 @@ */ package com.intellij.ide.fileTemplates; +import com.intellij.application.options.CodeStyle; import com.intellij.ide.IdeBundle; import com.intellij.ide.fileTemplates.impl.CustomFileTemplate; import com.intellij.openapi.application.ApplicationManager; @@ -27,7 +28,6 @@ import com.intellij.openapi.fileTypes.FileTypes; import com.intellij.openapi.fileTypes.ex.FileTypeManagerEx; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.project.ex.ProjectManagerEx; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.ClassLoaderUtil; import com.intellij.openapi.util.ThrowableComputable; @@ -240,7 +240,7 @@ public class FileTemplateUtil { final String result = stringWriter.toString(); if (useSystemLineSeparators) { - final String newSeparator = CodeStyleSettingsManager.getSettings(ProjectManagerEx.getInstanceEx().getDefaultProject()).getLineSeparator(); + final String newSeparator = CodeStyle.getDefaultSettings().getLineSeparator(); if (!"\n".equals(newSeparator)) { return StringUtil.convertLineSeparators(result, newSeparator); } @@ -363,7 +363,7 @@ public class FileTemplateUtil { } public static String indent(String methodText, Project project, FileType fileType) { - int indent = CodeStyleSettingsManager.getSettings(project).getIndentSize(fileType); + int indent = CodeStyle.getSettings(project).getIndentSize(fileType); return methodText.replaceAll("\n", "\n" + StringUtil.repeatSymbol(' ', indent)); } diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/AbstractProjectViewPane.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/AbstractProjectViewPane.java index f175133d3566..7eddd57f0f2c 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/AbstractProjectViewPane.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/AbstractProjectViewPane.java @@ -297,6 +297,12 @@ public abstract class AbstractProjectViewPane implements DataProvider, Disposabl @Override public Object getData(String dataId) { + Object data = + myTreeStructure instanceof AbstractTreeStructureBase ? + ((AbstractTreeStructureBase)myTreeStructure).getDataFromProviders(getSelectedNodes(AbstractTreeNode.class), dataId) : null; + if (data != null) { + return data; + } if (CommonDataKeys.NAVIGATABLE_ARRAY.is(dataId)) { TreePath[] paths = getSelectionPaths(); if (paths == null) return null; @@ -318,9 +324,6 @@ public abstract class AbstractProjectViewPane implements DataProvider, Disposabl return navigatables.toArray(new Navigatable[0]); } } - if (myTreeStructure instanceof AbstractTreeStructureBase) { - return ((AbstractTreeStructureBase) myTreeStructure).getDataFromProviders(getSelectedNodes(AbstractTreeNode.class), dataId); - } return null; } diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/CompoundTreeStructureProvider.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/CompoundTreeStructureProvider.java index 7b8ae5015e42..89f3c960c1d3 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/CompoundTreeStructureProvider.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/CompoundTreeStructureProvider.java @@ -73,8 +73,8 @@ public final class CompoundTreeStructureProvider implements TreeStructureProvide @Nullable @Override - public Object getData(Collection selection, String id) { - if (id != null && selection != null && !selection.isEmpty()) { + public Object getData(@NotNull Collection selection, String id) { + if (id != null && !selection.isEmpty()) { for (TreeStructureProvider provider : providers) { try { Object data = provider.getData(selection, id); diff --git a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoClassModel2.java b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoClassModel2.java index b252baf65934..2e858a5424e5 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoClassModel2.java +++ b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoClassModel2.java @@ -27,6 +27,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; +import com.intellij.ui.ProjectUICustomization; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.indexing.FileBasedIndex; @@ -68,12 +69,12 @@ public class GotoClassModel2 extends FilteringGotoByModel { @Override public String getCheckBoxName() { - return IdeBundle.message("checkbox.include.non.project.classes"); + return ProjectUICustomization.replaceProjectConceptName(IdeBundle.message("checkbox.include.non.project.classes")); } @Override public String getNotInMessage() { - return IdeBundle.message("label.no.matches.found.in.project"); + return ProjectUICustomization.replaceProjectConceptName(IdeBundle.message("label.no.matches.found.in.project")); } @Override diff --git a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoFileModel.java b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoFileModel.java index 4f07e8823786..53644dffe9d8 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoFileModel.java +++ b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoFileModel.java @@ -24,6 +24,7 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFileSystemItem; import com.intellij.psi.codeStyle.MinusculeMatcher; import com.intellij.psi.codeStyle.NameUtil; +import com.intellij.ui.ProjectUICustomization; import com.intellij.util.containers.JBIterable; import com.intellij.util.indexing.FileBasedIndex; import org.jetbrains.annotations.NotNull; @@ -94,7 +95,7 @@ public class GotoFileModel extends FilteringGotoByModel implements Dum if (NonProjectScopeDisablerEP.isSearchInNonProjectDisabled()) { return null; } - return IdeBundle.message("checkbox.include.non.project.files"); + return ProjectUICustomization.replaceProjectConceptName(IdeBundle.message("checkbox.include.non.project.files")); } @Override diff --git a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoSymbolModel2.java b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoSymbolModel2.java index e878553c7f2c..0456b46e4717 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoSymbolModel2.java +++ b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoSymbolModel2.java @@ -26,6 +26,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.SystemInfo; import com.intellij.psi.PsiElement; import com.intellij.psi.presentation.java.SymbolPresentationUtil; +import com.intellij.ui.ProjectUICustomization; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -66,12 +67,12 @@ public class GotoSymbolModel2 extends FilteringGotoByModel { @Override public String getCheckBoxName() { - return IdeBundle.message("checkbox.include.non.project.symbols"); + return ProjectUICustomization.replaceProjectConceptName(IdeBundle.message("checkbox.include.non.project.symbols")); } @Override public String getNotInMessage() { - return IdeBundle.message("label.no.matches.found.in.project"); + return ProjectUICustomization.replaceProjectConceptName(IdeBundle.message("label.no.matches.found.in.project")); } @Override diff --git a/platform/lang-impl/src/com/intellij/internal/statistic/editor/EditorSettingsStatisticsCollector.java b/platform/lang-impl/src/com/intellij/internal/statistic/editor/EditorSettingsStatisticsCollector.java index 3b786032f582..eb86ccfd524b 100644 --- a/platform/lang-impl/src/com/intellij/internal/statistic/editor/EditorSettingsStatisticsCollector.java +++ b/platform/lang-impl/src/com/intellij/internal/statistic/editor/EditorSettingsStatisticsCollector.java @@ -106,6 +106,7 @@ class EditorSettingsStatisticsCollector extends ApplicationUsagesCollector { addBoolIfDiffers(set, cis, cisDefault, s -> s.SHOW_PARAMETER_NAME_HINTS_ON_COMPLETION, "completionHints"); addBoolIfDiffers(set, cis, cisDefault, s -> s.SHOW_EXTERNAL_ANNOTATIONS_INLINE, "externalAnnotationsInline"); addBoolIfDiffers(set, cis, cisDefault, s -> s.SHOW_INFERRED_ANNOTATIONS_INLINE, "inferredAnnotationsInline"); + addBoolIfDiffers(set, cis, cisDefault, s -> s.TAB_EXITS_BRACKETS_AND_QUOTES, "tabExitsBracketsAndQuotes"); return set; } diff --git a/platform/lang-impl/src/com/intellij/internal/statistic/editor/LegacyEditorSettingsStatisticsCollector.java b/platform/lang-impl/src/com/intellij/internal/statistic/editor/LegacyEditorSettingsStatisticsCollector.java index 6dcd974dc87a..6fe70ef9af53 100644 --- a/platform/lang-impl/src/com/intellij/internal/statistic/editor/LegacyEditorSettingsStatisticsCollector.java +++ b/platform/lang-impl/src/com/intellij/internal/statistic/editor/LegacyEditorSettingsStatisticsCollector.java @@ -113,6 +113,7 @@ class LegacyEditorSettingsStatisticsCollector extends UsagesCollector { addBoolIfDiffers(set, cis, cisDefault, s -> s.SHOW_PARAMETER_NAME_HINTS_ON_COMPLETION, "completionHints"); addBoolIfDiffers(set, cis, cisDefault, s -> s.SHOW_EXTERNAL_ANNOTATIONS_INLINE, "externalAnnotationsInline"); addBoolIfDiffers(set, cis, cisDefault, s -> s.SHOW_INFERRED_ANNOTATIONS_INLINE, "inferredAnnotationsInline"); + addBoolIfDiffers(set, cis, cisDefault, s -> s.TAB_EXITS_BRACKETS_AND_QUOTES, "tabExitsBracketsAndQuotes"); return set; } diff --git a/platform/lang-impl/src/com/intellij/psi/stubs/StubTreeLoaderImpl.java b/platform/lang-impl/src/com/intellij/psi/stubs/StubTreeLoaderImpl.java index c09422b05da4..8871cba816d1 100644 --- a/platform/lang-impl/src/com/intellij/psi/stubs/StubTreeLoaderImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/stubs/StubTreeLoaderImpl.java @@ -189,11 +189,6 @@ public class StubTreeLoaderImpl extends StubTreeLoader { } } - @Override - public boolean isStubReloadingProhibited() { - return ourStubReloadingProhibited; - } - private static int getCurrentTextContentLength(Project project, VirtualFile vFile, Document document, PsiFile psiFile) { if (vFile.getFileType().isBinary()) { return -1; diff --git a/platform/platform-api/src/com/intellij/execution/configurations/PtyCommandLine.java b/platform/platform-api/src/com/intellij/execution/configurations/PtyCommandLine.java index 7f793f7ff2ee..65f2198f6246 100644 --- a/platform/platform-api/src/com/intellij/execution/configurations/PtyCommandLine.java +++ b/platform/platform-api/src/com/intellij/execution/configurations/PtyCommandLine.java @@ -70,10 +70,10 @@ public class PtyCommandLine extends GeneralCommandLine { logContent = "Unable to retrieve log: " + e.getMessage(); } - LOG.error("Couldn't run process with PTY", t, logContent); + LOG.debug("Couldn't run process with PTY", t, logContent); } else { - LOG.error("Couldn't run process with PTY", t); + LOG.debug("Couldn't run process with PTY", t); } } diff --git a/platform/platform-api/src/com/intellij/ide/GeneralSettings.java b/platform/platform-api/src/com/intellij/ide/GeneralSettings.java index ef23b4e61994..33f1c067a908 100644 --- a/platform/platform-api/src/com/intellij/ide/GeneralSettings.java +++ b/platform/platform-api/src/com/intellij/ide/GeneralSettings.java @@ -8,6 +8,7 @@ import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.ObjectUtils; import com.intellij.util.PlatformUtils; import com.intellij.util.xmlb.XmlSerializerUtil; import com.intellij.util.xmlb.annotations.OptionTag; @@ -33,12 +34,15 @@ public class GeneralSettings implements PersistentStateComponent
- + diff --git a/platform/platform-impl/src/com/intellij/ide/actions/CloseProjectAction.java b/platform/platform-impl/src/com/intellij/ide/actions/CloseProjectAction.java index 60ae40bc804b..3a014b3e197e 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/CloseProjectAction.java +++ b/platform/platform-impl/src/com/intellij/ide/actions/CloseProjectAction.java @@ -26,6 +26,7 @@ import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeFrame; import com.intellij.projectImport.ProjectAttachProcessor; +import com.intellij.ui.ProjectUICustomization; public class CloseProjectAction extends AnAction implements DumbAware { @Override @@ -43,10 +44,10 @@ public class CloseProjectAction extends AnAction implements DumbAware { Project project = event.getData(CommonDataKeys.PROJECT); presentation.setEnabled(project != null); if (ProjectAttachProcessor.canAttachToProject() && project != null && ModuleManager.getInstance(project).getModules().length > 1) { - presentation.setText(IdeBundle.message("action.close.projects.in.current.window")); + presentation.setText(ProjectUICustomization.replaceProjectConceptNameForAction(IdeBundle.message("action.close.projects.in.current.window"))); } else { - presentation.setText(IdeBundle.message("action.close.project")); + presentation.setText(ProjectUICustomization.replaceProjectConceptNameForAction(IdeBundle.message("action.close.project"))); } } } diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java index 0781a3fc32d1..1f2a2d8277d5 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java @@ -744,6 +744,10 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App } public void exit(boolean force, boolean exitConfirmed, boolean restart, @NotNull String[] beforeRestart) { + exit(force, exitConfirmed, restart, false, beforeRestart); + } + + public void exit(boolean force, boolean exitConfirmed, boolean restart, boolean elevate, @NotNull String[] beforeRestart) { if (!force) { if (myExitInProgress) return; if (!exitConfirmed && getDefaultModalityState() != ModalityState.NON_MODAL) return; @@ -751,14 +755,14 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App myExitInProgress = true; if (isDispatchThread()) { - doExit(force, exitConfirmed, restart, beforeRestart); + doExit(force, exitConfirmed, restart, elevate, beforeRestart); } else { - invokeLater(() -> doExit(force, exitConfirmed, restart, beforeRestart), ModalityState.NON_MODAL); + invokeLater(() -> doExit(force, exitConfirmed, restart, elevate, beforeRestart), ModalityState.NON_MODAL); } } - private void doExit(boolean force, boolean exitConfirmed, boolean restart, String[] beforeRestart) { + private void doExit(boolean force, boolean exitConfirmed, boolean restart, boolean elevate, String[] beforeRestart) { try { if (!force && !confirmExitIfNeeded(exitConfirmed)) { return; @@ -786,7 +790,7 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App int exitCode = 0; if (restart && Restarter.isSupported()) { try { - Restarter.scheduleRestart(beforeRestart); + Restarter.scheduleRestart(elevate, beforeRestart); } catch (Throwable t) { LOG.error("Restart failed", t); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorSettingsExternalizable.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorSettingsExternalizable.java index f92bb0896681..a107ff7abd40 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorSettingsExternalizable.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorSettingsExternalizable.java @@ -24,6 +24,9 @@ import java.util.Set; @State(name = "EditorSettings", storages = @Storage("editor.xml")) public class EditorSettingsExternalizable implements PersistentStateComponent { + @NonNls + public static String PROP_VIRTUAL_SPACE = "VirtualSpace"; + public static final UINumericRange BLINKING_RANGE = new UINumericRange(500, 10, 1500); public static final UINumericRange QUICK_DOC_DELAY_RANGE = new UINumericRange(500, 1, 5000); @@ -374,7 +377,9 @@ public class EditorSettingsExternalizable implements PersistentStateComponent myWindow; - PlaceInfo(@NotNull VirtualFile file, + public PlaceInfo(@NotNull VirtualFile file, @NotNull FileEditorState navigationState, @NotNull String editorTypeId, @Nullable EditorWindow window) { diff --git a/platform/platform-impl/src/com/intellij/ui/popup/MovablePopup.java b/platform/platform-impl/src/com/intellij/ui/popup/MovablePopup.java index 636f9b56b3b2..0e019d2a409c 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/MovablePopup.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/MovablePopup.java @@ -177,6 +177,7 @@ public class MovablePopup { if (owner != null) { if (myHeavyWeight) { Window view = new JWindow(owner); + view.setType(Window.Type.POPUP); setAlwaysOnTop(view, myAlwaysOnTop); setWindowFocusable(view, myWindowFocusable); setWindowShadow(view, myWindowShadow); diff --git a/platform/platform-impl/src/com/intellij/util/Restarter.java b/platform/platform-impl/src/com/intellij/util/Restarter.java index dbb46f73d590..a2f2029f7277 100644 --- a/platform/platform-impl/src/com/intellij/util/Restarter.java +++ b/platform/platform-impl/src/com/intellij/util/Restarter.java @@ -104,9 +104,13 @@ public class Restarter { } public static void scheduleRestart(@NotNull String... beforeRestart) throws IOException { + scheduleRestart(false, beforeRestart); + } + + public static void scheduleRestart(Boolean elevate, @NotNull String... beforeRestart) throws IOException { Logger.getInstance(Restarter.class).info("restart: " + Arrays.toString(beforeRestart)); if (SystemInfo.isWindows) { - restartOnWindows(beforeRestart); + restartOnWindows(elevate, beforeRestart); } else if (SystemInfo.isMac) { restartOnMac(beforeRestart); @@ -119,7 +123,7 @@ public class Restarter { } } - private static void restartOnWindows(String... beforeRestart) throws IOException { + private static void restartOnWindows(Boolean elevate, String... beforeRestart) throws IOException { Kernel32 kernel32 = Native.loadLibrary("kernel32", Kernel32.class); Shell32 shell32 = Native.loadLibrary("shell32", Shell32.class); @@ -146,7 +150,13 @@ public class Restarter { args.add(String.valueOf(pid)); args.add(String.valueOf(beforeRestart.length)); Collections.addAll(args, beforeRestart); - args.add(String.valueOf(argv.length)); + if (elevate) { + args.add(String.valueOf(argv.length + 1)); + args.add(new File(PathManager.getBinPath(), "launcher.exe").getPath()); + } + else { + args.add(String.valueOf(argv.length)); + } Collections.addAll(args, argv); runRestarter(new File(PathManager.getBinPath(), "restarter.exe"), args); diff --git a/platform/platform-resources-en/src/messages/ActionsBundle.properties b/platform/platform-resources-en/src/messages/ActionsBundle.properties index f8813c379645..6e11fc34ff13 100644 --- a/platform/platform-resources-en/src/messages/ActionsBundle.properties +++ b/platform/platform-resources-en/src/messages/ActionsBundle.properties @@ -1630,4 +1630,6 @@ action.TogglePresentationMode.enter=Enter Presentation Mode action.EmojiAndSymbols.description=Input special characters using macOS system palette action.MethodOverloadSwitchUp.text=Previous Method Overload -action.MethodOverloadSwitchDown.text=Next Method Overload \ No newline at end of file +action.MethodOverloadSwitchDown.text=Next Method Overload + +action.BraceOrQuoteOut.text=Exit Current Braces/Quotes \ No newline at end of file diff --git a/platform/platform-resources-en/src/messages/ApplicationBundle.properties b/platform/platform-resources-en/src/messages/ApplicationBundle.properties index baf97471b6b8..9950df3ae907 100644 --- a/platform/platform-resources-en/src/messages/ApplicationBundle.properties +++ b/platform/platform-resources-en/src/messages/ApplicationBundle.properties @@ -453,6 +453,7 @@ checkbox.honor.camelhumps.words.settings.on.double.click=Honor "CamelHumps group.smart.keys=Smart Keys checkbox.use.camelhumps.words=Use "CamelHumps" words checkbox.enable.double.ctrl=Add multiple carets on double {0} with arrow keys +checkbox.tab.exists.brackets.and.quotes=Tab exits brackets/quotes checkbox.insert.pair.quote=Insert pair quote checkbox.insert.pair.bracket=Insert paired brackets (), [], {}, <> checkbox.javadoc.stub.after.slash.star.star=Insert documentation comment stub diff --git a/platform/platform-resources-en/src/messages/VcsBundle.properties b/platform/platform-resources-en/src/messages/VcsBundle.properties index b5a355ea541c..ce2d0ed46d3b 100644 --- a/platform/platform-resources-en/src/messages/VcsBundle.properties +++ b/platform/platform-resources-en/src/messages/VcsBundle.properties @@ -274,6 +274,8 @@ commit.dialog.refresh.files=Synchronizing files... commit.dialog.rejected.commit.template=Rejected commit: {0} commit.dialog.changelist.label=Changelis&t: commit.dialog.changed.files.label=Changed &Files +commit.dialog.partial.commit.warning.title=Partial Commit Is Not Supported +commit.dialog.partial.commit.warning.body=Partial commit is not supported for ''{0}''.\nAll changes in the selected files will be committed. commit.wait.util.synced.message=Please wait until VCS synchronization is finished. commit.wait.util.synced.title=Finishing VCS refresh change.list.manager.wait.lists.synchronization={0}: Local Changes Refresh diff --git a/platform/platform-resources/src/META-INF/LangExtensions.xml b/platform/platform-resources/src/META-INF/LangExtensions.xml index 96c8202a69e9..741ef9864f2b 100644 --- a/platform/platform-resources/src/META-INF/LangExtensions.xml +++ b/platform/platform-resources/src/META-INF/LangExtensions.xml @@ -991,6 +991,7 @@ + diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index 4646adc31c9a..cb0d52aa9ae8 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -294,6 +294,8 @@ + + diff --git a/platform/platform-resources/src/idea/LangActions.xml b/platform/platform-resources/src/idea/LangActions.xml index 33537b677256..089c670aebf9 100644 --- a/platform/platform-resources/src/idea/LangActions.xml +++ b/platform/platform-resources/src/idea/LangActions.xml @@ -48,6 +48,8 @@ + + diff --git a/platform/platform-resources/src/keymaps/$default.xml b/platform/platform-resources/src/keymaps/$default.xml index 573556aefd61..399489979755 100644 --- a/platform/platform-resources/src/keymaps/$default.xml +++ b/platform/platform-resources/src/keymaps/$default.xml @@ -593,6 +593,12 @@ + + + + + + @@ -1081,4 +1087,8 @@ + + + + \ No newline at end of file diff --git a/platform/platform-resources/src/keymaps/Eclipse.xml b/platform/platform-resources/src/keymaps/Eclipse.xml index 8be0f588d7cd..dedb1a82cd4e 100644 --- a/platform/platform-resources/src/keymaps/Eclipse.xml +++ b/platform/platform-resources/src/keymaps/Eclipse.xml @@ -360,4 +360,6 @@ + + diff --git a/platform/platform-resources/src/keymaps/Emacs.xml b/platform/platform-resources/src/keymaps/Emacs.xml index 49b90da7f04f..e0f82f51fb72 100644 --- a/platform/platform-resources/src/keymaps/Emacs.xml +++ b/platform/platform-resources/src/keymaps/Emacs.xml @@ -310,4 +310,6 @@ + + diff --git a/platform/platform-resources/src/keymaps/Mac OS X 10.5+.xml b/platform/platform-resources/src/keymaps/Mac OS X 10.5+.xml index 115afd7ad07e..6293abe69fb9 100644 --- a/platform/platform-resources/src/keymaps/Mac OS X 10.5+.xml +++ b/platform/platform-resources/src/keymaps/Mac OS X 10.5+.xml @@ -266,6 +266,12 @@ + + + + + + diff --git a/platform/platform-resources/src/keymaps/Mac OS X.xml b/platform/platform-resources/src/keymaps/Mac OS X.xml index 1163d8b36e7b..c0ece8c598c2 100644 --- a/platform/platform-resources/src/keymaps/Mac OS X.xml +++ b/platform/platform-resources/src/keymaps/Mac OS X.xml @@ -289,6 +289,12 @@ + + + + + + @@ -401,4 +407,6 @@ + + diff --git a/platform/platform-resources/src/keymaps/NetBeans 6.5.xml b/platform/platform-resources/src/keymaps/NetBeans 6.5.xml index a374aed9d0bd..cd5393470e5e 100644 --- a/platform/platform-resources/src/keymaps/NetBeans 6.5.xml +++ b/platform/platform-resources/src/keymaps/NetBeans 6.5.xml @@ -384,4 +384,6 @@ + + diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/actions/EditorTabOutTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/actions/EditorTabOutTest.java new file mode 100644 index 000000000000..f60ea0a93b25 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/actions/EditorTabOutTest.java @@ -0,0 +1,30 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.openapi.editor.actions; + +import com.intellij.codeInsight.CodeInsightSettings; +import com.intellij.openapi.actionSystem.IdeActions; +import com.intellij.openapi.editor.impl.AbstractEditorTest; + +public class EditorTabOutTest extends AbstractEditorTest { + public void testMethodCall() { + doTest("class C { void m() { System.getenv(\"a\") } }", 1); + } + + public void testStringLiteral() { + doTest("class C { void m() { String s = \"a\" } }", 1); + } + + private void doTest(String fileText, int expectedCaretShift) { + boolean savedSetting = CodeInsightSettings.getInstance().TAB_EXITS_BRACKETS_AND_QUOTES; + CodeInsightSettings.getInstance().TAB_EXITS_BRACKETS_AND_QUOTES = true; + try { + configureFromFileText(getTestName(false) + ".java", fileText); + int originalCaretOffset = myEditor.getCaretModel().getOffset(); + executeAction(IdeActions.ACTION_BRACE_OR_QUOTE_OUT); + assertEquals("Unexpected caret offset", originalCaretOffset + expectedCaretShift, myEditor.getCaretModel().getOffset()); + } + finally { + CodeInsightSettings.getInstance().TAB_EXITS_BRACKETS_AND_QUOTES = savedSetting; + } + } +} diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/updateSettings/UpdateOptionsTest.kt b/platform/platform-tests/testSrc/com/intellij/openapi/updateSettings/UpdateOptionsTest.kt index 56bfcc6b6759..3e13a31fb9c5 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/updateSettings/UpdateOptionsTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/openapi/updateSettings/UpdateOptionsTest.kt @@ -4,6 +4,7 @@ package com.intellij.openapi.updateSettings import com.intellij.configurationStore.deserialize import com.intellij.openapi.updateSettings.impl.UpdateOptions import com.intellij.testFramework.ProjectRule +import com.intellij.testFramework.assertions.Assertions.assertThat import com.intellij.util.loadElement import org.junit.ClassRule import org.junit.Test @@ -15,6 +16,12 @@ class UpdateOptionsTest { val projectRule = ProjectRule() } + @Test + fun `auto check is enabled`() { + // it is quite important default value (true), so, ensure that it is not modified without changing test expectation (as an additional check) + assertThat(UpdateOptions().isCheckNeeded).isTrue() + } + @Test fun test() { loadElement(""" diff --git a/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/GeneralIdBasedToSMTRunnerEventsConvertor.java b/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/GeneralIdBasedToSMTRunnerEventsConvertor.java index e8faf435e3ac..35ab1ca57bae 100644 --- a/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/GeneralIdBasedToSMTRunnerEventsConvertor.java +++ b/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/GeneralIdBasedToSMTRunnerEventsConvertor.java @@ -209,21 +209,25 @@ public class GeneralIdBasedToSMTRunnerEventsConvertor extends GeneralTestEventsP } testProxy.setFrameworkOutputFile(testFinishedEvent.getOutputFile()); testProxy.setFinished(); - if (node.getState() != State.FAILED) { - LOG.debug("onTestFinished: node.getState() != State.FAILED"); - - // Don't count the same test twice if 'testFailed' message is followed by 'testFinished' message - // which may happen if generated TeamCity messages adhere rules from - // https://confluence.jetbrains.com/display/TCD10/Build+Script+Interaction+with+TeamCity - // Anyway, this id-based converter already breaks TeamCity protocol by expecting messages with - // non-standard TeamCity attributes: 'nodeId'/'parentNodeId' instead of 'name'. - fireOnTestFinished(testProxy); - } + fireOnTestFinishedIfNeeded(testProxy, node); terminateNode(node, State.FINISHED); } }); } + private void fireOnTestFinishedIfNeeded(@NotNull SMTestProxy testProxy, @NotNull Node node) { + // allow clients to omit sending 'testFinished' messages after 'testFailed'/'testIgnored' messages + if (node.getState() != State.FINISHED && node.getState() != State.FAILED && node.getState() != State.IGNORED) { + LOG.debug("onTestFinished: state != FINISHED && state != FAILED && state != IGNORED"); + // Don't count the same test twice if 'testFailed' or 'testIgnored' message is followed by 'testFinished' message + // which may happen if generated TeamCity messages adhere rules from + // https://confluence.jetbrains.com/display/TCD10/Build+Script+Interaction+with+TeamCity + // Anyway, this id-based converter already breaks TeamCity protocol by expecting messages with + // non-standard TeamCity attributes: 'nodeId'/'parentNodeId' instead of 'name'. + fireOnTestFinished(testProxy); + } + } + public void onSuiteFinished(@NotNull final TestSuiteFinishedEvent suiteFinishedEvent) { if (LOG.isDebugEnabled()) { LOG.debug("onSuiteFinished " + suiteFinishedEvent.getId()); @@ -328,10 +332,9 @@ public class GeneralIdBasedToSMTRunnerEventsConvertor extends GeneralTestEventsP if (duration >= 0) { testProxy.setDuration(duration); } - fireOnTestFinished(testProxy); - // fire event fireOnTestFailed(testProxy); + fireOnTestFinishedIfNeeded(testProxy, node); terminateNode(node, State.FAILED); }); @@ -349,8 +352,10 @@ public class GeneralIdBasedToSMTRunnerEventsConvertor extends GeneralTestEventsP } SMTestProxy testProxy = node.getProxy(); testProxy.setTestIgnored(testIgnoredEvent.getIgnoreComment(), testIgnoredEvent.getStacktrace()); - // fire event + fireOnTestIgnored(testProxy); + fireOnTestFinishedIfNeeded(testProxy, node); + terminateNode(node, State.IGNORED); } }); diff --git a/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/events/TestIgnoredEvent.java b/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/events/TestIgnoredEvent.java index 500cecb109de..ea090421a46c 100644 --- a/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/events/TestIgnoredEvent.java +++ b/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/events/TestIgnoredEvent.java @@ -26,14 +26,16 @@ public class TestIgnoredEvent extends TreeNodeEvent { private final String myStacktrace; public TestIgnoredEvent(@NotNull String testName, @NotNull String ignoreComment, @Nullable String stacktrace) { - super(testName, null); - myIgnoreComment = ignoreComment; - myStacktrace = stacktrace; + this(testName, null, ignoreComment, stacktrace); } public TestIgnoredEvent(@NotNull TestIgnored testIgnored, @Nullable String stacktrace) { - super(testIgnored.getTestName(), TreeNodeEvent.getNodeId(testIgnored)); - myIgnoreComment = testIgnored.getIgnoreComment(); + this(testIgnored.getTestName(), TreeNodeEvent.getNodeId(testIgnored), testIgnored.getIgnoreComment(), stacktrace); + } + + public TestIgnoredEvent(@Nullable String name, @Nullable String id, @Nullable String ignoreComment, @Nullable String stacktrace) { + super(name, id); + myIgnoreComment = ignoreComment; myStacktrace = stacktrace; } diff --git a/platform/smRunner/testSrc/com/intellij/execution/testframework/sm/runner/GeneralIdBasedToSMTRunnerEventsConvertorTest.java b/platform/smRunner/testSrc/com/intellij/execution/testframework/sm/runner/GeneralIdBasedToSMTRunnerEventsConvertorTest.java index eaca233b754b..cb09a89ff4dc 100644 --- a/platform/smRunner/testSrc/com/intellij/execution/testframework/sm/runner/GeneralIdBasedToSMTRunnerEventsConvertorTest.java +++ b/platform/smRunner/testSrc/com/intellij/execution/testframework/sm/runner/GeneralIdBasedToSMTRunnerEventsConvertorTest.java @@ -15,23 +15,32 @@ */ package com.intellij.execution.testframework.sm.runner; -import com.intellij.execution.testframework.sm.runner.events.TestFailedEvent; -import com.intellij.execution.testframework.sm.runner.events.TestStartedEvent; -import com.intellij.execution.testframework.sm.runner.events.TestSuiteStartedEvent; -import com.intellij.execution.testframework.sm.runner.events.TreeNodeEvent; +import com.intellij.execution.testframework.TestConsoleProperties; +import com.intellij.execution.testframework.sm.runner.events.*; +import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerConsoleView; +import com.intellij.execution.testframework.sm.runner.ui.SMTestRunnerResultsForm; import com.intellij.openapi.util.Disposer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class GeneralIdBasedToSMTRunnerEventsConvertorTest extends BaseSMTRunnerTestCase { + private SMTRunnerConsoleView myConsole; private GeneralIdBasedToSMTRunnerEventsConvertor myEventsProcessor; private SMTestProxy.SMRootTestProxy myRootProxy; + private SMTestRunnerResultsForm myResultsViewer; @Override protected void setUp() throws Exception { super.setUp(); + + TestConsoleProperties consoleProperties = createConsoleProperties(); + myConsole = new SMTRunnerConsoleView(consoleProperties); + myConsole.initUI(); + myResultsViewer = myConsole.getResultsViewer(); + myRootProxy = new SMTestProxy.SMRootTestProxy(); myEventsProcessor = new GeneralIdBasedToSMTRunnerEventsConvertor(getProject(), myRootProxy, "test"); + myEventsProcessor.addEventsListener(myResultsViewer); myEventsProcessor.onStartTesting(); } @@ -39,6 +48,7 @@ public class GeneralIdBasedToSMTRunnerEventsConvertorTest extends BaseSMTRunnerT protected void tearDown() throws Exception { try { Disposer.dispose(myEventsProcessor); + Disposer.dispose(myConsole); } finally { super.tearDown(); @@ -84,6 +94,27 @@ public class GeneralIdBasedToSMTRunnerEventsConvertorTest extends BaseSMTRunnerT assertTrue(suiteProxy.isInProgress()); } + public void testIgnoredEvent() { + onSuiteStarted("Suite", null, "1", TreeNodeEvent.ROOT_NODE_ID); + SMTestProxy suite = validateSuite("1", "Suite", null, myRootProxy); + onTestStarted("testA", null, "A", "1", true); + SMTestProxy testA = validateTest("A", "testA", null, true, suite); + onTestIgnored("A"); + validateTestIgnored("A", testA); + assertFalse(testA.isInProgress()); + assertTrue(suite.isInProgress()); + assertEquals(1, myResultsViewer.getFinishedTestCount()); + onTestFinished("A", null); + assertEquals(1, myResultsViewer.getFinishedTestCount()); + + onTestStarted("testB", null, "B", "1", true); + SMTestProxy testB = validateTest("B", "testB", null, true, suite); + assertEquals(1, myResultsViewer.getFinishedTestCount()); + onTestIgnored("B"); + assertEquals(2, myResultsViewer.getFinishedTestCount()); + validateTestIgnored("B", testB); + } + @NotNull private SMTestProxy validateSuite(@NotNull String id, @NotNull String expectedName, @@ -129,6 +160,14 @@ public class GeneralIdBasedToSMTRunnerEventsConvertorTest extends BaseSMTRunnerT return test; } + private void validateTestIgnored(@NotNull String id, @NotNull SMTestProxy expectedTestProxy) { + SMTestProxy test = myEventsProcessor.findProxyById(id); + assertEquals(expectedTestProxy, test); + assertFalse(test.isSuite()); + assertTrue(test.isFinal()); + assertTrue(test.isIgnored()); + } + private void onSuiteStarted(@NotNull String suiteName, @Nullable String metainfo, @NotNull String id, @NotNull String parentId) { myEventsProcessor.onSuiteStarted(new TestSuiteStartedEvent(suiteName, id, parentId, null, metainfo, null, null, false)); } @@ -141,9 +180,16 @@ public class GeneralIdBasedToSMTRunnerEventsConvertorTest extends BaseSMTRunnerT myEventsProcessor.onTestStarted(new TestStartedEvent(testName, id, parentId, null, metainfo, null, null, running)); } + private void onTestFinished(@NotNull String id, @Nullable Long duration) { + myEventsProcessor.onTestFinished(new TestFinishedEvent(null, id, duration)); + } + private void onTestFailed(@NotNull String id, @NotNull String errorMessage, int durationMillis) { myEventsProcessor.onTestFailure(new TestFailedEvent(null, id, errorMessage, null, false, null, null, null, null, false, false, durationMillis)); } + private void onTestIgnored(@NotNull String id) { + myEventsProcessor.onTestIgnored(new TestIgnoredEvent(null, id, null, null)); + } } diff --git a/platform/testFramework/extensions/src/com/intellij/keymap/KeymapsTestCase.java b/platform/testFramework/extensions/src/com/intellij/keymap/KeymapsTestCase.java index 8c0759e1d5ee..1b142fb3e296 100644 --- a/platform/testFramework/extensions/src/com/intellij/keymap/KeymapsTestCase.java +++ b/platform/testFramework/extensions/src/com/intellij/keymap/KeymapsTestCase.java @@ -66,6 +66,7 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase { { "shift F7", "PreviousDiff", "SmartStepInto"}, { "shift TAB", "PreviousTemplateVariable", "PrevParameter", "EditorUnindentSelection", "PrevTemplateParameter"}, { "shift alt L", "org.jetbrains.plugins.ruby.console.LoadInIrbConsoleAction", "context.load"}, + { "shift alt M", "ChangesView.Move", "Vcs.MoveChangedLinesToChangelist"}, { "shift control D", "TagDocumentationNavigation", "Diff.ShowSettingsPopup", "Uml.ShowDiff", "Console.TableResult.CompareCells"}, { "shift control DOWN", "ResizeToolWindowDown", "MoveStatementDown"}, { "shift control ENTER", "EditorCompleteStatement", "Console.Jpa.GenerateSql"}, @@ -105,6 +106,7 @@ public abstract class KeymapsTestCase extends KeymapsTestCaseBase { { "meta N", "FileChooser.NewFolder", "Generate", "NewElement"}, { "meta O", "GotoClass", "GotoChangedFile"}, { "shift meta G", "ClassTemplateNavigation", "GoToClass", "FindPrevious"}, + { "shift meta M", "ChangesView.Move", "Vcs.MoveChangedLinesToChangelist"}, { "shift meta LEFT", "EditorLineStartWithSelection", "ResizeToolWindowLeft", }, { "shift meta RIGHT", "EditorLineEndWithSelection", "ResizeToolWindowRight", }, { "alt R", "Django.RunManageTaskAction", "org.jetbrains.plugins.ruby.tasks.rake.actions.RakeTasksPopupAction"}, diff --git a/platform/testFramework/src/com/intellij/testFramework/FixtureRule.kt b/platform/testFramework/src/com/intellij/testFramework/FixtureRule.kt index f877329280ef..26ea93d276b5 100644 --- a/platform/testFramework/src/com/intellij/testFramework/FixtureRule.kt +++ b/platform/testFramework/src/com/intellij/testFramework/FixtureRule.kt @@ -3,6 +3,7 @@ package com.intellij.testFramework import com.intellij.ide.highlighter.ProjectFileType import com.intellij.idea.IdeaTestApplication +import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.runUndoTransparentWriteAction import com.intellij.openapi.components.ComponentManager @@ -16,6 +17,7 @@ import com.intellij.openapi.project.ProjectManager import com.intellij.openapi.project.ex.ProjectEx import com.intellij.openapi.project.ex.ProjectManagerEx import com.intellij.openapi.project.impl.ProjectManagerImpl +import com.intellij.openapi.util.Disposer import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS import com.intellij.openapi.vfs.newvfs.persistent.PersistentFSImpl @@ -305,4 +307,17 @@ fun createOrLoadProject(tempDirManager: TemporaryDirectory, projectCreator: ((Vi fun ComponentManager.saveStore() { stateStore.save(SmartList(), true) +} + +class DisposableRule : ExternalResource() { + private var _disposable = lazy { Disposer.newDisposable() } + + val disposable: Disposable + get() = _disposable.value + + override fun after() { + if (_disposable.isInitialized()) { + Disposer.dispose(_disposable.value) + } + } } \ No newline at end of file diff --git a/platform/util/src/com/intellij/execution/process/UnixProcessManager.java b/platform/util/src/com/intellij/execution/process/UnixProcessManager.java index 0507c03ebe93..e90f4b2611a6 100644 --- a/platform/util/src/com/intellij/execution/process/UnixProcessManager.java +++ b/platform/util/src/com/intellij/execution/process/UnixProcessManager.java @@ -115,7 +115,11 @@ public class UnixProcessManager { public static boolean sendSignalToProcessTree(int processId, int signal) { checkCLib(); - final int our_pid = C_LIB.getpid(); + final int ourPid = C_LIB.getpid(); + return sendSignalToProcessTree(processId, signal, ourPid); + } + + public static boolean sendSignalToProcessTree(int processId, int signal, int ourPid) { if (LOG.isDebugEnabled()) { LOG.debug("Sending signal " + signal + " to process tree with root PID " + processId); } @@ -124,7 +128,7 @@ public class UnixProcessManager { final ProcessInfo processInfo = new ProcessInfo(); final List childrenPids = new ArrayList(); - findChildProcesses(our_pid, processId, foundPid, processInfo, childrenPids); + findChildProcesses(ourPid, processId, foundPid, processInfo, childrenPids); // result is true if signal was sent to at least one process final boolean result; diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/changes/CommitExecutor.java b/platform/vcs-api/src/com/intellij/openapi/vcs/changes/CommitExecutor.java index 30a9a4e2716d..a0c811989fc9 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/changes/CommitExecutor.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/changes/CommitExecutor.java @@ -22,6 +22,10 @@ public interface CommitExecutor { return null; } + default boolean supportsPartialCommit() { + return false; + } + @NotNull CommitSession createCommitSession(); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/VcsActionPromoter.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/VcsActionPromoter.java index cbec6ef9e859..95952c336491 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/VcsActionPromoter.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/VcsActionPromoter.java @@ -15,39 +15,54 @@ */ package com.intellij.openapi.vcs.actions; +import com.intellij.openapi.actionSystem.ActionManager; import com.intellij.openapi.actionSystem.ActionPromoter; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.diff.actions.DiffWalkerAction; -import com.intellij.openapi.vcs.ex.MoveChangesLineStatusAction; -import com.intellij.openapi.vcs.ex.RollbackLineStatusAction; +import com.intellij.util.containers.ContainerUtil; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; -/** - * @author Konstantin Bulenkov - */ public class VcsActionPromoter implements ActionPromoter { @Override public List promote(List actions, DataContext context) { - List list = new ArrayList<>(0); + ActionManager am = ActionManager.getInstance(); + List reorderedActions = new ArrayList<>(actions); + List reorderedIds = ContainerUtil.map(reorderedActions, it -> am.getId(it)); - for (AnAction action : actions) { - if (action instanceof RollbackLineStatusAction) { - list.add(action); - } - if (action instanceof MoveChangesLineStatusAction) { - list.add(action); - } - if (action instanceof ShowMessageHistoryAction) { - list.add(action); - } - if (action instanceof DiffWalkerAction) { - list.add(action); - } - } + reorderActionPair(reorderedActions, reorderedIds, "Vcs.MoveChangedLinesToChangelist", "ChangesView.Move"); + reorderActionPair(reorderedActions, reorderedIds, "Vcs.RollbackChangedLines", "ChangesView.Revert"); - return list; + Set promoted = new HashSet<>(ContainerUtil.filter(actions, action -> { + return action instanceof ShowMessageHistoryAction || + action instanceof DiffWalkerAction; + })); + + reorderedActions.removeAll(promoted); + reorderedActions.addAll(0, promoted); + + return reorderedActions; + } + + /** + * Ensures that one global action has priority over another global action. + * But is not pushing it ahead of other actions (ex: of some local action with same shortcut). + */ + private static void reorderActionPair(List reorderedActions, List reorderedIds, + String highPriority, String lowPriority) { + int highPriorityIndex = reorderedIds.indexOf(highPriority); + int lowPriorityIndex = reorderedIds.indexOf(lowPriority); + if (highPriorityIndex == -1 || lowPriorityIndex == -1) return; + if (highPriorityIndex < lowPriorityIndex) return; + + String id = reorderedIds.remove(highPriorityIndex); + AnAction action = reorderedActions.remove(highPriorityIndex); + + reorderedIds.add(lowPriorityIndex, id); + reorderedActions.add(lowPriorityIndex, action); } } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/CreatePatchCommitExecutor.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/CreatePatchCommitExecutor.java index 818cecfa0471..a87540a5586e 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/CreatePatchCommitExecutor.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/CreatePatchCommitExecutor.java @@ -76,6 +76,11 @@ public class CreatePatchCommitExecutor extends LocalCommitExecutor implements Pr return "reference.dialogs.vcs.patch.create"; } + @Override + public boolean supportsPartialCommit() { + return true; + } + @Override @NotNull public CommitSession createCommitSession() { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ShelveChangesCommitExecutor.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ShelveChangesCommitExecutor.java index 62703650c920..3b95f0315b15 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ShelveChangesCommitExecutor.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/ShelveChangesCommitExecutor.java @@ -55,6 +55,11 @@ public class ShelveChangesCommitExecutor extends LocalCommitExecutor { return "reference.dialogs.vcs.shelve"; } + @Override + public boolean supportsPartialCommit() { + return true; + } + private class ShelveChangesCommitSession implements CommitSession, CommitSessionContextAware { @Override public void setContext(CommitContext context) { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java index 1ded9b521741..d3dce30db30c 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/CommitChangeListDialog.java @@ -1,6 +1,7 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.openapi.vcs.changes.ui; +import com.intellij.CommonBundle; import com.intellij.diff.util.DiffPlaces; import com.intellij.diff.util.DiffUserDataKeysEx; import com.intellij.diff.util.DiffUtil; @@ -29,6 +30,7 @@ import com.intellij.openapi.vcs.changes.*; import com.intellij.openapi.vcs.changes.actions.ScheduleForAdditionAction; import com.intellij.openapi.vcs.changes.actions.diff.lst.LocalChangeListDiffTool; import com.intellij.openapi.vcs.checkin.*; +import com.intellij.openapi.vcs.ex.PartialLocalLineStatusTracker; import com.intellij.openapi.vcs.impl.CheckinHandlersManager; import com.intellij.openapi.vcs.impl.LineStatusTrackerManager; import com.intellij.openapi.vcs.impl.PartialChangesUtil; @@ -99,6 +101,8 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj @NotNull private final List myExecutors; @NotNull private final List myHandlers = newArrayList(); private final boolean myAllOfDefaultChangeListChangesIncluded; + @NotNull private final String myCommitActionName; + private final boolean myEnablePartialCommit; @NotNull private final Map myListComments; @NotNull private final List myExecutorActions; @@ -289,7 +293,6 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj myIsAlien = isAlien; myResultHandler = customResultHandler; myListComments = newHashMap(); - myDiffDetails = new MyChangeProcessor(myProject); if (!myShowVcsCommit && isEmpty(executors)) { throw new IllegalArgumentException("nothing found to execute commit with"); @@ -297,6 +300,25 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj myAllOfDefaultChangeListChangesIncluded = newHashSet(changes).containsAll(newHashSet(defaultChangeList.getChanges())); + myHandlers.addAll(createCheckinHandlers(project, this, myCommitContext)); + + setTitle(myShowVcsCommit ? TITLE : getExecutorPresentableText(executors.get(0))); + myCommitActionName = getCommitActionName(myAffectedVcses); + myExecutorActions = createExecutorActions(executors); + if (myShowVcsCommit) { + myCommitAction = new CommitAction(myCommitActionName); + myCommitAction.setOptions(myExecutorActions); + } + else { + myCommitAction = null; + myExecutorActions.get(0).putValue(DEFAULT_ACTION, Boolean.TRUE); + } + myHelpId = myShowVcsCommit ? HELP_ID : getHelpId(executors); + + myEnablePartialCommit = ContainerUtil.exists(getAffectedVcses(), AbstractVcs::arePartialChangelistsSupported) && + (myShowVcsCommit || ContainerUtil.exists(myExecutors, executor -> executor.supportsPartialCommit())); + + myDiffDetails = new MyChangeProcessor(myProject, myEnablePartialCommit); myCommitMessageArea = new CommitMessage(project, true, true, myShowVcsCommit); if (myIsAlien) { @@ -312,7 +334,8 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj else { LineStatusTrackerManager.getInstanceImpl(myProject).resetExcludedFromCommitMarkers(); - MultipleLocalChangeListsBrowser browser = new MultipleLocalChangeListsBrowser(project, true, true, myShowVcsCommit); + MultipleLocalChangeListsBrowser browser = new MultipleLocalChangeListsBrowser(project, true, true, + myShowVcsCommit, myEnablePartialCommit); myBrowser = browser; if (initialSelection != null) browser.setSelectedChangeList(initialSelection); @@ -352,21 +375,9 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj setComment(initialSelection, comment); } - initCheckinHandlers(project); myCommitOptions = new CommitOptionsPanel(this, myHandlers, getAffectedVcses()); restoreState(); - setTitle(myShowVcsCommit ? TITLE : trimEllipsis(executors.get(0).getActionText())); - myCommitAction = myShowVcsCommit ? new CommitAction(getCommitActionName()) : null; - myExecutorActions = createExecutorActions(executors); - if (myCommitAction != null) { - myCommitAction.setOptions(myExecutorActions); - } - else { - myExecutorActions.get(0).putValue(DEFAULT_ACTION, Boolean.TRUE); - } - myHelpId = myCommitAction != null ? HELP_ID : getHelpId(executors); - myWarningLabel = new JBLabel(); myWarningLabel.setForeground(JBColor.RED); myWarningLabel.setBorder(JBUI.Borders.empty(5, 5, 0, 5)); @@ -444,13 +455,18 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj showDetailsIfSaved(); } - private void initCheckinHandlers(@NotNull Project project) { + @NotNull + private static List createCheckinHandlers(@NotNull Project project, + @NotNull CheckinProjectPanel checkinPanel, + @NotNull CommitContext commitContext) { + List handlers = new ArrayList<>(); for (BaseCheckinHandlerFactory factory : getCheckInFactories(project)) { - CheckinHandler handler = factory.createHandler(this, myCommitContext); + CheckinHandler handler = factory.createHandler(checkinPanel, commitContext); if (!CheckinHandler.DUMMY.equals(handler)) { - myHandlers.add(handler); + handlers.add(handler); } } + return handlers; } @NotNull @@ -503,7 +519,7 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj private void updateOnListSelection() { updateComment(); - myCommitOptions.onChangeListSelected((LocalChangeList)myBrowser.getSelectedChangeList(), + myCommitOptions.onChangeListSelected(myBrowser.getSelectedChangeList(), ChangeListManagerImpl.getInstanceImpl(myProject).getUnversionedFiles()); } @@ -617,6 +633,7 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj return; } + if (!checkCommitOptionsSupported(commitExecutor)) return; if (!saveDialogState()) return; saveComments(true); @@ -628,7 +645,7 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj JComponent configurationUI = SessionDialog.createConfigurationUI(session, getIncludedChanges(), getCommitMessage()); if (configurationUI != null) { DialogWrapper sessionDialog = - new SessionDialog(commitExecutor.getActionText(), getProject(), session, getIncludedChanges(), getCommitMessage(), + new SessionDialog(getExecutorPresentableText(commitExecutor), getProject(), session, getIncludedChanges(), getCommitMessage(), configurationUI); if (!sessionDialog.showAndGet()) { session.executionCanceled(); @@ -703,8 +720,8 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj private void updateComment() { if (myVcsConfiguration.CLEAR_INITIAL_COMMIT_MESSAGE) return; - LocalChangeList list = (LocalChangeList)myBrowser.getSelectedChangeList(); - if (list == null || list.getName().equals(myLastSelectedListName)) { + LocalChangeList list = myBrowser.getSelectedChangeList(); + if (list.getName().equals(myLastSelectedListName)) { return; } else if (myLastSelectedListName != null) { saveCommentIntoChangeList(); @@ -746,19 +763,22 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj PropertiesComponent.getInstance().setValue(DETAILS_SHOW_OPTION, myDetailsSplitter.isOn(), DETAILS_SHOW_OPTION_DEFAULT); } + @NotNull @Override public String getCommitActionName() { - String name = null; - for (AbstractVcs vcs : myAffectedVcses) { + return myCommitActionName; + } + + @NotNull + private static String getCommitActionName(@NotNull Collection affectedVcses) { + Set names = map2SetNotNull(affectedVcses, vcs -> { CheckinEnvironment checkinEnvironment = vcs.getCheckinEnvironment(); - if (name == null && checkinEnvironment != null) { - name = checkinEnvironment.getCheckinOperationName(); - } - else { - name = VcsBundle.getString("commit.dialog.default.commit.operation.name"); - } + return checkinEnvironment != null ? checkinEnvironment.getCheckinOperationName() : null; + }); + if (names.size() == 1) { + return notNull(ContainerUtil.getFirstItem(names)); } - return name != null ? name : VcsBundle.getString("commit.dialog.default.commit.operation.name"); + return VcsBundle.getString("commit.dialog.default.commit.operation.name"); } private boolean checkComment() { @@ -883,6 +903,25 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj myListComments.forEach((changeListName, comment) -> changeListManager.editComment(changeListName, comment)); } + private boolean checkCommitOptionsSupported(@NotNull CommitExecutor commitExecutor) { + if (myIsAlien) return true; + + if (!commitExecutor.supportsPartialCommit()) { + boolean hasPartialChanges = ContainerUtil.exists(getIncludedChanges(), change -> { + PartialLocalLineStatusTracker tracker = PartialChangesUtil.getPartialTracker(myProject, change); + return tracker != null && tracker.hasPartialChangesToCommit(); + }); + if (hasPartialChanges) { + return Messages.YES == + Messages.showYesNoDialog(myProject, + message("commit.dialog.partial.commit.warning.body", getExecutorPresentableText(commitExecutor)), + message("commit.dialog.partial.commit.warning.title"), + commitExecutor.getActionText(), CommonBundle.getCancelButtonText(), Messages.getWarningIcon()); + } + } + return true; + } + @Override public void doCancelAction() { myCommitOptions.saveChangeListComponentsState(); @@ -1071,9 +1110,14 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj return myBrowser.getData(dataId); } + @NotNull + static String getExecutorPresentableText(@NotNull CommitExecutor executor) { + return trimEllipsis(removeMnemonic(executor.getActionText())); + } + @NotNull static String trimEllipsis(@NotNull String title) { - return StringUtil.trimEnd(title, "..."); + return StringUtil.trimEnd(StringUtil.trimEnd(title, "..."), "\u2026"); } private void ensureDataIsActual(@NotNull Runnable runnable) { @@ -1133,11 +1177,11 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj } private class MyChangeProcessor extends ChangeViewDiffRequestProcessor { - public MyChangeProcessor(@NotNull Project project) { + public MyChangeProcessor(@NotNull Project project, boolean enablePartialCommit) { super(project, DiffPlaces.COMMIT_DIALOG); putContextUserData(DiffUserDataKeysEx.SHOW_READ_ONLY_LOCK, true); - putContextUserData(LocalChangeListDiffTool.ALLOW_EXCLUDE_FROM_COMMIT, true); + putContextUserData(LocalChangeListDiffTool.ALLOW_EXCLUDE_FROM_COMMIT, enablePartialCommit); } @Override @@ -1160,7 +1204,7 @@ public class CommitChangeListDialog extends DialogWrapper implements CheckinProj @Override protected void selectChange(@NotNull Wrapper change) { //noinspection unchecked - myBrowser.selectEntries((List)singletonList(change.getUserObject())); + myBrowser.selectEntries(singletonList(change.getUserObject())); } @NotNull diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/MultipleLocalChangeListsBrowser.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/MultipleLocalChangeListsBrowser.java index ca6786f188a3..8bd99c188f97 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/MultipleLocalChangeListsBrowser.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/MultipleLocalChangeListsBrowser.java @@ -62,6 +62,7 @@ public class MultipleLocalChangeListsBrowser extends CommitDialogChangesBrowser new MergingUpdateQueue("MultipleLocalChangeListsBrowser", 300, true, ANY_COMPONENT, this); private final boolean myEnableUnversioned; + private final boolean myEnablePartialCommit; @Nullable private JComponent myBottomDiffComponent; @NotNull private final ChangeListChooser myChangeListChooser; @@ -78,9 +79,11 @@ public class MultipleLocalChangeListsBrowser extends CommitDialogChangesBrowser public MultipleLocalChangeListsBrowser(@NotNull Project project, boolean showCheckboxes, boolean highlightProblems, - boolean enableUnversioned) { + boolean enableUnversioned, + boolean enablePartialCommit) { super(project, showCheckboxes, highlightProblems); myEnableUnversioned = enableUnversioned; + myEnablePartialCommit = enablePartialCommit; myChangeList = ChangeListManager.getInstance(project).getDefaultChangeList(); myChangeListChooser = new ChangeListChooser(); @@ -151,7 +154,7 @@ public class MultipleLocalChangeListsBrowser extends CommitDialogChangesBrowser protected void updateDiffContext(@NotNull DiffRequestChain chain) { super.updateDiffContext(chain); chain.putUserData(DiffUserDataKeysEx.BOTTOM_PANEL, myBottomDiffComponent); - chain.putUserData(LocalChangeListDiffTool.ALLOW_EXCLUDE_FROM_COMMIT, true); + chain.putUserData(LocalChangeListDiffTool.ALLOW_EXCLUDE_FROM_COMMIT, myEnablePartialCommit); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/PartialLocalLineStatusTracker.kt b/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/PartialLocalLineStatusTracker.kt index 8669c5b512ba..9f089fc815dc 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/PartialLocalLineStatusTracker.kt +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/PartialLocalLineStatusTracker.kt @@ -17,6 +17,8 @@ package com.intellij.openapi.vcs.ex import com.intellij.diff.util.Side import com.intellij.openapi.Disposable +import com.intellij.openapi.actionSystem.ActionManager +import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.DefaultActionGroup import com.intellij.openapi.actionSystem.Separator import com.intellij.openapi.application.ModalityState @@ -31,6 +33,8 @@ import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.event.DocumentEvent import com.intellij.openapi.editor.event.DocumentListener +import com.intellij.openapi.keymap.KeymapUtil +import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.registry.Registry @@ -517,7 +521,10 @@ class PartialLocalLineStatusTracker(project: Project, } } - override fun createAdditionalInfoPanel(editor: Editor, range: Range, mousePosition: Point?): JComponent? { + override fun createAdditionalInfoPanel(editor: Editor, + range: Range, + mousePosition: Point?, + disposable: Disposable): JComponent? { if (range !is LocalRange) return null val changeLists = ChangeListManager.getInstance(tracker.project).changeLists @@ -536,6 +543,18 @@ class PartialLocalLineStatusTracker(project: Project, val link = ActionGroupLink(rangeList.name, null, group) + val moveChangesShortcutSet = ActionManager.getInstance().getAction("Vcs.MoveChangedLinesToChangelist").shortcutSet + object : DumbAwareAction() { + override fun actionPerformed(e: AnActionEvent?) { + link.linkLabel.doClick() + } + }.registerCustomShortcutSet(moveChangesShortcutSet, editor.component, disposable) + + val shortcuts = moveChangesShortcutSet.shortcuts + if (shortcuts.isNotEmpty()) { + link.linkLabel.toolTipText = "Move lines to another changelist (${KeymapUtil.getShortcutText(shortcuts.first())})" + } + val panel = JPanel(BorderLayout()) panel.add(link, BorderLayout.CENTER) panel.border = JBUI.Borders.emptyLeft(7) diff --git a/platform/xdebugger-api/src/com/intellij/xdebugger/breakpoints/XBreakpointType.java b/platform/xdebugger-api/src/com/intellij/xdebugger/breakpoints/XBreakpointType.java index 9a52d7416316..bf1cd555af5b 100644 --- a/platform/xdebugger-api/src/com/intellij/xdebugger/breakpoints/XBreakpointType.java +++ b/platform/xdebugger-api/src/com/intellij/xdebugger/breakpoints/XBreakpointType.java @@ -19,6 +19,7 @@ package com.intellij.xdebugger.breakpoints; import com.intellij.icons.AllIcons; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.project.Project; +import com.intellij.xdebugger.XDebuggerBundle; import com.intellij.xdebugger.XSourcePosition; import com.intellij.xdebugger.breakpoints.ui.XBreakpointCustomPropertiesPanel; import com.intellij.xdebugger.breakpoints.ui.XBreakpointGroupingRule; @@ -245,6 +246,10 @@ public abstract class XBreakpointType, P extends XBreak return getDisplayText(breakpoint); } + public String getLogMessageLabelText() { + return XDebuggerBundle.message("xbreakpoints.log.message.label"); + } + public interface XBreakpointCreator

{ @NotNull XBreakpoint

createBreakpoint(@Nullable P properties); diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/ui/XBreakpointActionsPanel.form b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/ui/XBreakpointActionsPanel.form index 68c68f0276ae..9fceb61ee1cc 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/ui/XBreakpointActionsPanel.form +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/ui/XBreakpointActionsPanel.form @@ -77,7 +77,7 @@ - + diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/ui/XBreakpointActionsPanel.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/ui/XBreakpointActionsPanel.java index eb41796c0d24..cba96840add8 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/ui/XBreakpointActionsPanel.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/ui/XBreakpointActionsPanel.java @@ -34,10 +34,12 @@ public class XBreakpointActionsPanel extends XBreakpointPropertiesSubPanel { private JPanel myExpressionPanel; private JPanel myLanguageChooserPanel; private JCheckBox myLogStack; + private JLabel myLogToConsoleLabel; private XDebuggerExpressionComboBox myLogExpressionComboBox; public void init(Project project, XBreakpointManager breakpointManager, @NotNull XBreakpointBase breakpoint, @Nullable XDebuggerEditorsProvider debuggerEditorsProvider) { init(project, breakpointManager, breakpoint); + myLogToConsoleLabel.setText(breakpoint.getType().getLogMessageLabelText()); if (debuggerEditorsProvider != null) { ActionListener listener = new ActionListener() { public void actionPerformed(final ActionEvent e) { diff --git a/platform/xdebugger-impl/testSrc/com/intellij/xdebugger/XDebuggerTestUtil.java b/platform/xdebugger-impl/testSrc/com/intellij/xdebugger/XDebuggerTestUtil.java index db43e9fcbe07..afbddb77ce00 100644 --- a/platform/xdebugger-impl/testSrc/com/intellij/xdebugger/XDebuggerTestUtil.java +++ b/platform/xdebugger-impl/testSrc/com/intellij/xdebugger/XDebuggerTestUtil.java @@ -38,6 +38,7 @@ import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; +import java.util.function.BiFunction; import java.util.concurrent.TimeoutException; import static org.junit.Assert.*; @@ -122,9 +123,13 @@ public class XDebuggerTestUtil { } public static Pair, String> collectThreadsWithErrors(@NotNull XDebugSession session) { + return collectThreadsWithErrors(session, XDebuggerTestUtil::waitFor); + } + + public static Pair, String> collectThreadsWithErrors(@NotNull XDebugSession session, @NotNull BiFunction waitFunction) { XTestExecutionStackContainer container = new XTestExecutionStackContainer(); session.getSuspendContext().computeExecutionStacks(container); - return container.waitFor(TIMEOUT_MS); + return container.waitFor(TIMEOUT_MS, waitFunction); } public static List collectFrames(@NotNull XDebugSession session) { @@ -146,13 +151,21 @@ public class XDebuggerTestUtil { } public static List collectFrames(XExecutionStack thread, long timeout) { - return collectFramesWithError(thread, timeout).first; + return collectFrames(thread, timeout, XDebuggerTestUtil::waitFor); + } + + public static List collectFrames(XExecutionStack thread, long timeout, BiFunction waitFunction) { + return collectFramesWithError(thread, timeout, waitFunction).first; } public static Pair, String> collectFramesWithError(XExecutionStack thread, long timeout) { + return collectFramesWithError(thread, timeout, XDebuggerTestUtil::waitFor); + } + + public static Pair, String> collectFramesWithError(XExecutionStack thread, long timeout, BiFunction waitFunction) { XTestStackFrameContainer container = new XTestStackFrameContainer(); thread.computeStackFrames(0, container); - return container.waitFor(timeout); + return container.waitFor(timeout, waitFunction); } public static Pair, XStackFrame> collectFramesWithSelected(@NotNull XDebugSession session, long timeout) { @@ -160,9 +173,13 @@ public class XDebuggerTestUtil { } public static Pair, XStackFrame> collectFramesWithSelected(XExecutionStack thread, long timeout) { + return collectFramesWithSelected(thread, timeout, XDebuggerTestUtil::waitFor); + } + + public static Pair, XStackFrame> collectFramesWithSelected(XExecutionStack thread, long timeout, BiFunction waitFunction) { XTestStackFrameContainer container = new XTestStackFrameContainer(); thread.computeStackFrames(0, container); - List all = container.waitFor(timeout).first; + List all = container.waitFor(timeout, waitFunction).first; return Pair.create(all, container.frameToSelect); } @@ -175,31 +192,50 @@ public class XDebuggerTestUtil { } public static List collectChildren(XValueContainer value) { + return collectChildren(value, XDebuggerTestUtil::waitFor); + } + + public static List collectChildren(XValueContainer value, BiFunction waitFunction) { XTestCompositeNode container = new XTestCompositeNode(); value.computeChildren(container); - return container.waitFor(TIMEOUT_MS).first; + + return container.waitFor(TIMEOUT_MS, waitFunction).first; } public static Pair evaluate(XDebugSession session, XExpression expression) { return evaluate(session, expression, TIMEOUT_MS); } + public static Pair evaluate(XDebugSession session, XExpression expression, BiFunction waitFunction) { + return evaluate(session, expression, TIMEOUT_MS, waitFunction); + } + public static Pair evaluate(XDebugSession session, String expression) { - return evaluate(session, XExpressionImpl.fromText(expression), TIMEOUT_MS); + return evaluate(session, expression, XDebuggerTestUtil::waitFor); + } + public static Pair evaluate(XDebugSession session, String expression, BiFunction waitFunction) { + return evaluate(session, XExpressionImpl.fromText(expression), TIMEOUT_MS, waitFunction); } public static Pair evaluate(XDebugSession session, String expression, long timeout) { - return evaluate(session, XExpressionImpl.fromText(expression), timeout); + return evaluate(session, expression, timeout, XDebuggerTestUtil::waitFor); + } + public static Pair evaluate(XDebugSession session, String expression, long timeout, BiFunction waitFunction) { + return evaluate(session, XExpressionImpl.fromText(expression), timeout, waitFunction); } private static Pair evaluate(XDebugSession session, XExpression expression, long timeout) { + return evaluate(session, expression, timeout, XDebuggerTestUtil::waitFor); + } + + private static Pair evaluate(XDebugSession session, XExpression expression, long timeout, BiFunction waitFunction) { XStackFrame frame = session.getCurrentStackFrame(); assertNotNull(frame); XDebuggerEvaluator evaluator = frame.getEvaluator(); assertNotNull(evaluator); XTestEvaluationCallback callback = new XTestEvaluationCallback(); evaluator.evaluate(expression, callback, session.getCurrentPosition()); - return callback.waitFor(timeout); + return callback.waitFor(timeout, waitFunction); } public static void waitForSwing() throws InterruptedException { @@ -226,16 +262,22 @@ public class XDebuggerTestUtil { } public static XTestValueNode computePresentation(@NotNull XValue value) { - return computePresentation(value, TIMEOUT_MS); + return computePresentation(value, XDebuggerTestUtil::waitFor); + } + public static XTestValueNode computePresentation(@NotNull XValue value, BiFunction waitFunction) { + return computePresentation(value, TIMEOUT_MS, waitFunction); } public static XTestValueNode computePresentation(XValue value, long timeout) { + return computePresentation(value, timeout, XDebuggerTestUtil::waitFor); + } + public static XTestValueNode computePresentation(XValue value, long timeout, BiFunction waitFunction) { XTestValueNode node = new XTestValueNode(); if (value instanceof XNamedValue) { node.myName = ((XNamedValue)value).getName(); } value.computePresentation(node, XValuePlace.TREE); - node.waitFor(timeout); + node.waitFor(timeout, waitFunction); return node; } @@ -244,7 +286,16 @@ public class XDebuggerTestUtil { @Nullable String type, @Nullable String value, @Nullable Boolean hasChildren) { - XTestValueNode node = computePresentation(var); + assertVariable(var, name, type, value, hasChildren, XDebuggerTestUtil::waitFor); + } + + public static void assertVariable(XValue var, + @Nullable String name, + @Nullable String type, + @Nullable String value, + @Nullable Boolean hasChildren, + BiFunction waitFunction) { + XTestValueNode node = computePresentation(var, waitFunction); if (name != null) assertEquals(name, node.myName); if (type != null) assertEquals(type, node.myType); @@ -299,7 +350,16 @@ public class XDebuggerTestUtil { @Nullable String type, @Nullable @Language("RegExp") String valuePattern, @Nullable Boolean hasChildren) { - XTestValueNode node = computePresentation(var); + assertVariableValueMatches(var, name, type, valuePattern, hasChildren, XDebuggerTestUtil::waitFor); + } + + public static void assertVariableValueMatches(@NotNull XValue var, + @Nullable String name, + @Nullable String type, + @Nullable @Language("RegExp") String valuePattern, + @Nullable Boolean hasChildren, + BiFunction waitFunction) { + XTestValueNode node = computePresentation(var, waitFunction); if (name != null) assertEquals(name, node.myName); if (type != null) assertEquals(type, node.myType); if (valuePattern != null) { @@ -317,7 +377,14 @@ public class XDebuggerTestUtil { public static void assertVariableTypeMatches(@NotNull XValue var, @Nullable String name, @Nullable @Language("RegExp") String typePattern) { - XTestValueNode node = computePresentation(var); + assertVariableTypeMatches(var, name, typePattern, XDebuggerTestUtil::waitFor); + } + + public static void assertVariableTypeMatches(@NotNull XValue var, + @Nullable String name, + @Nullable @Language("RegExp") String typePattern, + @NotNull BiFunction waitFunction) { + XTestValueNode node = computePresentation(var, waitFunction); if (name != null) { assertEquals(name, node.myName); } @@ -328,7 +395,13 @@ public class XDebuggerTestUtil { public static void assertVariableFullValue(@NotNull XValue var, @Nullable String value) throws Exception { - XTestValueNode node = computePresentation(var); + assertVariableFullValue(var, value, XDebuggerTestUtil::waitFor); + } + + public static void assertVariableFullValue(@NotNull XValue var, + @Nullable String value, + @NotNull BiFunction waitFunction) throws Exception { + XTestValueNode node = computePresentation(var, waitFunction); if (value == null) { assertNull("full value evaluator should be null", node.myFullValueEvaluator); diff --git a/platform/xdebugger-impl/testSrc/com/intellij/xdebugger/XTestContainer.java b/platform/xdebugger-impl/testSrc/com/intellij/xdebugger/XTestContainer.java index 57ac8996651c..9f3c86b5afc6 100644 --- a/platform/xdebugger-impl/testSrc/com/intellij/xdebugger/XTestContainer.java +++ b/platform/xdebugger-impl/testSrc/com/intellij/xdebugger/XTestContainer.java @@ -13,6 +13,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.List; import java.util.concurrent.Semaphore; +import java.util.function.BiFunction; public class XTestContainer { private final List myChildren = new SmartList<>(); @@ -41,7 +42,11 @@ public class XTestContainer { } public Pair, String> waitFor(long timeoutMs) { - if (!XDebuggerTestUtil.waitFor(myFinished, timeoutMs)) { + return waitFor(timeoutMs, (semaphore, timeout) -> XDebuggerTestUtil.waitFor(myFinished, timeout)); + } + + public Pair, String> waitFor(long timeoutMs, BiFunction waitFunction) { + if (!waitFunction.apply(myFinished, timeoutMs)) { throw new AssertionError("Waiting timed out"); } diff --git a/platform/xdebugger-impl/testSrc/com/intellij/xdebugger/XTestEvaluationCallback.java b/platform/xdebugger-impl/testSrc/com/intellij/xdebugger/XTestEvaluationCallback.java index 6425a685a604..0d298ad9a945 100644 --- a/platform/xdebugger-impl/testSrc/com/intellij/xdebugger/XTestEvaluationCallback.java +++ b/platform/xdebugger-impl/testSrc/com/intellij/xdebugger/XTestEvaluationCallback.java @@ -21,6 +21,7 @@ import com.intellij.xdebugger.impl.ui.tree.nodes.XEvaluationCallbackBase; import org.jetbrains.annotations.NotNull; import java.util.concurrent.Semaphore; +import java.util.function.BiFunction; import static org.junit.Assert.*; @@ -42,7 +43,11 @@ public class XTestEvaluationCallback extends XEvaluationCallbackBase { } public Pair waitFor(long timeoutInMilliseconds) { - assertTrue("timed out", XDebuggerTestUtil.waitFor(myFinished, timeoutInMilliseconds)); + return waitFor(timeoutInMilliseconds, XDebuggerTestUtil::waitFor); + } + + public Pair waitFor(long timeoutInMilliseconds, BiFunction waitFunction) { + assertTrue("timed out", waitFunction.apply(myFinished, timeoutInMilliseconds)); return Pair.create(myResult, myErrorMessage); } } diff --git a/platform/xdebugger-impl/testSrc/com/intellij/xdebugger/XTestValueNode.java b/platform/xdebugger-impl/testSrc/com/intellij/xdebugger/XTestValueNode.java index 32c78f2533c9..95d5b2e5e0eb 100644 --- a/platform/xdebugger-impl/testSrc/com/intellij/xdebugger/XTestValueNode.java +++ b/platform/xdebugger-impl/testSrc/com/intellij/xdebugger/XTestValueNode.java @@ -12,6 +12,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.concurrent.Semaphore; +import java.util.function.BiFunction; public class XTestValueNode extends XValueNodePresentationConfigurator.ConfigurableXValueNodeImpl { public Icon myIcon; @@ -42,7 +43,10 @@ public class XTestValueNode extends XValueNodePresentationConfigurator.Configura } public void waitFor(long timeoutInMillis) { - if (!XDebuggerTestUtil.waitFor(myFinished, timeoutInMillis)) { + waitFor(timeoutInMillis, XDebuggerTestUtil::waitFor); + } + public void waitFor(long timeoutInMillis, BiFunction waitFunction) { + if (!waitFunction.apply(myFinished, timeoutInMillis)) { throw new AssertionError("Waiting timed out"); } } diff --git a/plugins/git4idea/remote-servers-git/src/com/intellij/remoteServer/util/CloudGitDeploymentRuntime.java b/plugins/git4idea/remote-servers-git/src/com/intellij/remoteServer/util/CloudGitDeploymentRuntime.java index a0f1ac27d937..2b95602922ac 100644 --- a/plugins/git4idea/remote-servers-git/src/com/intellij/remoteServer/util/CloudGitDeploymentRuntime.java +++ b/plugins/git4idea/remote-servers-git/src/com/intellij/remoteServer/util/CloudGitDeploymentRuntime.java @@ -81,6 +81,11 @@ public class CloudGitDeploymentRuntime extends CloudDeploymentRuntime { return "Commit and Push"; } + @Override + public boolean supportsPartialCommit() { + return true; + } + @NotNull @Override public CommitSession createCommitSession() { diff --git a/plugins/git4idea/src/git4idea/checkin/GitCommitAndPushExecutor.java b/plugins/git4idea/src/git4idea/checkin/GitCommitAndPushExecutor.java index 89f8869b4255..8988d0cbfc90 100644 --- a/plugins/git4idea/src/git4idea/checkin/GitCommitAndPushExecutor.java +++ b/plugins/git4idea/src/git4idea/checkin/GitCommitAndPushExecutor.java @@ -35,6 +35,11 @@ public class GitCommitAndPushExecutor implements CommitExecutor { return ID; } + @Override + public boolean supportsPartialCommit() { + return true; + } + @NotNull public CommitSession createCommitSession() { myCheckinEnvironment.setNextCommitIsPushed(true); diff --git a/plugins/gradle/java/src/service/project/wizard/GradleModuleBuilder.java b/plugins/gradle/java/src/service/project/wizard/GradleModuleBuilder.java index caf915e6f114..19cadb3fa2a4 100644 --- a/plugins/gradle/java/src/service/project/wizard/GradleModuleBuilder.java +++ b/plugins/gradle/java/src/service/project/wizard/GradleModuleBuilder.java @@ -1,6 +1,7 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.plugins.gradle.service.project.wizard; +import com.intellij.application.options.CodeStyle; import com.intellij.ide.fileTemplates.FileTemplate; import com.intellij.ide.fileTemplates.FileTemplateManager; import com.intellij.ide.highlighter.ModuleFileType; @@ -29,7 +30,6 @@ import com.intellij.openapi.fileEditor.impl.LoadTextUtil; import com.intellij.openapi.module.*; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ex.ProjectManagerEx; import com.intellij.openapi.projectRoots.JavaSdkType; import com.intellij.openapi.projectRoots.SdkTypeId; import com.intellij.openapi.roots.ModifiableRootModel; @@ -46,7 +46,6 @@ import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; -import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.util.ThreeState; import com.intellij.util.containers.ContainerUtil; import org.gradle.util.GradleVersion; @@ -476,7 +475,7 @@ public class GradleModuleBuilder extends AbstractExternalModuleBuilder selected, String dataName) { - if (selected == null) return null; + public Object getData(@NotNull Collection selected, String dataName) { if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataName)) { for (AbstractTreeNode selectedElement : selected) { Object element = selectedElement.getValue(); diff --git a/plugins/ui-designer/src/com/intellij/uiDesigner/projectView/FormMergerTreeStructureProvider.java b/plugins/ui-designer/src/com/intellij/uiDesigner/projectView/FormMergerTreeStructureProvider.java index ba705feb7c23..3538450139d7 100644 --- a/plugins/ui-designer/src/com/intellij/uiDesigner/projectView/FormMergerTreeStructureProvider.java +++ b/plugins/ui-designer/src/com/intellij/uiDesigner/projectView/FormMergerTreeStructureProvider.java @@ -102,24 +102,22 @@ public class FormMergerTreeStructureProvider implements TreeStructureProvider { return result; } - public Object getData(Collection selected, String dataId) { - if (selected != null) { - if (Form.DATA_KEY.is(dataId)) { - List

result = new ArrayList<>(); - for(AbstractTreeNode node: selected) { - if (node.getValue() instanceof Form) { - result.add((Form) node.getValue()); - } - } - if (!result.isEmpty()) { - return result.toArray(new Form[0]); + public Object getData(@NotNull Collection selected, String dataId) { + if (Form.DATA_KEY.is(dataId)) { + List result = new ArrayList<>(); + for(AbstractTreeNode node: selected) { + if (node.getValue() instanceof Form) { + result.add((Form) node.getValue()); } } - else if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) { - for(AbstractTreeNode node: selected) { - if (node.getValue() instanceof Form) { - return new MyDeleteProvider(selected); - } + if (!result.isEmpty()) { + return result.toArray(new Form[0]); + } + } + else if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) { + for(AbstractTreeNode node: selected) { + if (node.getValue() instanceof Form) { + return new MyDeleteProvider(selected); } } } diff --git a/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java b/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java index b7576cdec096..789b90373dfc 100644 --- a/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java +++ b/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java @@ -282,24 +282,17 @@ public class PyQualifiedReference extends PyReferenceImpl { final PyQualifiedExpression qualifierExpression = (PyQualifiedExpression)qualifier; final QualifiedName qualifiedName = qualifierExpression.asQualifiedName(); if (qualifiedName != null) { - final Collection attrs = collectAssignedAttributes(qualifiedName, qualifier); - for (PyExpression ex : attrs) { - final String name = ex.getName(); + final Collection attrs = collectAssignedAttributes(qualifiedName, qualifier); + for (PyTargetExpression expression : attrs) { + final String name = expression.getName(); if (name != null && name.endsWith(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED)) { continue; } - if (ex instanceof PsiNamedElement && qualifierType instanceof PyClassType && name != null) { - variants.add(LookupElementBuilder.createWithSmartPointer(name, ex) + if (qualifierType instanceof PyClassType && name != null) { + variants.add(LookupElementBuilder.createWithSmartPointer(name, expression) .withTypeText(qualifierType.getName()) .withIcon(PlatformIcons.FIELD_ICON)); - } - if (ex instanceof PyReferenceExpression) { - PyReferenceExpression refExpr = (PyReferenceExpression)ex; - namesAlready.add(refExpr.getReferencedName()); - } - else if (ex instanceof PyTargetExpression) { - PyTargetExpression targetExpr = (PyTargetExpression)ex; - namesAlready.add(targetExpr.getName()); + namesAlready.add(name); } } } @@ -414,10 +407,10 @@ public class PyQualifiedReference extends PyReferenceImpl { * Can be used for completion. */ @NotNull - public static Collection collectAssignedAttributes(@NotNull final QualifiedName qualifierQName, + public static Collection collectAssignedAttributes(@NotNull final QualifiedName qualifierQName, @NotNull final PsiElement anchor) { final Set names = new HashSet<>(); - final List results = new ArrayList<>(); + final List results = new ArrayList<>(); for (ScopeOwner owner = ScopeUtil.getScopeOwner(anchor); owner != null; owner = ScopeUtil.getScopeOwner(owner)) { final Scope scope = ControlFlowCache.getScope(owner); for (final PyTargetExpression target : scope.getTargetExpressions()) { diff --git a/python/testData/completion/completionForUsedAttribute/a.after.py b/python/testData/completion/completionForUsedAttribute/a.after.py new file mode 100644 index 000000000000..2eb90c203e55 --- /dev/null +++ b/python/testData/completion/completionForUsedAttribute/a.after.py @@ -0,0 +1,4 @@ +import m1 + +m1.a +m1.a = 0 diff --git a/python/testData/completion/completionForUsedAttribute/a.py b/python/testData/completion/completionForUsedAttribute/a.py new file mode 100644 index 000000000000..08c782d2f2e2 --- /dev/null +++ b/python/testData/completion/completionForUsedAttribute/a.py @@ -0,0 +1,4 @@ +import m1 + +m1. +m1.a = 0 diff --git a/python/testData/completion/completionForUsedAttribute/m1.py b/python/testData/completion/completionForUsedAttribute/m1.py new file mode 100644 index 000000000000..d25d49e0ffc3 --- /dev/null +++ b/python/testData/completion/completionForUsedAttribute/m1.py @@ -0,0 +1 @@ +a = 1 \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java index 9f13f9fd1052..f1d3ee3c173a 100644 --- a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java +++ b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java @@ -1335,6 +1335,11 @@ public class PythonCompletionTest extends PyTestCase { runWithLanguageLevel(LanguageLevel.PYTHON34, this::assertSingleVariantInExtendedCompletion); } + // PY-28341 + public void testCompletionForUsedAttribute() { + doMultiFileTest(); + } + // PY-28103 public void testPrintFunctionWithoutFuture() { final List suggested = doTestByText("pr"); diff --git a/xml/dom-impl/src/com/intellij/util/xml/impl/FileDescriptionCachedValueProvider.java b/xml/dom-impl/src/com/intellij/util/xml/impl/FileDescriptionCachedValueProvider.java index c3693eaab3ca..1802cfcad4b1 100644 --- a/xml/dom-impl/src/com/intellij/util/xml/impl/FileDescriptionCachedValueProvider.java +++ b/xml/dom-impl/src/com/intellij/util/xml/impl/FileDescriptionCachedValueProvider.java @@ -18,6 +18,7 @@ package com.intellij.util.xml.impl; import com.intellij.ide.highlighter.DomSupportEnabled; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.ProjectCoreUtil; import com.intellij.openapi.util.Condition; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileWithId; @@ -26,19 +27,14 @@ import com.intellij.psi.stubs.ObjectStubTree; import com.intellij.psi.stubs.StubTreeLoader; import com.intellij.psi.xml.XmlFile; import com.intellij.semantic.SemElement; -import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; -import java.util.HashSet; import com.intellij.util.xml.*; -import com.intellij.util.xml.events.DomEvent; import com.intellij.util.xml.stubs.FileStub; import com.intellij.xml.util.XmlUtil; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.lang.ref.WeakReference; -import java.util.Collections; -import java.util.List; +import java.util.HashSet; import java.util.Set; /** @@ -65,7 +61,7 @@ class FileDescriptionCachedValueProvider implements SemEle public final DomFileElementImpl getFileElement() { if (myComputed) return myLastResult; - DomFileElementImpl result = _computeFileElement(false, myDomService.getXmlFileHeader(myXmlFile), null); + DomFileElementImpl result = _computeFileElement(null); synchronized (myCondition) { if (myComputed) return myLastResult; @@ -79,12 +75,7 @@ class FileDescriptionCachedValueProvider implements SemEle } @Nullable - private DomFileElementImpl _computeFileElement(final boolean fireEvents, - @NotNull final XmlFileHeader rootTagName, @Nullable StringBuilder sb) { - if (sb != null) { - sb.append(rootTagName).append("\n"); - } - + private DomFileElementImpl _computeFileElement(@Nullable StringBuilder sb) { if (!myXmlFile.isValid()) { return null; } @@ -92,12 +83,14 @@ class FileDescriptionCachedValueProvider implements SemEle sb.append("File is valid\n"); } - if (!(myXmlFile.getFileType() instanceof DomSupportEnabled)) { + VirtualFile file = myXmlFile.getVirtualFile(); + if (!(myXmlFile.getFileType() instanceof DomSupportEnabled) || file != null && ProjectCoreUtil.isProjectOrWorkspaceFile(file)) { return null; } + XmlFileHeader rootTagName = myDomService.getXmlFileHeader(myXmlFile); if (sb != null) { - sb.append("File is of dom file type\n"); + sb.append(rootTagName).append(", file is of dom file type\n"); } final DomFileDescription description = findFileDescription(rootTagName, sb); @@ -106,12 +99,6 @@ class FileDescriptionCachedValueProvider implements SemEle if (sb != null) { sb.append("last " + oldValue + "\n"); } - final List events = fireEvents ? new SmartList<>() : Collections.emptyList(); - if (oldValue != null) { - if (fireEvents) { - events.add(new DomEvent(oldValue, false)); - } - } if (description == null) { return null; @@ -122,7 +109,6 @@ class FileDescriptionCachedValueProvider implements SemEle assert xmlName != null; final EvaluatedXmlNameImpl rootTagName1 = EvaluatedXmlNameImpl.createEvaluatedXmlName(xmlName, xmlName.getNamespaceKey(), false); - VirtualFile file = myXmlFile.getVirtualFile(); FileStub stub = null; if (description.hasStubs() && file instanceof VirtualFileWithId && !isFileParsed()) { ApplicationManager.getApplication().assertReadAccessAllowed(); @@ -139,9 +125,6 @@ class FileDescriptionCachedValueProvider implements SemEle sb.append("success " + result + "\n"); } - if (fireEvents) { - events.add(new DomEvent(result, true)); - } return result; } @@ -151,6 +134,7 @@ class FileDescriptionCachedValueProvider implements SemEle @Nullable private DomFileDescription findFileDescription(final XmlFileHeader xmlFileHeader, @Nullable StringBuilder sb) { + //noinspection unchecked final DomFileDescription mockDescription = myXmlFile.getUserData(DomManagerImpl.MOCK_DESCRIPTION); if (mockDescription != null) return mockDescription; @@ -176,9 +160,11 @@ class FileDescriptionCachedValueProvider implements SemEle if (sb != null) { sb.append("named " + new HashSet<>(namedDescriptions) + "\n"); } + //noinspection unchecked DomFileDescription description = ContainerUtil.find(namedDescriptions, myCondition); if (description == null) { final Set unnamed = myDomManager.getAcceptingOtherRootTagNameDescriptions(); + //noinspection unchecked description = ContainerUtil.find(unnamed, myCondition); } if (sb != null) { @@ -193,9 +179,8 @@ class FileDescriptionCachedValueProvider implements SemEle } public String getFileElementWithLogging() { - final XmlFileHeader rootTagName = myDomService.getXmlFileHeader(myXmlFile); - final StringBuilder log = new StringBuilder(); - myLastResult = _computeFileElement(false, rootTagName, log); + StringBuilder log = new StringBuilder(); + myLastResult = _computeFileElement(log); return log.toString(); }