1 /*
2     SPDX-FileCopyrightText: 2020 Jonathan L. Verner <jonathan.verner@matfyz.cz>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #ifndef KDEVPLATFORM_PLUGIN_COMMIT_TOOLVIEW_H
8 #define KDEVPLATFORM_PLUGIN_COMMIT_TOOLVIEW_H
9 
10 #include "repostatusmodel.h"
11 
12 #include <interfaces/iuicontroller.h>
13 
14 #include <KTextEditor/Attribute>
15 
16 #include <QWidget>
17 
18 class ActiveStyledDelegate;
19 class DiffViewsCtrl;
20 class FilterEmptyItemsProxyModel;
21 class RepoStatusModel;
22 class SimpleCommitForm;
23 
24 class QAction;
25 class QMenu;
26 class QModelIndex;
27 class QLineEdit;
28 class QTreeView;
29 class QAbstractProxyModel;
30 class QUrl;
31 
32 namespace KDevelop {
33     class IBasicVersionControl;
34     class IDocument;
35     class IProject;
36     class VcsJob;
37 }
38 
39 namespace KTextEditor {
40     class View;
41     class Document;
42 }
43 
44 /**
45  * This implements the git-cola like toolview for preparing commits.
46  *
47  * The view contains a list of projects. Each project contains four
48  * lists:
49  *
50  *   - the staged changes list lists all files in the project which
51  *     have changes staged for commit;
52  *   - the unstaged changes list lists all files in the project which
53  *     have changes which are not currently staged for commit;
54  *   - the conflicts list lists all files which have unresolved (merge)
55  *     conflicts; and
56  *   - the untracked list which lists all files not tracked in the VCS
57  *
58  * Clicking on a file in one of the staged/unstaged lists opens a document
59  * tab with the diff showing the changes. The user can then select lines/hunks
60  * from the diff and remove/add them from the staged changes using the context menu.
61  *
62  * Double clicking on a file will, instead, stage/unstage all changes in the file
63  * or mark the conflicts as resolved or add the file to be tracked in VCS.
64  *
65  * Above these lists a lineedit and a textedit may be used to prepare a
66  * commit message. The commit button will commit the staged changes to the
67  * repo. If several projects are listed, the one which is expanded will be
68  * used (only one project is allowed to be expaned to show the lists at a time,
69  * an expaned project is automatically collapsed when a different one is expanded).
70  *
71  * @author Jonathan L. Verner <jonathan.verner@matfyz.cz>
72  */
73 
74 class CommitToolView : public QWidget
75 {
76     Q_OBJECT
77 
78 public:
79     enum ShowDiffParams { Activate, NoActivate };
80 
81     /**
82      * @note: m_statusmodel remains the property of the caller whose
83      * responsibility is to delete it (and care must be taken not
84      * to delete it before the CommitToolView is deleted)
85      */
86     CommitToolView(QWidget* parent, RepoStatusModel* m_statusmodel);
87 
88     /**
89      * @returns the currently active project (i.e. the one that
90      *          is expanded in the treeview)
91      */
92     KDevelop::IProject* activeProject() const;
93 
94     /**
95      * @returns the index of the currently active project (i.e. the one that
96      *          is expanded in the treeview)
97      */
98     QStandardItem* activeProjectItem() const;
99 
100     /**
101      * @returns true if the item pointed to by the repostatusmodel index
102      *          idx is the root item of the currently active project.
103      */
104     bool isActiveProject(const QModelIndex& idx) const;
105 
106 Q_SIGNALS:
107 
108     /**
109      * This signal is emitted when the view wants to show a diff
110      *
111      * @param url the url to display the changes for
112      * @param area the type of changes to display
113      */
114     void showDiff(const QUrl& url, const RepoStatusModel::Areas area);
115 
116     /**
117      * This signal is emitted when the view wants to show a file
118      *
119      * @param url the url of the file to show
120      */
121     void showSource(const QUrl& url);
122 
123     /**
124      * This signal is emitted when the diff showing changes of type @param area
125      * to the file @param url needs to be updated.
126      */
127     void updateDiff(const QUrl& url, const RepoStatusModel::Areas area);
128 
129     /**
130      * This signal is emitted when all diffs showing changes to files in
131      * project @param project need to be updated.
132      */
133     void updateProjectDiffs(KDevelop::IProject* project);
134 
135     /**
136      * This signal is emitted when all diffs showing changes to the file
137      * @param url need to be updated.
138      *
139      * @note: In contrast to the updateDiff signal, this also includes diffs
140      * showing all changes to the owning project (staged/unstaged)
141      */
142     void updateUrlDiffs(const QUrl& url);
143 
144 public Q_SLOTS:
145     /**
146      * Shows the toolview context menu
147      */
148     void popupContextMenu(const QPoint& pos);
149 
150     /**
151      * A handler called when the user double clicks
152      * an item in the treeview.
153      */
154     void dblClicked(const QModelIndex& idx);
155 
156     /**
157      * A handler called when the user clicks an item
158      * in the treeview.
159      */
160     void clicked(const QModelIndex& idx);
161 
162     /**
163      * A handler called when a user expands an item
164      * in the treeview.
165      */
166     void activateProject(const QModelIndex& idx);
167 
168     /**
169      * Stages the staged changes in the given files.
170      *
171      * @param urls the list of files whose changes to stage
172      */
173     void stageSelectedFiles(const QList<QUrl>& urls);
174 
175     /**
176      * Unstages the staged changes in the given files.
177      *
178      * @param urls the list of files whose changes to unstage
179      */
180     void unstageSelectedFiles(const QList<QUrl>& urls);
181 
182     /**
183      * Reverts the uncommited changes in the given files.
184      *
185      * @param urls the list of files whose changes to revert
186      *
187      * @note: This is an irreversible and dangerous action,
188      * a confirmation dialog is shown before it is applied
189      */
190     void revertSelectedFiles(const QList<QUrl>& urls);
191 
192     /**
193      * Runs git commit on the staged changes. The commit message
194      * is constructed from the data in the commit form.
195      *
196      * @note This function assumes that there are some staged changes.
197      * @note The extended description of the commit is wrapped at 70 columns
198      */
199     void commitActiveProject();
200 
201 private:
202 
203     /* Describes an action on selected lines/hunk in a diff */
204     enum ApplyAction {
205         Stage,
206         Unstage,
207         Revert,
208     };
209 
210     /**
211      * Updates the toolview layout based on the dock area position:
212      *
213      * When the toolview is placed on the left/right, all the widgets
214      * sit on top of each other; when it is placed on the top/bottom,
215      * the commit area (commit header, button, description textedit)
216      * will sit to the left of the changes view with the search filter.
217      */
218 
219     void doLayOut(const Qt::DockWidgetArea area);
220 
221     /**
222      * A helper function which return the VCS plugin which
223      * handles `url`.
224      *
225      * @param url the url for which the plugin is returned
226      *
227      * @note: Returns nullptr if no project/VCS plugin for the
228      * given url exists.
229      */
230     KDevelop::IBasicVersionControl* vcsPluginForUrl(const QUrl& url) const;
231 
232     /**
233      * The model which lists the projects and staged/modified/... files
234      * which are shown in the treeview.
235      */
236     RepoStatusModel* m_statusmodel;
237 
238     /**
239      * The filtered repostatus model
240      */
241     FilterEmptyItemsProxyModel* m_proxymodel;
242 
243     /** The form for composing the commit message and doing the commit. */
244     SimpleCommitForm* m_commitForm = nullptr;
245 
246     /** The treeview listing the projects & their staged/modified/... files */
247     QTreeView* m_view = nullptr;
248 
249     /** The lineedit for filtering the treeview */
250     QLineEdit* m_filter = nullptr;
251 
252     /****************************************
253      * Various contextmenus & their actions *
254      ****************************************/
255 
256     QMenu
257         /** Menu with a single "Refresh" action (shown for projects in the toolview) */
258           *m_refreshMenu
259         /** Menu with stage/unstage/revert actions (shown for files in the toolview) */
260         , *m_toolviewMenu
261         ;
262     QAction *m_refreshModelAct
263           , *m_stageFilesAct
264           , *m_unstageFilesAct
265           , *m_revertFilesAct
266           ;
267 
268     /** A style delegate for showing the currently selected project in bold */
269     ActiveStyledDelegate* m_styleDelegate;
270 };
271 
272 /**
273  * A factory for creating CommitToolViews.
274  */
275 class CommitToolViewFactory : public KDevelop::IToolViewFactory
276 {
277 public:
278     explicit CommitToolViewFactory(RepoStatusModel* statusModel);
279     ~CommitToolViewFactory();
280     QWidget* create(QWidget* parent = nullptr) override;
281     Qt::DockWidgetArea defaultPosition() const override;
282     QString id() const override;
283 
284 private:
285     RepoStatusModel* m_statusmodel;
286     DiffViewsCtrl* m_diffViewsCtrl;
287 };
288 
289 #endif
290