mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:19:59 +07:00
IJPL-161089 vcs marker: Initial implementation of committing separate chunk from gutter
GitOrigin-RevId: 7009ec8c785bed0074084c44495749e52c30e9e4
This commit is contained in:
committed by
intellij-monorepo-bot
parent
aa3a77d9a0
commit
86d58fbf8c
@@ -268,15 +268,14 @@ public class LineStatusMarkerPopupPanel extends JPanel {
|
||||
DiffDrawUtil.LAYER_PRIORITY_LST, parentDisposable);
|
||||
|
||||
int currentStartOffset = currentTextRange.getStartOffset();
|
||||
List<RangeHighlighter> highlighters = new ArrayList<>();
|
||||
|
||||
highlighters.addAll(
|
||||
new DiffDrawUtil.LineHighlighterBuilder(editor, startLine, endLine, TextDiffType.MODIFIED)
|
||||
.withLayerPriority(DiffDrawUtil.LAYER_PRIORITY_LST)
|
||||
.withIgnored(true)
|
||||
.withHideStripeMarkers(true)
|
||||
.withHideGutterMarkers(true)
|
||||
.done());
|
||||
List<RangeHighlighter> highlighters =
|
||||
new ArrayList<>(new DiffDrawUtil.LineHighlighterBuilder(editor, startLine, endLine, TextDiffType.MODIFIED)
|
||||
.withLayerPriority(DiffDrawUtil.LAYER_PRIORITY_LST)
|
||||
.withIgnored(true)
|
||||
.withHideStripeMarkers(true)
|
||||
.withHideGutterMarkers(true)
|
||||
.done());
|
||||
|
||||
for (DiffFragment fragment : wordDiff) {
|
||||
int currentStart = currentStartOffset + fragment.getStartOffset2();
|
||||
|
||||
@@ -29,14 +29,15 @@ abstract class LineStatusTrackerMarkerRenderer(
|
||||
final override fun createPopupPanel(editor: Editor,
|
||||
range: Range,
|
||||
mousePosition: Point?,
|
||||
disposable: Disposable): LineStatusMarkerPopupPanel {
|
||||
popupDisposable: Disposable): LineStatusMarkerPopupPanel {
|
||||
var editorComponent: JComponent? = null
|
||||
if (range.hasVcsLines()) {
|
||||
editorComponent = createVcsContentComponent(range, editor, disposable)
|
||||
editorComponent = createVcsContentComponent(range, editor, popupDisposable)
|
||||
}
|
||||
val actions = createToolbarActions(editor, range, mousePosition)
|
||||
val toolbar = LineStatusMarkerPopupPanel.buildToolbar(editor, actions, disposable)
|
||||
val additionalInfoPanel = createAdditionalInfoPanel(editor, range, mousePosition, disposable)
|
||||
val actions = createToolbarActions(editor, range, mousePosition) +
|
||||
createAdditionalToolbarActions(editor, range, mousePosition, popupDisposable)
|
||||
val toolbar = LineStatusMarkerPopupPanel.buildToolbar(editor, actions, popupDisposable)
|
||||
val additionalInfoPanel = createAdditionalInfoPanel(editor, range, mousePosition, popupDisposable)
|
||||
return LineStatusMarkerPopupPanel.create(editor, toolbar, editorComponent, additionalInfoPanel)
|
||||
}
|
||||
|
||||
@@ -51,6 +52,7 @@ abstract class LineStatusTrackerMarkerRenderer(
|
||||
}
|
||||
|
||||
protected open fun createToolbarActions(editor: Editor, range: Range, mousePosition: Point?): List<AnAction> = emptyList()
|
||||
protected open fun createAdditionalToolbarActions(editor: Editor, range: Range, mousePosition: Point?, popupDisposable: Disposable): List<AnAction> = emptyList()
|
||||
|
||||
protected open fun createAdditionalInfoPanel(editor: Editor,
|
||||
range: Range,
|
||||
|
||||
@@ -83,6 +83,11 @@ public class JBPanel<T extends JBPanel> extends JPanel implements JBComponent<T>
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
public final T resetPreferredHeight() {
|
||||
myPreferredHeight = null;
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
public final T withPreferredSize(int width, int height) {
|
||||
myPreferredWidth = width;
|
||||
myPreferredHeight = height;
|
||||
|
||||
@@ -127,6 +127,7 @@ public final class VcsConfiguration implements PersistentStateComponent<VcsConfi
|
||||
@XCollection(elementName = "MESSAGE")
|
||||
public List<String> myLastCommitMessages = new ArrayList<>();
|
||||
public @Nullable String LAST_COMMIT_MESSAGE = null;
|
||||
public @NotNull String LAST_CHUNK_COMMIT_MESSAGE = "";
|
||||
public boolean MAKE_NEW_CHANGELIST_ACTIVE = false;
|
||||
public boolean PRESELECT_EXISTING_CHANGELIST = false;
|
||||
|
||||
@@ -171,6 +172,14 @@ public final class VcsConfiguration implements PersistentStateComponent<VcsConfi
|
||||
});
|
||||
}
|
||||
|
||||
public void saveTempChunkCommitMessage(@NotNull final String comment) {
|
||||
LAST_CHUNK_COMMIT_MESSAGE = comment;
|
||||
}
|
||||
|
||||
public @NotNull String getTempChunkCommitMessage() {
|
||||
return LAST_CHUNK_COMMIT_MESSAGE;
|
||||
}
|
||||
|
||||
private static void addCommitMessage(@NotNull List<? super String> recentMessages, @NotNull String comment) {
|
||||
if (recentMessages.size() >= MAX_STORED_MESSAGES) {
|
||||
recentMessages.remove(0);
|
||||
|
||||
@@ -1268,4 +1268,7 @@ activity.name.get.from=Get from {0}
|
||||
activity.name.shelve=Shelve changes
|
||||
activity.name.rollback=Rollback
|
||||
activity.name.apply.patch=Apply patch
|
||||
activity.name.unshelve=Unshelve
|
||||
activity.name.unshelve=Unshelve
|
||||
|
||||
# commit from gutter
|
||||
commit.from.gutter.placeholder=Commit this change
|
||||
@@ -18,6 +18,8 @@ public final class PlatformVcsImplIcons {
|
||||
private static @NotNull Icon load(@NotNull String expUIPath, @NotNull String path, int cacheKey, int flags) {
|
||||
return IconManager.getInstance().loadRasterizedIcon(path, expUIPath, PlatformVcsImplIcons.class.getClassLoader(), cacheKey, flags);
|
||||
}
|
||||
/** 16x16 */ public static final @NotNull Icon AmendInline = load("icons/AmendInline.svg", 1594676608, 2);
|
||||
/** 16x16 */ public static final @NotNull Icon CommitInline = load("icons/CommitInline.svg", 747759737, 2);
|
||||
/** 16x16 */ public static final @NotNull Icon Shelve = load("icons/new/stash.svg", "icons/Shelve.svg", -1645293825, 2);
|
||||
/** 16x16 */ public static final @NotNull Icon Stash = load("icons/new/stash.svg", "icons/Stash.svg", -451629034, 2);
|
||||
/** 16x16 */ public static final @NotNull Icon Vcs = load("icons/new/vcs.svg", 1023462254, 2);
|
||||
|
||||
4
platform/vcs-impl/resources/icons/AmendInline.svg
Normal file
4
platform/vcs-impl/resources/icons/AmendInline.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.3176 2.42105C10.7627 1.86723 9.84952 1.85121 9.28596 2.42376L7.38018 4.31787L7.37942 4.31863L2 9.68822L2.0004 13.2222H4.8125V12.2222H3.00029L3.00005 10.1029L7.73232 5.37929L9.8791 7.52606L8.4805 8.92188H9.89612L10.9404 7.87969L12.7968 6.02031C13.3676 5.46125 13.3557 4.56317 12.8112 4.00772L11.327 2.43041L11.3176 2.42105ZM9.9978 3.1261C10.1608 2.95962 10.4362 2.95595 10.6096 3.12724L12.0867 4.69722L12.0956 4.70618C12.2663 4.87869 12.2624 5.14475 12.0962 5.30675L10.5863 6.81903L8.44084 4.67359L9.99307 3.13093L9.9978 3.1261Z" fill="#6C707E"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.7129 12.3659H13.0907C13.3362 12.3659 13.5352 12.1669 13.5352 11.9214C13.5352 11.676 13.3362 11.477 13.0907 11.477H11.7129C11.507 10.4627 10.6102 9.69922 9.53516 9.69922C8.46006 9.69922 7.56328 10.4627 7.35739 11.477H5.9796C5.73414 11.477 5.53516 11.676 5.53516 11.9214C5.53516 12.1669 5.73414 12.3659 5.9796 12.3659H7.35739C7.56328 13.3802 8.46006 14.1437 9.53516 14.1437C10.6102 14.1437 11.507 13.3802 11.7129 12.3659ZM10.8685 11.9214C10.8685 12.6578 10.2715 13.2548 9.53516 13.2548C8.79878 13.2548 8.20182 12.6578 8.20182 11.9214C8.20182 11.1851 8.79878 10.5881 9.53516 10.5881C10.2715 10.5881 10.8685 11.1851 10.8685 11.9214Z" fill="#3574F0"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
4
platform/vcs-impl/resources/icons/AmendInline_dark.svg
Normal file
4
platform/vcs-impl/resources/icons/AmendInline_dark.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.3176 2.42105C10.7627 1.86723 9.84952 1.85121 9.28596 2.42376L7.38018 4.31787L7.37942 4.31863L2 9.68822L2.0004 13.2222H4.8125V12.2222H3.00029L3.00005 10.1029L7.73232 5.37929L9.8791 7.52606L8.4805 8.92188H9.89612L10.9404 7.87969L12.7968 6.02031C13.3676 5.46125 13.3557 4.56317 12.8112 4.00772L11.327 2.43041L11.3176 2.42105ZM9.9978 3.1261C10.1608 2.95962 10.4362 2.95595 10.6096 3.12724L12.0867 4.69722L12.0956 4.70618C12.2663 4.87869 12.2624 5.14475 12.0962 5.30675L10.5863 6.81903L8.44084 4.67359L9.99307 3.13093L9.9978 3.1261Z" fill="#CED0D6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.7129 12.3659H13.0907C13.3362 12.3659 13.5352 12.1669 13.5352 11.9214C13.5352 11.676 13.3362 11.477 13.0907 11.477H11.7129C11.507 10.4627 10.6102 9.69922 9.53516 9.69922C8.46006 9.69922 7.56328 10.4627 7.35739 11.477H5.9796C5.73414 11.477 5.53516 11.676 5.53516 11.9214C5.53516 12.1669 5.73414 12.3659 5.9796 12.3659H7.35739C7.56328 13.3802 8.46006 14.1437 9.53516 14.1437C10.6102 14.1437 11.507 13.3802 11.7129 12.3659ZM10.8685 11.9214C10.8685 12.6578 10.2715 13.2548 9.53516 13.2548C8.79878 13.2548 8.20182 12.6578 8.20182 11.9214C8.20182 11.1851 8.79878 10.5881 9.53516 10.5881C10.2715 10.5881 10.8685 11.1851 10.8685 11.9214Z" fill="#548AF7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
4
platform/vcs-impl/resources/icons/CommitInline.svg
Normal file
4
platform/vcs-impl/resources/icons/CommitInline.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<!-- Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.5 8H3.5L2.5 14.5L14.5 8L2.5 1.5L3.192 6" stroke="#818594" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 351 B |
4
platform/vcs-impl/resources/icons/CommitInline_dark.svg
Normal file
4
platform/vcs-impl/resources/icons/CommitInline_dark.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<!-- Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.5 8H3.5L2.5 14.5L14.5 8L2.5 1.5L3.192 6" stroke="#818594" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 351 B |
@@ -4,12 +4,10 @@ package com.intellij.openapi.vcs.ex
|
||||
import com.intellij.codeWithMe.ClientId
|
||||
import com.intellij.diff.util.DiffUtil
|
||||
import com.intellij.diff.util.Side
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.ide.DataManager
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.DefaultActionGroup
|
||||
import com.intellij.openapi.actionSystem.Separator
|
||||
import com.intellij.openapi.actionSystem.*
|
||||
import com.intellij.openapi.command.CommandEvent
|
||||
import com.intellij.openapi.command.CommandListener
|
||||
import com.intellij.openapi.command.CommandProcessor
|
||||
@@ -25,6 +23,7 @@ import com.intellij.openapi.editor.event.DocumentEvent
|
||||
import com.intellij.openapi.editor.event.DocumentListener
|
||||
import com.intellij.openapi.keymap.KeymapUtil
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import com.intellij.openapi.project.DumbAwareToggleAction
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory
|
||||
import com.intellij.openapi.util.Disposer
|
||||
@@ -37,6 +36,7 @@ import com.intellij.openapi.vcs.changes.ChangeListWorker
|
||||
import com.intellij.openapi.vcs.changes.LocalChangeList
|
||||
import com.intellij.openapi.vcs.ex.DocumentTracker.Block
|
||||
import com.intellij.openapi.vcs.ex.LineStatusTrackerBlockOperations.Companion.isSelectedByLine
|
||||
import com.intellij.openapi.vcs.ex.commit.CommitChunkService
|
||||
import com.intellij.openapi.vcs.impl.ActiveChangeListTracker
|
||||
import com.intellij.openapi.vcs.impl.LineStatusTrackerManager
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
@@ -198,7 +198,7 @@ class ChangelistsLocalLineStatusTracker internal constructor(project: Project,
|
||||
|
||||
override fun getAffectedChangeListsIds(): List<String> {
|
||||
return documentTracker.readLock {
|
||||
assert(!affectedChangeLists.isEmpty())
|
||||
assert(!affectedChangeLists.isEmpty)
|
||||
affectedChangeLists.toList()
|
||||
}
|
||||
}
|
||||
@@ -654,33 +654,47 @@ class ChangelistsLocalLineStatusTracker internal constructor(project: Project,
|
||||
mousePosition: Point?,
|
||||
disposable: Disposable): JComponent? {
|
||||
val superPanel = super.createAdditionalInfoPanel(editor, range, mousePosition, disposable)
|
||||
val changelistPanel = createChangelistInfoPanel(editor, range, mousePosition, disposable)
|
||||
if (superPanel == null || changelistPanel == null) return superPanel ?: changelistPanel
|
||||
val commitPanel = createCommitPanel(editor, range, mousePosition, disposable)
|
||||
|
||||
return JBUI.Panels.simplePanel(changelistPanel)
|
||||
if (superPanel == null || commitPanel == null) return superPanel ?: commitPanel
|
||||
|
||||
val panel = JBUI.Panels.simplePanel(commitPanel)
|
||||
.addToRight(superPanel)
|
||||
.andTransparent()
|
||||
return panel
|
||||
}
|
||||
|
||||
private fun createChangelistInfoPanel(editor: Editor,
|
||||
range: Range,
|
||||
mousePosition: Point?,
|
||||
disposable: Disposable): JComponent? {
|
||||
private fun createCommitPanel(editor: Editor, range: Range, point: Point?, disposable: Disposable): JComponent? {
|
||||
if (range !is LocalRange) return null
|
||||
return CommitChunkService.getInstance(project!!).getComponent(tracker, range, disposable).getCommitInput()
|
||||
}
|
||||
|
||||
override fun createAdditionalToolbarActions(editor: Editor, range: Range, mousePosition: Point?, popupDisposable: Disposable): List<AnAction> {
|
||||
return createChangeListActions(editor, range, mousePosition, popupDisposable)
|
||||
}
|
||||
|
||||
private fun createChangeListActions(editor: Editor,
|
||||
range: Range,
|
||||
mousePosition: Point?,
|
||||
disposable: Disposable): List<AnAction> {
|
||||
if (range !is LocalRange) return emptyList()
|
||||
|
||||
val changeLists = ChangeListManager.getInstance(tracker.project).changeLists
|
||||
val rangeList = changeLists.find { it.id == range.changelistId } ?: return null
|
||||
val rangeList = changeLists.find { it.id == range.changelistId } ?: return emptyList()
|
||||
|
||||
val group = DefaultActionGroup()
|
||||
val group = DefaultActionGroup(VcsBundle.message("ex.changelists"), null, AllIcons.Vcs.Changelist)
|
||||
group.isPopup = true
|
||||
if (changeLists.size > 1) {
|
||||
group.add(Separator(VcsBundle.message("ex.changelists")))
|
||||
for (changeList in changeLists) {
|
||||
group.add(MoveToChangeListAction(editor, range, mousePosition, changeList))
|
||||
group.add(MoveToChangeListToggleAction(editor, range, mousePosition, changeList))
|
||||
}
|
||||
group.add(Separator.getInstance())
|
||||
}
|
||||
group.add(MoveToAnotherChangeListAction(editor, range, mousePosition))
|
||||
|
||||
return listOf(group)
|
||||
|
||||
|
||||
val link = DropDownLink(rangeList.name) { linkLabel ->
|
||||
val dataContext = DataManager.getInstance().getDataContext(linkLabel)
|
||||
@@ -702,7 +716,7 @@ class ChangelistsLocalLineStatusTracker internal constructor(project: Project,
|
||||
link.toolTipText = VcsBundle.message("ex.move.lines.to.another.changelist.0", KeymapUtil.getShortcutText(shortcuts.first()))
|
||||
}
|
||||
|
||||
return link
|
||||
//return link
|
||||
}
|
||||
|
||||
private inner class MoveToAnotherChangeListAction(editor: Editor, range: Range, val mousePosition: Point?)
|
||||
@@ -719,17 +733,31 @@ class ChangelistsLocalLineStatusTracker internal constructor(project: Project,
|
||||
}
|
||||
}
|
||||
|
||||
private inner class MoveToChangeListAction(editor: Editor, range: Range, val mousePosition: Point?, val changelist: LocalChangeList)
|
||||
: LineStatusMarkerPopupActions.RangeMarkerAction(editor, tracker, range, null) {
|
||||
private inner class MoveToChangeListToggleAction(
|
||||
private val editor: Editor, private val range: Range, val mousePosition: Point?, val changelist: LocalChangeList
|
||||
) : DumbAwareToggleAction(changelist.name) {
|
||||
init {
|
||||
templatePresentation.setText(StringUtil.trimMiddle(changelist.name, 60), false)
|
||||
templatePresentation.keepPopupOnPerform = KeepPopupOnPerform.Never
|
||||
}
|
||||
|
||||
override fun isEnabled(editor: Editor, range: Range): Boolean = range is LocalRange
|
||||
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT
|
||||
|
||||
override fun actionPerformed(editor: Editor, range: Range) {
|
||||
tracker.moveToChangelist(range, changelist)
|
||||
reopenRange(editor, range, mousePosition)
|
||||
override fun update(e: AnActionEvent) {
|
||||
super.update(e)
|
||||
val newRange = rangesSource.findRange(range)
|
||||
e.presentation.setEnabled(newRange != null && !editor.isDisposed() && newRange is LocalRange)
|
||||
}
|
||||
|
||||
override fun isSelected(e: AnActionEvent): Boolean {
|
||||
val newRange = rangesSource.findRange(range)
|
||||
return (newRange as LocalRange).changelistId == changelist.id
|
||||
}
|
||||
|
||||
override fun setSelected(e: AnActionEvent, state: Boolean) {
|
||||
val newRange = rangesSource.findRange(range) ?: return
|
||||
tracker.moveToChangelist(newRange, changelist)
|
||||
reopenRange(editor, newRange, mousePosition)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -816,6 +844,13 @@ class ChangelistsLocalLineStatusTracker internal constructor(project: Project,
|
||||
fireExcludedFromCommitChanged()
|
||||
}
|
||||
|
||||
fun excludeAllBlocks() {
|
||||
documentTracker.writeLock {
|
||||
blocks.forEach { b -> b.excludedFromCommit = RangeExclusionState.Excluded }
|
||||
}
|
||||
fireExcludedFromCommitChanged()
|
||||
}
|
||||
|
||||
override fun setPartiallyExcludedFromCommit(lines: BitSet, side: Side, isExcluded: Boolean) {
|
||||
documentTracker.writeLock {
|
||||
for (block in blocks) {
|
||||
@@ -903,7 +938,7 @@ class ChangelistsLocalLineStatusTracker internal constructor(project: Project,
|
||||
}
|
||||
|
||||
@RequiresReadLock
|
||||
private fun collectRangeStates(): List<RangeState> {
|
||||
internal fun collectRangeStates(): List<RangeState> {
|
||||
return documentTracker.readLock {
|
||||
blocks.map { RangeState(it.range, it.marker.changelistId, it.excludedFromCommit) }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,399 @@
|
||||
// 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.openapi.vcs.ex.commit
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.actionSystem.*
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.editor.colors.EditorColorsManager
|
||||
import com.intellij.openapi.editor.event.DocumentEvent
|
||||
import com.intellij.openapi.editor.event.DocumentListener
|
||||
import com.intellij.openapi.editor.ex.EditorEx
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.vcs.CheckinProjectPanel
|
||||
import com.intellij.openapi.vcs.FilePath
|
||||
import com.intellij.openapi.vcs.ProjectLevelVcsManager
|
||||
import com.intellij.openapi.vcs.VcsBundle
|
||||
import com.intellij.openapi.vcs.changes.Change
|
||||
import com.intellij.openapi.vcs.changes.ChangeListManager
|
||||
import com.intellij.openapi.vcs.changes.CommitExecutor
|
||||
import com.intellij.openapi.vcs.ex.ChangelistsLocalLineStatusTracker
|
||||
import com.intellij.openapi.vcs.ex.LocalRange
|
||||
import com.intellij.openapi.vcs.ex.RangeExclusionState
|
||||
import com.intellij.openapi.vcs.ui.CommitMessage
|
||||
import com.intellij.platform.vcs.impl.icons.PlatformVcsImplIcons
|
||||
import com.intellij.ui.components.panels.Wrapper
|
||||
import com.intellij.util.EventDispatcher
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import com.intellij.util.ui.Animator
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.components.BorderLayoutPanel
|
||||
import com.intellij.vcs.commit.*
|
||||
import java.awt.Color
|
||||
import java.awt.Dimension
|
||||
import java.awt.event.FocusAdapter
|
||||
import java.awt.event.FocusEvent
|
||||
import javax.swing.JComponent
|
||||
|
||||
private class CommitChunkPanel(private val tracker: ChangelistsLocalLineStatusTracker,
|
||||
private val amendCommitHandler: NonModalAmendCommitHandler) : NonModalCommitPanel(tracker.project) {
|
||||
override val commitProgressUi: CommitProgressUi = object : CommitProgressPanel() {
|
||||
override var isEmptyMessage: Boolean
|
||||
get() = commitMessage.text.isBlank()
|
||||
set(_) {}
|
||||
}
|
||||
|
||||
private val executorEventDispatcher = EventDispatcher.create(CommitExecutorListener::class.java)
|
||||
|
||||
private val rightWrapper = Wrapper()
|
||||
private val bottomWrapper = Wrapper()
|
||||
|
||||
override var editedCommit: EditedCommitPresentation? = null
|
||||
|
||||
private val commitAction = object : DumbAwareAction(VcsBundle.message("commit.from.gutter.placeholder"), null, PlatformVcsImplIcons.CommitInline) {
|
||||
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
e.presentation.isEnabled = commitMessage.text.isNotBlank()
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
executorEventDispatcher.multicaster.executorCalled(null)
|
||||
}
|
||||
}
|
||||
|
||||
private val amendCommitToggle = object : ToggleAction(VcsBundle.message("checkbox.amend") , null, PlatformVcsImplIcons.AmendInline) {
|
||||
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
super.update(e)
|
||||
val p = e.presentation
|
||||
p.isVisible = amendCommitHandler.isAmendCommitModeSupported() == true
|
||||
p.isEnabled = isVisible && amendCommitHandler.isAmendCommitModeTogglingEnabled == true
|
||||
}
|
||||
|
||||
override fun isSelected(e: AnActionEvent): Boolean = amendCommitHandler.isAmendCommitMode
|
||||
|
||||
override fun setSelected(e: AnActionEvent, state: Boolean) {
|
||||
amendCommitHandler.isAmendCommitMode = state
|
||||
}
|
||||
}.apply {
|
||||
val amendShortcut = ActionManager.getInstance().getAction("Vcs.ToggleAmendCommitMode").shortcutSet
|
||||
registerCustomShortcutSet(amendShortcut, this@CommitChunkPanel, this@CommitChunkPanel)
|
||||
}
|
||||
|
||||
var forcedWidth: Int = Spec.DEFAULT_WIDTH
|
||||
val actionToolbar: ActionToolbar
|
||||
|
||||
init {
|
||||
actionToolbar = buildActions()
|
||||
|
||||
// layout
|
||||
rightWrapper.setContent(actionToolbar.component)
|
||||
|
||||
centerPanel.removeAll()
|
||||
centerPanel
|
||||
.addToCenter(commitMessage)
|
||||
.addToRight(rightWrapper)
|
||||
.addToBottom(BorderLayoutPanel().addToRight(bottomWrapper).andTransparent())
|
||||
|
||||
// ui adjustment
|
||||
centerPanel.andTransparent().withBackground(Spec.INPUT_BACKGROUND)
|
||||
resetPreferredHeight()
|
||||
andTransparent()
|
||||
|
||||
val editor = commitMessage.editorField.getEditor(true)
|
||||
commitMessage.editorField.setPlaceholder(VcsBundle.message("commit.from.gutter.placeholder"))
|
||||
if (editor != null) {
|
||||
adjustEditorSettings(editor)
|
||||
centerPanel.border = CommitInputBorder(editor, this)
|
||||
}
|
||||
|
||||
setupResizing(commitMessage)
|
||||
setupDocumentLengthTracker(commitMessage)
|
||||
}
|
||||
|
||||
private fun setupDocumentLengthTracker(message: CommitMessage) {
|
||||
message.editorField.addDocumentListener(object : DocumentListener {
|
||||
override fun documentChanged(event: DocumentEvent) {
|
||||
val length = event.document.textLength
|
||||
val lineCount = event.document.lineCount
|
||||
process(length, lineCount)
|
||||
}
|
||||
|
||||
private fun process(length: Int, lineCount: Int) {
|
||||
if (length > Spec.INLINED_ACTIONS_TEXT_LIMIT || lineCount > 1) {
|
||||
rightWrapper.setContent(null)
|
||||
bottomWrapper.setContent(actionToolbar.component)
|
||||
} else {
|
||||
bottomWrapper.setContent(null)
|
||||
rightWrapper.setContent(actionToolbar.component)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun buildActions(): ActionToolbar {
|
||||
val actionGroup = DefaultActionGroup(listOf(amendCommitToggle, Separator.create(), commitAction))
|
||||
val toolbar = ActionManager.getInstance().createActionToolbar("CommitChange", actionGroup, true)
|
||||
.apply {
|
||||
minimumButtonSize = Spec.MINIMUM_BUTTON_SIZE
|
||||
}
|
||||
|
||||
return toolbar.apply {
|
||||
targetComponent = commitMessage
|
||||
component.border = JBUI.Borders.empty()
|
||||
component.isOpaque = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun addExecutorListener(listener: CommitExecutorListener, parent: Disposable) {
|
||||
executorEventDispatcher.addListener(listener, parent)
|
||||
}
|
||||
|
||||
|
||||
override fun getIncludedChanges(): List<Change> {
|
||||
val rangeStates = tracker.collectRangeStates()
|
||||
val rangeToCommit = rangeStates.first { it.excludedFromCommit == RangeExclusionState.Included }
|
||||
val changelistId = rangeToCommit.changelistId
|
||||
val changeList = ChangeListManager.getInstance(project).getChangeList(changelistId)!!
|
||||
|
||||
val changes = changeList.changes.filter { it.virtualFile == tracker.virtualFile }
|
||||
return changes
|
||||
}
|
||||
|
||||
private val animator = lazy {
|
||||
object : Animator("Commit Input Animation", Spec.Animation.FRAMES, Spec.Animation.DURATION, false, true) {
|
||||
override fun paintNow(frame: Int, totalFrames: Int, cycle: Int) {
|
||||
val nextWidth = (Spec.MAX_WIDTH - Spec.DEFAULT_WIDTH) / totalFrames * frame + Spec.DEFAULT_WIDTH
|
||||
this@CommitChunkPanel.resizeInput(nextWidth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupResizing(commitMessage: CommitMessage) {
|
||||
commitMessage.editorField.addFocusListener(object : FocusAdapter() {
|
||||
override fun focusGained(e: FocusEvent?) {
|
||||
animator.value.resume()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun resizeInput(newWidth: Int) {
|
||||
forcedWidth = newWidth
|
||||
revalidate()
|
||||
repaint()
|
||||
}
|
||||
|
||||
|
||||
override fun getPreferredSize(): Dimension {
|
||||
val pref = super.preferredSize
|
||||
pref.height = minOf(pref.height, Spec.MAX_HEIGHT)
|
||||
pref.width = forcedWidth
|
||||
return pref
|
||||
}
|
||||
|
||||
override fun activate(): Boolean = true
|
||||
override fun getIncludedUnversionedFiles(): List<FilePath> = emptyList()
|
||||
override fun getDisplayedChanges(): List<Change> = emptyList()
|
||||
override fun getDisplayedUnversionedFiles(): List<FilePath> = emptyList()
|
||||
|
||||
fun resetSize() {
|
||||
resizeInput(Spec.DEFAULT_WIDTH)
|
||||
animator.value.reset()
|
||||
}
|
||||
}
|
||||
|
||||
private class CommitChunkWorkflow(project: Project) : NonModalCommitWorkflow(project) {
|
||||
lateinit var state: ChangeListCommitState
|
||||
|
||||
init {
|
||||
val vcses = ProjectLevelVcsManager.getInstance(project).allActiveVcss.toSet()
|
||||
updateVcses(vcses)
|
||||
}
|
||||
|
||||
override val isDefaultCommitEnabled: Boolean
|
||||
get() = true
|
||||
|
||||
override fun performCommit(sessionInfo: CommitSessionInfo) {
|
||||
val committer = LocalChangesCommitter(project, state, commitContext)
|
||||
addCommonResultHandlers(sessionInfo, committer)
|
||||
committer.addResultHandler(ShowNotificationCommitResultHandler(committer))
|
||||
committer.runCommit(VcsBundle.message("commit.changes"), false)
|
||||
}
|
||||
}
|
||||
|
||||
private class CommitChunkWorkFlowHandler(
|
||||
val tracker: ChangelistsLocalLineStatusTracker,
|
||||
val rangeProvider: () -> LocalRange,
|
||||
) : NonModalCommitWorkflowHandler<CommitChunkWorkflow, CommitChunkPanel>() {
|
||||
override val workflow: CommitChunkWorkflow = CommitChunkWorkflow(tracker.project)
|
||||
override val amendCommitHandler: NonModalAmendCommitHandler = NonModalAmendCommitHandler(this)
|
||||
override val ui: CommitChunkPanel = CommitChunkPanel(tracker, amendCommitHandler)
|
||||
override val commitPanel: CheckinProjectPanel = CommitProjectPanelAdapter(this)
|
||||
|
||||
private val commitMessagePolicy = ChunkCommitMessagePolicy(project, ui.commitMessageUi)
|
||||
|
||||
init {
|
||||
ui.addExecutorListener(this, this)
|
||||
workflow.addListener(this, this)
|
||||
workflow.addVcsCommitListener(object : CommitStateCleaner() {
|
||||
override fun onSuccess() {
|
||||
commitMessagePolicy.onAfterCommit()
|
||||
super.onSuccess()
|
||||
}
|
||||
}, this)
|
||||
workflow.addVcsCommitListener(PostCommitChecksRunner(), this)
|
||||
commitMessagePolicy.init()
|
||||
|
||||
setupDumbModeTracking()
|
||||
setupCommitHandlersTracking()
|
||||
setupCommitChecksResultTracking()
|
||||
vcsesChanged()
|
||||
}
|
||||
|
||||
override suspend fun updateWorkflow(sessionInfo: CommitSessionInfo): Boolean {
|
||||
workflow.state = getCommitState()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun getCommitState(): ChangeListCommitState {
|
||||
val changes = getIncludedChanges()
|
||||
val rangeStates = tracker.collectRangeStates()
|
||||
val first = rangeStates.first { it.excludedFromCommit == RangeExclusionState.Included }
|
||||
val changeList = ChangeListManager.getInstance(project).getChangeList(first.changelistId)!!
|
||||
return ChangeListCommitState(changeList, changes, ui.commitMessage.text)
|
||||
}
|
||||
|
||||
override fun executorCalled(executor: CommitExecutor?) {
|
||||
tracker.excludeAllBlocks()
|
||||
tracker.setExcludedFromCommit(rangeProvider(), false)
|
||||
super.executorCalled(executor)
|
||||
}
|
||||
|
||||
override fun saveCommitMessageBeforeCommit() {
|
||||
commitMessagePolicy.onBeforeCommit()
|
||||
}
|
||||
|
||||
fun setPopup(popupDisposable: Disposable) {
|
||||
ui.resetSize()
|
||||
|
||||
workflow.addListener(object : CommitWorkflowListener {
|
||||
override fun executionStarted() {
|
||||
Disposer.dispose(popupDisposable)
|
||||
}
|
||||
}, popupDisposable)
|
||||
|
||||
Disposer.register(popupDisposable, Disposable {
|
||||
commitMessagePolicy.saveTempChunkCommitMessage(ui.commitMessageUi.text)
|
||||
})
|
||||
|
||||
commitMessagePolicy.init()
|
||||
}
|
||||
}
|
||||
|
||||
internal class CommitChunkComponent(
|
||||
tracker: ChangelistsLocalLineStatusTracker,
|
||||
) {
|
||||
internal var range: LocalRange? = null
|
||||
|
||||
private val workflowHandler = CommitChunkWorkFlowHandler(tracker) { range!! }
|
||||
|
||||
init {
|
||||
Disposer.register(tracker.disposable, workflowHandler)
|
||||
}
|
||||
|
||||
fun getCommitInput(): JComponent = workflowHandler.ui
|
||||
|
||||
fun setPopup(disposable: Disposable) {
|
||||
workflowHandler.setPopup(disposable)
|
||||
}
|
||||
}
|
||||
|
||||
private fun adjustEditorSettings(editor: EditorEx) {
|
||||
editor.scrollPane.border = JBUI.Borders.empty()
|
||||
editor.backgroundColor = Spec.INPUT_BACKGROUND
|
||||
editor.settings.isShowIntentionBulb = false
|
||||
}
|
||||
|
||||
private object Spec {
|
||||
val DEFAULT_WIDTH: Int
|
||||
get() = JBUI.scale(255)
|
||||
|
||||
val MAX_WIDTH: Int
|
||||
get() = JBUI.scale(660)
|
||||
|
||||
val MAX_HEIGHT: Int
|
||||
get() = JBUI.scale(100)
|
||||
|
||||
val INPUT_BACKGROUND: Color
|
||||
get() = EditorColorsManager.getInstance().globalScheme.defaultBackground
|
||||
|
||||
// actions will be moved to the bottom after a message reaches this limit
|
||||
const val INLINED_ACTIONS_TEXT_LIMIT: Int = 50
|
||||
|
||||
val MINIMUM_BUTTON_SIZE: Dimension = Dimension(22, 22)
|
||||
|
||||
object Animation {
|
||||
const val FRAMES = 30
|
||||
const val DURATION = 200
|
||||
}
|
||||
}
|
||||
|
||||
private class ChunkCommitMessagePolicy(
|
||||
project: Project,
|
||||
private val commitMessageUi: CommitMessageUi,
|
||||
) : AbstractCommitMessagePolicy(project) {
|
||||
|
||||
fun init() {
|
||||
commitMessageUi.text = getCommitMessage()
|
||||
}
|
||||
|
||||
fun onBeforeCommit() {
|
||||
val commitMessage = commitMessageUi.text
|
||||
vcsConfiguration.saveCommitMessage(commitMessage)
|
||||
}
|
||||
|
||||
fun onAfterCommit() {
|
||||
saveTempChunkCommitMessage("")
|
||||
commitMessageUi.text = getCommitMessage()
|
||||
}
|
||||
|
||||
private fun getCommitMessage(): String {
|
||||
return vcsConfiguration.tempChunkCommitMessage
|
||||
}
|
||||
|
||||
fun saveTempChunkCommitMessage(commitMessage: String) {
|
||||
vcsConfiguration.saveTempChunkCommitMessage(commitMessage)
|
||||
}
|
||||
}
|
||||
|
||||
@Service(Service.Level.PROJECT)
|
||||
internal class CommitChunkService() {
|
||||
private val components = mutableMapOf<ChangelistsLocalLineStatusTracker, CommitChunkComponent>()
|
||||
|
||||
@RequiresEdt
|
||||
fun getComponent(tracker: ChangelistsLocalLineStatusTracker, range: LocalRange, popupDisposable: Disposable): CommitChunkComponent {
|
||||
return components.getOrPut(tracker) {
|
||||
createComponentForTracker(tracker)
|
||||
}.apply {
|
||||
this.range = range
|
||||
this.setPopup(popupDisposable)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createComponentForTracker(tracker: ChangelistsLocalLineStatusTracker): CommitChunkComponent {
|
||||
val component = CommitChunkComponent(tracker)
|
||||
Disposer.register(tracker.disposable, Disposable {
|
||||
components.remove(tracker)
|
||||
})
|
||||
return component
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getInstance(project: Project) = project.service<CommitChunkService>()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// 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.openapi.vcs.ex.commit
|
||||
|
||||
import com.intellij.ide.ui.laf.darcula.DarculaUIUtil
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.ex.EditorEx
|
||||
import com.intellij.openapi.editor.ex.FocusChangeListener
|
||||
import com.intellij.openapi.ui.ErrorBorderCapable
|
||||
import com.intellij.util.ui.JBInsets
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.MacUIUtil
|
||||
import java.awt.*
|
||||
import java.awt.geom.Rectangle2D
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.border.Border
|
||||
|
||||
internal class CommitInputBorder(
|
||||
private val editor: EditorEx,
|
||||
private val borderOwner: JComponent,
|
||||
) : Border, ErrorBorderCapable {
|
||||
init {
|
||||
editor.addFocusListener(object : FocusChangeListener {
|
||||
override fun focusGained(editor: Editor) = repaintOwner()
|
||||
override fun focusLost(editor: Editor) = repaintOwner()
|
||||
private fun repaintOwner() {
|
||||
borderOwner.repaint()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun paintBorder(c: Component, g: Graphics, x: Int, y: Int, width: Int, height: Int) {
|
||||
val hasFocus = editor.contentComponent.hasFocus()
|
||||
val r = Rectangle(x, y, width, height)
|
||||
val g2 = g.create() as Graphics2D
|
||||
try {
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
|
||||
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
|
||||
if (MacUIUtil.USE_QUARTZ) RenderingHints.VALUE_STROKE_PURE else RenderingHints.VALUE_STROKE_NORMALIZE)
|
||||
JBInsets.removeFrom(r, JBUI.insets(1))
|
||||
g2.translate(r.x, r.y)
|
||||
val bw = DarculaUIUtil.BW.float
|
||||
val outer = Rectangle2D.Float(bw, bw, r.width - bw * 2, r.height - bw * 2)
|
||||
g2.color = c.background
|
||||
g2.fill(outer)
|
||||
|
||||
if (editor.contentComponent.isEnabled && editor.contentComponent.isVisible) {
|
||||
val op = DarculaUIUtil.getOutline(c as JComponent)
|
||||
val hasFocusInPopup = hasFocus //&& !AIChatPopup.chatInPopupEnabled(parent.mode)
|
||||
if (op == null) {
|
||||
g2.color = if (hasFocusInPopup) JBUI.CurrentTheme.Focus.focusColor()
|
||||
else DarculaUIUtil.getOutlineColor(editor.contentComponent.isEnabled, false)
|
||||
}
|
||||
else {
|
||||
op.setGraphicsColor(g2, hasFocus)
|
||||
}
|
||||
DarculaUIUtil.doPaint(g2, r.width, r.height, 5f, if (hasFocus) 1f else 0.5f, true)
|
||||
}
|
||||
}
|
||||
finally {
|
||||
g2.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBorderInsets(c: Component): Insets = JBInsets.create(4, 8).asUIResource()
|
||||
override fun isBorderOpaque(): Boolean = true
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ import com.intellij.vcs.commit.AbstractCommitWorkflow.Companion.getCommitExecuto
|
||||
import kotlinx.coroutines.*
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.Nls
|
||||
import java.lang.Runnable
|
||||
import kotlin.properties.Delegates.observable
|
||||
|
||||
private val LOG = logger<NonModalCommitWorkflowHandler<*, *>>()
|
||||
|
||||
Reference in New Issue
Block a user