diff --git a/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/FrontendXBreakpointManager.kt b/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/FrontendXBreakpointManager.kt index 1d087e7254a5..37bb50a4f5b6 100644 --- a/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/FrontendXBreakpointManager.kt +++ b/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/FrontendXBreakpointManager.kt @@ -416,7 +416,7 @@ class FrontendXBreakpointManager(private val project: Project, private val cs: C override fun findBreakpointsAtLine(type: XLineBreakpointTypeProxy, file: VirtualFile, line: Int, placement: XLineBreakpointVerticalPlacement): List { return breakpoints.values.filterIsInstance().filter { - it.type == type && it.getFile()?.url == file.url && it.getLine() == line && + it.type == type && it.getFile() == file && it.getLine() == line && it.getPlacement() == placement } } diff --git a/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/hotswap/HotSwapFloatingToolbarProvider.kt b/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/hotswap/HotSwapFloatingToolbarProvider.kt index 74230b2e7016..8956e66e9b0e 100644 --- a/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/hotswap/HotSwapFloatingToolbarProvider.kt +++ b/platform/xdebugger-impl/frontend/src/com/intellij/platform/debugger/impl/frontend/hotswap/HotSwapFloatingToolbarProvider.kt @@ -220,7 +220,7 @@ internal class HotSwapFloatingToolbarProvider : FloatingToolbarProvider { override fun register(dataContext: DataContext, component: FloatingToolbarComponent, parentDisposable: Disposable) { val project = dataContext.getData(CommonDataKeys.PROJECT) ?: return - val editorTag = dataContext.editorTag + val editorTag = if (logger.isDebugEnabled) dataContext.editorTag else "" if (component is JComponent) { component.installPopupMenu() component.accessibleContext.accessibleName = HotSwapUiExtension.computeSafeIfAvailable { it.toolbarAccessibleName } diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/XBreakpointManagerImpl.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/XBreakpointManagerImpl.java index e4d8718b58d7..35c1a755d558 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/XBreakpointManagerImpl.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/XBreakpointManagerImpl.java @@ -148,7 +148,7 @@ public final class XBreakpointManagerImpl implements XBreakpointManager { messageBusConnection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkVirtualFileListenerAdapter(new VirtualFileUrlChangeAdapter() { @Override public void fileDeleted(@NotNull VirtualFileEvent event) { - myLineBreakpointManager.onFileDeleted(event.getFile().getUrl()); + myLineBreakpointManager.onFileDeleted(event.getFile()); } @Override diff --git a/platform/xdebugger-impl/ui/src/com/intellij/xdebugger/impl/XDebuggerWatchesManagerImpl.java b/platform/xdebugger-impl/ui/src/com/intellij/xdebugger/impl/XDebuggerWatchesManagerImpl.java index e185d33da5ed..ff02e6f9413c 100644 --- a/platform/xdebugger-impl/ui/src/com/intellij/xdebugger/impl/XDebuggerWatchesManagerImpl.java +++ b/platform/xdebugger-impl/ui/src/com/intellij/xdebugger/impl/XDebuggerWatchesManagerImpl.java @@ -19,6 +19,7 @@ import com.intellij.platform.debugger.impl.shared.XDebuggerWatchesManager; import com.intellij.platform.debugger.impl.shared.proxy.XDebugManagerProxy; import com.intellij.platform.debugger.impl.shared.proxy.XDebugSessionProxy; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.MultiMap; import com.intellij.util.ui.EDT; import com.intellij.util.ui.update.MergingUpdateQueue; import com.intellij.util.ui.update.Update; @@ -35,7 +36,6 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -43,7 +43,6 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; import java.util.stream.Stream; @ApiStatus.Internal @@ -56,9 +55,9 @@ public final class XDebuggerWatchesManagerImpl implements XDebuggerWatchesManage */ private final Map> watches = new ConcurrentHashMap<>(); /** - * Maps file URL to a set of inline watches. + * Maps file to its inline watches. */ - private final Map> inlineWatches = new ConcurrentHashMap<>(); + private final MultiMap inlineWatches = MultiMap.createConcurrent(); private final MergingUpdateQueue myInlinesUpdateQueue; private final Project myProject; @@ -117,7 +116,7 @@ public final class XDebuggerWatchesManagerImpl implements XDebuggerWatchesManage @Override public @NotNull List getInlineWatches() { - return inlineWatches.values().stream().flatMap(l -> l.stream()).collect(Collectors.toList()); + return List.copyOf(inlineWatches.values()); } @ApiStatus.Internal @@ -127,11 +126,9 @@ public final class XDebuggerWatchesManagerImpl implements XDebuggerWatchesManage watches.forEach((key, value) -> expressions.add(new ConfigurationState(key, value))); List inlineExpressionStates = state.getInlineExpressionStates(); inlineExpressionStates.clear(); - inlineWatches.values().stream() - .flatMap(l -> l.stream()) - .forEach((value) -> { - inlineExpressionStates.add(new InlineWatchState(value.getExpression(), value.getLine(), value.getPosition().getFile().getUrl())); - }); + inlineWatches.values().forEach(value -> { + inlineExpressionStates.add(new InlineWatchState(value.getExpression(), value.getLine(), value.getPosition().getFile().getUrl())); + }); return state; } @@ -172,15 +169,15 @@ public final class XDebuggerWatchesManagerImpl implements XDebuggerWatchesManage if (position == null || expression == null) continue; InlineWatch watch = new InlineWatch(expression, position); - inlineWatches.computeIfAbsent(inlineWatchState.getFileUrl(), (k) -> new HashSet<>()).add(watch); + inlineWatches.putValue(file, watch); } // set markers in the background ApplicationManager.getApplication().executeOnPooledThread(() -> { - for (InlineWatch i : ContainerUtil.flatten(inlineWatches.values())) { + for (InlineWatch i : inlineWatches.values()) { ReadAction.nonBlocking(() -> { if (!i.setMarker()) { - inlineWatches.get(i.getPosition().getFile().getUrl()).remove(i); + inlineWatches.remove(i.getPosition().getFile(), i); } }).executeSynchronously(); } @@ -198,7 +195,7 @@ public final class XDebuggerWatchesManagerImpl implements XDebuggerWatchesManage @Override public void inlineWatchesRemoved(@NotNull List removed, XInlineWatchesView watchesView) { - inlineWatches.values().forEach(set -> removed.forEach(set::remove)); + removeFromStorage(removed); getWatchesViews().filter(v -> v != watchesView).forEach(view -> view.removeInlineWatches(removed)); } @@ -218,8 +215,7 @@ public final class XDebuggerWatchesManagerImpl implements XDebuggerWatchesManage EDT.assertIsEdt(); InlineWatch watch = new InlineWatch(expression, position); watch.setMarker(); - String fileUrl = position.getFile().getUrl(); - inlineWatches.computeIfAbsent(fileUrl, (k) -> new HashSet<>()).add(watch); + inlineWatches.putValue(position.getFile(), watch); getWatchesViews().forEach(view -> view.addInlineWatchExpression(watch, index, navigateToWatchNode)); } @@ -239,13 +235,16 @@ public final class XDebuggerWatchesManagerImpl implements XDebuggerWatchesManage } private void removeInlines(Collection remove) { - for (InlineWatch watch : remove) { - inlineWatches.get(watch.getPosition().getFile().getUrl()).remove(watch); - } - + removeFromStorage(remove); getWatchesViews().forEach(view -> view.removeInlineWatches(remove)); } + private void removeFromStorage(Collection remove) { + for (InlineWatch watch : remove) { + inlineWatches.remove(watch.getPosition().getFile(), watch); + } + } + private Stream getWatchesViews() { return StreamEx.of(XDebugManagerProxy.getInstance().getSessions(myProject)) .map(XDebugSessionProxy::getSessionTab) @@ -257,10 +256,7 @@ public final class XDebuggerWatchesManagerImpl implements XDebuggerWatchesManage public @NotNull Collection getDocumentInlines(@NotNull Document document) { VirtualFile file = FileDocumentManager.getInstance().getFile(document); if (file != null) { - Set inlineWatches = this.inlineWatches.get(file.getUrl()); - if (inlineWatches != null) { - return new ArrayList<>(inlineWatches); - } + return List.copyOf(inlineWatches.get(file)); } return Collections.emptyList(); } diff --git a/platform/xdebugger-impl/ui/src/com/intellij/xdebugger/impl/breakpoints/XBreakpointVisualRepresentation.kt b/platform/xdebugger-impl/ui/src/com/intellij/xdebugger/impl/breakpoints/XBreakpointVisualRepresentation.kt index 03a53ccfcea4..d04362974232 100644 --- a/platform/xdebugger-impl/ui/src/com/intellij/xdebugger/impl/breakpoints/XBreakpointVisualRepresentation.kt +++ b/platform/xdebugger-impl/ui/src/com/intellij/xdebugger/impl/breakpoints/XBreakpointVisualRepresentation.kt @@ -266,23 +266,26 @@ class XBreakpointVisualRepresentation( fun createBreakpointDraggableObject(): GutterDraggableObject { return object : GutterDraggableObject { override fun copy(line: Int, file: VirtualFile?, actionId: Int): Boolean { - if (canMoveTo(line, file)) { + if (file != null && canMoveTo(line, file)) { // TODO IJPL-185322 implement DnD for light breakpoints? if (myBreakpoint !is XLineBreakpointProxy) { return false } val breakpointManager = XDebugManagerProxy.getInstance().getBreakpointManagerProxy(myProject) if (isCopyAction(actionId)) { - breakpointManager.copyLineBreakpoint(myBreakpoint, file!!, line) + breakpointManager.copyLineBreakpoint(myBreakpoint, file, line) } else { - myBreakpoint.setFileUrl(file!!.url) - myBreakpoint.setLine(line) - val sessionProxy = XDebugManagerProxy.getInstance().getCurrentSessionProxy(myProject) - if (sessionProxy != null) { - breakpointManager.onBreakpointRemoval(myBreakpoint, sessionProxy) + val cs = myProject.service().cs + cs.launch { // switch to avoid blocking url call on EDT + myBreakpoint.setFileUrl(file.url) + myBreakpoint.setLine(line) + val sessionProxy = XDebugManagerProxy.getInstance().getCurrentSessionProxy(myProject) + if (sessionProxy != null) { + breakpointManager.onBreakpointRemoval(myBreakpoint, sessionProxy) + } + DebuggerUIUtil.notifyBreakpointAttachments(myBreakpoint) } - DebuggerUIUtil.notifyBreakpointAttachments(myBreakpoint) return true } } @@ -336,6 +339,9 @@ class XBreakpointVisualRepresentation( } } +@Service(Service.Level.PROJECT) +private class BreakpointDraggableObjectScopeProvider(val cs: CoroutineScope) + @Service(Service.Level.PROJECT) private class RedrawInlaysService(private val cs: CoroutineScope) { private val limitedDispatcher = Dispatchers.Default.limitedParallelism(1) diff --git a/platform/xdebugger-impl/ui/src/com/intellij/xdebugger/impl/breakpoints/XLineBreakpointManager.kt b/platform/xdebugger-impl/ui/src/com/intellij/xdebugger/impl/breakpoints/XLineBreakpointManager.kt index 11c9dccde69f..b1789b7c3bae 100644 --- a/platform/xdebugger-impl/ui/src/com/intellij/xdebugger/impl/breakpoints/XLineBreakpointManager.kt +++ b/platform/xdebugger-impl/ui/src/com/intellij/xdebugger/impl/breakpoints/XLineBreakpointManager.kt @@ -46,7 +46,7 @@ import com.intellij.openapi.util.registry.Registry import com.intellij.openapi.util.registry.RegistryValue import com.intellij.openapi.util.registry.RegistryValueListener import com.intellij.openapi.vfs.VirtualFile -import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.platform.debugger.impl.rpc.XBreakpointId import com.intellij.platform.debugger.impl.shared.proxy.XBreakpointManagerProxy import com.intellij.platform.debugger.impl.shared.proxy.XBreakpointProxy import com.intellij.platform.debugger.impl.shared.proxy.XLightLineBreakpointProxy @@ -76,8 +76,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import org.jetbrains.annotations.ApiStatus -import org.jetbrains.annotations.TestOnly import java.awt.event.MouseEvent +import java.util.concurrent.ConcurrentHashMap private val log = logger() @@ -90,7 +90,9 @@ class XLineBreakpointManager( ): XLineBreakpointManagerProxy { private val cs = coroutineScope.childScope("XLineBreakpointManager") - private val myBreakpoints = MultiMap.createConcurrent() + private val myBreakpointsById = ConcurrentHashMap() + private val myBreakpointsByFile = MultiMap.createConcurrent() + private val breakpointUpdateQueue: MergingUpdateQueue = MergingUpdateQueue.mergingUpdateQueue( name = "XLine breakpoints", mergingTimeSpan = 300, @@ -114,8 +116,7 @@ class XLineBreakpointManager( Registry.get(XDebuggerUtil.INLINE_BREAKPOINTS_KEY).addListener(object : RegistryValueListener { override fun afterValueChanged(value: RegistryValue) { - for (fileUrl in myBreakpoints.keySet()) { - val file = VirtualFileManager.getInstance().findFileByUrl(fileUrl) ?: continue + for (file in myBreakpointsByFile.keySet()) { if (XDebuggerUtil.areInlineBreakpointsEnabled(file)) continue val document = FileDocumentManager.getInstance().getDocument(file) ?: continue // Multiple breakpoints on the single line should be joined in this case. @@ -129,7 +130,7 @@ class XLineBreakpointManager( busConnection.subscribe(EditorColorsManager.TOPIC, MyEditorColorsListener()) busConnection.subscribe(FileDocumentManagerListener.TOPIC, object : FileDocumentManagerListener { override fun fileContentLoaded(file: VirtualFile, document: Document) { - myBreakpoints[file.url].asSequence() + getFileBreakpoints(file).asSequence() .filter { it.getHighlighter() == null } .forEach { queueBreakpointUpdate(it) } } @@ -140,15 +141,13 @@ class XLineBreakpointManager( } } - @ApiStatus.Internal - fun onFileDeleted(url: String) { - removeBreakpoints(myBreakpoints[url]) + fun onFileDeleted(file: VirtualFile) { + removeBreakpoints(getFileBreakpoints(file)) } - @ApiStatus.Internal fun onFileUrlChanged(oldUrl: String, newUrl: String) { - myBreakpoints.values().forEach { breakpoint -> - val url = breakpoint.getFile()?.url ?: breakpoint.getFileUrl() + getAllBreakpoints().forEach { breakpoint -> + val url = breakpoint.getFileUrl() if (FileUtil.startsWith(url, oldUrl)) { breakpoint.setFileUrl(newUrl + url.substring(oldUrl.length)) } @@ -175,25 +174,40 @@ class XLineBreakpointManager( if (initUI) { updateBreakpointNow(breakpoint) } - val fileUrl = breakpoint.getFile()?.url ?: breakpoint.getFileUrl() - log.debug { "Register line breakpoint ${breakpoint.id} ${breakpoint.javaClass.simpleName}: $fileUrl" } - myBreakpoints.putValue(fileUrl, breakpoint) + myBreakpointsById[breakpoint.id] = breakpoint + log.debug { "Register line breakpoint ${breakpoint.id} ${breakpoint.javaClass.simpleName}: ${breakpoint.getFileUrl()}" } + val file = breakpoint.getFile() + if (file != null) { + myBreakpointsByFile.putValue(file, breakpoint) + } + else { + log.warn("Breakpoint(${breakpoint.id}) file is not found during registration: ${breakpoint.getFileUrl()}") + } } fun unregisterBreakpoint(breakpoint: XLineBreakpointProxy) { - val fileUrl = breakpoint.getFile()?.url ?: breakpoint.getFileUrl() - val removed = myBreakpoints.remove(fileUrl, breakpoint) - log.debug { "Unregister line breakpoint ${breakpoint.id} [removed=$removed] ${breakpoint.javaClass.simpleName}: $fileUrl" } + val removed = myBreakpointsById.remove(breakpoint.id) != null + val removedByFile = breakpoint.getFile()?.let { myBreakpointsByFile.remove(it, breakpoint) } ?: false + if (removed != removedByFile) { + val associatedFile = myBreakpointsByFile.entrySet().firstOrNull { it.value == breakpoint }?.key + if (associatedFile != null) { + myBreakpointsByFile.remove(associatedFile, breakpoint) + } + } + log.debug { "Unregister line breakpoint ${breakpoint.id} [removed=$removed] ${breakpoint.javaClass.simpleName}: ${breakpoint.getFileUrl()}" } } override fun getDocumentBreakpointProxies(document: Document): Collection { val file = FileDocumentManager.getInstance().getFile(document) ?: return emptyList() - return myBreakpoints[file.url] + return getFileBreakpoints(file) } - @TestOnly override fun getAllBreakpoints(): Collection { - return myBreakpoints.values() + return myBreakpointsById.values + } + + fun getFileBreakpoints(file: VirtualFile): Collection { + return myBreakpointsByFile[file] } @RequiresEdt @@ -408,7 +422,7 @@ class XLineBreakpointManager( fun queueAllBreakpointsUpdate() { breakpointUpdateQueue.queue(object : Update("all breakpoints") { override fun run() { - for (breakpoint in myBreakpoints.values()) { + for (breakpoint in getAllBreakpoints()) { breakpoint.doUpdateUI() } }