mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -289,17 +289,19 @@ public class MethodBytecodeUtil {
|
||||
|
||||
List<Location> res = new ArrayList<>();
|
||||
for (Map.Entry<Method, Collection<Location>> entry : byMethod.entrySet()) {
|
||||
res.addAll(removeMethodSameLineLocations(entry.getKey(), entry.getValue()));
|
||||
res.addAll(removeMethodSameLineLocations(entry.getKey(), (List<Location>)entry.getValue()));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private static Collection<Location> removeMethodSameLineLocations(@NotNull Method method, @NotNull Collection<Location> locations) {
|
||||
if (locations.size() < 2) {
|
||||
private static Collection<Location> removeMethodSameLineLocations(@NotNull Method method, @NotNull List<Location> locations) {
|
||||
int locationsSize = locations.size();
|
||||
if (locationsSize < 2) {
|
||||
return locations;
|
||||
}
|
||||
//noinspection ConstantConditions
|
||||
int lineNumber = ContainerUtil.getFirstItem(locations).lineNumber();
|
||||
List<Boolean> mask = new ArrayList<>();
|
||||
List<Boolean> 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<Location> res = new ArrayList<>();
|
||||
|
||||
if (mask.size() == locationsSize) {
|
||||
locations.sort(Comparator.comparing(Location::codeIndex));
|
||||
List<Location> res = new ArrayList<>(locationsSize);
|
||||
int pos = 0;
|
||||
for (Location location : locations) {
|
||||
if (mask.get(pos++)) {
|
||||
|
||||
+2
-1
@@ -137,7 +137,8 @@ public class AddExceptionToExistingCatchFix extends PsiElementBaseIntentionActio
|
||||
if (unhandledExceptions.isEmpty()) return null;
|
||||
boolean containsInCatchOrFinally = containsInCatchOrFinally(element);
|
||||
List<PsiTryStatement> 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);
|
||||
}
|
||||
|
||||
@@ -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<PsiComment> 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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
+18
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
@@ -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 <caret>FileInputStream("");
|
||||
};
|
||||
} catch (RuntimeException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
@@ -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 <caret>FileInputStream("");
|
||||
};
|
||||
} catch (RuntimeException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// "Sort content" "true"
|
||||
|
||||
public enum OSType {
|
||||
LINUX// linux
|
||||
,
|
||||
|
||||
MAC//mac
|
||||
,
|
||||
WIN//after win
|
||||
// win
|
||||
|
||||
;
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// "Sort content" "true"
|
||||
|
||||
public enum OSType {
|
||||
WIN// win
|
||||
//after win
|
||||
, LIN<caret>UX // linux
|
||||
,MAC //mac
|
||||
;
|
||||
}
|
||||
@@ -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<T>{}")
|
||||
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'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<UserDefinedJsonSchemaConfiguration.Item> 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<String> 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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, JsonSchemaObject> 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<JsonSchemaObject> 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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -25,7 +25,7 @@ public class JsonSchemaCatalogManager {
|
||||
@NotNull private final ConcurrentMap<String, String> 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();
|
||||
|
||||
@@ -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, <warning>\"foo\"</warning>]");
|
||||
}
|
||||
|
||||
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, <warning>\"3\"</warning>]");
|
||||
}
|
||||
|
||||
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, <warning>\"2\"</warning>]");
|
||||
}
|
||||
|
||||
public static String rootObjectRedefinedSchema() {
|
||||
return "{\n" +
|
||||
" \"$schema\": \"http://json-schema.org/draft-04/schema#\",\n" +
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
+7
@@ -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 {
|
||||
|
||||
@@ -51,7 +51,8 @@ public abstract class StubBase<T extends PsiElement> extends ObjectStubBase<Stub
|
||||
return myStubList.findChildStubByType(id, elementType);
|
||||
}
|
||||
|
||||
public void setPsi(@NotNull final T psi) {
|
||||
public void setPsi(@NotNull T psi) {
|
||||
assert myPsi == null || myPsi == psi;
|
||||
myPsi = psi;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static com.intellij.psi.SyntaxTraverser.psiTraverser;
|
||||
|
||||
@@ -697,11 +698,11 @@ public class PsiTreeUtil {
|
||||
return aClass.cast(element);
|
||||
}
|
||||
|
||||
public static <T extends PsiElement> List<T> collectParentsOfType(PsiElement element, Class<T> parent, Class<? extends PsiElement>... stopClasses) {
|
||||
public static <T extends PsiElement> List<T> collectParents(PsiElement element, Class<T> parent, Predicate<PsiElement> stopCondition) {
|
||||
element = element.getParent();
|
||||
List<T> 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));
|
||||
}
|
||||
|
||||
@@ -84,7 +84,6 @@ public class StubBasedPsiElementBase<T extends StubElement> extends ASTDelegateP
|
||||
public static final Key<String> 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<T extends StubElement> 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<T extends StubElement> 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<T extends StubElement> extends ASTDelegateP
|
||||
public final T getGreenStub() {
|
||||
ProgressIndicatorProvider.checkCanceled(); // Hope, this is called often
|
||||
//noinspection unchecked
|
||||
return (T)mySubstrateRef.getGreenStub(myStubIndex);
|
||||
return (T)mySubstrateRef.getGreenStub();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.<p/>
|
||||
*
|
||||
* 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<AstPath, MyReference> myMap = ContainerUtil.newConcurrentMap();
|
||||
private volatile Boolean myHasUnbindableCachedPsi = null;
|
||||
|
||||
private static final Key<MyReferenceQueue> 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<? extends StubBasedPsiElementBase<?>> 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<StubBasedPsiElementBase<?>> {
|
||||
final AstPath pathRef;
|
||||
|
||||
MyReference(StubBasedPsiElementBase psi, AstPath ref, ReferenceQueue<StubBasedPsiElementBase<?>> queue) {
|
||||
super(psi, queue);
|
||||
pathRef = ref;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MyReferenceQueue extends ReferenceQueue<StubBasedPsiElementBase<?>> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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<StubTree> myStub;
|
||||
private final Getter<FileElement> 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<Reference<StubBasedPsiElementBase>> myRefToPsi;
|
||||
|
||||
private FileTrees(@Nullable Reference<StubTree> stub, @Nullable Getter<FileElement> ast, boolean astLoaded, boolean useStrongRefs) {
|
||||
private FileTrees(@NotNull PsiFileImpl file,
|
||||
@Nullable Reference<StubTree> stub,
|
||||
@Nullable Getter<FileElement> ast,
|
||||
@Nullable List<Reference<StubBasedPsiElementBase>> 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<StubBasedPsiElementBase> getAllCachedPsi(@NotNull List<Reference<StubBasedPsiElementBase>> refToPsi) {
|
||||
return refToPsi.stream().map(SoftReference::dereference).filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
boolean useSpineRefs() {
|
||||
return myRefToPsi != null;
|
||||
}
|
||||
|
||||
FileTrees switchToSpineRefs(@NotNull StubbedSpine srcSpine) {
|
||||
List<Reference<StubBasedPsiElementBase>> 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<FileElement> 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<PsiFileImpl> 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<StubElement<?>> stubList = stubTree == null ? null : stubTree.getPlainList();
|
||||
List<CompositeElement> 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<StubElement<?>> stubList, List<CompositeElement> 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<StubElement<?>> stubList, List<CompositeElement> 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() +
|
||||
'}' ;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<FileElement> myFileElementBeingLoaded = new ThreadLocal<>();
|
||||
protected final PsiManagerEx myManager;
|
||||
public static final Key<Boolean> 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<Pair<StubBasedPsiElementBase, AstPath>> 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<Pair<StubBasedPsiElementBase, AstPath>> 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<Pair<StubBasedPsiElementBase, AstPath>> bindings, FileTrees trees) {
|
||||
if (!bindings.isEmpty() && trees.useStrongRefs) {
|
||||
List<String> psiStrings = ContainerUtil.map(bindings, pair -> pair.first.getClass().getName());
|
||||
LOG.error(this + " of " + getClass() + "; " + psiStrings);
|
||||
}
|
||||
|
||||
for (int i = 0; i < bindings.size(); i++) {
|
||||
Pair<StubBasedPsiElementBase, AstPath> 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<Pair<StubBasedPsiElementBase, AstPath>> 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<Pair<StubBase, TreeElement>> 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<Pair<IStubFileElementType, PsiFile>> 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<PsiFileImpl, StubTree> 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<PsiFileImpl, StubTree> prepareAllStubTrees(List<Pair<IStubFileElementType, PsiFile>> roots, PsiFileStub[] rootStubs) {
|
||||
Map<PsiFileImpl, StubTree> 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<StubBasedPsiElementBase<?>> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<CompositeElement[]> PATH_CHILDREN = Key.create("PATH_CHILDREN");
|
||||
private static final Key<AstPath> 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<CompositeElement> 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<CompositeElement> 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -57,7 +57,8 @@ public class AstSpine implements StubbedSpine {
|
||||
return index >= myNodes.size() ? null : myNodes.get(index).getElementType();
|
||||
}
|
||||
|
||||
List<CompositeElement> getNodes() {
|
||||
@NotNull
|
||||
public List<CompositeElement> getSpineNodes() {
|
||||
return myNodes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 extends PsiElement> T getPsi(@NotNull Class<T> 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);
|
||||
|
||||
@@ -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<AstSpine> 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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<Pair<StubBase, TreeElement>> bindings = calcStubAstBindings(stubTree, tree);
|
||||
|
||||
for (int i = 0; i < bindings.size(); i++) {
|
||||
Pair<StubBase, TreeElement> pair = bindings.get(i);
|
||||
StubBasedPsiElementBase psi = (StubBasedPsiElementBase)pair.second.getPsi();
|
||||
//noinspection unchecked
|
||||
pair.first.setPsi(psi);
|
||||
psi.setStubIndex(i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<Pair<StubBase, TreeElement>> calcStubAstBindings(@NotNull StubTree stubTree, @NotNull FileElement tree) throws StubBindingException {
|
||||
PsiFile file = (PsiFile)tree.getPsi();
|
||||
List<CompositeElement> nodes = tree.getStubbedSpine().getNodes();
|
||||
List<StubElement<?>> stubs = stubTree.getPlainList();
|
||||
if (stubs.size() != nodes.size()) {
|
||||
throw new StubBindingException("Stub list in " + file.getName() + " length differs from PSI");
|
||||
}
|
||||
|
||||
List<Pair<StubBase, TreeElement>> 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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
+5
-2
@@ -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);
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ public interface TreeStructureProvider {
|
||||
* @see com.intellij.openapi.actionSystem.DataProvider
|
||||
*/
|
||||
@Nullable
|
||||
default Object getData(Collection<AbstractTreeNode> selected, String dataName) {
|
||||
default Object getData(@NotNull Collection<AbstractTreeNode> selected, String dataName) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +64,11 @@ public class TodoConfiguration implements PersistentStateComponent<Element> {
|
||||
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),
|
||||
|
||||
@@ -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);
|
||||
|
||||
+16
-7
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.application.options.editor.EditorSmartKeysConfigurable">
|
||||
<grid id="27dc6" binding="myRootPanel" layout-manager="GridLayoutManager" row-count="13" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="0">
|
||||
<grid id="27dc6" binding="myRootPanel" layout-manager="GridLayoutManager" row-count="14" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="0">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="500" height="437"/>
|
||||
@@ -10,7 +10,7 @@
|
||||
<children>
|
||||
<vspacer id="a9cd">
|
||||
<constraints>
|
||||
<grid row="12" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
<grid row="13" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
<component id="18e81" class="javax.swing.JCheckBox" binding="myCbSmartHome">
|
||||
@@ -60,7 +60,7 @@
|
||||
<grid id="6ebf" layout-manager="GridLayoutManager" row-count="1" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="5" right="5"/>
|
||||
<constraints>
|
||||
<grid row="10" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="1" fill="3" indent="0" use-parent-layout="false"/>
|
||||
<grid row="11" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="1" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
@@ -88,7 +88,7 @@
|
||||
</grid>
|
||||
<grid id="b08a8" binding="myAddonPanel" layout-manager="GridBagLayout">
|
||||
<constraints>
|
||||
<grid row="11" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="8" fill="2" indent="0" use-parent-layout="false"/>
|
||||
<grid row="12" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="8" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
@@ -114,7 +114,7 @@
|
||||
<grid id="e1eb4" layout-manager="GridLayoutManager" row-count="1" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="9" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
<grid row="10" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<clientProperties>
|
||||
@@ -146,7 +146,7 @@
|
||||
<grid id="5344e" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="8" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
<grid row="9" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<clientProperties>
|
||||
@@ -184,7 +184,7 @@
|
||||
</grid>
|
||||
<hspacer id="6cc57">
|
||||
<constraints>
|
||||
<grid row="8" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
<grid row="9" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</hspacer>
|
||||
<component id="892b" class="javax.swing.JCheckBox" binding="myCbEnableAddingCaretsOnDoubleCtrlArrows">
|
||||
@@ -196,6 +196,15 @@
|
||||
<text resource-bundle="messages/ApplicationBundle" key="checkbox.enable.double.ctrl"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="db94d" class="javax.swing.JCheckBox" binding="myCbTabExistsBracketsAndQuotes">
|
||||
<constraints>
|
||||
<grid row="8" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<selected value="false"/>
|
||||
<text resource-bundle="messages/ApplicationBundle" key="checkbox.tab.exists.brackets.and.quotes"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
|
||||
+6
-16
@@ -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<UnnamedCo
|
||||
private JCheckBox myCbReformatBlockOnTypingRBrace;
|
||||
private JComboBox mySmartBackspaceCombo;
|
||||
private JCheckBox myCbEnableAddingCaretsOnDoubleCtrlArrows;
|
||||
private JCheckBox myCbTabExistsBracketsAndQuotes;
|
||||
private boolean myAddonsInitialized = false;
|
||||
|
||||
private static final String NO_REFORMAT = ApplicationBundle.message("combobox.paste.reformat.none");
|
||||
@@ -180,7 +167,7 @@ public class EditorSmartKeysConfigurable extends CompositeConfigurable<UnnamedCo
|
||||
myCbCamelWords.setSelected(editorSettings.isCamelWords());
|
||||
|
||||
myCbSurroundSelectionOnTyping.setSelected(codeInsightSettings.SURROUND_SELECTION_ON_QUOTE_TYPED);
|
||||
|
||||
myCbTabExistsBracketsAndQuotes.setSelected(codeInsightSettings.TAB_EXITS_BRACKETS_AND_QUOTES);
|
||||
myCbEnableAddingCaretsOnDoubleCtrlArrows.setSelected(editorSettings.addCaretsOnDoubleCtrl());
|
||||
|
||||
SmartBackspaceMode backspaceMode = codeInsightSettings.getBackspaceMode();
|
||||
@@ -215,6 +202,7 @@ public class EditorSmartKeysConfigurable extends CompositeConfigurable<UnnamedCo
|
||||
codeInsightSettings.AUTOINSERT_PAIR_QUOTE = myCbInsertPairQuote.isSelected();
|
||||
codeInsightSettings.REFORMAT_BLOCK_ON_RBRACE = myCbReformatBlockOnTypingRBrace.isSelected();
|
||||
codeInsightSettings.SURROUND_SELECTION_ON_QUOTE_TYPED = myCbSurroundSelectionOnTyping.isSelected();
|
||||
codeInsightSettings.TAB_EXITS_BRACKETS_AND_QUOTES = myCbTabExistsBracketsAndQuotes.isSelected();
|
||||
editorSettings.setCamelWords(myCbCamelWords.isSelected());
|
||||
codeInsightSettings.REFORMAT_ON_PASTE = getReformatPastedBlockValue();
|
||||
codeInsightSettings.setBackspaceMode(getSmartBackspaceModeValue());
|
||||
@@ -248,6 +236,8 @@ public class EditorSmartKeysConfigurable extends CompositeConfigurable<UnnamedCo
|
||||
|
||||
isModified |= (getSmartBackspaceModeValue() != codeInsightSettings.getBackspaceMode());
|
||||
|
||||
isModified |= isModified(myCbTabExistsBracketsAndQuotes, codeInsightSettings.TAB_EXITS_BRACKETS_AND_QUOTES);
|
||||
|
||||
return isModified;
|
||||
|
||||
}
|
||||
|
||||
@@ -149,6 +149,8 @@ public class CodeInsightSettings implements PersistentStateComponent<Element>, 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.
|
||||
|
||||
+1
-1
@@ -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()) {
|
||||
|
||||
+71
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<AnAction> promote(List<AnAction> actions, DataContext context) {
|
||||
List<AnAction> 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;
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
+1
-7
@@ -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<Block> 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;
|
||||
|
||||
|
||||
+5
-1
@@ -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")
|
||||
|
||||
@@ -672,7 +672,7 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA
|
||||
UIManager.getColor("SearchEverywhere.shortcutForeground") : foregroundColor;
|
||||
|
||||
StringBuilder cbText = new StringBuilder("<html>");
|
||||
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("<b>");
|
||||
cbText.append("<font color=#").append(ColorUtil.toHex(shortcutColor)).append(">").append(getShortcut()).append("</font>");
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
+6
-3
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -73,8 +73,8 @@ public final class CompoundTreeStructureProvider implements TreeStructureProvide
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object getData(Collection<AbstractTreeNode> selection, String id) {
|
||||
if (id != null && selection != null && !selection.isEmpty()) {
|
||||
public Object getData(@NotNull Collection<AbstractTreeNode> selection, String id) {
|
||||
if (id != null && !selection.isEmpty()) {
|
||||
for (TreeStructureProvider provider : providers) {
|
||||
try {
|
||||
Object data = provider.getData(selection, id);
|
||||
|
||||
@@ -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<Language> {
|
||||
|
||||
@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
|
||||
|
||||
@@ -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<FileType> 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
|
||||
|
||||
@@ -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<Language> {
|
||||
|
||||
@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
|
||||
|
||||
+1
@@ -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;
|
||||
}
|
||||
|
||||
+1
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<GeneralSettings
|
||||
public static final String PROP_INACTIVE_TIMEOUT = "inactiveTimeout";
|
||||
public static final String PROP_SUPPORT_SCREEN_READERS = "supportScreenReaders";
|
||||
|
||||
public static final String SUPPORT_SCREEN_READERS = "ide.support.screenreaders.enabled";
|
||||
private static final Boolean SUPPORT_SCREEN_READERS_OVERRIDEN = getSupportScreenReadersOverriden();
|
||||
|
||||
static final UINumericRange SAVE_FILES_AFTER_IDLE_SEC = new UINumericRange(15, 1, 300);
|
||||
|
||||
private String myBrowserPath = BrowserUtil.getDefaultAlternativeBrowserPath();
|
||||
private boolean myShowTipsOnStartup = true;
|
||||
private boolean myReopenLastProject = true;
|
||||
private boolean mySupportScreenReaders = false;
|
||||
private boolean mySupportScreenReaders = ObjectUtils.chooseNotNull(SUPPORT_SCREEN_READERS_OVERRIDEN, Boolean.FALSE);
|
||||
private boolean mySyncOnFrameActivation = true;
|
||||
private boolean mySaveOnFrameDeactivation = true;
|
||||
private boolean myAutoSaveIfInactive = false; // If true the IDEA automatically saves files if it is inactive for some seconds
|
||||
@@ -121,6 +125,19 @@ public class GeneralSettings implements PersistentStateComponent<GeneralSettings
|
||||
myReopenLastProject = reopenLastProject;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Boolean getSupportScreenReadersOverriden() {
|
||||
String prop = System.getProperty(SUPPORT_SCREEN_READERS);
|
||||
if (prop != null) {
|
||||
return Boolean.parseBoolean(prop);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isSupportScreenReadersOverriden() {
|
||||
return SUPPORT_SCREEN_READERS_OVERRIDEN != null;
|
||||
}
|
||||
|
||||
public boolean isSupportScreenReaders() {
|
||||
return mySupportScreenReaders;
|
||||
}
|
||||
|
||||
@@ -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.openapi.actionSystem;
|
||||
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
@@ -344,4 +330,6 @@ public interface IdeActions {
|
||||
@NonNls String ACTION_METHOD_OVERLOAD_SWITCH_DOWN = "MethodOverloadSwitchDown";
|
||||
|
||||
@NonNls String ACTION_UPDATE_RUNNING_APPLICATION = "UpdateRunningApplication";
|
||||
|
||||
@NonNls String ACTION_BRACE_OR_QUOTE_OUT = "BraceOrQuoteOut";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// 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.ui;
|
||||
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.border.TitledBorder;
|
||||
|
||||
/**
|
||||
* Allows to apply IDE-specific customizations to the terms used in platform UI features.
|
||||
*/
|
||||
public class ProjectUICustomization {
|
||||
public static ProjectUICustomization getInstance() {
|
||||
return ServiceManager.getService(ProjectUICustomization.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name to be displayed in the UI for the "project" concept (Rider changes this to "solution").
|
||||
*/
|
||||
public String getProjectConceptName() {
|
||||
return "project";
|
||||
}
|
||||
|
||||
public static String replaceProjectConceptName(String text) {
|
||||
return text.replace("project", getInstance().getProjectConceptName());
|
||||
}
|
||||
|
||||
|
||||
public static void replaceProjectConceptName(AbstractButton component) {
|
||||
component.setText(component.getText().replace("project", getInstance().getProjectConceptName()));
|
||||
}
|
||||
|
||||
public static void replaceProjectConceptName(Border border) {
|
||||
if (border instanceof TitledBorder) {
|
||||
TitledBorder titledBorder = (TitledBorder)border;
|
||||
titledBorder.setTitle(titledBorder.getTitle().replace("project", getInstance().getProjectConceptName()));
|
||||
}
|
||||
}
|
||||
|
||||
public static String replaceProjectConceptNameForAction(String actionName) {
|
||||
String name = getInstance().getProjectConceptName();
|
||||
if (name.equals("project")) {
|
||||
return actionName;
|
||||
}
|
||||
return actionName.replace("_", "").replace("project", name).replace("Project", StringUtil.capitalize(name));
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,9 @@ public class OSProcessUtil {
|
||||
if (SystemInfo.isWindows) {
|
||||
try {
|
||||
if (process instanceof WinPtyProcess) {
|
||||
boolean res = WinProcessManager.kill(((WinPtyProcess)process).getChildProcessId(), true);
|
||||
int pid = ((WinPtyProcess) process).getChildProcessId();
|
||||
if (pid == -1) return true;
|
||||
boolean res = WinProcessManager.kill(pid, true);
|
||||
process.destroy();
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.intellij.openapi.options.ConfigurationException;
|
||||
import com.intellij.openapi.options.SearchableConfigurable;
|
||||
import com.intellij.openapi.options.ex.ConfigurableWrapper;
|
||||
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
|
||||
import com.intellij.ui.ProjectUICustomization;
|
||||
import com.intellij.ui.components.JBRadioButton;
|
||||
import com.intellij.util.PlatformUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -152,6 +153,11 @@ public class GeneralSettingsConfigurable extends CompositeConfigurable<Searchabl
|
||||
GeneralSettings settings = GeneralSettings.getInstance();
|
||||
myComponent.myChkReopenLastProject.setSelected(settings.isReopenLastProject());
|
||||
myComponent.myChkSupportScreenReaders.setSelected(settings.isSupportScreenReaders());
|
||||
if (GeneralSettings.isSupportScreenReadersOverriden()) {
|
||||
myComponent.myChkSupportScreenReaders.setEnabled(false);
|
||||
myComponent.myChkSupportScreenReaders.setToolTipText(
|
||||
"The option is overriden by the JVM property: \"" + GeneralSettings.SUPPORT_SCREEN_READERS + "\"");
|
||||
}
|
||||
myComponent.myChkSyncOnFrameActivation.setSelected(settings.isSyncOnFrameActivation());
|
||||
myComponent.myChkSaveOnFrameDeactivation.setSelected(settings.isSaveOnFrameDeactivation());
|
||||
myComponent.myChkAutoSaveIfInactive.setSelected(settings.isAutoSaveIfInactive());
|
||||
@@ -216,8 +222,15 @@ public class GeneralSettingsConfigurable extends CompositeConfigurable<Searchabl
|
||||
private JBRadioButton myDisconnectJBRadioButton;
|
||||
private JBRadioButton myAskJBRadioButton;
|
||||
private TextFieldWithBrowseButton myProjectDirectoryTextField;
|
||||
private JPanel myProjectOpeningPanel;
|
||||
|
||||
public MyComponent() { }
|
||||
public MyComponent() {
|
||||
ProjectUICustomization.replaceProjectConceptName(myChkReopenLastProject);
|
||||
ProjectUICustomization.replaceProjectConceptName(myProjectOpeningPanel.getBorder());
|
||||
ProjectUICustomization.replaceProjectConceptName(myOpenProjectInNewWindow);
|
||||
ProjectUICustomization.replaceProjectConceptName(myOpenProjectInSameWindow);
|
||||
ProjectUICustomization.replaceProjectConceptName(myConfirmWindowToOpenProject);
|
||||
}
|
||||
|
||||
private void createUIComponents() {
|
||||
myProjectDirectoryTextField = new TextFieldWithBrowseButton();
|
||||
|
||||
@@ -181,7 +181,7 @@
|
||||
<border type="none"/>
|
||||
<children/>
|
||||
</grid>
|
||||
<grid id="dab62" layout-manager="GridLayoutManager" row-count="5" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<grid id="dab62" binding="myProjectOpeningPanel" layout-manager="GridLayoutManager" row-count="5" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
|
||||
@@ -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")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-4
@@ -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);
|
||||
|
||||
+5
@@ -24,6 +24,9 @@ import java.util.Set;
|
||||
|
||||
@State(name = "EditorSettings", storages = @Storage("editor.xml"))
|
||||
public class EditorSettingsExternalizable implements PersistentStateComponent<EditorSettingsExternalizable.OptionSet> {
|
||||
@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<Ed
|
||||
}
|
||||
|
||||
public void setVirtualSpace(boolean val) {
|
||||
boolean oldValue = myOptions.IS_VIRTUAL_SPACE;
|
||||
myOptions.IS_VIRTUAL_SPACE = val;
|
||||
myPropertyChangeSupport.firePropertyChange(PROP_VIRTUAL_SPACE, oldValue, val);
|
||||
}
|
||||
|
||||
public boolean isCaretInsideTabs() {
|
||||
|
||||
+3
-3
@@ -446,7 +446,7 @@ public class IdeDocumentHistoryImpl extends IdeDocumentHistory implements Projec
|
||||
return file != null ? myEditorManager.getSelectedEditorWithProvider(file) : null;
|
||||
}
|
||||
|
||||
private PlaceInfo createPlaceInfo(@NotNull final FileEditor fileEditor, final FileEditorProvider fileProvider) {
|
||||
protected PlaceInfo createPlaceInfo(@NotNull final FileEditor fileEditor, final FileEditorProvider fileProvider) {
|
||||
if (!fileEditor.isValid()) return null;
|
||||
|
||||
final VirtualFile file = myEditorManager.getFile(fileEditor);
|
||||
@@ -485,13 +485,13 @@ public class IdeDocumentHistoryImpl extends IdeDocumentHistory implements Projec
|
||||
return myFileDocumentManager;
|
||||
}
|
||||
|
||||
private static final class PlaceInfo {
|
||||
protected static final class PlaceInfo {
|
||||
private final VirtualFile myFile;
|
||||
private final FileEditorState myNavigationState;
|
||||
private final String myEditorTypeId;
|
||||
private final Reference<EditorWindow> myWindow;
|
||||
|
||||
PlaceInfo(@NotNull VirtualFile file,
|
||||
public PlaceInfo(@NotNull VirtualFile file,
|
||||
@NotNull FileEditorState navigationState,
|
||||
@NotNull String editorTypeId,
|
||||
@Nullable EditorWindow window) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
action.MethodOverloadSwitchDown.text=Next Method Overload
|
||||
|
||||
action.BraceOrQuoteOut.text=Exit Current Braces/Quotes
|
||||
@@ -453,6 +453,7 @@ checkbox.honor.camelhumps.words.settings.on.double.click=<html>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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -991,6 +991,7 @@
|
||||
<elementPreviewProvider implementation="com.intellij.codeInsight.preview.ElementPreviewHintProvider"/>
|
||||
|
||||
<actionPromoter implementation="com.intellij.ui.EditorTextFieldActionPromoter"/>
|
||||
<actionPromoter implementation="com.intellij.codeInsight.editorActions.TabActionsPromoter"/>
|
||||
|
||||
<stepsBeforeRunProvider implementation="com.intellij.execution.impl.RunConfigurationBeforeRunProvider"/>
|
||||
|
||||
|
||||
@@ -294,6 +294,8 @@
|
||||
|
||||
<postStartupActivity implementation="com.intellij.internal.IconsLoadTime$StartupLoadTime"/>
|
||||
|
||||
<applicationService serviceImplementation="com.intellij.ui.ProjectUICustomization"/>
|
||||
|
||||
<!-- General -->
|
||||
<applicationConfigurable groupId="appearance" groupWeight="120" key="title.general" bundle="messages.IdeBundle" id="preferences.general"
|
||||
instance="com.intellij.ide.GeneralSettingsConfigurable"/>
|
||||
|
||||
@@ -48,6 +48,8 @@
|
||||
|
||||
<action id="EditorIndentLineOrSelection" class="com.intellij.openapi.editor.actions.IndentLineOrSelectionAction"/>
|
||||
|
||||
<action id="BraceOrQuoteOut" class="com.intellij.codeInsight.editorActions.BraceOrQuoteOutAction"/>
|
||||
|
||||
<group id="CodeInsightEditorActions">
|
||||
<reference ref="LookupActions"/>
|
||||
<action id="EmacsStyleIndent" class="com.intellij.codeInsight.editorActions.EmacsStyleIndentAction"/>
|
||||
|
||||
@@ -593,6 +593,12 @@
|
||||
<action id="ChangesView.Revert">
|
||||
<keyboard-shortcut first-keystroke="control alt Z"/>
|
||||
</action>
|
||||
<action id="Vcs.MoveChangedLinesToChangelist">
|
||||
<keyboard-shortcut first-keystroke="alt shift M"/>
|
||||
</action>
|
||||
<action id="ChangesView.Move">
|
||||
<keyboard-shortcut first-keystroke="alt shift M"/>
|
||||
</action>
|
||||
|
||||
<action id="GotoNextError">
|
||||
<keyboard-shortcut first-keystroke="F2"/>
|
||||
@@ -1081,4 +1087,8 @@
|
||||
<action id="RunDashboard.ShowConfigurations">
|
||||
<keyboard-shortcut first-keystroke="control shift T"/>
|
||||
</action>
|
||||
|
||||
<action id="BraceOrQuoteOut">
|
||||
<keyboard-shortcut first-keystroke="TAB"/>
|
||||
</action>
|
||||
</keymap>
|
||||
@@ -360,4 +360,6 @@
|
||||
<action id="RunDashboard.ShowConfigurations">
|
||||
<keyboard-shortcut first-keystroke="shift alt H"/>
|
||||
</action>
|
||||
<action id="Vcs.MoveChangedLinesToChangelist"/>
|
||||
<action id="ChangesView.Move"/>
|
||||
</keymap>
|
||||
|
||||
@@ -310,4 +310,6 @@
|
||||
<action id="GotoImplementation"/>
|
||||
<action id="EditorScrollUp"/>
|
||||
<action id="EditorScrollDown"/>
|
||||
<action id="Vcs.MoveChangedLinesToChangelist"/>
|
||||
<action id="ChangesView.Move"/>
|
||||
</keymap>
|
||||
|
||||
@@ -266,6 +266,12 @@
|
||||
<action id="CallHierarchy">
|
||||
<keyboard-shortcut first-keystroke="control alt H"/>
|
||||
</action>
|
||||
<action id="Vcs.MoveChangedLinesToChangelist">
|
||||
<keyboard-shortcut first-keystroke="meta shift M"/>
|
||||
</action>
|
||||
<action id="ChangesView.Move">
|
||||
<keyboard-shortcut first-keystroke="meta shift M"/>
|
||||
</action>
|
||||
|
||||
<action id="CommentByBlockComment">
|
||||
<keyboard-shortcut first-keystroke="meta alt SLASH"/>
|
||||
|
||||
@@ -289,6 +289,12 @@
|
||||
<action id="CallHierarchy">
|
||||
<keyboard-shortcut first-keystroke="control alt H"/>
|
||||
</action>
|
||||
<action id="Vcs.MoveChangedLinesToChangelist">
|
||||
<keyboard-shortcut first-keystroke="meta shift M"/>
|
||||
</action>
|
||||
<action id="ChangesView.Move">
|
||||
<keyboard-shortcut first-keystroke="meta shift M"/>
|
||||
</action>
|
||||
|
||||
<action id="CommentByBlockComment">
|
||||
<keyboard-shortcut first-keystroke="control shift SLASH"/>
|
||||
@@ -401,4 +407,6 @@
|
||||
<action id="GotoPrevElementUnderCaretUsage">
|
||||
<keyboard-shortcut first-keystroke="ctrl alt UP"/>
|
||||
</action>
|
||||
<action id="Vcs.MoveChangedLinesToChangelist"/>
|
||||
<action id="ChangesView.Move"/>
|
||||
</keymap>
|
||||
|
||||
@@ -384,4 +384,6 @@
|
||||
<action id="ShelvedChanges.Rename">
|
||||
<keyboard-shortcut first-keystroke="control R"/>
|
||||
</action>
|
||||
<action id="Vcs.MoveChangedLinesToChangelist"/>
|
||||
<action id="ChangesView.Move"/>
|
||||
</keymap>
|
||||
|
||||
+30
@@ -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\"<caret>) } }", 1);
|
||||
}
|
||||
|
||||
public void testStringLiteral() {
|
||||
doTest("class C { void m() { String s = \"a<caret>\" } }", 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
@@ -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("""
|
||||
|
||||
+18
-13
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
+7
-5
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+50
-4
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Integer> childrenPids = new ArrayList<Integer>();
|
||||
|
||||
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;
|
||||
|
||||
@@ -22,6 +22,10 @@ public interface CommitExecutor {
|
||||
return null;
|
||||
}
|
||||
|
||||
default boolean supportsPartialCommit() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
CommitSession createCommitSession();
|
||||
}
|
||||
|
||||
@@ -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<AnAction> promote(List<AnAction> actions, DataContext context) {
|
||||
List<AnAction> list = new ArrayList<>(0);
|
||||
ActionManager am = ActionManager.getInstance();
|
||||
List<AnAction> reorderedActions = new ArrayList<>(actions);
|
||||
List<String> 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<AnAction> 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<AnAction> reorderedActions, List<String> 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);
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -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() {
|
||||
|
||||
+5
@@ -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) {
|
||||
|
||||
+78
-34
@@ -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<CommitExecutor> myExecutors;
|
||||
@NotNull private final List<CheckinHandler> myHandlers = newArrayList();
|
||||
private final boolean myAllOfDefaultChangeListChangesIncluded;
|
||||
@NotNull private final String myCommitActionName;
|
||||
private final boolean myEnablePartialCommit;
|
||||
|
||||
@NotNull private final Map<String, String> myListComments;
|
||||
@NotNull private final List<CommitExecutorAction> 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<CheckinHandler> createCheckinHandlers(@NotNull Project project,
|
||||
@NotNull CheckinProjectPanel checkinPanel,
|
||||
@NotNull CommitContext commitContext) {
|
||||
List<CheckinHandler> 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<AbstractVcs> affectedVcses) {
|
||||
Set<String> 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
|
||||
|
||||
+5
-2
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<B extends XBreakpoint<P>, P extends XBreak
|
||||
return getDisplayText(breakpoint);
|
||||
}
|
||||
|
||||
public String getLogMessageLabelText() {
|
||||
return XDebuggerBundle.message("xbreakpoints.log.message.label");
|
||||
}
|
||||
|
||||
public interface XBreakpointCreator<P extends XBreakpointProperties> {
|
||||
@NotNull
|
||||
XBreakpoint<P> createBreakpoint(@Nullable P properties);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user