[IFT] Add suggester heuristics for quick evaluation promotion

IJPL-159194

GitOrigin-RevId: ad83113d67ed9f6cd0f6f9d5d8f863c9e7a9752b
This commit is contained in:
Alexey Merkulov
2024-08-28 00:41:28 +02:00
committed by intellij-monorepo-bot
parent 6a1837c790
commit 4d8776250f
11 changed files with 135 additions and 11 deletions

View File

@@ -2579,6 +2579,7 @@ f:com.intellij.openapi.keymap.KeymapUtil
- s:getKeyText(I):java.lang.String
- s:getKeystrokeText(javax.swing.KeyStroke):java.lang.String
- s:getMnemonicAsShortcut(I):com.intellij.openapi.actionSystem.CustomShortcutSet
- s:getModifiersText(I):java.lang.String
- s:getMouseShortcutString(com.intellij.openapi.actionSystem.MouseShortcut):java.lang.String
- s:getMouseShortcutText(com.intellij.openapi.actionSystem.MouseShortcut):java.lang.String
- s:getPreferredShortcutText(com.intellij.openapi.actionSystem.Shortcut[]):java.lang.String

View File

@@ -90,11 +90,11 @@ public class KeymapTextContext {
else {
throw new IllegalStateException("unknown clickCount: " + clickCount);
}
return KeyMapBundle.message(resource, getModifiersText(mapNewModifiers(modifiers)), button);
return KeyMapBundle.message(resource, getModifiersText(mapNewModifiers(modifiers), true), button);
}
@JdkConstants.InputEventMask
private static int mapNewModifiers(@JdkConstants.InputEventMask int modifiers) {
static int mapNewModifiers(@JdkConstants.InputEventMask int modifiers) {
if ((modifiers & InputEvent.SHIFT_DOWN_MASK) != 0) {
modifiers |= InputEvent.SHIFT_MASK;
}
@@ -122,7 +122,7 @@ public class KeymapTextContext {
String acceleratorText = "";
int modifiers = accelerator.getModifiers();
if (modifiers > 0) {
acceleratorText = getModifiersText(modifiers);
acceleratorText = getModifiersText(modifiers, true);
}
int code = accelerator.getKeyCode();
@@ -158,7 +158,7 @@ public class KeymapTextContext {
return ClientSystemInfo.isMac() && AdvancedSettings.getInstanceIfCreated() != null && AdvancedSettings.getBoolean("ide.macos.disable.native.shortcut.symbols");
}
private @NotNull String getModifiersText(@JdkConstants.InputEventMask int modifiers) {
@NotNull String getModifiersText(@JdkConstants.InputEventMask int modifiers, boolean addPlus) {
if (isNativeMacShortcuts()) {
//try {
// Class appleLaf = Class.forName(APPLE_LAF_AQUA_LOOK_AND_FEEL_CLASS_NAME);
@@ -178,7 +178,7 @@ public class KeymapTextContext {
final String keyModifiersText = isSimplifiedMacShortcuts() ? getSimplifiedMacKeyModifiersText(modifiers)
: KeyEvent.getKeyModifiersText(modifiers);
return keyModifiersText.isEmpty() ? keyModifiersText : keyModifiersText + "+";
return !keyModifiersText.isEmpty() && addPlus ? keyModifiersText + "+" : keyModifiersText;
}
private static String getSimplifiedMacKeyModifiersText(int modifiers) {

View File

@@ -53,7 +53,7 @@ public final class KeymapUtil {
return ourDefaultKeymapTextContext.getShortcutText(shortcut);
}
public static @NotNull String getMouseShortcutText(@NotNull MouseShortcut shortcut) {
public static @NotNull @NlsSafe String getMouseShortcutText(@NotNull MouseShortcut shortcut) {
return ourDefaultKeymapTextContext.getMouseShortcutText(shortcut);
}
@@ -287,6 +287,10 @@ public final class KeymapUtil {
return toolTipText;
}
public static String getModifiersText(@JdkConstants.InputEventMask int modifiers) {
return ourDefaultKeymapTextContext.getModifiersText(KeymapTextContext.mapNewModifiers(modifiers), false);
}
/**
* Checks that one of the mouse shortcuts assigned to the provided action has the same modifiers as provided
*/

View File

@@ -0,0 +1,16 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.xdebugger
import com.intellij.util.messages.Topic
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
interface XEvaluationListener {
companion object {
@Topic.AppLevel
@JvmField
val TOPIC: Topic<XEvaluationListener> = Topic(XEvaluationListener::class.java, Topic.BroadcastDirection.NONE)
}
fun inlineEvaluatorInvoked(session: XDebugSession, expression: XExpression) { }
}

View File

@@ -34,10 +34,7 @@ import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.components.BorderLayoutPanel;
import com.intellij.util.ui.tree.TreeUtil;
import com.intellij.xdebugger.XDebugSession;
import com.intellij.xdebugger.XDebuggerBundle;
import com.intellij.xdebugger.XDebuggerManager;
import com.intellij.xdebugger.XExpression;
import com.intellij.xdebugger.*;
import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider;
import com.intellij.xdebugger.frame.XStackFrame;
import com.intellij.xdebugger.impl.XDebugSessionImpl;
@@ -296,6 +293,13 @@ public class XWatchesViewImpl extends XVariablesView implements DnDNativeTarget,
if (!XDebuggerUtilImpl.isEmptyExpression(expression)) {
myEvaluateComboBox.saveTextInHistory();
XDebugSession session = getSession(getTree());
if (session != null) {
ApplicationManager.getApplication().getMessageBus().syncPublisher(XEvaluationListener.TOPIC)
.inlineEvaluatorInvoked(session, expression);
}
else {
LOG.error("No session available while trying evaluate " + expression);
}
myRootNode.addResultNode(session != null ? session.getCurrentStackFrame() : null, expression);
DebuggerEvaluationStatisticsCollector.INLINE_EVALUATE.log(getTree().getProject());
}

View File

@@ -167,6 +167,8 @@
implementation="training.featuresSuggester.suggesters.IntroduceVariableSuggester"/>
<ifs.suggester
implementation="training.featuresSuggester.suggesters.CopyPasteSuggester"/>
<ifs.suggester
implementation="training.featuresSuggester.suggesters.QuickEvaluateSuggester"/>
<ifs.suggester
implementation="training.featuresSuggester.suggesters.SurroundWithSuggester"/>
<ifs.suggester
@@ -226,5 +228,7 @@
<applicationListeners>
<listener class="training.featuresSuggester.listeners.EditorActionsListener"
topic="com.intellij.openapi.actionSystem.ex.AnActionListener"/>
<listener class="training.featuresSuggester.listeners.EvaluationListener"
topic="com.intellij.xdebugger.XEvaluationListener"/>
</applicationListeners>
</idea-plugin>

View File

@@ -17,6 +17,8 @@ completion.popup.name=Show the completion popup
completion.popup.message=You can use a shortcut to call the completion popup.
paste.from.history.name=Paste from history
paste.from.history.message=You can preview the clipboard history.
quick.evaluate.name=Quick Evaluate
quick.evaluate.message=You can quickly evaluate an expression by holding {0} and clicking on it in the editor.
edit.breakpoint.name=Edit a breakpoint
edit.breakpoint.message=You can edit the breakpoint and make it conditional instead of waiting for the needed iteration. \
Right-click the breakpoint.

View File

@@ -3,6 +3,7 @@ package training.featuresSuggester.actions
import com.intellij.lang.Language
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.xdebugger.XExpression
import com.intellij.xdebugger.XSourcePosition
import com.intellij.xdebugger.breakpoints.XBreakpoint
@@ -54,6 +55,12 @@ data class BeforeDebugSessionResumedAction(
override val timeMillis: Long
) : DebugSessionAction()
data class InlineEvaluatorInvokedAction(
override val project: Project,
val expression: XExpression,
override val timeMillis: Long
) : DebugAction()
// -------------------------------------Breakpoint Actions--------------------------------------------------------------
abstract class BreakpointAction : DebugAction() {
abstract val breakpoint: XBreakpoint<*>

View File

@@ -0,0 +1,14 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package training.featuresSuggester.listeners
import com.intellij.xdebugger.XDebugSession
import com.intellij.xdebugger.XEvaluationListener
import com.intellij.xdebugger.XExpression
import training.featuresSuggester.SuggestingUtils.handleAction
import training.featuresSuggester.actions.InlineEvaluatorInvokedAction
private class EvaluationListener : XEvaluationListener {
override fun inlineEvaluatorInvoked(session: XDebugSession, expression: XExpression) {
handleAction(session.project, InlineEvaluatorInvokedAction(session.project, expression, System.currentTimeMillis()))
}
}

View File

@@ -57,7 +57,7 @@ abstract class AbstractFeatureSuggester : FeatureSuggester {
}
@Nls
fun getShortcutText(actionId: String): String {
open fun getShortcutText(actionId: String): String {
val shortcut = KeymapUtil.getShortcutText(actionId)
return if (shortcut == "<no shortcut>") {
FeatureSuggesterBundle.message("shortcut.not.found.message")

View File

@@ -0,0 +1,72 @@
package training.featuresSuggester.suggesters
import com.intellij.lang.Language
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.MouseShortcut
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.ide.CopyPasteManager
import com.intellij.openapi.keymap.KeymapUtil
import com.intellij.openapi.vfs.VirtualFile
import training.featuresSuggester.FeatureSuggesterBundle
import training.featuresSuggester.NoSuggestion
import training.featuresSuggester.SuggestingUtils.asString
import training.featuresSuggester.Suggestion
import training.featuresSuggester.actions.Action
import training.featuresSuggester.actions.EditorCopyAction
import training.featuresSuggester.actions.InlineEvaluatorInvokedAction
import java.lang.ref.WeakReference
class QuickEvaluateSuggester : AbstractFeatureSuggester() {
override val id: String = "QuickEvaluateSuggester"
override val suggestingActionDisplayName: String = FeatureSuggesterBundle.message("quick.evaluate.name")
override val suggestingActionId = "QuickEvaluateExpression"
override val suggestingDocUrl = "https://www.jetbrains.com/help/idea/examining-suspended-program.html#quick-evaluate"
override val minSuggestingIntervalDays = 14
override val languages = listOf(Language.ANY.id)
private var lastCopyFromFile = WeakReference<VirtualFile>(null)
private var lastCopiedText = ""
override fun isSuggestionNeeded(): Boolean {
return super.isSuggestionNeeded() && getMouseShortcut() != null
}
override val message: String get() {
val modifiersText = getMouseShortcut()?.modifiers?.let { KeymapUtil.getModifiersText(it) } ?: ""
return FeatureSuggesterBundle.message("quick.evaluate.message", modifiersText)
}
override fun getSuggestion(action: Action): Suggestion {
when (action) {
is InlineEvaluatorInvokedAction -> {
val clipboardContent = CopyPasteManager.getInstance().allContents.firstOrNull()?.asString()
if (clipboardContent != lastCopiedText) return NoSuggestion
if (action.expression.expression != lastCopiedText) return NoSuggestion
val neededVirtualFile = lastCopyFromFile.get() ?: return NoSuggestion
val fileIsOpenedNow = FileEditorManager.getInstance(action.project).selectedEditors.map { it.file }.contains(neededVirtualFile)
if (!fileIsOpenedNow) return NoSuggestion
return createSuggestion()
}
is EditorCopyAction -> {
val virtualFile = action.editor.virtualFile ?: return NoSuggestion
lastCopyFromFile = WeakReference<VirtualFile>(virtualFile)
lastCopiedText = action.text
}
}
return NoSuggestion
}
override fun getShortcutText(actionId: String): String {
getMouseShortcut()?.let {
return KeymapUtil.getMouseShortcutText(it)
}
return super.getShortcutText(actionId)
}
private fun getMouseShortcut() =
ActionManager.getInstance().getAction(suggestingActionId)?.shortcutSet?.shortcuts?.filterIsInstance<MouseShortcut>()?.firstOrNull()
}