Merge remote-tracking branch 'origin/master'

This commit is contained in:
Konstantin Bulenkov
2016-10-06 19:18:40 +02:00
7 changed files with 396 additions and 35 deletions
@@ -15,7 +15,8 @@
*/
package com.intellij.codeInsight.daemon.impl;
import com.intellij.openapi.util.Couple;
import com.intellij.codeInsight.hints.filtering.Matcher;
import com.intellij.codeInsight.hints.filtering.MatcherConstructor;
import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.TypeConversionUtil;
@@ -26,22 +27,25 @@ import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.intellij.openapi.util.text.StringUtil.containsIgnoreCase;
import static com.intellij.psi.CommonClassNames.JAVA_LANG_STRING;
public class ParameterNameHintsManager {
private static final List<Couple<String>> COMMONLY_USED_PARAMETER_PAIR = ContainerUtil.newArrayList(
Couple.of("begin", "end"),
Couple.of("start", "end"),
Couple.of("first", "last"),
Couple.of("first", "second"),
Couple.of("from", "to"),
Couple.of("key", "value"),
Couple.of("min", "max"),
Couple.of("format", "arg")
);
private static List<Matcher> MATCHERS = Stream.of(
"(begin*, end*)",
"(start*, end*)",
"(first*, last*)",
"(first*, second*)",
"(from*, to*)",
"(min*, max*)",
"(key, value)",
"(format, arg)"
).map((s) -> MatcherConstructor.INSTANCE.createMatcher(s))
.collect(Collectors.toList());
private static final Set<String> COMMON_METHOD_NAMES = ContainerUtil.newHashSet("set", "print", "println");
@NotNull
@@ -173,7 +177,7 @@ public class ParameterNameHintsManager {
final int totalDescriptors = descriptors.size();
if (totalDescriptors == 1 && shouldIgnoreSingleHint(parameters, descriptors)
|| totalDescriptors == 2 && parameters.length == 2 && isParamPairToIgnore(descriptors.get(0), descriptors.get(1)))
|| totalDescriptors == 2 && parameters.length == 2 && isParamPairToIgnore(descriptors))
{
return ContainerUtil.emptyList();
}
@@ -206,18 +210,13 @@ public class ParameterNameHintsManager {
return new InlayInfo(paramName, callArgument.getTextRange().getStartOffset(), callArgument);
}
private static boolean isParamPairToIgnore(InlayInfo first, InlayInfo second) {
String firstParamName = first.getText();
String secondParamName = second.getText();
private static boolean isParamPairToIgnore(List<InlayInfo> descriptors) {
List<String> params = descriptors
.stream()
.map((e) -> e.getText())
.collect(Collectors.toList());
for (Couple<String> knownPair : COMMONLY_USED_PARAMETER_PAIR) {
if (containsIgnoreCase(firstParamName, knownPair.first)
&& containsIgnoreCase(secondParamName, knownPair.second)) {
return true;
}
}
return false;
return MATCHERS.stream().anyMatch((e) -> e.isMatching("", params));
}
private static boolean shouldInlineParameterName(@NotNull PsiExpression argument,
@@ -0,0 +1,96 @@
/*
* 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.codeInsight.hints.filtering
import com.intellij.openapi.util.Couple
interface ParamMatcher {
fun isMatching(paramNames: List<String>): Boolean
}
interface MethodMatcher {
fun isMatching(fullyQualifiedMethodName: String, paramNames: List<String>): Boolean
}
object AnyParamMatcher: ParamMatcher {
override fun isMatching(paramNames: List<String>) = true
}
class StringParamMatcher(private val paramMatchers: List<StringMatcher>): ParamMatcher {
override fun isMatching(paramNames: List<String>): Boolean {
if (paramNames.size != paramMatchers.size) {
return false
}
return paramMatchers
.zip(paramNames)
.find { !it.first.isMatching(it.second) } == null
}
}
class Matcher(private val methodNameMatcher: StringMatcher,
private val paramMatchers: ParamMatcher): MethodMatcher
{
override fun isMatching(fullyQualifiedMethodName: String, paramNames: List<String>): Boolean {
return methodNameMatcher.isMatching(fullyQualifiedMethodName) && paramMatchers.isMatching(paramNames)
}
}
object MatcherConstructor {
fun extract(matcher: String): Couple<String>? {
val trimmedMatcher = matcher.trim()
if (trimmedMatcher.isEmpty()) return null
val index = trimmedMatcher.indexOf('(')
if (index < 0) {
return Couple(trimmedMatcher, "")
}
else if (index == 0) {
return Couple("", trimmedMatcher)
}
val methodMatcher = trimmedMatcher.substring(0, index)
val paramsMatcher = trimmedMatcher.substring(index)
return Couple(methodMatcher.trim(), paramsMatcher.trim())
}
private fun createParametersMatcher(paramsMatcher: String): ParamMatcher? {
if (paramsMatcher.length <= 2) return null
val paramsString = paramsMatcher.substring(1, paramsMatcher.length - 1)
val params = paramsString.split(',').map(String::trim)
if (params.find(String::isEmpty) != null) return null
val matchers = params.mapNotNull { StringMatcherBuilder.create(it) }
return if (matchers.size == params.size) StringParamMatcher(matchers) else null
}
fun createMatcher(matcher: String): Matcher? {
val pair = extract(matcher) ?: return null
val methodNameMatcher = StringMatcherBuilder.create(pair.first) ?: return null
val paramMatcher = if (pair.second.isEmpty()) AnyParamMatcher else createParametersMatcher(pair.second)
return if (paramMatcher != null) Matcher(methodNameMatcher, paramMatcher) else null
}
}
@@ -0,0 +1,56 @@
/*
* 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.codeInsight.hints.filtering
interface StringMatcher {
fun isMatching(text: String): Boolean
}
class StringMatcherImpl(private val matcher: (String) -> Boolean) : StringMatcher {
override fun isMatching(text: String) = matcher(text)
}
object StringMatcherBuilder {
fun create(matcher: String): StringMatcher? {
if (matcher.isEmpty()) return StringMatcherImpl { true }
val asterisksCount = matcher.count { it == '*' }
if (asterisksCount > 1) return null
if (asterisksCount == 1) return createAsterisksMatcher(matcher)
return StringMatcherImpl { it == matcher }
}
private fun createAsterisksMatcher(matcher: String): StringMatcher? {
if (matcher == "*") {
return StringMatcherImpl { true }
}
if (matcher.startsWith('*')) {
val target = matcher.substring(1)
return StringMatcherImpl { it.endsWith(target) }
}
if (matcher.endsWith('*')) {
val target = matcher.substring(0, matcher.length - 1)
return StringMatcherImpl { it.startsWith(target) }
}
return null
}
}
@@ -0,0 +1,67 @@
/*
* 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.codeInsight.hints.filtering
import junit.framework.TestCase
import org.assertj.core.api.Assertions.assertThat
class MatcherTest : TestCase() {
fun Matcher.assertIsMatching(fullyQualifiedMethodName: String, vararg params: String) {
assertThat(isMatching(fullyQualifiedMethodName, listOf(*params))).isTrue()
}
fun Matcher.assertNotMatching(fullyQualifiedMethodName: String, vararg params: String) {
assertThat(isMatching(fullyQualifiedMethodName, listOf(*params))).isFalse()
}
fun `test simple matcher`() {
val matcher = MatcherConstructor.createMatcher("*.String(old*, new*)")!!
matcher.assertIsMatching("java.lang.String", "oldValue", "newValue")
matcher.assertIsMatching("java.lang.String", "old", "new")
matcher.assertNotMatching("java.lang.String", "valueOld", "valueNew")
matcher.assertNotMatching("Boolean", "old", "new")
}
fun `test any single param method matcher`() {
val matchers = listOf(MatcherConstructor.createMatcher("*(*)")!!, MatcherConstructor.createMatcher("(*)")!!)
matchers.forEach {
it.assertIsMatching("java.lang.String.indexOf", "ch")
it.assertIsMatching("java.lang.String.charAt", "index")
it.assertIsMatching("java.lang.Boolean.valueOf", "value")
it.assertNotMatching("java.lang.Boolean.substring", "from", "to")
}
}
fun `test any with key value`() {
val matcher = MatcherConstructor.createMatcher("*(key, value)")!!
matcher.assertIsMatching("java.util.Map.put", "key", "value")
matcher.assertIsMatching("java.util.HashMap.put", "key", "value")
matcher.assertIsMatching("java.util.HashMap.putIfNeeded", "key", "value")
}
fun `test couple contains`() {
val matcher = MatcherConstructor.createMatcher("*(first*, last*)")!!
matcher.assertIsMatching("java.util.Str.subs", "firstIndex", "lastIndex")
matcher.assertIsMatching("java.util.Str.subs", "first", "last")
}
}
@@ -0,0 +1,85 @@
/*
* 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.codeInsight.hints.filtering
import junit.framework.TestCase
import org.assertj.core.api.Assertions.assertThat
class PatternExtractionTest : TestCase() {
fun String.assertMatcher(nameMatcher: String, paramsMatcher: String) {
val matcher = MatcherConstructor.extract(this)
if (matcher == null) {
assertThat(nameMatcher).isEmpty()
assertThat(paramsMatcher).isEmpty()
return
}
assertThat(nameMatcher).isEqualTo(matcher.first)
assertThat(paramsMatcher).isEqualTo(matcher.second)
}
fun `test match all methods from package`() {
val text = "java.lang.*"
text.assertMatcher("java.lang.*", "")
}
fun `test match all methods from class String`() {
val text = "java.lang.String.*"
text.assertMatcher("java.lang.String.*", "")
}
fun `test match all replace methods`() {
val text = "*.replace"
text.assertMatcher("*.replace", "")
}
fun `test match all replace methods with couple params`() {
val text = "*.replace(*, *)"
text.assertMatcher("*.replace", "(*, *)")
}
fun `test match only one replace method with couple params`() {
val text = "java.lang.String.replace(*,*)"
text.assertMatcher("java.lang.String.replace", "(*,*)")
}
fun `test match debug particular method`() {
val text = "org.logger.Log.debug(format,arg)"
text.assertMatcher("org.logger.Log.debug", "(format,arg)")
}
fun `test match method with single param`() {
val text = "class.method(*)"
text.assertMatcher("class.method", "(*)")
}
fun `test match all methods with single param`() {
val text = "*(*)"
text.assertMatcher("*", "(*)")
}
fun `test simplified params matcher`() {
val text = "(*)"
text.assertMatcher("", "(*)")
}
fun `test shit`() {
val text = " foooo (*) "
text.assertMatcher("foooo", "(*)")
}
}
@@ -0,0 +1,52 @@
/*
* 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.codeInsight.hints.filtering
import junit.framework.TestCase
import org.assertj.core.api.Assertions.assertThat
class StringMatchingTest : TestCase() {
fun com.intellij.codeInsight.hints.filtering.StringMatcher.assertMatches(vararg matched: String) {
matched.forEach { assertThat(isMatching(it)).isTrue() }
}
fun com.intellij.codeInsight.hints.filtering.StringMatcher.assertNotMatches(vararg unmatched: String) {
unmatched.forEach { assertThat(isMatching(it)).isFalse() }
}
fun `test simple`() {
val matcher = com.intellij.codeInsight.hints.filtering.StringMatcherBuilder.create("aaa")!!
matcher.assertMatches("aaa")
matcher.assertNotMatches("aaaa", "aab", "", "*", "a", "baaa")
}
fun `test asterisks before`() {
val matcher = com.intellij.codeInsight.hints.filtering.StringMatcherBuilder.create("aaa*")!!
matcher.assertMatches("aaa", "aaaa", "aaaaaa", "aaaqwe")
matcher.assertNotMatches("baaa", "nnaaa", "qweaaa")
}
fun `test asterisks after`() {
val matcher = com.intellij.codeInsight.hints.filtering.StringMatcherBuilder.create("*aaa")!!
matcher.assertMatches("aaa", "aaaa", "baaa", "aawweraaa")
matcher.assertNotMatches("aaab", "aaabaa")
}
}
@@ -17,15 +17,12 @@ package com.intellij.vcs.log.ui.frame;
import com.google.common.primitives.Ints;
import com.intellij.ide.CopyProvider;
import com.intellij.ide.IdeTooltip;
import com.intellij.ide.IdeTooltipManager;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.DataProvider;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.ui.LoadingDecorator;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Couple;
import com.intellij.openapi.util.Pair;
@@ -33,7 +30,6 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.*;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.panels.Wrapper;
import com.intellij.ui.table.JBTable;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.DateFormatUtil;
@@ -59,7 +55,6 @@ import com.intellij.vcs.log.ui.VcsLogUiImpl;
import com.intellij.vcs.log.ui.render.GraphCommitCell;
import com.intellij.vcs.log.ui.render.GraphCommitCellRenderer;
import com.intellij.vcs.log.ui.tables.GraphTableModel;
import com.intellij.vcs.log.util.VcsUserUtil;
import gnu.trove.TIntHashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -74,7 +69,9 @@ import java.awt.datatransfer.StringSelection;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.Collection;
import java.util.Date;
import java.util.EventObject;
import java.util.List;
public class VcsLogGraphTable extends TableWithProgress implements DataProvider, CopyProvider {
@@ -94,6 +91,7 @@ public class VcsLogGraphTable extends TableWithProgress implements DataProvider,
@NotNull private final TableCellRenderer myDummyRenderer = new DefaultTableCellRenderer();
@NotNull private final GraphCommitCellRenderer myGraphCommitCellRenderer;
@NotNull private final GraphTableController myController;
private final StringCellRenderer myStringCellRenderer;
private boolean myColumnsSizeInitialized = false;
@Nullable private Selection mySelection = null;
@@ -113,12 +111,13 @@ public class VcsLogGraphTable extends TableWithProgress implements DataProvider,
myUi = ui;
myLogData = logData;
myGraphCommitCellRenderer = new GraphCommitCellRenderer(logData, myGraphCellPainter, this);
myStringCellRenderer = new StringCellRenderer();
myLogData.getProgress().addProgressIndicatorListener(new MyProgressListener(), ui);
setDefaultRenderer(VirtualFile.class, new RootCellRenderer(myUi));
setDefaultRenderer(GraphCommitCell.class, myGraphCommitCellRenderer);
setDefaultRenderer(String.class, new StringCellRenderer());
setDefaultRenderer(String.class, myStringCellRenderer);
setShowHorizontalLines(false);
setIntercellSpacing(JBUI.emptySize());
@@ -187,11 +186,13 @@ public class VcsLogGraphTable extends TableWithProgress implements DataProvider,
maxWidth = Math.max(getFontMetrics(tableFont.deriveFont(Font.BOLD)).stringWidth(value), maxWidth);
if (!value.isEmpty()) sizeCalculated = true;
}
int min = Math.min(maxWidth + UIUtil.DEFAULT_HGAP, MAX_DEFAULT_AUTHOR_COLUMN_WIDTH);
int min =
Math.min(maxWidth + UIUtil.DEFAULT_HGAP, MAX_DEFAULT_AUTHOR_COLUMN_WIDTH + myStringCellRenderer.getHorizontalTextPadding());
column.setPreferredWidth(min);
}
else if (i == GraphTableModel.DATE_COLUMN) { // all dates have nearly equal sizes
int min = getFontMetrics(tableFont.deriveFont(Font.BOLD)).stringWidth("mm" + DateFormatUtil.formatDateTime(new Date()));
int min = getFontMetrics(tableFont.deriveFont(Font.BOLD)).stringWidth(DateFormatUtil.formatDateTime(new Date())) +
myStringCellRenderer.getHorizontalTextPadding();
column.setPreferredWidth(min);
}
}
@@ -645,7 +646,12 @@ public class VcsLogGraphTable extends TableWithProgress implements DataProvider,
return;
}
append(value.toString(), applyHighlighters(this, row, column, value.toString(), hasFocus, selected));
setBorder(null);
}
public int getHorizontalTextPadding() {
Insets borderInsets = getMyBorder().getBorderInsets(this);
Insets ipad = getIpad();
return borderInsets.left + borderInsets.right + ipad.left + ipad.right;
}
}