From a3964f5d0d5154962babdda050fcb1cda0c9c69c Mon Sep 17 00:00:00 2001 From: Bartosz Janusz Date: Wed, 21 Jan 2026 13:44:46 +0100 Subject: [PATCH] [gl] Add highlighting of comment range for GitLab's review discussions #IJPL-220068 Fixed Space-RevId: 1a44bf21e79de563fcab08dc4629bd5c5bc13225 GitOrigin-RevId: 60e598bd75c1caa434d2a457ada7a8184894f12d --- ...GitLabMergeRequestNewDiscussionPosition.kt | 4 + .../GitLabMergeRequestNotePositionMapping.kt | 2 +- .../mergerequest/data/GitLabNoteLocation.kt | 11 +++ .../mergerequest/data/GitLabNotePosition.kt | 29 +++++-- .../data/gitLabNotePositionUtil.kt | 79 +++++++++++++++---- .../diff/GitLabMergeRequestDiffExtension.kt | 72 +++++++++++------ .../diff/GitLabMergeRequestDiffViewModel.kt | 4 +- ...tLabMergeRequestDiffDiscussionViewModel.kt | 14 ++-- .../GitLabMergeRequestDiffReviewViewModel.kt | 19 ++++- ...abMergeRequestEditorDiscussionViewModel.kt | 31 +++++--- ...itLabMergeRequestEditorReviewController.kt | 25 ++++-- ...abMergeRequestEditorReviewFileViewModel.kt | 13 ++- .../GitLabMergeRequestEditorReviewUIModel.kt | 34 +++++--- .../ui/editor/editorInlayRenderers.kt | 59 ++++++++------ .../mergerequest/ui/editor/editorInlays.kt | 2 +- ...GitLabMergeRequestDiscussionsViewModels.kt | 37 ++------- .../util/GitLabMergeRequestDiscussionUtil.kt | 4 +- 17 files changed, 291 insertions(+), 148 deletions(-) create mode 100644 plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/data/GitLabNoteLocation.kt diff --git a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/data/GitLabMergeRequestNewDiscussionPosition.kt b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/data/GitLabMergeRequestNewDiscussionPosition.kt index 9087c49ab58b..693634edf969 100644 --- a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/data/GitLabMergeRequestNewDiscussionPosition.kt +++ b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/data/GitLabMergeRequestNewDiscussionPosition.kt @@ -24,6 +24,10 @@ data class GitLabMergeRequestNewDiscussionPosition( override val filePathAfter: String? get() = paths.newPath override val lineIndexLeft: Int? get() = oldLineIndex override val lineIndexRight: Int? get() = newLineIndex + override val startOldLine: Int? get() = lineRange?.start?.oldLine + override val startNewLine: Int? get() = lineRange?.start?.newLine + override val endOldLine: Int? get() = lineRange?.end?.oldLine + override val endNewLine: Int? get() = lineRange?.end?.newLine companion object { fun calcFor(diffData: GitTextFilePatchWithHistory, location: DiffLineLocation): GitLabMergeRequestNewDiscussionPosition { diff --git a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/data/GitLabMergeRequestNotePositionMapping.kt b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/data/GitLabMergeRequestNotePositionMapping.kt index 4fec5a97955e..6c1ea52a2002 100644 --- a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/data/GitLabMergeRequestNotePositionMapping.kt +++ b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/data/GitLabMergeRequestNotePositionMapping.kt @@ -16,7 +16,7 @@ interface GitLabMergeRequestNotePositionMapping { private val LOG = logger() fun map(mrChanges: GitBranchComparisonResult, position: GitLabNotePosition): GitLabMergeRequestNotePositionMapping { - val textLocation = position.getLocation(Side.LEFT) + val textLocation = position.getLocation(Side.LEFT)?.second val changes = if (position.parentSha == mrChanges.mergeBaseSha) { // first commit diff --git a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/data/GitLabNoteLocation.kt b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/data/GitLabNoteLocation.kt new file mode 100644 index 000000000000..1b5b01cb7200 --- /dev/null +++ b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/data/GitLabNoteLocation.kt @@ -0,0 +1,11 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.plugins.gitlab.mergerequest.data + +import com.intellij.diff.util.Side + +data class GitLabNoteLocation( + val startSide: Side, + val startLineIdx: Int, + val side: Side, + val lineIdx: Int, +) diff --git a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/data/GitLabNotePosition.kt b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/data/GitLabNotePosition.kt index 41992e62da7b..97de3a62b7fa 100644 --- a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/data/GitLabNotePosition.kt +++ b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/data/GitLabNotePosition.kt @@ -1,7 +1,7 @@ // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.plugins.gitlab.mergerequest.data -import com.intellij.collaboration.ui.codereview.diff.DiffLineLocation +import com.intellij.collaboration.ui.codereview.diff.DiffLineRange import com.intellij.diff.util.Side import com.intellij.openapi.diagnostic.logger import org.jetbrains.plugins.gitlab.api.dto.GitLabMergeRequestDraftNoteRestDTO @@ -16,6 +16,10 @@ sealed interface GitLabNotePosition { interface WithLine : GitLabNotePosition { val lineIndexLeft: Int? val lineIndexRight: Int? + val startOldLine: Int? + val startNewLine: Int? + val endOldLine: Int? + val endNewLine: Int? } data class Text( @@ -25,6 +29,10 @@ sealed interface GitLabNotePosition { override val filePathAfter: String?, override val lineIndexLeft: Int?, override val lineIndexRight: Int?, + override val startOldLine: Int?, + override val startNewLine: Int?, + override val endOldLine: Int?, + override val endNewLine: Int?, ) : WithLine data class Image( @@ -48,7 +56,11 @@ sealed interface GitLabNotePosition { val sha = position.headSha return when (position.positionType) { - "text" -> Text(parentSha, sha, position.oldPath, position.newPath, position.oldLine?.dec(), position.newLine?.dec()) + "text" -> Text(parentSha, sha, position.oldPath, position.newPath, + position.oldLine?.dec(), position.newLine?.dec(), + position.lineRange?.start?.oldLine?.dec(), position.lineRange?.start?.newLine?.dec(), + position.lineRange?.end?.oldLine?.dec(), position.lineRange?.end?.newLine?.dec() + ) else -> Image(parentSha, sha, position.oldPath, position.newPath) } } @@ -64,7 +76,11 @@ sealed interface GitLabNotePosition { val sha = position.headSha ?: return null return when (position.positionType) { - "text" -> Text(parentSha, sha, position.oldPath, position.newPath, position.oldLine?.dec(), position.newLine?.dec()) + "text" -> Text(parentSha, sha, position.oldPath, position.newPath, + position.oldLine?.dec(), position.newLine?.dec(), + position.lineRange?.start?.oldLine?.dec(), position.lineRange?.start?.newLine?.dec(), + position.lineRange?.end?.oldLine?.dec(), position.lineRange?.end?.newLine?.dec() + ) else -> Image(parentSha, sha, position.oldPath, position.newPath) } } @@ -74,7 +90,8 @@ sealed interface GitLabNotePosition { val GitLabNotePosition.filePath: String get() = (filePathAfter ?: filePathBefore)!! -fun GitLabNotePosition.getLocation(contextSide: Side = Side.LEFT): DiffLineLocation? { - if (this !is GitLabNotePosition.WithLine) return null - return GitLabNotePositionUtil.getLocation(lineIndexLeft, lineIndexRight, contextSide) +fun GitLabNotePosition.getLocation(contextSide: Side = Side.LEFT): DiffLineRange? { + val position = this + if (position !is GitLabNotePosition.WithLine) return null + return GitLabNotePositionUtil.getLocation(position, contextSide) } \ No newline at end of file diff --git a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/data/gitLabNotePositionUtil.kt b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/data/gitLabNotePositionUtil.kt index f7da83d6b317..400fb6c2e1e1 100644 --- a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/data/gitLabNotePositionUtil.kt +++ b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/data/gitLabNotePositionUtil.kt @@ -2,11 +2,54 @@ package org.jetbrains.plugins.gitlab.mergerequest.data import com.intellij.collaboration.ui.codereview.diff.DiffLineLocation +import com.intellij.collaboration.ui.codereview.diff.DiffLineRange import com.intellij.diff.util.Side import git4idea.changes.GitTextFilePatchWithHistory object GitLabNotePositionUtil { - fun getLocation(lineIndexLeft: Int?, lineIndexRight: Int?, contextSide: Side = Side.LEFT): DiffLineLocation? = when { + fun getLocation(position: GitLabNotePosition.WithLine, contextSide: Side = Side.LEFT): DiffLineRange? = + when { + position.lineIndexLeft != null && position.lineIndexRight != null -> when (contextSide) { + Side.LEFT -> getLeftSideLocation(position) + + Side.RIGHT -> getRightSideLocation(position) + } + position.lineIndexLeft != null -> getLeftSideLocation(position) + position.lineIndexRight != null -> getRightSideLocation(position) + else -> null + } + + private fun getLeftSideLocation(position: GitLabNotePosition.WithLine): DiffLineRange? { + val startLine = position.startOldLine + val endLine = position.endOldLine + + return if (startLine == null || endLine == null || endLine != position.lineIndexLeft) { // fallback to a single line + getSingleLineLocation(position.lineIndexLeft, position.lineIndexRight, Side.LEFT) + ?.let { single -> DiffLineRange(single, single) } + } + else { + val startLoc = DiffLineLocation(Side.LEFT, startLine) + val endLoc = DiffLineLocation(Side.LEFT, endLine) + DiffLineRange(startLoc, endLoc) + } + } + + private fun getRightSideLocation(position: GitLabNotePosition.WithLine): DiffLineRange? { + val startLine = position.startNewLine + val endLine = position.endNewLine + + return if (startLine == null || endLine == null || endLine != position.lineIndexRight) { // fallback to a single line + getSingleLineLocation(position.lineIndexLeft, position.lineIndexRight, Side.RIGHT) + ?.let { single -> DiffLineRange(single, single) } + } + else { + val startLoc = DiffLineLocation(Side.RIGHT, startLine) + val endLoc = DiffLineLocation(Side.RIGHT, endLine) + DiffLineRange(startLoc, endLoc) + } + } + + private fun getSingleLineLocation(lineIndexLeft: Int?, lineIndexRight: Int?, contextSide: Side = Side.LEFT): DiffLineLocation? = when { lineIndexLeft != null && lineIndexRight != null -> when (contextSide) { Side.LEFT -> DiffLineLocation(Side.LEFT, lineIndexLeft) Side.RIGHT -> DiffLineLocation(Side.RIGHT, lineIndexRight) @@ -24,28 +67,36 @@ fun GitLabNotePosition.mapToRightSideLine(diffData: GitTextFilePatchWithHistory) mapToSidedLine(diffData, Side.RIGHT) private fun GitLabNotePosition.mapToSidedLine(diffData: GitTextFilePatchWithHistory, side: Side): Int? { - val (currentSide, lineIndex) = getLocation(side) ?: getLocation(side.other()) ?: return null - + val (_, endLineLocation) = getLocation(side) ?: getLocation(side.other()) ?: return null if (!diffData.contains(parentSha, filePathBefore, sha, filePathAfter)) return null - val revision = currentSide.select(parentSha, sha) - - return diffData.forcefullyMapLine(revision, lineIndex, side) + val revision = endLineLocation.first.select(parentSha, sha) + return diffData.forcefullyMapLine(revision, endLineLocation.second, side) } fun GitLabNotePosition.mapToLocation(diffData: GitTextFilePatchWithHistory, contextSide: Side = Side.LEFT) - : DiffLineLocation? { - val (side, lineIndex) = getLocation(contextSide) ?: return null + : GitLabNoteLocation? { + val unmappedLocation = getLocation(contextSide) ?: return null if (!diffData.contains(parentSha, filePathBefore, sha, filePathAfter)) return null - val revision = side.select(parentSha, sha) - return diffData.mapLine(revision, lineIndex, side) + return unmappedLocation.toMapped(diffData, parentSha, sha) } fun GitLabMergeRequestNewDiscussionPosition.mapToLocation(diffData: GitTextFilePatchWithHistory, contextSide: Side = Side.LEFT) - : DiffLineLocation? { - val (side, lineIndex) = GitLabNotePositionUtil.getLocation(oldLineIndex, newLineIndex, contextSide) ?: return null + : GitLabNoteLocation? { + val unmappedLocation = getLocation(contextSide) ?: return null if (!diffData.contains(baseSha, paths.oldPath, headSha, paths.newPath)) return null - val revision = contextSide.select(baseSha, headSha) - return diffData.mapLine(revision, lineIndex, side) + return unmappedLocation.toMapped(diffData, baseSha, sha) +} + +private fun DiffLineRange.toMapped(diffData: GitTextFilePatchWithHistory, parentSha: String, sha: String): GitLabNoteLocation? { + val (startLineLocation, endLineLocation) = this + val startRevision = startLineLocation.first.select(parentSha, sha) + val endRevision = endLineLocation.first.select(parentSha, sha) + val mappedStartLine = diffData.mapLine(startRevision, startLineLocation.second, startLineLocation.first) + val mappedEndLine = diffData.mapLine(endRevision, endLineLocation.second, endLineLocation.first) + return if (mappedStartLine != null && mappedEndLine != null) + GitLabNoteLocation(mappedStartLine.first, mappedStartLine.second, mappedEndLine.first, mappedEndLine.second) + else + mappedEndLine?.let { GitLabNoteLocation(it.first, it.second, it.first, it.second) } } private fun GitTextFilePatchWithHistory.contains(parentSha: String, pathAtParent: String?, diff --git a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/diff/GitLabMergeRequestDiffExtension.kt b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/diff/GitLabMergeRequestDiffExtension.kt index c3315302896b..8cbdb2a1eaf5 100644 --- a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/diff/GitLabMergeRequestDiffExtension.kt +++ b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/diff/GitLabMergeRequestDiffExtension.kt @@ -11,10 +11,7 @@ import com.intellij.collaboration.async.transformConsecutiveSuccesses import com.intellij.collaboration.ui.codereview.diff.DiffLineLocation import com.intellij.collaboration.ui.codereview.diff.UnifiedCodeReviewItemPosition import com.intellij.collaboration.ui.codereview.diff.viewer.showCodeReview -import com.intellij.collaboration.ui.codereview.editor.CodeReviewComponentInlayRenderer -import com.intellij.collaboration.ui.codereview.editor.CodeReviewEditorGutterControlsModel -import com.intellij.collaboration.ui.codereview.editor.CodeReviewEditorModel -import com.intellij.collaboration.ui.codereview.editor.CodeReviewNavigableEditorViewModel +import com.intellij.collaboration.ui.codereview.editor.* import com.intellij.collaboration.ui.icon.IconsProvider import com.intellij.collaboration.util.ComputedResult import com.intellij.collaboration.util.Hideable @@ -28,21 +25,20 @@ import com.intellij.diff.requests.DiffRequest import com.intellij.diff.tools.util.DiffNotifications import com.intellij.diff.tools.util.base.DiffViewerBase import com.intellij.diff.util.DiffUtil +import com.intellij.diff.util.LineRange import com.intellij.openapi.components.Service import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import com.intellij.util.cancelOnDispose import com.intellij.util.concurrency.annotations.RequiresEdt -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.withContext import org.jetbrains.plugins.gitlab.GitLabSettings import org.jetbrains.plugins.gitlab.api.dto.GitLabUserDTO import org.jetbrains.plugins.gitlab.data.GitLabImageLoader +import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabNoteLocation import org.jetbrains.plugins.gitlab.mergerequest.ui.diff.GitLabMergeRequestDiffDiscussionViewModel import org.jetbrains.plugins.gitlab.mergerequest.ui.diff.GitLabMergeRequestDiffDraftNoteViewModel import org.jetbrains.plugins.gitlab.mergerequest.ui.diff.GitLabMergeRequestDiffNewDiscussionViewModel @@ -88,15 +84,26 @@ class GitLabMergeRequestDiffExtension : DiffExtension() { changeVm.markViewed() } - viewer.showCodeReview( - modelFactory = { _, _, locationToLine, lineToLocations, lineToUnified -> - DiffEditorModel(this, project, changeVm, locationToLine, lineToLocations) { + viewer.showCodeReview { editor, _, locationToLine, lineToLocations, lineToUnified -> + coroutineScope { + val cs = this + val model = DiffEditorModel(cs, project, changeVm, locationToLine, lineToLocations) { val (leftLine, rightLine) = lineToUnified(it) UnifiedCodeReviewItemPosition(change, leftLine, rightLine) } - }, - rendererFactory = { createRenderer(it, changeVm.avatarIconsProvider, changeVm.imageLoader) } - ) + val activeRangesTracker = CodeReviewActiveRangesTracker() + editor.showCodeReview(model) { inlayModel -> + createRenderer(inlayModel, + changeVm.avatarIconsProvider, + changeVm.imageLoader, + activeRangesTracker).also { inlayRenderer -> + launchNow { + CodeReviewEditorInlayRangeOutlineUtils.showInlayOutline(editor, model, inlayModel, inlayRenderer, activeRangesTracker) + } + } + } + } + } } } }.cancelOnDispose(viewer) @@ -106,23 +113,25 @@ class GitLabMergeRequestDiffExtension : DiffExtension() { model: GitLabMergeRequestEditorMappedComponentModel, avatarIconsProvider: IconsProvider, imageLoader: GitLabImageLoader, + activeRangesTracker: CodeReviewActiveRangesTracker, ): CodeReviewComponentInlayRenderer = when (model) { is GitLabMergeRequestEditorMappedComponentModel.Discussion<*> -> - GitLabMergeRequestDiscussionInlayRenderer(this, project, model.vm, avatarIconsProvider, imageLoader, + GitLabMergeRequestDiscussionInlayRenderer(this, project, model, avatarIconsProvider, imageLoader, activeRangesTracker, GitLabStatistics.MergeRequestNoteActionPlace.DIFF) is GitLabMergeRequestEditorMappedComponentModel.DraftNote<*> -> - GitLabMergeRequestDraftNoteInlayRenderer(this, project, model.vm, avatarIconsProvider, imageLoader, + GitLabMergeRequestDraftNoteInlayRenderer(this, project, model, avatarIconsProvider, imageLoader, activeRangesTracker, GitLabStatistics.MergeRequestNoteActionPlace.DIFF) is GitLabMergeRequestEditorMappedComponentModel.NewDiscussion<*> -> - GitLabMergeRequestNewDiscussionInlayRenderer(this, project, model.vm, avatarIconsProvider, + GitLabMergeRequestNewDiscussionInlayRenderer(this, project, model, avatarIconsProvider, activeRangesTracker, GitLabStatistics.MergeRequestNoteActionPlace.DIFF, model::cancel) } } } internal interface GitLabReviewDiffEditorModel : CodeReviewEditorModel, - CodeReviewNavigableEditorViewModel + CodeReviewNavigableEditorViewModel, + CodeReviewCommentableEditorModel.WithMultilineComments private class DiffEditorModel( cs: CoroutineScope, @@ -152,8 +161,8 @@ private class DiffEditorModel( diffReviewVm.locationsWithDiscussions, diffReviewVm.locationsWithNewDiscussions ) { locationsWithDiscussions, locationsWithNewDiscussions -> - val linesWithDiscussions = locationsWithDiscussions.mapNotNullTo(mutableSetOf(), locationToLine) - val linesWithNewDiscussions = locationsWithNewDiscussions.mapNotNullTo(mutableSetOf(), locationToLine) + val linesWithDiscussions = locationsWithDiscussions.mapNotNullTo(mutableSetOf(), { locationToLine(it.side to it.lineIdx) }) + val linesWithNewDiscussions = locationsWithNewDiscussions.mapNotNullTo(mutableSetOf(), { locationToLine(it.side to it.lineIdx) }) GutterState(linesWithDiscussions, linesWithNewDiscussions) }.stateInNow(cs, null) @@ -167,6 +176,14 @@ private class DiffEditorModel( diffReviewVm.cancelNewDiscussion(loc) } + override fun requestNewComment(lineRange: LineRange) { + TODO("not implemented") + } + + override fun canCreateComment(lineRange: LineRange): Boolean { + TODO("not implemented") + } + override fun toggleComments(lineIdx: Int) { inlays.value.asSequence().filter { it.line.value == lineIdx }.filterIsInstance().syncOrToggleAll() GitLabStatistics.logToggledComments(project) @@ -214,20 +231,23 @@ private class DiffEditorModel( private inner class MappedDiscussion(vm: GitLabMergeRequestDiffDiscussionViewModel) : GitLabMergeRequestEditorMappedComponentModel.Discussion(vm) { override val isVisible: StateFlow = vm.isVisible.combineState(hiddenState) { visible, hidden -> visible && !hidden } - override val line: StateFlow = vm.location.mapState { loc -> loc?.let { locationToLine(it) } } + override val range: StateFlow = vm.location.mapState { it?.toLineRange(locationToLine) } + override val line: StateFlow = range.mapState { it?.end } } private inner class MappedDraftNote(vm: GitLabMergeRequestDiffDraftNoteViewModel) : GitLabMergeRequestEditorMappedComponentModel.DraftNote(vm) { override val isVisible: StateFlow = vm.isVisible.combineState(hiddenState) { visible, hidden -> visible && !hidden } - override val line: StateFlow = vm.location.mapState { loc -> loc?.let { locationToLine(it) } } + override val range: StateFlow = vm.location.mapState { it?.toLineRange(locationToLine) } + override val line: StateFlow = range.mapState { it?.end } } private inner class MappedNewDiscussion(vm: GitLabMergeRequestDiffNewDiscussionViewModel) : GitLabMergeRequestEditorMappedComponentModel.NewDiscussion(vm) { override val key: Any = "NEW_${vm.location.value}" override val isVisible: StateFlow = MutableStateFlow(true) - override val line: StateFlow = vm.location.mapState { loc -> loc?.let { locationToLine(it) } } + override val range: StateFlow = vm.location.mapState { it?.toLineRange(locationToLine) } + override val line: StateFlow = range.mapState { it?.end } override fun cancel() { vm.location.value?.let(diffReviewVm::cancelNewDiscussion) @@ -240,4 +260,10 @@ private class DiffEditorModel( ) : CodeReviewEditorGutterControlsModel.ControlsState { override fun isLineCommentable(lineIdx: Int): Boolean = true } +} + +private fun GitLabNoteLocation.toLineRange(locationToLine: (DiffLineLocation) -> Int?): LineRange? { + val start = locationToLine(startSide to startLineIdx) ?: return null + val end = locationToLine(side to lineIdx) ?: return null + return LineRange(start, end) } \ No newline at end of file diff --git a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/diff/GitLabMergeRequestDiffViewModel.kt b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/diff/GitLabMergeRequestDiffViewModel.kt index a92b9fe0b9d1..a3f76c4f6454 100644 --- a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/diff/GitLabMergeRequestDiffViewModel.kt +++ b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/diff/GitLabMergeRequestDiffViewModel.kt @@ -178,7 +178,9 @@ internal class GitLabMergeRequestDiffProcessorViewModelImpl( val change = mappedVm.diffData.value?.change ?: return - delegate.showChange(change, location?.let(DiffViewerScrollRequest::toLine)) + delegate.showChange(change, location?.let { + DiffViewerScrollRequest.toLine(it.side to it.lineIdx) + }) mappedVm.requestFocus() } diff --git a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/diff/GitLabMergeRequestDiffDiscussionViewModel.kt b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/diff/GitLabMergeRequestDiffDiscussionViewModel.kt index f90bd4e86d61..1ca1735976a0 100644 --- a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/diff/GitLabMergeRequestDiffDiscussionViewModel.kt +++ b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/diff/GitLabMergeRequestDiffDiscussionViewModel.kt @@ -5,11 +5,11 @@ import com.intellij.collaboration.async.combineState import com.intellij.collaboration.async.combineStates import com.intellij.collaboration.async.mapState import com.intellij.collaboration.ui.FocusableViewModel -import com.intellij.collaboration.ui.codereview.diff.DiffLineLocation import com.intellij.collaboration.ui.codereview.diff.DiscussionsViewOption import com.intellij.diff.util.Side import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabNoteLocation import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabNotePosition import org.jetbrains.plugins.gitlab.mergerequest.data.mapToLocation import org.jetbrains.plugins.gitlab.mergerequest.ui.DiffDataMappedGitLabMergeRequestInlayModel @@ -21,7 +21,7 @@ import org.jetbrains.plugins.gitlab.ui.comment.NewGitLabNoteViewModel interface DiffDataMappedGitLabMergeRequestDiffInlayViewModel : FocusableViewModel, DiffDataMappedGitLabMergeRequestInlayModel { - val location: StateFlow + val location: StateFlow val isVisible: StateFlow } @@ -30,7 +30,7 @@ class GitLabMergeRequestDiffDiscussionViewModel internal constructor( override val diffData: StateFlow, discussionsViewOption: StateFlow, ) : GitLabMergeRequestDiscussionViewModel by base, DiffDataMappedGitLabMergeRequestDiffInlayViewModel { - override val location: StateFlow = mapPositionToDiffLine(base.position, diffData) + override val location: StateFlow = mapPositionToDiffLine(base.position, diffData) override val isVisible: StateFlow = isResolved.combineState(discussionsViewOption) { isResolved, viewOption -> when (viewOption) { @@ -46,7 +46,7 @@ class GitLabMergeRequestDiffDraftNoteViewModel internal constructor( override val diffData: StateFlow, discussionsViewOption: StateFlow, ) : GitLabNoteViewModel by base, DiffDataMappedGitLabMergeRequestDiffInlayViewModel { - override val location: StateFlow = mapPositionToDiffLine(base.position, diffData) + override val location: StateFlow = mapPositionToDiffLine(base.position, diffData) override val isVisible: StateFlow = discussionsViewOption.mapState { when (it) { @@ -58,17 +58,17 @@ class GitLabMergeRequestDiffDraftNoteViewModel internal constructor( class GitLabMergeRequestDiffNewDiscussionViewModel internal constructor( base: NewGitLabNoteViewModel, - originalLocation: DiffLineLocation, + originalLocation: GitLabNoteLocation, discussionsViewOption: StateFlow, ) : NewGitLabNoteViewModel by base { - val location: StateFlow = MutableStateFlow(originalLocation) + val location: StateFlow = MutableStateFlow(originalLocation) val isVisible: StateFlow = discussionsViewOption.mapState { it != DiscussionsViewOption.DONT_SHOW } } private fun mapPositionToDiffLine( position: StateFlow, diffData: StateFlow, -): StateFlow = +): StateFlow = combineStates(position, diffData) { positionOrNull, diffDataOrNull -> val position = positionOrNull ?: return@combineStates null val diffData = diffDataOrNull?.diffData ?: return@combineStates null diff --git a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/diff/GitLabMergeRequestDiffReviewViewModel.kt b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/diff/GitLabMergeRequestDiffReviewViewModel.kt index 7b209ceb157f..099ce5a18f2e 100644 --- a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/diff/GitLabMergeRequestDiffReviewViewModel.kt +++ b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/diff/GitLabMergeRequestDiffReviewViewModel.kt @@ -23,6 +23,7 @@ import org.jetbrains.plugins.gitlab.api.dto.GitLabUserDTO import org.jetbrains.plugins.gitlab.data.GitLabImageLoader import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabMergeRequest import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabMergeRequestNewDiscussionPosition +import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabNoteLocation import org.jetbrains.plugins.gitlab.mergerequest.data.mapToLocation import org.jetbrains.plugins.gitlab.mergerequest.diff.GitLabMergeRequestDiffViewModel import org.jetbrains.plugins.gitlab.mergerequest.ui.details.model.GitLabPersistentMergeRequestChangesViewedState @@ -39,8 +40,8 @@ interface GitLabMergeRequestDiffReviewViewModel { val draftDiscussions: StateFlow>> val newDiscussions: StateFlow> - val locationsWithDiscussions: StateFlow> - val locationsWithNewDiscussions: StateFlow> + val locationsWithDiscussions: StateFlow> + val locationsWithNewDiscussions: StateFlow> val avatarIconsProvider: IconsProvider val imageLoader: GitLabImageLoader @@ -54,6 +55,8 @@ interface GitLabMergeRequestDiffReviewViewModel { fun requestNewDiscussion(location: DiffLineLocation, focus: Boolean) fun cancelNewDiscussion(location: DiffLineLocation) + fun requestNewDiscussion(location: GitLabNoteLocation, focus: Boolean) + fun cancelNewDiscussion(location: GitLabNoteLocation) fun markViewed() } @@ -91,13 +94,13 @@ internal class GitLabMergeRequestDiffReviewViewModelImpl( } }.stateInNow(cs, emptyList()) - override val locationsWithDiscussions: StateFlow> = GitLabMergeRequestDiscussionUtil + override val locationsWithDiscussions: StateFlow> = GitLabMergeRequestDiscussionUtil .createDiscussionsPositionsFlow(mergeRequest, discussionsViewOption).toLocations { it.mapToLocation(diffData, Side.LEFT) }.stateInNow(cs, emptySet()) @OptIn(ExperimentalCoroutinesApi::class) - override val locationsWithNewDiscussions: StateFlow> = + override val locationsWithNewDiscussions: StateFlow> = discussionsContainer.newDiscussions .map { it.keys.mapNotNullTo(mutableSetOf()) { @@ -120,6 +123,14 @@ internal class GitLabMergeRequestDiffReviewViewModelImpl( discussionsContainer.cancelNewDiscussion(position) } + override fun requestNewDiscussion(location: GitLabNoteLocation, focus: Boolean) { + TODO("not implemented") + } + + override fun cancelNewDiscussion(location: GitLabNoteLocation) { + TODO("not implemented") + } + override fun markViewed() { val sha = mergeRequest.details.value.diffRefs?.headSha ?: return persistentChangesViewedState.markViewed( diff --git a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/GitLabMergeRequestEditorDiscussionViewModel.kt b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/GitLabMergeRequestEditorDiscussionViewModel.kt index 90675450e4ef..d1f2d9c061f9 100644 --- a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/GitLabMergeRequestEditorDiscussionViewModel.kt +++ b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/GitLabMergeRequestEditorDiscussionViewModel.kt @@ -11,6 +11,7 @@ import com.intellij.diff.util.Side import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import org.jetbrains.annotations.ApiStatus +import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabNoteLocation import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabNotePosition import org.jetbrains.plugins.gitlab.mergerequest.data.mapToLocation import org.jetbrains.plugins.gitlab.mergerequest.ui.DiffDataMappedGitLabMergeRequestInlayModel @@ -18,12 +19,15 @@ import org.jetbrains.plugins.gitlab.ui.comment.GitLabMergeRequestDiscussionViewM import org.jetbrains.plugins.gitlab.ui.comment.GitLabMergeRequestStandaloneDraftNoteViewModelBase import org.jetbrains.plugins.gitlab.ui.comment.GitLabNoteViewModel import org.jetbrains.plugins.gitlab.ui.comment.NewGitLabNoteViewModel +import java.util.* @ApiStatus.Internal interface DiffDataMappedGitLabMergeRequestEditorViewModel : CodeReviewInlayModel, DiffDataMappedGitLabMergeRequestInlayModel, - FocusableViewModel + FocusableViewModel { + val location: StateFlow +} @ApiStatus.Internal class GitLabMergeRequestEditorDiscussionViewModel( @@ -32,7 +36,8 @@ class GitLabMergeRequestEditorDiscussionViewModel( discussionsViewOption: StateFlow, ) : GitLabMergeRequestDiscussionViewModel by base, DiffDataMappedGitLabMergeRequestEditorViewModel { override val key: Any = base.id - override val line: StateFlow = mapPositionToRightLine(base.position, diffData) + override val location: StateFlow = mapPositionToRightLocation(base.position, diffData) + override val line: StateFlow = location.mapState { it?.lineIdx } override val isVisible: StateFlow = isResolved.combineState(discussionsViewOption) { isResolved, viewOption -> return@combineState when (viewOption) { @@ -50,7 +55,8 @@ class GitLabMergeRequestEditorDraftNoteViewModel internal constructor( discussionsViewOption: StateFlow, ) : GitLabNoteViewModel by base, DiffDataMappedGitLabMergeRequestEditorViewModel { override val key: Any = base.id - override val line: StateFlow = mapPositionToRightLine(base.position, diffData) + override val location: StateFlow = mapPositionToRightLocation(base.position, diffData) + override val line: StateFlow = location.mapState { it?.lineIdx } override val isVisible: StateFlow = discussionsViewOption.mapState { when (it) { @@ -63,33 +69,34 @@ class GitLabMergeRequestEditorDraftNoteViewModel internal constructor( @ApiStatus.Internal class GitLabMergeRequestEditorNewDiscussionViewModel internal constructor( base: NewGitLabNoteViewModel, - originalLine: Int, + originalLocation: GitLabNoteLocation, discussionsViewOption: StateFlow, ) : NewGitLabNoteViewModel by base, CodeReviewInlayModel { - override val key: Any = "NEW_${originalLine}" - override val line: StateFlow = MutableStateFlow(originalLine) + override val key: Any = "NEW_${UUID.randomUUID()}" + val location: StateFlow = MutableStateFlow(originalLocation) + override val line: StateFlow = location.mapState { it?.lineIdx } override val isVisible: StateFlow = discussionsViewOption.mapState { it != DiscussionsViewOption.DONT_SHOW } } -private fun mapPositionToRightLine( +private fun mapPositionToRightLocation( position: GitLabNotePosition?, diffData: StateFlow, -): StateFlow = +): StateFlow = diffData.mapState { diffDataOrNull -> val diffData = diffDataOrNull?.diffData ?: return@mapState null position?.mapToLocation(diffData, Side.RIGHT) - ?.takeIf { it.first == Side.RIGHT }?.second + ?.takeIf { it.startSide == Side.RIGHT && it.side == Side.RIGHT } } -private fun mapPositionToRightLine( +private fun mapPositionToRightLocation( position: StateFlow, diffData: StateFlow, -): StateFlow = +): StateFlow = combineStates(position, diffData) { positionOrNull, diffDataOrNull -> val position = positionOrNull ?: return@combineStates null val diffData = diffDataOrNull?.diffData ?: return@combineStates null position.mapToLocation(diffData, Side.RIGHT) - ?.takeIf { it.first == Side.RIGHT }?.second + ?.takeIf { it.startSide == Side.RIGHT && it.side == Side.RIGHT } } diff --git a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/GitLabMergeRequestEditorReviewController.kt b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/GitLabMergeRequestEditorReviewController.kt index c48889e07114..738f7921a277 100644 --- a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/GitLabMergeRequestEditorReviewController.kt +++ b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/GitLabMergeRequestEditorReviewController.kt @@ -4,9 +4,11 @@ package org.jetbrains.plugins.gitlab.mergerequest.ui.editor import com.intellij.collaboration.async.collectScoped import com.intellij.collaboration.async.launchNow import com.intellij.collaboration.ui.codereview.diff.DiscussionsViewOption +import com.intellij.collaboration.ui.codereview.editor.CodeReviewActiveRangesTracker import com.intellij.collaboration.ui.codereview.editor.CodeReviewCommentableEditorModel import com.intellij.collaboration.ui.codereview.editor.CodeReviewEditorGutterChangesRenderer import com.intellij.collaboration.ui.codereview.editor.CodeReviewEditorGutterControlsRenderer +import com.intellij.collaboration.ui.codereview.editor.CodeReviewEditorInlayRangeOutlineUtils import com.intellij.collaboration.ui.codereview.editor.CodeReviewNavigableEditorViewModel import com.intellij.collaboration.ui.codereview.editor.ReviewInEditorUtil import com.intellij.collaboration.ui.codereview.editor.renderInlays @@ -40,6 +42,7 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.withContext import org.jetbrains.plugins.gitlab.api.dto.GitLabUserDTO @@ -146,8 +149,15 @@ internal class GitLabMergeRequestEditorReviewController(private val project: Pro CodeReviewEditorGutterControlsRenderer.render(model, editor) } launchNow { - editor.renderInlays(model.inlays, HashingUtil.mappingStrategy(GitLabMergeRequestEditorMappedComponentModel::key)) { - createRenderer(it, fileVm.avatarIconsProvider, fileVm.imageLoader) + val activeRangesTracker = CodeReviewActiveRangesTracker() + editor.renderInlays( + model.inlays, + HashingUtil.mappingStrategy(GitLabMergeRequestEditorMappedComponentModel::key)) { inlayModel -> + createRenderer(inlayModel, fileVm.avatarIconsProvider, fileVm.imageLoader, activeRangesTracker).also { inlayRenderer -> + launch { + CodeReviewEditorInlayRangeOutlineUtils.showInlayOutline(editor, model, inlayModel, inlayRenderer, activeRangesTracker) + } + } } } @@ -167,18 +177,19 @@ internal class GitLabMergeRequestEditorReviewController(private val project: Pro inlayModel: GitLabMergeRequestEditorMappedComponentModel, avatarIconsProvider: IconsProvider, imageLoader: GitLabImageLoader, + activeRangesTracker: CodeReviewActiveRangesTracker, ) = when (inlayModel) { is GitLabMergeRequestEditorMappedComponentModel.Discussion<*> -> - GitLabMergeRequestDiscussionInlayRenderer(this, project, inlayModel.vm, avatarIconsProvider, - imageLoader, + GitLabMergeRequestDiscussionInlayRenderer(this, project, inlayModel, avatarIconsProvider, + imageLoader, activeRangesTracker, GitLabStatistics.MergeRequestNoteActionPlace.EDITOR) is GitLabMergeRequestEditorMappedComponentModel.DraftNote<*> -> - GitLabMergeRequestDraftNoteInlayRenderer(this, project, inlayModel.vm, avatarIconsProvider, - imageLoader, + GitLabMergeRequestDraftNoteInlayRenderer(this, project, inlayModel, avatarIconsProvider, + imageLoader, activeRangesTracker, GitLabStatistics.MergeRequestNoteActionPlace.EDITOR) is GitLabMergeRequestEditorMappedComponentModel.NewDiscussion<*> -> - GitLabMergeRequestNewDiscussionInlayRenderer(this, project, inlayModel.vm, avatarIconsProvider, + GitLabMergeRequestNewDiscussionInlayRenderer(this, project, inlayModel, avatarIconsProvider, activeRangesTracker, GitLabStatistics.MergeRequestNoteActionPlace.EDITOR, inlayModel::cancel) } diff --git a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/GitLabMergeRequestEditorReviewFileViewModel.kt b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/GitLabMergeRequestEditorReviewFileViewModel.kt index 1fce9e1f45de..cfde60cadd6e 100644 --- a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/GitLabMergeRequestEditorReviewFileViewModel.kt +++ b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/GitLabMergeRequestEditorReviewFileViewModel.kt @@ -124,15 +124,18 @@ internal class GitLabMergeRequestEditorReviewFileViewModelImpl( override val newDiscussions: StateFlow> = discussionsContainer.newDiscussions.map { it.mapNotNull { (position, vm) -> - val line = position.mapToLocation(diffData)?.takeIf { it.first == Side.RIGHT }?.second ?: return@mapNotNull null - GitLabMergeRequestEditorNewDiscussionViewModel(vm, line, discussionsViewOption) + val location = + position.mapToLocation(diffData)?.takeIf { it.startSide == Side.RIGHT && it.side == Side.RIGHT } ?: return@mapNotNull null + GitLabMergeRequestEditorNewDiscussionViewModel(vm, location, discussionsViewOption) } }.stateInNow(cs, emptyList()) override val linesWithDiscussions: StateFlow> = GitLabMergeRequestDiscussionUtil .createDiscussionsPositionsFlow(mergeRequest, discussionsViewOption).toLines { - it.mapToLocation(diffData, Side.RIGHT)?.takeIf { it.first == Side.RIGHT }?.second + it.mapToLocation(diffData, Side.RIGHT)?.takeIf { + it.startSide == Side.RIGHT && it.side == Side.RIGHT + }?.lineIdx }.stateInNow(cs, emptySet()) override val canNavigate: Boolean = diffData.isCumulative @@ -144,7 +147,9 @@ internal class GitLabMergeRequestEditorReviewFileViewModelImpl( discussionsContainer.newDiscussions .map { it.keys.mapNotNullTo(mutableSetOf()) { - it.mapToLocation(diffData)?.takeIf { it.first == Side.RIGHT }?.second ?: return@mapNotNullTo null + it.mapToLocation(diffData)?.takeIf { + it.startSide == Side.RIGHT && it.side == Side.RIGHT + }?.lineIdx ?: return@mapNotNullTo null } } .stateInNow(cs, emptySet()) diff --git a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/GitLabMergeRequestEditorReviewUIModel.kt b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/GitLabMergeRequestEditorReviewUIModel.kt index a5b1bb5323e4..d083ee2229f3 100644 --- a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/GitLabMergeRequestEditorReviewUIModel.kt +++ b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/GitLabMergeRequestEditorReviewUIModel.kt @@ -3,9 +3,11 @@ package org.jetbrains.plugins.gitlab.mergerequest.ui.editor import com.intellij.collaboration.async.combineState import com.intellij.collaboration.async.launchNow +import com.intellij.collaboration.async.mapState import com.intellij.collaboration.async.mapStatefulToStateful import com.intellij.collaboration.async.stateInNow import com.intellij.collaboration.async.transformConsecutiveSuccesses +import com.intellij.collaboration.ui.codereview.editor.CodeReviewCommentableEditorModel import com.intellij.collaboration.ui.codereview.editor.CodeReviewEditorGutterActionableChangesModel import com.intellij.collaboration.ui.codereview.editor.CodeReviewEditorGutterChangesModel import com.intellij.collaboration.ui.codereview.editor.CodeReviewEditorGutterControlsModel @@ -32,6 +34,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import org.jetbrains.plugins.gitlab.mergerequest.GitLabMergeRequestsPreferences +import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabNoteLocation import org.jetbrains.plugins.gitlab.util.GitLabStatistics /** @@ -48,6 +51,7 @@ internal class GitLabMergeRequestEditorReviewUIModel internal constructor( CodeReviewEditorGutterActionableChangesModel, CodeReviewEditorInlaysModel, CodeReviewEditorGutterControlsModel, + CodeReviewCommentableEditorModel.WithMultilineComments, CodeReviewNavigableEditorViewModel { private val postReviewRanges = MutableStateFlow?>(null) @@ -105,6 +109,14 @@ internal class GitLabMergeRequestEditorReviewUIModel internal constructor( fileVm.cancelNewDiscussion(originalLine) } + override fun requestNewComment(lineRange: LineRange) { + TODO("not implemented") + } + + override fun canCreateComment(lineRange: LineRange): Boolean { + TODO("not implemented") + } + override fun toggleComments(lineIdx: Int) { inlays.value.asSequence().filter { it.line.value == lineIdx }.filterIsInstance().syncOrToggleAll() GitLabStatistics.logToggledComments(project) @@ -184,12 +196,12 @@ internal class GitLabMergeRequestEditorReviewUIModel internal constructor( return inlay.isVisible.value } - private fun StateFlow.shiftLine(): StateFlow = - combineState(postReviewRanges) { line, ranges -> - if (ranges != null && line != null) { - ReviewInEditorUtil.transferLineToAfter(ranges, line).takeIf { it >= 0 } - } - else null + private fun StateFlow.shiftLineRange(): StateFlow = + combineState(postReviewRanges) { vmsLocation, ranges -> + if (ranges == null || vmsLocation == null) return@combineState null + val start = ReviewInEditorUtil.transferLineToAfter(ranges, vmsLocation.startLineIdx) + val end = ReviewInEditorUtil.transferLineToAfter(ranges, vmsLocation.lineIdx) + LineRange(start, end) } private fun Int.shiftLineToAfter(): Int? { @@ -205,21 +217,23 @@ internal class GitLabMergeRequestEditorReviewUIModel internal constructor( private inner class ShiftedDiscussion(vm: GitLabMergeRequestEditorDiscussionViewModel) : GitLabMergeRequestEditorMappedComponentModel.Discussion(vm) { override val isVisible: StateFlow = vm.isVisible.combineState(hiddenState) { visible, hidden -> visible && !hidden } - override val line: StateFlow = vm.line.shiftLine() + override val range: StateFlow = vm.location.shiftLineRange() + override val line: StateFlow = range.mapState { it?.end } } private inner class ShiftedDraftNote(vm: GitLabMergeRequestEditorDraftNoteViewModel) : GitLabMergeRequestEditorMappedComponentModel.DraftNote(vm) { override val isVisible: StateFlow = vm.isVisible.combineState(hiddenState) { visible, hidden -> visible && !hidden } - override val line: StateFlow = vm.line.shiftLine() + override val range: StateFlow = vm.location.shiftLineRange() + override val line: StateFlow = range.mapState { it?.end } } private inner class ShiftedNewDiscussion(vm: GitLabMergeRequestEditorNewDiscussionViewModel) : GitLabMergeRequestEditorMappedComponentModel.NewDiscussion(vm) { override val key: Any = vm.key override val isVisible: StateFlow = MutableStateFlow(true) - override val line: StateFlow = vm.line.shiftLine() - + override val range: StateFlow = vm.location.shiftLineRange() + override val line: StateFlow = range.mapState { it?.end } override fun cancel() { vm.line.value?.let(::cancelNewDiscussion) } diff --git a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/editorInlayRenderers.kt b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/editorInlayRenderers.kt index 40fc02e497f2..ebc82a9cd64e 100644 --- a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/editorInlayRenderers.kt +++ b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/editorInlayRenderers.kt @@ -1,7 +1,9 @@ // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.plugins.gitlab.mergerequest.ui.editor +import com.intellij.collaboration.ui.codereview.editor.CodeReviewActiveRangesTracker import com.intellij.collaboration.ui.codereview.editor.CodeReviewComponentInlayRenderer +import com.intellij.collaboration.ui.codereview.editor.CodeReviewEditorInlayRangeOutlineUtils import com.intellij.collaboration.ui.icon.IconsProvider import com.intellij.openapi.project.Project import kotlinx.coroutines.CoroutineScope @@ -9,43 +11,52 @@ import org.jetbrains.annotations.ApiStatus import org.jetbrains.plugins.gitlab.api.dto.GitLabUserDTO import org.jetbrains.plugins.gitlab.data.GitLabImageLoader import org.jetbrains.plugins.gitlab.mergerequest.ui.diff.GitLabMergeRequestDiffInlayComponentsFactory -import org.jetbrains.plugins.gitlab.ui.comment.GitLabMergeRequestDiscussionViewModel -import org.jetbrains.plugins.gitlab.ui.comment.GitLabNoteViewModel -import org.jetbrains.plugins.gitlab.ui.comment.NewGitLabNoteViewModel import org.jetbrains.plugins.gitlab.util.GitLabStatistics @ApiStatus.Internal class GitLabMergeRequestDiscussionInlayRenderer internal constructor( cs: CoroutineScope, project: Project, - vm: GitLabMergeRequestDiscussionViewModel, + model: GitLabMergeRequestEditorMappedComponentModel.Discussion<*>, avatarIconsProvider: IconsProvider, imageLoader: GitLabImageLoader, + activeRangesTracker: CodeReviewActiveRangesTracker, place: GitLabStatistics.MergeRequestNoteActionPlace, -) - : CodeReviewComponentInlayRenderer( - GitLabMergeRequestDiffInlayComponentsFactory.createDiscussion(project, cs, avatarIconsProvider, imageLoader, vm, place) +) : CodeReviewComponentInlayRenderer( + GitLabMergeRequestDiffInlayComponentsFactory.createDiscussion(project, cs, avatarIconsProvider, imageLoader, model.vm, place) + .let { newCommentComponent -> + CodeReviewEditorInlayRangeOutlineUtils.wrapWithDimming(newCommentComponent, model, activeRangesTracker) + } ) @ApiStatus.Internal -class GitLabMergeRequestDraftNoteInlayRenderer internal constructor(cs: CoroutineScope, - project: Project, - vm: GitLabNoteViewModel, - avatarIconsProvider: IconsProvider, - imageLoader: GitLabImageLoader, - place: GitLabStatistics.MergeRequestNoteActionPlace, -) - : CodeReviewComponentInlayRenderer( - GitLabMergeRequestDiffInlayComponentsFactory.createDraftNote(project, cs, avatarIconsProvider, imageLoader, vm, place) +class GitLabMergeRequestDraftNoteInlayRenderer internal constructor( + cs: CoroutineScope, + project: Project, + model: GitLabMergeRequestEditorMappedComponentModel.DraftNote<*>, + avatarIconsProvider: IconsProvider, + imageLoader: GitLabImageLoader, + activeRangesTracker: CodeReviewActiveRangesTracker, + place: GitLabStatistics.MergeRequestNoteActionPlace, +) : CodeReviewComponentInlayRenderer( + GitLabMergeRequestDiffInlayComponentsFactory.createDraftNote(project, cs, avatarIconsProvider, imageLoader, model.vm, place) + .let { newCommentComponent -> + CodeReviewEditorInlayRangeOutlineUtils.wrapWithDimming(newCommentComponent, model, activeRangesTracker) + } ) @ApiStatus.Internal -class GitLabMergeRequestNewDiscussionInlayRenderer internal constructor(cs: CoroutineScope, - project: Project, - vm: NewGitLabNoteViewModel, - avatarIconsProvider: IconsProvider, - place: GitLabStatistics.MergeRequestNoteActionPlace, - onCancel: () -> Unit) - : CodeReviewComponentInlayRenderer( - GitLabMergeRequestDiffInlayComponentsFactory.createNewDiscussion(project, cs, avatarIconsProvider, vm, onCancel, place) +class GitLabMergeRequestNewDiscussionInlayRenderer internal constructor( + cs: CoroutineScope, + project: Project, + model: GitLabMergeRequestEditorMappedComponentModel.NewDiscussion<*>, + avatarIconsProvider: IconsProvider, + activeRangesTracker: CodeReviewActiveRangesTracker, + place: GitLabStatistics.MergeRequestNoteActionPlace, + onCancel: () -> Unit, +) : CodeReviewComponentInlayRenderer( + GitLabMergeRequestDiffInlayComponentsFactory.createNewDiscussion(project, cs, avatarIconsProvider, model.vm, onCancel, place) + .let { newCommentComponent -> + CodeReviewEditorInlayRangeOutlineUtils.wrapWithDimming(newCommentComponent, model, activeRangesTracker) + } ) \ No newline at end of file diff --git a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/editorInlays.kt b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/editorInlays.kt index 4f61dcc646be..cc81c6523589 100644 --- a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/editorInlays.kt +++ b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/editor/editorInlays.kt @@ -9,7 +9,7 @@ import org.jetbrains.plugins.gitlab.ui.comment.GitLabMergeRequestDiscussionViewM import org.jetbrains.plugins.gitlab.ui.comment.GitLabNoteViewModel import org.jetbrains.plugins.gitlab.ui.comment.NewGitLabNoteViewModel -internal sealed interface GitLabMergeRequestEditorMappedComponentModel : CodeReviewInlayModel { +internal sealed interface GitLabMergeRequestEditorMappedComponentModel : CodeReviewInlayModel.Ranged { val vm: CodeReviewTrackableItemViewModel abstract class Discussion(override val vm: VM) diff --git a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/review/GitLabMergeRequestDiscussionsViewModels.kt b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/review/GitLabMergeRequestDiscussionsViewModels.kt index 4c8b6afcbf46..14c33418fa5a 100644 --- a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/review/GitLabMergeRequestDiscussionsViewModels.kt +++ b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/review/GitLabMergeRequestDiscussionsViewModels.kt @@ -1,12 +1,7 @@ // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.plugins.gitlab.mergerequest.ui.review -import com.intellij.collaboration.async.cancelAndJoinSilently -import com.intellij.collaboration.async.mapFiltered -import com.intellij.collaboration.async.mapState -import com.intellij.collaboration.async.mapStatefulToStateful -import com.intellij.collaboration.async.stateInNow -import com.intellij.collaboration.async.transformConsecutiveSuccesses +import com.intellij.collaboration.async.* import com.intellij.collaboration.ui.codereview.diff.DiffLineLocation import com.intellij.collaboration.ui.codereview.diff.UnifiedCodeReviewItemPosition import com.intellij.collaboration.util.ComputedResult @@ -22,34 +17,12 @@ import git4idea.changes.findCumulativeChange import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.flow.updateAndGet +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import org.jetbrains.plugins.gitlab.api.dto.GitLabUserDTO -import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabMergeRequest -import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabMergeRequestNewDiscussionPosition -import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabNotePosition -import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabProject -import org.jetbrains.plugins.gitlab.mergerequest.data.filePath -import org.jetbrains.plugins.gitlab.mergerequest.data.mapToLeftSideLine -import org.jetbrains.plugins.gitlab.mergerequest.data.mapToLocation -import org.jetbrains.plugins.gitlab.mergerequest.data.mapToRightSideLine +import org.jetbrains.plugins.gitlab.mergerequest.data.* import org.jetbrains.plugins.gitlab.ui.GitLabMarkdownToHtmlConverter -import org.jetbrains.plugins.gitlab.ui.comment.GitLabMergeRequestDiscussionViewModel -import org.jetbrains.plugins.gitlab.ui.comment.GitLabMergeRequestDiscussionViewModelBase -import org.jetbrains.plugins.gitlab.ui.comment.GitLabMergeRequestStandaloneDraftNoteViewModelBase -import org.jetbrains.plugins.gitlab.ui.comment.GitLabNoteEditingViewModel -import org.jetbrains.plugins.gitlab.ui.comment.NewGitLabNoteViewModel -import org.jetbrains.plugins.gitlab.ui.comment.onDoneIn +import org.jetbrains.plugins.gitlab.ui.comment.* import java.time.Instant.EPOCH import java.util.Date import java.util.TreeSet @@ -94,7 +67,7 @@ interface GitLabMergeRequestDiscussionsViewModels { } } -fun GitLabMergeRequestDiscussionsViewModels.NewDiscussionPosition.mapToLocation(diffData: GitTextFilePatchWithHistory): DiffLineLocation? = +fun GitLabMergeRequestDiscussionsViewModels.NewDiscussionPosition.mapToLocation(diffData: GitTextFilePatchWithHistory): GitLabNoteLocation? = position.mapToLocation(diffData, side) internal class GitLabMergeRequestDiscussionsViewModelsImpl( diff --git a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/util/GitLabMergeRequestDiscussionUtil.kt b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/util/GitLabMergeRequestDiscussionUtil.kt index 4c2cdd4c280a..640e66b9b702 100644 --- a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/util/GitLabMergeRequestDiscussionUtil.kt +++ b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/util/GitLabMergeRequestDiscussionUtil.kt @@ -2,7 +2,6 @@ package org.jetbrains.plugins.gitlab.mergerequest.util import com.intellij.collaboration.async.flatMapLatestEach -import com.intellij.collaboration.ui.codereview.diff.DiffLineLocation import com.intellij.collaboration.ui.codereview.diff.DiscussionsViewOption import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -11,6 +10,7 @@ import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabMergeRequest import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabMergeRequestDiscussion import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabMergeRequestDraftNote import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabMergeRequestNote +import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabNoteLocation import org.jetbrains.plugins.gitlab.mergerequest.data.GitLabNotePosition internal object GitLabMergeRequestDiscussionUtil { @@ -67,5 +67,5 @@ internal object GitLabMergeRequestDiscussionUtil { fun Flow>.toLines(mapper: (GitLabNotePosition) -> Int?): Flow> = map { it.mapNotNullTo(mutableSetOf(), mapper) } -fun Flow>.toLocations(mapper: (GitLabNotePosition) -> DiffLineLocation?): Flow> = +fun Flow>.toLocations(mapper: (GitLabNotePosition) -> GitLabNoteLocation?): Flow> = map { it.mapNotNullTo(mutableSetOf(), mapper) } \ No newline at end of file