├── .gitignore
├── LICENCE
├── README.md
├── build.gradle
└── src
└── main
├── java
└── com
│ └── neon
│ └── intellij
│ └── plugins
│ └── gitlab
│ ├── ChangeIssueStateAction.java
│ ├── CloseIssueEditorAction.java
│ ├── ConnectionPropertiesSupplier.java
│ ├── DeleteIssueAction.java
│ ├── GIPGroupObserver.java
│ ├── GIPIssueObserver.java
│ ├── GIPProjectObserver.java
│ ├── GIPUserObserver.java
│ ├── GetGroupsTask.java
│ ├── GetIssuesTask.java
│ ├── GetProjectsTask.java
│ ├── GetUsersTask.java
│ ├── GitLabService.java
│ ├── GitLabServiceSupplier.java
│ ├── GitLabToolWindowFactory.java
│ ├── OpenIssueEditorAction.java
│ ├── RefreshProjectIssuesAction.java
│ ├── RetrofitSupplier.java
│ ├── SafeOkHttpClientSupplier.java
│ ├── SaveIssueAction.java
│ ├── UnsafeOkHttpClientSupplier.java
│ ├── VoidComponentView.java
│ ├── controller
│ ├── GLIController.java
│ ├── JBFacade.java
│ └── editor
│ │ ├── GLAbstractFileEditor.java
│ │ ├── GLEditorProvider.java
│ │ ├── GLFileEditorState.java
│ │ ├── GLFileType.java
│ │ ├── GLIssueEditor.java
│ │ └── GLIssueVirtualFile.java
│ ├── model
│ ├── EditableView.java
│ ├── gitlab
│ │ ├── GIPGroup.java
│ │ ├── GIPIssue.java
│ │ ├── GIPIssueAssignee.java
│ │ ├── GIPIssueAuthor.java
│ │ ├── GIPNamespace.java
│ │ ├── GIPProject.java
│ │ └── GIPUser.java
│ └── intellij
│ │ ├── ConfigurableState.java
│ │ ├── GLGroupNode.java
│ │ ├── GLIssueNode.java
│ │ ├── GLProjectNode.java
│ │ ├── GLTreeNode.java
│ │ └── ProjectModule.java
│ └── view
│ ├── GitLabView.java
│ ├── configurable
│ ├── GitLabConfigurable.java
│ ├── GitLabConfigurableProvider.java
│ └── SettingsView.java
│ ├── issues
│ └── GLIssueEditorView.java
│ └── toolwindow
│ ├── FilteredTreeModel.java
│ ├── GLIssueListMouseAdapter.java
│ ├── GLIssueListRenderer.java
│ ├── GLIssueListView.java
│ ├── GLIssuePopup.java
│ ├── GLIssuesFilterView.java
│ └── GLProjectPopup.java
└── resources
├── META-INF
└── plugin.xml
└── icons
└── gitlab.jpg
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | target/
3 | .idea/
4 | out/
5 | gitlab-integration-plugin.zip
6 | .gradle/
7 | *.ipr
8 | *.iws
9 | build/
10 | gradle.properties
11 |
12 |
--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Diogo Neves
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Gitlab Integration Plugin
2 |
3 | Lets you interact with gitlab from within your IDE.
4 |
5 | Features:
6 |
7 | - List projects (by namespaces) and their issues
8 | - Filter issues by author, assignee or custom text
9 | - Re-open / close issues (right click)
10 | - Edit issues (double click)
11 |
12 |
13 | Please, leave a comment or drop me an email with any issues/reports.
14 |
15 | ## How To
16 |
17 | After plugin install, go to IDE preferences, and look for Gitlab Integration.
18 | You'll need the host and your API key from your gitlab (which you can find under Profile Settings -> Account)
19 |
20 | ## Change notes
21 |
22 | ( 2018-11-20 ) v1.1.2
23 |
24 | - Fixing nullpointer (for now)
25 |
26 | ( 2018-11-17 ) v1.1.1
27 |
28 | - Minor improvements to the plugin initialization
29 | - Minor improvements to the plugin settings view
30 |
31 | ( 2018-11-13 ) v1.1.0
32 |
33 | - Redone plugin to use Gitlab API v4
34 |
35 | ( 2014-10-14 ) v1.0.6
36 |
37 | - Java 6 / 7 versions
38 | - displaying issue Iid instead of Id
39 |
40 | ( 2014-07-08 ) v1.0.5
41 |
42 | - Fixed possible ssl error (ignoring certificate errors by default)
43 |
44 | ( 2014-07-07 ) v1.0.4
45 |
48 | ( 2014-06-23 ) v1.0.3
49 |
50 | - Issues list filter
51 | - minor improvements
52 |
53 | ( 2014-04-29 ) v1.0.2
54 |
55 | - Set plugin dependency to build 133 (IntelliJ IDEA 13, PyCharm 3.1, WebStorm 7, PhpStorm 7) - intellij 13 was the first to support java7
56 |
57 | ( 2014-04-29 ) v1.0.1
58 |
59 | - Improves in the issue editing view
60 |
61 | ( 2014-04-25 ) v1.0
62 |
63 | - Plugin creation
64 |
65 |
66 | ## Author
67 |
68 | Author:: Diogo Neves ( diogo.sousa.neves@gmail.com )
69 |
70 | ## Licence
71 |
72 | The MIT License (MIT)
73 |
74 | Copyright (c) 2018 Diogo Neves
75 |
76 | Permission is hereby granted, free of charge, to any person obtaining a copy
77 | of this software and associated documentation files (the "Software"), to deal
78 | in the Software without restriction, including without limitation the rights
79 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
80 | copies of the Software, and to permit persons to whom the Software is
81 | furnished to do so, subject to the following conditions:
82 |
83 | The above copyright notice and this permission notice shall be included in all
84 | copies or substantial portions of the Software.
85 |
86 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
87 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
88 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
89 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
90 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
91 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
92 | SOFTWARE.
93 |
94 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | mavenCentral()
4 | }
5 | }
6 |
7 | plugins {
8 | id "org.jetbrains.intellij" version "0.3.12"
9 | }
10 |
11 | apply plugin: 'idea'
12 | apply plugin: 'org.jetbrains.intellij'
13 | apply plugin: 'java'
14 |
15 | intellij {
16 | //IntelliJ IDEA 2016.3 dependency; for a full list of IntelliJ IDEA releases please see https://www.jetbrains.com/intellij-repository/releases
17 | version 'IC-2016.3'
18 |
19 | //Bundled plugin dependencies
20 | plugins 'coverage'
21 |
22 | // pluginName 'GitLab Integration Plugin'
23 |
24 | intellij.updateSinceUntilBuild false
25 | }
26 |
27 | publishPlugin {
28 | username intellijPublishUsername
29 | password intellijPublishPassword
30 | }
31 |
32 | group 'org.jetbrains'
33 | version '1.1.2' // Plugin version
34 |
35 |
36 | repositories {
37 | mavenCentral()
38 | }
39 |
40 | dependencies {
41 | compile "io.reactivex.rxjava2:rxjava:2.2.2"
42 |
43 | compile 'com.squareup.retrofit2:retrofit:2.4.0'
44 | compile 'com.squareup.retrofit2:converter-moshi:2.4.0'
45 | compile 'com.squareup.okhttp3:okhttp:3.11.0'
46 | compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
47 |
48 | compile 'tablelayout:TableLayout:20050920'
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/ChangeIssueStateAction.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab;
2 |
3 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPIssue;
4 |
5 | import java.util.function.BiFunction;
6 |
7 | public interface ChangeIssueStateAction extends BiFunction {
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/CloseIssueEditorAction.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab;
2 |
3 | import com.neon.intellij.plugins.gitlab.controller.editor.GLIssueVirtualFile;
4 |
5 | import java.util.function.Consumer;
6 |
7 | public interface CloseIssueEditorAction extends Consumer {
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/ConnectionPropertiesSupplier.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab;
2 |
3 | import com.neon.intellij.plugins.gitlab.model.intellij.ConfigurableState;
4 |
5 | import java.util.function.Supplier;
6 |
7 | public class ConnectionPropertiesSupplier implements Supplier {
8 |
9 | @Override
10 | public ConnectionProperties get() {
11 | ConfigurableState state = ConfigurableState.getInstance();
12 | return new ConnectionProperties( state.host, state.token, state.ignoreCertificateErrors );
13 | }
14 |
15 | public static class ConnectionProperties {
16 | public final String host;
17 | public final String privateToken;
18 | public final boolean ignoreSSLCertificateErrors;
19 |
20 | public ConnectionProperties(String host, String privateToken, boolean ignoreSSLCertificateErrors) {
21 | this.host = host;
22 | this.privateToken = privateToken;
23 | this.ignoreSSLCertificateErrors = ignoreSSLCertificateErrors;
24 | }
25 |
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/DeleteIssueAction.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab;
2 |
3 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPIssue;
4 |
5 | import java.util.function.Consumer;
6 |
7 | public interface DeleteIssueAction extends Consumer {
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/GIPGroupObserver.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab;
2 |
3 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPGroup;
4 |
5 | public interface GIPGroupObserver {
6 |
7 | default void onStartGroupsUpdate() {
8 |
9 | }
10 |
11 | void accept( GIPGroup group );
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/GIPIssueObserver.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab;
2 |
3 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPIssue;
4 |
5 | public interface GIPIssueObserver {
6 |
7 | void accept(GIPIssue issue);
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/GIPProjectObserver.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab;
2 |
3 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPProject;
4 |
5 | public interface GIPProjectObserver {
6 |
7 | default void onStartProjectUpdate( GIPProject project ) {
8 |
9 | }
10 |
11 | void accept( GIPProject project );
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/GIPUserObserver.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab;
2 |
3 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPUser;
4 |
5 | import java.util.List;
6 |
7 | public interface GIPUserObserver {
8 |
9 | default void onStartUsersUpdate() {
10 |
11 | }
12 |
13 | void accept(List< GIPUser > users);
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/GetGroupsTask.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab;
2 |
3 | import com.intellij.ide.plugins.PluginManager;
4 | import com.intellij.openapi.progress.ProgressIndicator;
5 | import com.intellij.openapi.progress.Task;
6 | import com.intellij.openapi.project.Project;
7 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPGroup;
8 | import io.reactivex.Observer;
9 | import io.reactivex.disposables.Disposable;
10 | import org.jetbrains.annotations.NotNull;
11 | import org.jetbrains.annotations.Nullable;
12 |
13 | import java.util.List;
14 | import java.util.logging.Level;
15 | import java.util.logging.Logger;
16 |
17 |
18 | public class GetGroupsTask extends Task.Backgroundable {
19 |
20 | private static final Logger LOGGER = Logger.getLogger( GetGroupsTask.class.getName() );
21 |
22 | private final GitLabService gitLabService;
23 |
24 | private final GIPGroupObserver groupObserver;
25 |
26 | public GetGroupsTask(@Nullable Project project, GitLabService gitLabService, GIPGroupObserver groupObserver ) {
27 | super(project, "Getting Groups From Gitlab", true);
28 | this.gitLabService = gitLabService;
29 | this.groupObserver = groupObserver;
30 | }
31 |
32 | @Override
33 | public void run( @NotNull ProgressIndicator indicator ) {
34 | indicator.setIndeterminate(true);
35 | request( 10, 1 );
36 | }
37 |
38 | private void request( final int limit, final int page ) {
39 | gitLabService.listGroups( limit, page )
40 | .subscribe(new Observer>() {
41 | @Override
42 | public void onSubscribe(Disposable d) {
43 |
44 | }
45 |
46 | @Override
47 | public void onNext(List groups) {
48 | if ( groups == null ) {
49 | return ;
50 | }
51 |
52 | groups.forEach(groupObserver::accept);
53 |
54 | if ( groups.size() >= limit ) {
55 | request( limit, page + 1 );
56 | }
57 | }
58 |
59 | @Override
60 | public void onError(Throwable e) {
61 | LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e );
62 |
63 | PluginManager.getLogger().error( e );
64 | }
65 |
66 | @Override
67 | public void onComplete() {
68 |
69 | }
70 | });
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/GetIssuesTask.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab;
2 |
3 | import com.intellij.openapi.progress.ProgressIndicator;
4 | import com.intellij.openapi.progress.Task;
5 | import com.intellij.openapi.project.Project;
6 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPIssue;
7 | import io.reactivex.Observer;
8 | import io.reactivex.disposables.Disposable;
9 | import org.jetbrains.annotations.NotNull;
10 | import org.jetbrains.annotations.Nullable;
11 |
12 | import java.util.List;
13 | import java.util.logging.Level;
14 | import java.util.logging.Logger;
15 |
16 | public class GetIssuesTask extends Task.Backgroundable {
17 |
18 | private static final Logger LOGGER = Logger.getLogger( GetIssuesTask.class.getName() );
19 |
20 | private final GitLabService gitLabService;
21 |
22 | private final GIPIssueObserver issueObserver;
23 |
24 | private final Integer projectId;
25 |
26 | public GetIssuesTask(@Nullable Project project, GitLabService gitLabService, GIPIssueObserver issueObserver, Integer projectId ) {
27 | super(project, "Getting Issues From Gitlab", true);
28 | this.gitLabService = gitLabService;
29 | this.issueObserver = issueObserver;
30 | this.projectId = projectId;
31 | }
32 |
33 | @Override
34 | public void run(@NotNull ProgressIndicator indicator) {
35 | indicator.setIndeterminate(true);
36 | request( 10, 1 );
37 | }
38 |
39 | private void request( final int limit, final int page ) {
40 | gitLabService.listProjectIssues( projectId, limit, page ).subscribe(new Observer>() {
41 | @Override
42 | public void onSubscribe(Disposable d) {
43 |
44 | }
45 |
46 | @Override
47 | public void onNext(List projects) {
48 | if ( projects == null ) {
49 | return ;
50 | }
51 |
52 | projects.forEach( issueObserver::accept );
53 |
54 | if ( projects.size() >= limit ) {
55 | request( limit, page + 1 );
56 | }
57 | }
58 |
59 | @Override
60 | public void onError(Throwable e) {
61 | LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e );
62 | }
63 |
64 | @Override
65 | public void onComplete() {
66 |
67 | }
68 | });
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/GetProjectsTask.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab;
2 |
3 | import com.intellij.openapi.progress.ProgressIndicator;
4 | import com.intellij.openapi.progress.Task;
5 | import com.intellij.openapi.project.Project;
6 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPProject;
7 | import io.reactivex.Observer;
8 | import io.reactivex.disposables.Disposable;
9 | import org.jetbrains.annotations.NotNull;
10 | import org.jetbrains.annotations.Nullable;
11 |
12 | import java.util.List;
13 | import java.util.logging.Level;
14 | import java.util.logging.Logger;
15 |
16 | public class GetProjectsTask extends Task.Backgroundable {
17 |
18 | private static final Logger LOGGER = Logger.getLogger( GetProjectsTask.class.getName() );
19 |
20 | private final GitLabService gitLabService;
21 |
22 | private final GIPProjectObserver projectObserver;
23 |
24 | private final Integer groupId;
25 |
26 | public GetProjectsTask(@Nullable Project project, GitLabService gitLabService, GIPProjectObserver projectObserver, Integer groupId ) {
27 | super(project, "Getting Projects From Gitlab", true);
28 | this.gitLabService = gitLabService;
29 | this.projectObserver = projectObserver;
30 | this.groupId = groupId;
31 | }
32 |
33 | @Override
34 | public void run(@NotNull ProgressIndicator indicator) {
35 | indicator.setIndeterminate(true);
36 | request( 10, 1 );
37 | }
38 |
39 | private void request( final int limit, final int page ) {
40 | gitLabService.listGroupProjects(groupId, limit, page).subscribe(new Observer>() {
41 | @Override
42 | public void onSubscribe(Disposable d) {
43 |
44 | }
45 |
46 | @Override
47 | public void onNext(List projects) {
48 | if ( projects == null ) {
49 | return ;
50 | }
51 |
52 | projects.forEach(projectObserver::accept);
53 |
54 | if ( projects.size() >= limit ) {
55 | request( limit, page + 1 );
56 | }
57 | }
58 |
59 | @Override
60 | public void onError(Throwable e) {
61 | LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e );
62 | }
63 |
64 | @Override
65 | public void onComplete() {
66 |
67 | }
68 | });
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/GetUsersTask.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab;
2 |
3 | import com.intellij.ide.plugins.PluginManager;
4 | import com.intellij.openapi.diagnostic.Logger;
5 | import com.intellij.openapi.progress.ProgressIndicator;
6 | import com.intellij.openapi.progress.Task;
7 | import com.intellij.openapi.project.Project;
8 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPUser;
9 | import io.reactivex.Observer;
10 | import io.reactivex.disposables.Disposable;
11 | import org.jetbrains.annotations.NotNull;
12 |
13 | import java.util.List;
14 |
15 | public class GetUsersTask extends Task.Backgroundable {
16 |
17 | private static final Logger LOG = Logger.getInstance("gitlab");
18 |
19 | private final GitLabService service;
20 |
21 | private final GIPUserObserver usersObserver;
22 |
23 | public GetUsersTask(final Project project, final GitLabService service, final GIPUserObserver usersObserver ) {
24 | super( project, "Get Users Task", true );
25 | this.service = service;
26 | this.usersObserver = usersObserver;
27 | }
28 |
29 | @Override
30 | public void run(@NotNull ProgressIndicator progressIndicator) {
31 | progressIndicator.setIndeterminate( true );
32 |
33 | usersObserver.onStartUsersUpdate();
34 |
35 | requestUsers( 1, usersObserver );
36 | }
37 |
38 | private void requestUsers( int page, GIPUserObserver usersObserver ) {
39 | service.listUsers( 10, page, true, "name", "asc" )
40 | .subscribe(new Observer>() {
41 | @Override
42 | public void onSubscribe(Disposable d) {
43 |
44 | }
45 |
46 | @Override
47 | public void onNext(List gipUsers) {
48 | if ( gipUsers == null || gipUsers.isEmpty() ) {
49 | return ;
50 | }
51 |
52 | usersObserver.accept( gipUsers );
53 |
54 | if ( gipUsers.size() >= 10 ) {
55 | requestUsers( page + 1, usersObserver );
56 | }
57 | }
58 |
59 | @Override
60 | public void onError(Throwable e) {
61 | LOG.error( e.getLocalizedMessage(), e );
62 |
63 | PluginManager.getLogger().error( e );
64 | }
65 |
66 | @Override
67 | public void onComplete() {
68 |
69 | }
70 | });
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/GitLabService.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab;
2 |
3 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPGroup;
4 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPIssue;
5 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPProject;
6 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPUser;
7 | import io.reactivex.Completable;
8 | import io.reactivex.Observable;
9 | import retrofit2.http.*;
10 |
11 | import java.util.List;
12 |
13 | public interface GitLabService {
14 |
15 | @GET( "groups" )
16 | Observable< List< GIPGroup > > listGroups( @Query("per_page") Integer limit,
17 | @Query("page") Integer page );
18 |
19 | @GET( "groups/{id}/projects" )
20 | Observable< List< GIPProject > > listGroupProjects( @Path("id") Integer groupId,
21 | @Query("per_page") Integer limit,
22 | @Query("page") Integer page );
23 |
24 | @GET( "projects/{id}/issues" )
25 | Observable< List > listProjectIssues( @Path("id") Integer projectId,
26 | @Query( "per_page" ) Integer limit,
27 | @Query( "page" ) Integer page );
28 |
29 | @GET( "users" )
30 | Observable< List > listUsers( @Query( "per_page" ) Integer limit,
31 | @Query( "page" ) Integer page,
32 | @Query( "active" ) Boolean active,
33 | @Query("order_by") String orderBy,
34 | @Query("sort") String sort );
35 |
36 | @POST( "projects/{projectId}/issues")
37 | Observable< GIPIssue > createIssue( @Path( "projectId" ) Integer projectId,
38 | @Query( "title" ) String title,
39 | @Query( "description" ) String description );
40 |
41 | @PUT( "projects/{projectId}/issues/{issueIid}" )
42 | Observable< GIPIssue > updateIssue( @Path( "projectId" ) Integer projectId,
43 | @Path( "issueIid" ) Integer projectIssueId,
44 | @Query( "title" ) String title,
45 | @Query( "description" ) String description,
46 | @Query( "state_event" ) String state );
47 |
48 | @PUT( "projects/{projectId}/issues/{issueIid}" )
49 | Observable< GIPIssue > changeIssueState( @Path( "projectId" ) Integer projectId,
50 | @Path( "issueIid" ) Integer projectIssueId,
51 | @Query( "state_event" ) String state );
52 |
53 | @DELETE( "projects/{projectId}/issues/{issueIid}" )
54 | Completable deleteIssue(@Path( "projectId" ) Integer projectId,
55 | @Path( "issueIid" ) Integer projectIssueId );
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/GitLabServiceSupplier.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab;
2 |
3 | import retrofit2.Retrofit;
4 |
5 | import java.util.function.Supplier;
6 |
7 | public class GitLabServiceSupplier implements Supplier< GitLabService > {
8 |
9 | private final Supplier connectionPropertiesSupplier;
10 |
11 | public GitLabServiceSupplier(Supplier connectionPropertiesSupplier) {
12 | this.connectionPropertiesSupplier = connectionPropertiesSupplier;
13 | }
14 |
15 | @Override
16 | public GitLabService get() {
17 | Retrofit retrofit = new RetrofitSupplier(connectionPropertiesSupplier).get();
18 |
19 | return retrofit.create(GitLabService.class);
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/GitLabToolWindowFactory.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab;
2 |
3 | import com.intellij.openapi.project.Project;
4 | import com.intellij.openapi.wm.ToolWindow;
5 | import com.intellij.openapi.wm.ToolWindowFactory;
6 | import com.intellij.ui.content.Content;
7 | import com.intellij.ui.content.ContentFactory;
8 | import com.neon.intellij.plugins.gitlab.controller.GLIController;
9 | import com.neon.intellij.plugins.gitlab.view.configurable.GitLabConfigurable;
10 | import org.jetbrains.annotations.NotNull;
11 |
12 | public class GitLabToolWindowFactory implements ToolWindowFactory {
13 |
14 | public GitLabToolWindowFactory() {
15 |
16 | }
17 |
18 | @Override
19 | public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
20 | ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();
21 |
22 | final ConnectionPropertiesSupplier connectionPropertiesSupplier = new ConnectionPropertiesSupplier();
23 |
24 | final GLIController controller = createPlugin(project, connectionPropertiesSupplier);
25 |
26 | final Content mainContent = contentFactory.createContent( controller.getView(), "", false );
27 |
28 | final Content initContent = contentFactory.createContent( new VoidComponentView( project ), "", false );
29 |
30 | GitLabConfigurable.getInstance().onApply( () -> {
31 | // switch views (if configurable got data
32 | if ( isConfigurableConfigured( connectionPropertiesSupplier ) ) {
33 |
34 | toolWindow.getContentManager().removeContent( initContent, true );
35 | toolWindow.getContentManager().addContent( mainContent );
36 |
37 | controller.run();
38 | }
39 | } );
40 |
41 | if ( ! isConfigurableConfigured( connectionPropertiesSupplier ) ) {
42 | toolWindow.getContentManager().addContent( initContent );
43 | } else {
44 | toolWindow.getContentManager().addContent( mainContent );
45 | controller.run();
46 | }
47 | }
48 |
49 | private boolean isConfigurableConfigured( ConnectionPropertiesSupplier connectionPropertiesSupplier ) {
50 | ConnectionPropertiesSupplier.ConnectionProperties connectionProperties = connectionPropertiesSupplier.get();
51 | return !(connectionProperties.host == null || connectionProperties.host.trim().isEmpty() ||
52 | connectionProperties.privateToken == null || connectionProperties.privateToken.trim().isEmpty());
53 | }
54 |
55 | private GLIController createPlugin( Project project, ConnectionPropertiesSupplier connectionPropertiesSupplier ) {
56 | final GitLabServiceSupplier gitLabServiceSupplier = new GitLabServiceSupplier(connectionPropertiesSupplier);
57 |
58 | return new GLIController( project, gitLabServiceSupplier );
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/OpenIssueEditorAction.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab;
2 |
3 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPIssue;
4 |
5 | import java.util.function.Consumer;
6 |
7 | public interface OpenIssueEditorAction extends Consumer {
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/RefreshProjectIssuesAction.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab;
2 |
3 | import com.neon.intellij.plugins.gitlab.model.intellij.GLProjectNode;
4 |
5 | import java.util.function.Consumer;
6 |
7 | public interface RefreshProjectIssuesAction extends Consumer {
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/RetrofitSupplier.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab;
2 |
3 | import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
4 | import okhttp3.HttpUrl;
5 | import okhttp3.Interceptor;
6 | import okhttp3.OkHttpClient;
7 | import okhttp3.Request;
8 | import retrofit2.Retrofit;
9 | import retrofit2.converter.moshi.MoshiConverterFactory;
10 |
11 | import java.util.Arrays;
12 | import java.util.function.Supplier;
13 |
14 | public class RetrofitSupplier implements Supplier {
15 |
16 | private final Supplier connectionPropertiesSupplier;
17 |
18 | public RetrofitSupplier( Supplier connectionPropertiesSupplier ) {
19 | this.connectionPropertiesSupplier = connectionPropertiesSupplier;
20 | }
21 |
22 | @Override
23 | public Retrofit get() {
24 | ConnectionPropertiesSupplier.ConnectionProperties connectionProperties = connectionPropertiesSupplier.get();
25 |
26 | Interceptor tokenInterceptor = chain -> {
27 | Request request = chain.request();
28 |
29 | request = request.newBuilder()
30 | .addHeader("Private-Token", connectionProperties.privateToken )
31 | .build();
32 |
33 | return chain.proceed(request);
34 | };
35 |
36 | OkHttpClient okHttpClient = getOkHttpClient( connectionProperties.ignoreSSLCertificateErrors, tokenInterceptor );
37 |
38 | HttpUrl baseUrl = HttpUrl.parse(connectionProperties.host)
39 | .newBuilder()
40 | .addPathSegments( "api/v4/" )
41 | .build();
42 |
43 | return new Retrofit.Builder()
44 | .baseUrl( baseUrl )
45 | .addConverterFactory(MoshiConverterFactory.create())
46 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
47 | .client(okHttpClient)
48 | .build();
49 | }
50 |
51 | private OkHttpClient getOkHttpClient( boolean unsafe, Interceptor ... interceptors ) {
52 | if ( unsafe ) {
53 | return new UnsafeOkHttpClientSupplier( Arrays.asList( interceptors ) ).get();
54 | }
55 | return new SafeOkHttpClientSupplier( Arrays.asList( interceptors ) ).get();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/SafeOkHttpClientSupplier.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab;
2 |
3 | import okhttp3.Interceptor;
4 | import okhttp3.OkHttpClient;
5 |
6 | import java.util.List;
7 | import java.util.function.Supplier;
8 |
9 | public class SafeOkHttpClientSupplier implements Supplier {
10 |
11 | private final List interceptors;
12 |
13 | public SafeOkHttpClientSupplier(List interceptors) {
14 | this.interceptors = interceptors;
15 | }
16 |
17 | @Override
18 | public OkHttpClient get() {
19 | OkHttpClient.Builder builder = new OkHttpClient.Builder();
20 |
21 | if ( interceptors != null ) {
22 | interceptors.forEach( interceptor -> builder.addInterceptor( interceptor ) );
23 | }
24 |
25 | return builder.build();
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/SaveIssueAction.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab;
2 |
3 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPIssue;
4 |
5 | import java.util.function.Function;
6 |
7 | public interface SaveIssueAction extends Function {
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/UnsafeOkHttpClientSupplier.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab;
2 |
3 | import okhttp3.Interceptor;
4 | import okhttp3.OkHttpClient;
5 |
6 | import javax.net.ssl.*;
7 | import java.security.cert.CertificateException;
8 | import java.util.List;
9 | import java.util.function.Supplier;
10 |
11 | public class UnsafeOkHttpClientSupplier implements Supplier< OkHttpClient > {
12 |
13 | private final List< Interceptor > interceptors;
14 |
15 | public UnsafeOkHttpClientSupplier(List interceptors) {
16 | this.interceptors = interceptors;
17 | }
18 |
19 | @Override
20 | public OkHttpClient get() {
21 | try {
22 | // Create a trust manager that does not validate certificate chains
23 | final TrustManager[] trustAllCerts = new TrustManager[] {
24 | new X509TrustManager() {
25 | @Override
26 | public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
27 | }
28 |
29 | @Override
30 | public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
31 | }
32 |
33 | @Override
34 | public java.security.cert.X509Certificate[] getAcceptedIssuers() {
35 | return new java.security.cert.X509Certificate[]{};
36 | }
37 | }
38 | };
39 |
40 | // Install the all-trusting trust manager
41 | final SSLContext sslContext = SSLContext.getInstance("SSL");
42 | sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
43 | // Create an ssl socket factory with our all-trusting manager
44 | final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
45 |
46 | OkHttpClient.Builder builder = new OkHttpClient.Builder();
47 | builder.sslSocketFactory(sslSocketFactory);
48 | builder.hostnameVerifier(new HostnameVerifier() {
49 | @Override
50 | public boolean verify(String hostname, SSLSession session) {
51 | return true;
52 | }
53 | });
54 |
55 | if ( interceptors != null ) {
56 | interceptors.forEach( interceptor -> builder.addInterceptor( interceptor ) );
57 | }
58 |
59 | return builder.build();
60 | } catch (Exception e) {
61 | throw new RuntimeException(e);
62 | }
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/VoidComponentView.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab;
2 |
3 | import com.intellij.openapi.options.ShowSettingsUtil;
4 | import com.intellij.openapi.project.Project;
5 | import com.neon.intellij.plugins.gitlab.view.configurable.GitLabConfigurable;
6 | import info.clearthought.layout.TableLayout;
7 | import info.clearthought.layout.TableLayoutConstraints;
8 |
9 | import javax.swing.*;
10 |
11 | public class VoidComponentView extends JPanel {
12 |
13 | public VoidComponentView( final Project project ) {
14 | JButton buttonPreferences = new JButton( "Open Plugin Configuration" );
15 | buttonPreferences.addActionListener(e -> ShowSettingsUtil.getInstance().showSettingsDialog( project, GitLabConfigurable.class ));
16 |
17 | TableLayout layout = new TableLayout( new double[][] {
18 | { TableLayout.FILL, TableLayout.MINIMUM, TableLayout.FILL },
19 | { TableLayout.FILL, TableLayout.PREFERRED, TableLayout.FILL }
20 | } );
21 | this.setLayout( layout );
22 | this.add( buttonPreferences, new TableLayoutConstraints( 1, 1, 1, 1 ) );
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/controller/GLIController.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.controller;
2 |
3 |
4 | import com.intellij.openapi.progress.ProgressManager;
5 | import com.intellij.openapi.project.Project;
6 | import com.neon.intellij.plugins.gitlab.*;
7 | import com.neon.intellij.plugins.gitlab.controller.editor.GLIssueVirtualFile;
8 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPIssue;
9 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPProject;
10 | import com.neon.intellij.plugins.gitlab.model.intellij.GLProjectNode;
11 | import com.neon.intellij.plugins.gitlab.view.GitLabView;
12 |
13 | import javax.swing.*;
14 | import java.util.logging.Logger;
15 |
16 | public class GLIController {
17 |
18 | private static final Logger LOGGER = Logger.getLogger( GLIController.class.getName() );
19 |
20 | private final Project project;
21 |
22 | private final JBFacade jbFacade;
23 |
24 | private final GitLabView view;
25 |
26 | private final GitLabServiceSupplier gitLabServiceSupplier;
27 |
28 | public GLIController(final Project project, final GitLabServiceSupplier gitLabServiceSupplier) {
29 | this.project = project;
30 | this.gitLabServiceSupplier = gitLabServiceSupplier;
31 |
32 | this.jbFacade = new JBFacade( project );
33 |
34 | this.view = new GitLabView( project,
35 | gipIssue -> openEditor( gipIssue ),
36 | projectNode -> refresh( projectNode ),
37 | gipIssue -> deleteIssue( gipIssue),
38 | (gipIssue, newState) -> changeState( gipIssue, newState ));
39 | }
40 |
41 | public GitLabView getView() {
42 | return view;
43 | }
44 |
45 | public void run() {
46 | refresh( view, view, view, view );
47 | }
48 |
49 | private void openEditor( GIPIssue issue ) {
50 | jbFacade.openEditor( new GLIssueVirtualFile( issue,
51 | gipIssue -> saveIssue( gipIssue ),
52 | vf -> closeEditor( vf ) ) );
53 | }
54 |
55 | private void closeEditor( final GLIssueVirtualFile vf ) {
56 | jbFacade.closeEditor(vf);
57 | }
58 |
59 | private void refreshSelectedProjectNodes() {
60 | GLProjectNode[] glProjectNodes = view.getSelectedNodes(GLProjectNode.class, null);
61 | if ( glProjectNodes != null && glProjectNodes.length > 0 ) {
62 | for (GLProjectNode glProjectNode : glProjectNodes) {
63 | refresh( glProjectNode );
64 | }
65 | }
66 | }
67 |
68 | private GIPIssue saveIssue( GIPIssue issue ) {
69 | GIPIssue response;
70 | if ( issue.id == null || issue.id <= 0 ) {
71 | response = gitLabServiceSupplier
72 | .get()
73 | .createIssue(issue.project_id, issue.title, issue.description)
74 | .blockingSingle();
75 | } else {
76 | response = gitLabServiceSupplier
77 | .get()
78 | .updateIssue( issue.project_id, issue.iid, issue.title, issue.description, issue.state )
79 | .blockingSingle();
80 | }
81 |
82 | refreshSelectedProjectNodes();
83 |
84 | return response;
85 | }
86 |
87 | private void deleteIssue(final GIPIssue issue) {
88 | gitLabServiceSupplier
89 | .get()
90 | .deleteIssue(issue.project_id, issue.iid)
91 | .blockingAwait();
92 |
93 | refreshSelectedProjectNodes();
94 | }
95 |
96 | private GIPIssue changeState(final GIPIssue issue, final String newState) {
97 | GIPIssue response = gitLabServiceSupplier
98 | .get()
99 | .changeIssueState(issue.project_id, issue.iid, newState)
100 | .blockingSingle();
101 |
102 | refreshSelectedProjectNodes();
103 |
104 | return response;
105 | }
106 | //
107 | private void refresh(GIPGroupObserver viewGroupObserver,
108 | GIPProjectObserver viewProjectObserver,
109 | GIPIssueObserver viewIssueObserver,
110 | GIPUserObserver viewUserObserver ) {
111 | GitLabService gitLabService = gitLabServiceSupplier.get();
112 |
113 | GIPGroupObserver groupObserver = group -> {
114 | SwingUtilities.invokeLater(() -> viewGroupObserver.accept( group ));
115 |
116 | GIPProjectObserver projectObserver = project -> {
117 | SwingUtilities.invokeLater(() -> viewProjectObserver.accept( project ));
118 |
119 | updateProjectIssues( gitLabService, project, viewProjectObserver, viewIssueObserver );
120 | };
121 |
122 | GetProjectsTask getProjectsTask = new GetProjectsTask( project, gitLabService, projectObserver, group.id);
123 | ProgressManager.getInstance().run(getProjectsTask);
124 | };
125 |
126 | groupObserver.onStartGroupsUpdate();
127 | ProgressManager.getInstance().run( new GetGroupsTask( project, gitLabService, groupObserver ) );
128 |
129 | ProgressManager.getInstance().run( new GetUsersTask( project, gitLabService, viewUserObserver ) );
130 | }
131 |
132 | private void updateProjectIssues( GitLabService gitLabService, GIPProject project,
133 | GIPProjectObserver viewProjectObserver,
134 | GIPIssueObserver viewIssueObserver ) {
135 | SwingUtilities.invokeLater(() -> {
136 | viewProjectObserver.onStartProjectUpdate( project );
137 |
138 | GetIssuesTask getIssuesTask = new GetIssuesTask(GLIController.this.project, gitLabService,
139 | issue -> SwingUtilities.invokeLater(() -> viewIssueObserver.accept(issue)), project.id);
140 | ProgressManager.getInstance().run(getIssuesTask);
141 | });
142 | }
143 |
144 | private void refresh( GLProjectNode projectNode ) {
145 | if ( projectNode == null ) {
146 | refresh( view, view, view, view );
147 | return ;
148 | }
149 |
150 | updateProjectIssues( gitLabServiceSupplier.get(), projectNode.getUserObject(), view, view );
151 | }
152 |
153 | }
154 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/controller/JBFacade.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.controller;
2 |
3 | import com.intellij.ide.util.PropertiesComponent;
4 | import com.intellij.openapi.fileEditor.FileEditorManager;
5 | import com.intellij.openapi.module.Module;
6 | import com.intellij.openapi.module.ModuleManager;
7 | import com.intellij.openapi.project.Project;
8 | import com.intellij.openapi.roots.ProjectRootManager;
9 | import com.intellij.openapi.vfs.VirtualFile;
10 | import com.neon.intellij.plugins.gitlab.model.intellij.ProjectModule;
11 |
12 | import java.util.LinkedList;
13 | import java.util.List;
14 |
15 | public class JBFacade {
16 |
17 | private final ProjectRootManager projectRootManager;
18 |
19 | private final ModuleManager moduleManager;
20 |
21 | private final FileEditorManager fileEditorManager;
22 |
23 | private final PropertiesComponent properties;
24 |
25 |
26 | public JBFacade(final Project project) {
27 | this.projectRootManager = ProjectRootManager.getInstance( project );
28 | this.moduleManager = ModuleManager.getInstance( project );
29 | this.fileEditorManager = FileEditorManager.getInstance(project);
30 | this.properties = PropertiesComponent.getInstance( project );
31 | }
32 |
33 | public List getModules() {
34 | List< ProjectModule > result = new LinkedList< ProjectModule >();
35 | Module[] modules = moduleManager.getModules();
36 | for (Module module : modules) {
37 | result.add( new ProjectModule( module ) );
38 | }
39 | return result;
40 | }
41 |
42 | public void openEditor( VirtualFile vf ) {
43 | fileEditorManager.openFile( vf, true );
44 | }
45 |
46 | public void closeEditor( VirtualFile vf ) {
47 | fileEditorManager.closeFile( vf );
48 | }
49 |
50 | public void setProperty( String key, String value ) {
51 | properties.setValue( key, value );
52 | }
53 |
54 | public String getProperty( String key ) {
55 | return properties.getValue( key );
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/controller/editor/GLAbstractFileEditor.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.controller.editor;
2 |
3 | import com.intellij.codeHighlighting.BackgroundEditorHighlighter;
4 | import com.intellij.ide.structureView.StructureViewBuilder;
5 | import com.intellij.openapi.fileEditor.FileEditor;
6 | import com.intellij.openapi.fileEditor.FileEditorLocation;
7 | import com.intellij.openapi.fileEditor.FileEditorState;
8 | import com.intellij.openapi.fileEditor.FileEditorStateLevel;
9 | import com.intellij.openapi.util.Key;
10 | import java.beans.PropertyChangeListener;
11 | import javax.swing.JComponent;
12 | import org.jetbrains.annotations.NotNull;
13 | import org.jetbrains.annotations.Nullable;
14 |
15 | public abstract class GLAbstractFileEditor implements FileEditor {
16 |
17 | public GLAbstractFileEditor() {
18 |
19 | }
20 |
21 | @Nullable
22 | @Override
23 | public JComponent getPreferredFocusedComponent() {
24 | return getComponent();
25 | }
26 |
27 |
28 | @NotNull
29 | @Override
30 | public FileEditorState getState(@NotNull FileEditorStateLevel fileEditorStateLevel) {
31 | return GLFileEditorState.DUMMY;
32 | }
33 |
34 | @Override
35 | public void setState(@NotNull FileEditorState fileEditorState) {
36 |
37 | }
38 |
39 | @Override
40 | public boolean isModified() {
41 | return false;
42 | }
43 |
44 | @Override
45 | public boolean isValid() {
46 | return true;
47 | }
48 |
49 | @Override
50 | public void selectNotify() {
51 |
52 | }
53 |
54 | @Override
55 | public void deselectNotify() {
56 |
57 | }
58 |
59 | @Override
60 | public void addPropertyChangeListener(@NotNull PropertyChangeListener propertyChangeListener) {
61 |
62 | }
63 |
64 | @Override
65 | public void removePropertyChangeListener(@NotNull PropertyChangeListener propertyChangeListener) {
66 |
67 | }
68 |
69 | @Nullable
70 | @Override
71 | public BackgroundEditorHighlighter getBackgroundHighlighter() {
72 | return null;
73 | }
74 |
75 | @Nullable
76 | @Override
77 | public FileEditorLocation getCurrentLocation() {
78 | return null;
79 | }
80 |
81 | @Nullable
82 | @Override
83 | public StructureViewBuilder getStructureViewBuilder() {
84 | return null;
85 | }
86 |
87 | @Override
88 | public void dispose() {
89 |
90 | }
91 |
92 | @Nullable
93 | @Override
94 | public T getUserData(@NotNull Key tKey) {
95 | return null;
96 | }
97 |
98 | @Override
99 | public void putUserData(@NotNull Key tKey, @Nullable T t) {
100 |
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/controller/editor/GLEditorProvider.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.controller.editor;
2 |
3 | import com.intellij.openapi.components.ApplicationComponent;
4 | import com.intellij.openapi.fileEditor.FileEditor;
5 | import com.intellij.openapi.fileEditor.FileEditorPolicy;
6 | import com.intellij.openapi.fileEditor.FileEditorProvider;
7 | import com.intellij.openapi.fileEditor.FileEditorState;
8 | import com.intellij.openapi.fileTypes.FileType;
9 | import com.intellij.openapi.project.Project;
10 | import com.intellij.openapi.vfs.VirtualFile;
11 | import org.jdom.Element;
12 | import org.jetbrains.annotations.NotNull;
13 |
14 | public class GLEditorProvider implements ApplicationComponent, FileEditorProvider {
15 |
16 | public static final FileType FILE_TYPE = new GLFileType();
17 |
18 |
19 | public GLEditorProvider() {
20 |
21 | }
22 |
23 |
24 | @Override
25 | public boolean accept(@NotNull Project project, @NotNull VirtualFile virtualFile) {
26 | return ( virtualFile.getFileType() == FILE_TYPE );
27 | }
28 |
29 | @NotNull
30 | @Override
31 | public FileEditor createEditor(@NotNull Project project, @NotNull VirtualFile virtualFile) {
32 | FileEditor result = null;
33 | if ( virtualFile instanceof GLIssueVirtualFile) {
34 | result = new GLIssueEditor( (GLIssueVirtualFile) virtualFile );
35 | }
36 | // TODO : resolv other kinds of 'files'
37 | return result;
38 | }
39 |
40 | @Override
41 | public void disposeEditor(@NotNull FileEditor fileEditor) {
42 | fileEditor.dispose();
43 | }
44 |
45 | @NotNull
46 | @Override
47 | public FileEditorState readState(@NotNull Element element, @NotNull Project project, @NotNull VirtualFile virtualFile) {
48 | return GLFileEditorState.DUMMY;
49 | }
50 |
51 | @Override
52 | public void writeState(@NotNull FileEditorState fileEditorState, @NotNull Project project, @NotNull Element element) {
53 |
54 | }
55 |
56 | @NotNull
57 | @Override
58 | public String getEditorTypeId() {
59 | return getComponentName();
60 | }
61 |
62 | @NotNull
63 | @Override
64 | public FileEditorPolicy getPolicy() {
65 | return FileEditorPolicy.HIDE_DEFAULT_EDITOR;
66 | }
67 |
68 | @NotNull
69 | public String getComponentName() {
70 | return "GLEditorProvider";
71 | }
72 |
73 | @Override
74 | public void initComponent() {
75 |
76 | }
77 |
78 | @Override
79 | public void disposeComponent() {
80 |
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/controller/editor/GLFileEditorState.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.controller.editor;
2 |
3 | import com.intellij.openapi.fileEditor.FileEditorState;
4 | import com.intellij.openapi.fileEditor.FileEditorStateLevel;
5 |
6 | public class GLFileEditorState implements FileEditorState {
7 |
8 | public static final FileEditorState DUMMY = new GLFileEditorState();
9 |
10 | @Override
11 | public boolean canBeMergedWith( FileEditorState otherState, FileEditorStateLevel level ) {
12 | return false;
13 | }
14 | }
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/controller/editor/GLFileType.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.controller.editor;
2 |
3 | import com.intellij.ide.structureView.StructureViewBuilder;
4 | import com.intellij.openapi.fileTypes.FileType;
5 | import com.intellij.openapi.fileTypes.SyntaxHighlighter;
6 | import com.intellij.openapi.project.Project;
7 | import com.intellij.openapi.vfs.VirtualFile;
8 | import javax.swing.Icon;
9 | import org.jetbrains.annotations.NonNls;
10 | import org.jetbrains.annotations.NotNull;
11 | import org.jetbrains.annotations.Nullable;
12 |
13 | public class GLFileType implements FileType {
14 |
15 | @NotNull
16 | @NonNls
17 | public String getName() {
18 | return "GitLab Integration file";
19 | }
20 |
21 | @NotNull
22 | public String getDescription() {
23 | return "GitLab Integration File";
24 | }
25 |
26 | @NotNull
27 | @NonNls
28 | public String getDefaultExtension() {
29 | return "gl";
30 | }
31 |
32 | @Nullable
33 | public Icon getIcon() {
34 | return null;
35 | }
36 |
37 | public boolean isBinary() {
38 | return false;
39 | }
40 |
41 | public boolean isReadOnly() {
42 | return false;
43 | }
44 |
45 | @Nullable
46 | @NonNls
47 | public String getCharset( @NotNull VirtualFile file ) {
48 | return null;
49 | }
50 |
51 | public String getCharset(@NotNull VirtualFile virtualFile, byte[] bytes) {
52 | return null;
53 | }
54 |
55 | @Nullable
56 | public SyntaxHighlighter getHighlighter( @Nullable Project project, final VirtualFile virtualFile ) {
57 | return null;
58 | }
59 |
60 | @Nullable
61 | public StructureViewBuilder getStructureViewBuilder( @NotNull VirtualFile file, @NotNull Project project ) {
62 | return null;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/controller/editor/GLIssueEditor.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.controller.editor;
2 |
3 | import com.neon.intellij.plugins.gitlab.view.issues.GLIssueEditorView;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | import javax.swing.*;
7 |
8 | public class GLIssueEditor extends GLAbstractFileEditor {
9 |
10 | private final GLIssueEditorView view;
11 |
12 | public GLIssueEditor(final GLIssueVirtualFile vf) {
13 | view = new GLIssueEditorView( vf );
14 | }
15 |
16 | @NotNull
17 | @Override
18 | public JComponent getComponent() {
19 | return view;
20 | }
21 |
22 | @NotNull
23 | @Override
24 | public String getName() {
25 | return "GitLab Issue Editor";
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/controller/editor/GLIssueVirtualFile.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.controller.editor;
2 |
3 | import com.intellij.testFramework.LightVirtualFile;
4 | import com.neon.intellij.plugins.gitlab.CloseIssueEditorAction;
5 | import com.neon.intellij.plugins.gitlab.SaveIssueAction;
6 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPIssue;
7 | import org.jetbrains.annotations.NotNull;
8 |
9 | public class GLIssueVirtualFile extends LightVirtualFile {
10 |
11 | private final SaveIssueAction saveIssueAction;
12 |
13 | private final CloseIssueEditorAction closeIssueEditorAction;
14 |
15 | private GIPIssue issue;
16 |
17 | public GLIssueVirtualFile(@NotNull GIPIssue issue, SaveIssueAction saveIssueAction, CloseIssueEditorAction closeIssueEditorAction ) {
18 | super( issue.title == null ? "" : issue.title, GLEditorProvider.FILE_TYPE, "" );
19 |
20 | this.saveIssueAction = saveIssueAction;
21 | this.closeIssueEditorAction = closeIssueEditorAction;
22 | this.issue = issue;
23 | }
24 |
25 | public GIPIssue getIssue() {
26 | return issue;
27 | }
28 |
29 | public void setIssue(GIPIssue issue) {
30 | this.issue = issue;
31 | }
32 |
33 | public void saveAndClose() {
34 | saveIssueAction.apply( issue );
35 | close();
36 | }
37 |
38 | public void close() {
39 | closeIssueEditorAction.accept( this );
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/model/EditableView.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.model;
2 |
3 | public interface EditableView< INPUT, OUTPUT > {
4 |
5 | void fill( INPUT input );
6 |
7 | OUTPUT save();
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/model/gitlab/GIPGroup.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.model.gitlab;
2 |
3 | public class GIPGroup {
4 |
5 | public Integer id;
6 |
7 | public String name;
8 |
9 | public String full_name;
10 |
11 | public String full_path;
12 |
13 | public String description;
14 |
15 | public String web_url;
16 |
17 | public Integer parent_id;
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/model/gitlab/GIPIssue.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.model.gitlab;
2 |
3 | import java.util.List;
4 |
5 | public class GIPIssue {
6 |
7 | public Integer id;
8 |
9 | public Integer iid;
10 |
11 | public String title;
12 |
13 | public String description;
14 |
15 | public String state;
16 |
17 | public Integer project_id;
18 |
19 | public List< GIPIssueAssignee > assignees;
20 |
21 | public GIPIssueAuthor author;
22 |
23 | public String web_url;
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/model/gitlab/GIPIssueAssignee.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.model.gitlab;
2 |
3 | public class GIPIssueAssignee {
4 |
5 | public Integer id;
6 |
7 | public String name;
8 |
9 | public String username;
10 |
11 | public String state;
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/model/gitlab/GIPIssueAuthor.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.model.gitlab;
2 |
3 | public class GIPIssueAuthor {
4 |
5 | public Integer id;
6 |
7 | public String name;
8 |
9 | public String username;
10 |
11 | public String state;
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/model/gitlab/GIPNamespace.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.model.gitlab;
2 |
3 | public class GIPNamespace {
4 |
5 | public Integer id;
6 |
7 | public String name;
8 |
9 | public String path;
10 |
11 | public String full_path;
12 |
13 | public String kind;
14 |
15 | public Integer parent_id;
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/model/gitlab/GIPProject.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.model.gitlab;
2 |
3 | public class GIPProject {
4 |
5 | public Integer id;
6 |
7 | public String name;
8 |
9 | public String description;
10 |
11 | public GIPNamespace namespace;
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/model/gitlab/GIPUser.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.model.gitlab;
2 |
3 | public class GIPUser {
4 |
5 | public Integer id;
6 |
7 | public String name;
8 |
9 | public String username;
10 |
11 | public String state;
12 |
13 | public String avatar_url;
14 |
15 | public String web_url;
16 |
17 | public String email;
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/model/intellij/ConfigurableState.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.model.intellij;
2 |
3 | import com.intellij.openapi.components.PersistentStateComponent;
4 | import com.intellij.openapi.components.ServiceManager;
5 | import com.intellij.openapi.components.State;
6 | import com.intellij.openapi.components.Storage;
7 | import com.intellij.util.xmlb.XmlSerializerUtil;
8 | import org.jetbrains.annotations.Nullable;
9 |
10 | @State(
11 | name = "ConfigurableState",
12 | storages = {
13 | @Storage( id = "default", file = "$APP_CONFIG$/gitlab-integration-settings.xml" )
14 | }
15 | )
16 | public class ConfigurableState implements PersistentStateComponent< ConfigurableState > {
17 |
18 | public String host;
19 |
20 | public String token;
21 |
22 | public Boolean ignoreCertificateErrors = true;
23 |
24 |
25 | public ConfigurableState() {
26 |
27 | }
28 |
29 |
30 | public static ConfigurableState getInstance() {
31 | return ServiceManager.getService( ConfigurableState.class );
32 | }
33 |
34 | @Nullable
35 | @Override
36 | public ConfigurableState getState() {
37 | return this;
38 | }
39 |
40 | @Override
41 | public void loadState( ConfigurableState configurableState ) {
42 | XmlSerializerUtil.copyBean( configurableState, this );
43 | }
44 |
45 | public String getHost() {
46 | return host == null ? "" : host;
47 | }
48 |
49 | public void setHost(String host) {
50 | this.host = host;
51 | }
52 |
53 | public String getToken() {
54 | return token == null ? "" : token;
55 | }
56 |
57 | public void setToken(String token) {
58 | this.token = token;
59 | }
60 |
61 | public Boolean getIgnoreCertificateErrors() {
62 | return ignoreCertificateErrors;
63 | }
64 |
65 | public void setIgnoreCertificateErrors(Boolean ignoreCertificateErrors) {
66 | this.ignoreCertificateErrors = ignoreCertificateErrors;
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/model/intellij/GLGroupNode.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.model.intellij;
2 |
3 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPGroup;
4 |
5 | public class GLGroupNode extends GLTreeNode {
6 |
7 | public GLGroupNode( GIPGroup group ) {
8 | super( group, true );
9 | }
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/model/intellij/GLIssueNode.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.model.intellij;
2 |
3 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPIssue;
4 |
5 | public class GLIssueNode extends GLTreeNode {
6 |
7 | public GLIssueNode( GIPIssue issue ) {
8 | super( issue, false );
9 | }
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/model/intellij/GLProjectNode.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.model.intellij;
2 |
3 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPProject;
4 |
5 | public class GLProjectNode extends GLTreeNode {
6 |
7 | public GLProjectNode( GIPProject project ) {
8 | super( project, true );
9 | }
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/model/intellij/GLTreeNode.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.model.intellij;
2 |
3 | import javax.swing.tree.DefaultMutableTreeNode;
4 |
5 | public class GLTreeNode< TYPE > extends DefaultMutableTreeNode {
6 |
7 | public GLTreeNode() {
8 | }
9 |
10 | public GLTreeNode( TYPE userObject ) {
11 | super(userObject);
12 | }
13 |
14 | public GLTreeNode( TYPE userObject, boolean allowsChildren ) {
15 | super(userObject, allowsChildren);
16 | }
17 |
18 | public TYPE getUserObject() {
19 | return (TYPE) super.getUserObject();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/model/intellij/ProjectModule.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.model.intellij;
2 |
3 | import com.intellij.openapi.module.Module;
4 | import com.intellij.openapi.roots.ModuleRootManager;
5 | import org.jetbrains.annotations.NotNull;
6 |
7 | public class ProjectModule {
8 |
9 | private final ModuleRootManager moduleRootManager;
10 |
11 | private final Module module;
12 |
13 | public ProjectModule( @NotNull Module module ) {
14 | this.module = module;
15 | this.moduleRootManager = ModuleRootManager.getInstance( module );
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/view/GitLabView.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.view;
2 |
3 | import com.intellij.icons.AllIcons;
4 | import com.intellij.openapi.actionSystem.*;
5 | import com.intellij.openapi.options.ShowSettingsUtil;
6 | import com.intellij.openapi.project.Project;
7 | import com.intellij.ui.treeStructure.Tree;
8 | import com.neon.intellij.plugins.gitlab.*;
9 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPGroup;
10 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPIssue;
11 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPProject;
12 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPUser;
13 | import com.neon.intellij.plugins.gitlab.model.intellij.GLProjectNode;
14 | import com.neon.intellij.plugins.gitlab.view.configurable.GitLabConfigurable;
15 | import com.neon.intellij.plugins.gitlab.view.toolwindow.GLIssueListView;
16 | import com.neon.intellij.plugins.gitlab.view.toolwindow.GLIssuesFilterView;
17 |
18 | import javax.swing.*;
19 | import java.awt.*;
20 | import java.util.List;
21 |
22 | public class GitLabView extends JPanel implements GIPGroupObserver, GIPProjectObserver, GIPIssueObserver, GIPUserObserver {
23 |
24 | private final GLIssueListView glIssueListView ;
25 |
26 | private final GLIssuesFilterView glIssuesFilterView;
27 |
28 | private final Project project;
29 |
30 | public GitLabView(final Project project,
31 | final OpenIssueEditorAction openIssueEditorAction,
32 | final RefreshProjectIssuesAction refreshProjectIssuesAction,
33 | final DeleteIssueAction deleteIssueAction,
34 | final ChangeIssueStateAction changeIssueStateAction) {
35 | this.project = project;
36 |
37 | this.glIssueListView = new GLIssueListView(openIssueEditorAction, refreshProjectIssuesAction,
38 | deleteIssueAction, changeIssueStateAction);
39 | this.glIssuesFilterView = new GLIssuesFilterView( glIssueListView );
40 |
41 | this.setLayout( new BorderLayout( 5, 5 ) );
42 | this.add( glIssuesFilterView, BorderLayout.NORTH );
43 | this.add( buildActionsPanel( this, openIssueEditorAction, refreshProjectIssuesAction ), BorderLayout.WEST );
44 | this.add( glIssueListView, BorderLayout.CENTER );
45 | }
46 |
47 | public < T > T[] getSelectedNodes( Class< T > clazz, Tree.NodeFilter< T > filter ) {
48 | return glIssueListView.getSelectedNodes( clazz, filter );
49 | }
50 |
51 | private Component buildActionsPanel( final JComponent target,
52 | final OpenIssueEditorAction openIssueEditorAction,
53 | final RefreshProjectIssuesAction refreshProjectIssuesAction ) {
54 | final DefaultActionGroup actionGroup = new DefaultActionGroup();
55 |
56 | actionGroup.add(new AnAction( "Settings", "Open plugin settings", AllIcons.General.Settings ) {
57 | @Override
58 | public void actionPerformed(AnActionEvent e) {
59 | ShowSettingsUtil.getInstance().showSettingsDialog( project, GitLabConfigurable.class );
60 | }
61 | });
62 | actionGroup.addSeparator();
63 | actionGroup.add( new AnAction( "Refresh All", "Refresh connection settings and projects list", AllIcons.Actions.Refresh ) {
64 | @Override
65 | public void actionPerformed(AnActionEvent anActionEvent) {
66 | GLProjectNode[] selected = glIssueListView.getSelectedNodes( GLProjectNode.class, null );
67 | if ( selected != null && selected.length > 0 ) {
68 | for (GLProjectNode glProjectNode : selected) {
69 | refreshProjectIssuesAction.accept( glProjectNode );
70 | }
71 | } else {
72 | refreshProjectIssuesAction.accept(null);
73 | }
74 | }
75 | });
76 | actionGroup.addSeparator();
77 | actionGroup.add( new AnAction("New Issue", "", AllIcons.General.Add) {
78 | @Override
79 | public void actionPerformed(AnActionEvent anActionEvent) {
80 | GLProjectNode[] selected = glIssueListView.getSelectedNodes(GLProjectNode.class, null);
81 | if (selected != null && selected.length > 0) {
82 | GLProjectNode node = selected[0];
83 | GIPProject project = node.getUserObject();
84 |
85 | GIPIssue issue = new GIPIssue();
86 | issue.project_id = project.id;
87 | openIssueEditorAction.accept(issue);
88 | }
89 | }
90 |
91 | @Override
92 | public void update(AnActionEvent e) {
93 | GLProjectNode[] glProjectNodes = glIssueListView.getSelectedNodes(GLProjectNode.class, null);
94 |
95 | Presentation presentation = e.getPresentation();
96 | presentation.setEnabled( glProjectNodes != null && glProjectNodes.length == 1 );
97 | }
98 | } );
99 |
100 | // actionGroup.addSeparator();
101 | // actionGroup.add( new AnAction( "Expand All", "", AllIcons.Actions.Expandall ) {
102 | // @Override
103 | // public void actionPerformed(AnActionEvent anActionEvent) {
104 | //// TODO : to implement
105 | // }
106 | // });
107 | // actionGroup.add(new AnAction("Collapse All", "", AllIcons.Actions.Collapseall) {
108 | // @Override
109 | // public void actionPerformed(AnActionEvent anActionEvent) {
110 | //// TODO : to implement
111 | // }
112 | // });
113 | // actionGroup.add( new AnAction( "Group By Module", "", AllIcons.Actions.GroupByModule ) {
114 | // @Override
115 | // public void actionPerformed(AnActionEvent anActionEvent) {
116 | //// TODO : to implement
117 | // }
118 | // });
119 |
120 | ActionManager actionManager = ActionManager.getInstance();
121 | ActionToolbar actionToolbar = actionManager.createActionToolbar("Gitlab Integration Toolbar", actionGroup, false);
122 | actionToolbar.setTargetComponent( target );
123 | return actionToolbar.getComponent();
124 | }
125 |
126 |
127 | @Override
128 | public void onStartGroupsUpdate() {
129 | glIssueListView.onStartGroupsUpdate();
130 | }
131 |
132 | @Override
133 | public void accept(GIPGroup group) {
134 | glIssueListView.accept( group );
135 | }
136 |
137 | @Override
138 | public void onStartProjectUpdate( GIPProject project ) {
139 | glIssueListView.onStartProjectUpdate( project );
140 | }
141 |
142 | @Override
143 | public void accept(GIPProject project) {
144 | glIssueListView.accept( project );
145 | }
146 |
147 | @Override
148 | public void accept(GIPIssue issue) {
149 | glIssueListView.accept( issue );
150 | }
151 |
152 | @Override
153 | public void onStartUsersUpdate() {
154 | glIssuesFilterView.onStartUsersUpdate();
155 | }
156 |
157 | @Override
158 | public void accept(List users) {
159 | glIssuesFilterView.accept( users );
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/view/configurable/GitLabConfigurable.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.view.configurable;
2 |
3 | import com.intellij.openapi.options.ConfigurationException;
4 | import com.intellij.openapi.options.SearchableConfigurable;
5 | import com.neon.intellij.plugins.gitlab.model.intellij.ConfigurableState;
6 | import org.jetbrains.annotations.Nls;
7 | import org.jetbrains.annotations.NotNull;
8 | import org.jetbrains.annotations.Nullable;
9 |
10 | import javax.swing.*;
11 | import java.net.URI;
12 |
13 | public class GitLabConfigurable implements SearchableConfigurable {
14 |
15 | private final ConfigurableState settings = ConfigurableState.getInstance();
16 |
17 | private static GitLabConfigurable INSTANCE = null;
18 |
19 | private SettingsView view;
20 |
21 | private Runnable onApply;
22 |
23 | private GitLabConfigurable( ) {
24 |
25 | }
26 |
27 | public static GitLabConfigurable getInstance() {
28 | if ( INSTANCE == null ) {
29 | INSTANCE = new GitLabConfigurable();
30 | }
31 | return INSTANCE;
32 | }
33 |
34 | public void onApply( Runnable runnable ) {
35 | this.onApply = runnable;
36 | }
37 |
38 | @Nls
39 | @Override
40 | public String getDisplayName() {
41 | return "GitLab Integration";
42 | }
43 |
44 | @Nullable
45 | @Override
46 | public String getHelpTopic() {
47 | return null;
48 | }
49 |
50 | @Override
51 | public JComponent createComponent() {
52 | view = new SettingsView();
53 | reset();
54 | return view;
55 | }
56 |
57 | /**
58 | * method constantly called to check for changed content in the view.
59 | * if false, 'apply' button will be disabled.
60 | *
61 | */
62 | public boolean isModified() {
63 | Object[] save = view.save();
64 | return save == null
65 | || ! settings.getHost().equals( save[0] )
66 | || ! settings.getToken().equals( save[1] )
67 | || ! settings.getIgnoreCertificateErrors().equals( save[2] );
68 | }
69 |
70 | /**
71 | * called on 'apply' or 'ok' button click.
72 | */
73 | @Override
74 | public void apply() throws ConfigurationException {
75 | Object[] save = view.save();
76 |
77 | String host = (String) save[0];
78 | if ( host != null && ! host.trim().isEmpty() ) {
79 | try {
80 | new URI(host).toURL();
81 | } catch ( Exception e) {
82 | throw new ConfigurationException("invalid gitlab url");
83 | }
84 | }
85 |
86 | if (host != null && ! host.trim().isEmpty()) {
87 | String token = (String) save[1];
88 | if (token == null || token.trim().isEmpty()) {
89 | throw new ConfigurationException("invalid gitlab authentication token");
90 | }
91 | }
92 |
93 | settings.setHost((String) save[0]);
94 | settings.setToken((String) save[1]);
95 | settings.setIgnoreCertificateErrors( ( Boolean ) save[2] );
96 | }
97 |
98 | /**
99 | * called on 'cancel' button click.
100 | */
101 | @Override
102 | public void reset() {
103 | if ( view != null ) {
104 | view.fill(settings);
105 | }
106 | }
107 |
108 | @Override
109 | public void disposeUIResources() {
110 | view = null;
111 | if ( onApply != null ) {
112 | onApply.run();
113 | }
114 | }
115 |
116 | @NotNull
117 | @Override
118 | public String getId() {
119 | return "com.neon.intellij.plugins.gitlab.configurable";
120 | }
121 |
122 | @Nullable
123 | @Override
124 | public Runnable enableSearch(String option) {
125 | return null;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/view/configurable/GitLabConfigurableProvider.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.view.configurable;
2 |
3 | import com.intellij.openapi.options.Configurable;
4 | import com.intellij.openapi.options.ConfigurableProvider;
5 | import org.jetbrains.annotations.Nullable;
6 |
7 | public class GitLabConfigurableProvider extends ConfigurableProvider {
8 |
9 | public GitLabConfigurableProvider() {
10 |
11 | }
12 |
13 | @Nullable
14 | @Override
15 | public Configurable createConfigurable() {
16 | // TODO: should set onApply on gitlab configurable so the view updates itself automatically
17 | return GitLabConfigurable.getInstance();
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/view/configurable/SettingsView.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.view.configurable;
2 |
3 | import com.neon.intellij.plugins.gitlab.model.EditableView;
4 | import com.neon.intellij.plugins.gitlab.model.intellij.ConfigurableState;
5 | import info.clearthought.layout.TableLayout;
6 | import info.clearthought.layout.TableLayoutConstraints;
7 |
8 | import javax.swing.JCheckBox;
9 | import javax.swing.JLabel;
10 | import javax.swing.JPanel;
11 | import javax.swing.JTextField;
12 |
13 | public class SettingsView extends JPanel implements EditableView {
14 |
15 | private final JLabel labelHost = new JLabel( "GitLab URL (ie, https://gitlab.com)" );
16 | private final JTextField textHost = new JTextField();
17 |
18 | private final JLabel labelAPI = new JLabel( "GitLab API Key" );
19 | private final JTextField textAPI = new JTextField();
20 |
21 | private final JLabel labelAPIHowTo = new JLabel( "Create a new API scoped on your gitlab: Settings > Access Tokens" );
22 |
23 | private final JCheckBox checkIgnoreCertificateErrors = new JCheckBox( "Ignore Certificate Errors", true );
24 |
25 | public SettingsView( ) {
26 | setupLayout();
27 | }
28 |
29 | private void setupLayout() {
30 | TableLayout layout = new TableLayout(
31 | new double[]{TableLayout.FILL},
32 | new double[]{
33 | TableLayout.MINIMUM, TableLayout.MINIMUM, TableLayout.MINIMUM,
34 | TableLayout.MINIMUM, TableLayout.MINIMUM, TableLayout.MINIMUM,
35 | TableLayout.MINIMUM
36 | }
37 | );
38 | layout.setVGap( 5 );
39 | layout.setHGap( 5 );
40 | this.setLayout( layout );
41 | this.add( labelHost, new TableLayoutConstraints( 0, 0, 0, 0, TableLayout.FULL, TableLayout.FULL ) );
42 | this.add( textHost, new TableLayoutConstraints( 0, 1, 0, 1, TableLayout.FULL, TableLayout.FULL ) );
43 | this.add( labelAPI, new TableLayoutConstraints( 0, 2, 0, 2, TableLayout.FULL, TableLayout.FULL ) );
44 | this.add( textAPI, new TableLayoutConstraints( 0, 3, 0, 3, TableLayout.FULL, TableLayout.FULL ) );
45 | this.add( labelAPIHowTo, new TableLayoutConstraints( 0, 4, 0, 4, TableLayout.FULL, TableLayout.FULL ) );
46 | this.add( new JLabel( " " ), new TableLayoutConstraints( 0, 5, 0, 5, TableLayout.FULL, TableLayout.FULL ) );
47 | this.add( checkIgnoreCertificateErrors, new TableLayoutConstraints( 0, 6, 0, 6, TableLayout.FULL, TableLayout.FULL ) );
48 | }
49 |
50 | @Override
51 | public void fill( ConfigurableState state ) {
52 | textHost.setText( state == null ? "" : state.getHost() );
53 | textAPI.setText( state == null ? "" : state.getToken() );
54 | checkIgnoreCertificateErrors.setSelected( state == null ? true : state.getIgnoreCertificateErrors() );
55 | }
56 |
57 | @Override
58 | public Object[] save() {
59 | return new Object[] { textHost.getText(), textAPI.getText(), checkIgnoreCertificateErrors.isSelected() };
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/view/issues/GLIssueEditorView.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.view.issues;
2 |
3 | import com.intellij.icons.AllIcons;
4 | import com.intellij.openapi.diagnostic.Logger;
5 | import com.neon.intellij.plugins.gitlab.controller.editor.GLIssueVirtualFile;
6 | import com.neon.intellij.plugins.gitlab.model.EditableView;
7 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPIssue;
8 | import info.clearthought.layout.TableLayout;
9 | import info.clearthought.layout.TableLayoutConstraints;
10 | import org.jetbrains.annotations.NotNull;
11 |
12 | import javax.swing.*;
13 |
14 | public class GLIssueEditorView extends JPanel implements EditableView {
15 |
16 | private static final Logger LOG = Logger.getInstance("gitlab");
17 |
18 | private final JButton buttonSave = new JButton( "save", AllIcons.Actions.Menu_saveall );
19 | private final JButton buttonClose = new JButton( "close", AllIcons.Actions.Close );
20 |
21 | private final JLabel labelTitle = new JLabel( "Title" );
22 | private final JTextField textTitle = new JTextField();
23 |
24 | private final JLabel labelDescription = new JLabel( "Description" );
25 | private final JTextArea textDescription = new JTextArea( 5, 20 );
26 |
27 | private GLIssueVirtualFile virtualFile;
28 |
29 | private GIPIssue model;
30 |
31 |
32 | public GLIssueEditorView( @NotNull final GLIssueVirtualFile vf ) {
33 | this.virtualFile = vf;
34 | this.model = vf.getIssue();
35 |
36 | setupComponents();
37 | setupLayout();
38 |
39 | fill( vf.getIssue() );
40 | }
41 |
42 | private void setupComponents() {
43 | textDescription.setWrapStyleWord( true );
44 | textDescription.setLineWrap( true );
45 |
46 | buttonSave.addActionListener(e -> {
47 | virtualFile.setIssue( save() );
48 | virtualFile.saveAndClose();
49 | });
50 | buttonClose.addActionListener(e -> virtualFile.close());
51 | }
52 |
53 | private void setupLayout() {
54 | JScrollPane dp = new JScrollPane( textDescription, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER );
55 |
56 | TableLayout layoutFields = new TableLayout(
57 | new double[]{TableLayout.MINIMUM, TableLayout.FILL},
58 | new double[]{TableLayout.MINIMUM, TableLayout.FILL}
59 | );
60 | layoutFields.setHGap( 5 );
61 | layoutFields.setVGap( 5 );
62 | JPanel fieldsPanel = new JPanel( layoutFields );
63 | fieldsPanel.add( labelTitle, new TableLayoutConstraints( 0, 0, 0, 0 ) );
64 | fieldsPanel.add( textTitle, new TableLayoutConstraints( 1, 0, 1, 0 ) );
65 |
66 | fieldsPanel.add( labelDescription, new TableLayoutConstraints( 0, 1, 0, 1, TableLayout.LEFT, TableLayout.TOP ) );
67 | fieldsPanel.add( dp, new TableLayoutConstraints( 1, 1, 1, 1 ) );
68 |
69 | TableLayout layoutButtons = new TableLayout(
70 | new double[]{TableLayout.MINIMUM, TableLayout.MINIMUM},
71 | new double[]{TableLayout.MINIMUM}
72 | );
73 | layoutButtons.setHGap( 5 );
74 | JPanel panelBottom = new JPanel( layoutButtons );
75 | panelBottom.add( buttonSave, new TableLayoutConstraints( 0, 0, 0, 0 ) );
76 | panelBottom.add( buttonClose, new TableLayoutConstraints( 1, 0, 1, 0 ) );
77 |
78 |
79 | TableLayout layout = new TableLayout(
80 | new double[]{ 5, TableLayout.FILL, 5 },
81 | new double[]{ TableLayout.FILL, TableLayout.MINIMUM }
82 | );
83 | layout.setHGap( 5 );
84 | layout.setVGap( 5 );
85 | this.setLayout( layout );
86 |
87 | this.add( fieldsPanel, new TableLayoutConstraints(1, 0, 1, 0 ) );
88 | this.add( panelBottom, new TableLayoutConstraints(1, 1, 1, 1, TableLayout.CENTER, TableLayout.CENTER) );
89 | }
90 |
91 | private void clear() {
92 | textTitle.setText( "" );
93 | textDescription.setText( "" );
94 | }
95 |
96 | @Override
97 | public void fill( GIPIssue issue ) {
98 | clear();
99 | textTitle.setText( issue.title );
100 | textDescription.setText( issue.description );
101 | }
102 |
103 | @Override
104 | public GIPIssue save() {
105 | model.title = textTitle.getText();
106 | model.description = textDescription.getText();
107 | return model;
108 | }
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/view/toolwindow/FilteredTreeModel.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.view.toolwindow;
2 |
3 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPGroup;
4 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPIssue;
5 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPProject;
6 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPUser;
7 | import com.neon.intellij.plugins.gitlab.model.intellij.GLGroupNode;
8 | import com.neon.intellij.plugins.gitlab.model.intellij.GLIssueNode;
9 | import com.neon.intellij.plugins.gitlab.model.intellij.GLProjectNode;
10 |
11 | import javax.swing.event.TreeModelListener;
12 | import javax.swing.tree.DefaultTreeModel;
13 | import javax.swing.tree.TreeModel;
14 | import javax.swing.tree.TreeNode;
15 | import javax.swing.tree.TreePath;
16 |
17 | /**
18 | * kindly 'stolen' and adapted from http://www.adrianwalker.org/2012/04/filtered-jtree.html
19 | */
20 | public final class FilteredTreeModel implements TreeModel {
21 |
22 | private TreeModel treeModel;
23 |
24 | private String filter;
25 |
26 | private boolean showEmptyNodes = false;
27 |
28 | private boolean showClosedIssues = false;
29 |
30 | private GIPUser author;
31 |
32 | private GIPUser assignee;
33 |
34 |
35 | public FilteredTreeModel( final TreeNode root ) {
36 | this( new DefaultTreeModel( root ) );
37 | }
38 |
39 | public FilteredTreeModel(final TreeModel treeModel) {
40 | this.treeModel = treeModel;
41 | this.filter = "";
42 | }
43 |
44 | public TreeModel getTreeModel() {
45 | return treeModel;
46 | }
47 |
48 | public void setShowEmptyNodes(boolean showEmptyNodes) {
49 | this.showEmptyNodes = showEmptyNodes;
50 | }
51 |
52 | public void setFilter(final String filter) {
53 | this.filter = filter;
54 | }
55 |
56 | public void setShowClosedIssues(boolean showClosedIssues) {
57 | this.showClosedIssues = showClosedIssues;
58 | }
59 |
60 | public void setAuthor(GIPUser author) {
61 | this.author = author;
62 | }
63 |
64 | public void setAssignee(GIPUser assignee) {
65 | this.assignee = assignee;
66 | }
67 |
68 | private boolean recursiveMatch( final Object node ) {
69 | boolean matches = true;
70 |
71 | if ( node instanceof GLGroupNode ) {
72 | GIPGroup group = ((GLGroupNode) node).getUserObject();
73 |
74 | if ( ! showEmptyNodes ) {
75 | matches = ((GLGroupNode) node).getChildCount() > 0;
76 | }
77 |
78 | matches &= group.name.toLowerCase().contains(filter.toLowerCase());
79 | } else if ( node instanceof GLProjectNode ) {
80 | GIPProject project = ((GLProjectNode) node).getUserObject();
81 |
82 | if ( ! showEmptyNodes ) {
83 | matches = ((GLProjectNode) node).getChildCount() > 0;
84 | }
85 |
86 | matches &= project.name.toLowerCase().contains(filter.toLowerCase());
87 | } else if ( node instanceof GLIssueNode ) {
88 | GIPIssue issue = ((GLIssueNode) node).getUserObject();
89 |
90 | // by state
91 | if ( ! showClosedIssues ) {
92 | matches = ! "closed".equalsIgnoreCase( issue.state );
93 | }
94 |
95 | // by text
96 | if ( filter != null && ! filter.trim().isEmpty() ) {
97 | matches &= issue.title.toLowerCase().contains(filter.toLowerCase());
98 | }
99 |
100 | // by author
101 | if ( author != null && author.id != null ) {
102 | matches &= author.id.equals( issue.author.id );
103 | }
104 |
105 | // by assignee
106 | if ( assignee != null && assignee.id != null ) {
107 | matches &= issue.assignees != null && ! issue.assignees.isEmpty();
108 | if ( matches ) {
109 | matches = issue.assignees.stream()
110 | .anyMatch(gipIssueAssignee -> assignee.id.equals( gipIssueAssignee.id ));
111 | }
112 | }
113 | }
114 |
115 | int childCount = treeModel.getChildCount(node);
116 | for (int i = 0; i < childCount; i++) {
117 | Object child = treeModel.getChild(node, i);
118 | matches |= recursiveMatch( child );
119 | }
120 |
121 | return matches;
122 | }
123 |
124 | @Override
125 | public Object getRoot() {
126 | return treeModel.getRoot();
127 | }
128 |
129 | @Override
130 | public Object getChild(final Object parent, final int index) {
131 | int count = 0;
132 | int childCount = treeModel.getChildCount(parent);
133 | for (int i = 0; i < childCount; i++) {
134 | Object child = treeModel.getChild(parent, i);
135 | if (recursiveMatch(child)) {
136 | if (count == index) {
137 | return child;
138 | }
139 | count++;
140 | }
141 | }
142 | return null;
143 | }
144 |
145 | @Override
146 | public int getChildCount(final Object parent) {
147 | int count = 0;
148 | int childCount = treeModel.getChildCount(parent);
149 | for (int i = 0; i < childCount; i++) {
150 | Object child = treeModel.getChild(parent, i);
151 | if (recursiveMatch(child)) {
152 | count++;
153 | }
154 | }
155 | return count;
156 | }
157 |
158 | @Override
159 | public boolean isLeaf(final Object node) {
160 | return treeModel.isLeaf(node);
161 | }
162 |
163 | @Override
164 | public void valueForPathChanged(final TreePath path, final Object newValue) {
165 | treeModel.valueForPathChanged(path, newValue);
166 | }
167 |
168 | @Override
169 | public int getIndexOfChild(final Object parent, final Object childToFind) {
170 | int childCount = treeModel.getChildCount(parent);
171 | for (int i = 0; i < childCount; i++) {
172 | Object child = treeModel.getChild(parent, i);
173 | if (recursiveMatch(child)) {
174 | if (childToFind.equals(child)) {
175 | return i;
176 | }
177 | }
178 | }
179 | return -1;
180 | }
181 |
182 | @Override
183 | public void addTreeModelListener(final TreeModelListener l) {
184 | treeModel.addTreeModelListener(l);
185 | }
186 |
187 | @Override
188 | public void removeTreeModelListener(final TreeModelListener l) {
189 | treeModel.removeTreeModelListener(l);
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/view/toolwindow/GLIssueListMouseAdapter.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.view.toolwindow;
2 |
3 | import com.neon.intellij.plugins.gitlab.ChangeIssueStateAction;
4 | import com.neon.intellij.plugins.gitlab.DeleteIssueAction;
5 | import com.neon.intellij.plugins.gitlab.OpenIssueEditorAction;
6 | import com.neon.intellij.plugins.gitlab.RefreshProjectIssuesAction;
7 | import com.neon.intellij.plugins.gitlab.model.intellij.GLIssueNode;
8 | import com.neon.intellij.plugins.gitlab.model.intellij.GLProjectNode;
9 |
10 | import javax.swing.*;
11 | import javax.swing.tree.DefaultMutableTreeNode;
12 | import javax.swing.tree.TreePath;
13 | import java.awt.event.MouseAdapter;
14 | import java.awt.event.MouseEvent;
15 |
16 | public class GLIssueListMouseAdapter extends MouseAdapter {
17 |
18 | private final JTree tree;
19 |
20 | private final OpenIssueEditorAction openIssueEditorAction;
21 | private final RefreshProjectIssuesAction refreshProjectIssuesAction;
22 | private final DeleteIssueAction deleteIssueAction;
23 | private final ChangeIssueStateAction changeIssueStateAction;
24 |
25 | public GLIssueListMouseAdapter(final OpenIssueEditorAction openIssueEditorAction,
26 | final RefreshProjectIssuesAction refreshProjectIssuesAction,
27 | final DeleteIssueAction deleteIssueAction,
28 | final ChangeIssueStateAction changeIssueStateAction,
29 | final JTree tree)
30 | {
31 | this.openIssueEditorAction = openIssueEditorAction;
32 | this.refreshProjectIssuesAction = refreshProjectIssuesAction;
33 | this.deleteIssueAction = deleteIssueAction;
34 | this.changeIssueStateAction = changeIssueStateAction;
35 | this.tree = tree;
36 | }
37 |
38 | @Override
39 | public void mouseClicked( MouseEvent e ) {
40 | final TreePath path = tree.getSelectionPath();
41 | // null path means nothing selected - we dont care
42 | DefaultMutableTreeNode node = path == null ? null : (DefaultMutableTreeNode) path.getLastPathComponent();
43 |
44 | if ( SwingUtilities.isRightMouseButton( e ) ) {
45 | contextMenu( node, e.getX(), e.getY() );
46 | } else if ( SwingUtilities.isLeftMouseButton( e ) && e.getClickCount() >= 2 ) {
47 | doubleClick( node );
48 | }
49 | }
50 |
51 | private void contextMenu( final DefaultMutableTreeNode node, final int x, final int y ) {
52 | if ( node instanceof GLProjectNode) {
53 | GLProjectNode projectNode = (GLProjectNode) node;
54 |
55 | JPopupMenu popup = new GLProjectPopup( projectNode, openIssueEditorAction, refreshProjectIssuesAction );
56 | popup.show( tree, x, y );
57 |
58 | } else if ( node instanceof GLIssueNode) {
59 | GLIssueNode issueNode = (GLIssueNode) node;
60 |
61 | JPopupMenu popup = new GLIssuePopup( issueNode, openIssueEditorAction, deleteIssueAction, changeIssueStateAction );
62 | popup.show( tree, x, y );
63 | }
64 | }
65 |
66 | private void doubleClick( final DefaultMutableTreeNode node ) {
67 | if ( node instanceof GLProjectNode ) {
68 | refreshProjectIssuesAction.accept((GLProjectNode) node);
69 | } else if ( node instanceof GLIssueNode ) {
70 | GLIssueNode issueNode = (GLIssueNode) node;
71 | openIssueEditorAction.accept( issueNode.getUserObject() );
72 | }
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/view/toolwindow/GLIssueListRenderer.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.view.toolwindow;
2 |
3 | import com.intellij.icons.AllIcons;
4 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPGroup;
5 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPIssue;
6 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPProject;
7 | import com.neon.intellij.plugins.gitlab.model.intellij.GLGroupNode;
8 | import com.neon.intellij.plugins.gitlab.model.intellij.GLIssueNode;
9 | import com.neon.intellij.plugins.gitlab.model.intellij.GLProjectNode;
10 | import info.clearthought.layout.TableLayout;
11 | import info.clearthought.layout.TableLayoutConstraints;
12 |
13 | import javax.swing.*;
14 | import javax.swing.tree.DefaultMutableTreeNode;
15 | import javax.swing.tree.DefaultTreeCellRenderer;
16 | import java.awt.*;
17 |
18 | public class GLIssueListRenderer extends DefaultTreeCellRenderer {
19 |
20 | private final Icon fileIcon = AllIcons.FileTypes.Any_type;
21 | private final Icon folderIcon = AllIcons.Nodes.Folder;
22 |
23 | public GLIssueListRenderer() {
24 |
25 | }
26 |
27 | @Override
28 | public Component getTreeCellRendererComponent( JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus ) {
29 | StringBuilder sb = new StringBuilder();
30 | DefaultMutableTreeNode node = ( DefaultMutableTreeNode ) value;
31 |
32 | // int childCount = tree.getModel().getChildCount( node );
33 |
34 | if ( node instanceof GLGroupNode) {
35 | GLGroupNode groupNode = (GLGroupNode) node;
36 | GIPGroup group = groupNode.getUserObject();
37 |
38 | sb.append( group.name );
39 |
40 | // if ( childCount > 0 ) {
41 | // sb.append( " ( " ).append(childCount).append( " )" );
42 | // }
43 |
44 | } else if ( node instanceof GLProjectNode) {
45 | GLProjectNode projectNode = (GLProjectNode) node;
46 | GIPProject project = projectNode.getUserObject();
47 |
48 | sb.append(project.name);
49 |
50 | // if ( childCount > 0 ) {
51 | // sb.append( " ( " ).append(childCount).append( " )" );
52 | // }
53 | } else if ( node instanceof GLIssueNode) {
54 | GLIssueNode issueNode = (GLIssueNode) node;
55 | GIPIssue issue = issueNode.getUserObject();
56 |
57 | sb.append( "#" ).append( issue.iid ).append( ": " ).append( issue.title );
58 |
59 | // TODO: show state (somehow) only if multile states selected (ie, show closed issues)
60 | // sb.append( " ( " ).append( issue.state ).append( " )" );
61 |
62 | } else {
63 | sb.append( node.toString() );
64 | // if ( childCount > 0 ) {
65 | // sb.append( " ( " ).append(childCount).append( " )" );
66 | // }
67 | }
68 |
69 | TableLayout layout = new TableLayout(
70 | new double[]{TableLayout.MINIMUM, TableLayout.FILL},
71 | new double[]{TableLayout.MINIMUM}
72 | );
73 | layout.setHGap( 5 );
74 |
75 | JPanel panel = new JPanel( layout );
76 | panel.add( new JLabel( leaf && ( node instanceof GLIssueNode ) ? fileIcon : folderIcon ), new TableLayoutConstraints( 0, 0 ) );
77 | panel.add( new JLabel( sb.toString() ), new TableLayoutConstraints( 1, 0 ) );
78 |
79 | return panel;
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/view/toolwindow/GLIssueListView.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.view.toolwindow;
2 |
3 | import com.intellij.ui.components.JBScrollPane;
4 | import com.intellij.ui.treeStructure.Tree;
5 | import com.neon.intellij.plugins.gitlab.*;
6 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPGroup;
7 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPIssue;
8 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPProject;
9 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPUser;
10 | import com.neon.intellij.plugins.gitlab.model.intellij.GLGroupNode;
11 | import com.neon.intellij.plugins.gitlab.model.intellij.GLIssueNode;
12 | import com.neon.intellij.plugins.gitlab.model.intellij.GLProjectNode;
13 | import info.clearthought.layout.TableLayout;
14 | import info.clearthought.layout.TableLayoutConstraints;
15 |
16 | import javax.swing.*;
17 | import javax.swing.tree.DefaultMutableTreeNode;
18 | import javax.swing.tree.DefaultTreeModel;
19 | import java.util.HashMap;
20 | import java.util.Map;
21 | import java.util.logging.Logger;
22 |
23 | public class GLIssueListView extends JPanel implements GIPGroupObserver, GIPProjectObserver, GIPIssueObserver {
24 |
25 | private static final Logger LOGGER = Logger.getLogger( GLIssueListView.class.getName() );
26 |
27 | private final FilteredTreeModel filteredModel = new FilteredTreeModel( new DefaultMutableTreeNode( "groups" ) );
28 |
29 | private final Tree tree = new Tree( filteredModel );
30 |
31 | public GLIssueListView(final OpenIssueEditorAction openIssueEditorAction,
32 | final RefreshProjectIssuesAction refreshProjectIssuesAction,
33 | final DeleteIssueAction deleteIssueAction,
34 | final ChangeIssueStateAction changeIssueStateAction) {
35 | tree.setCellRenderer( new GLIssueListRenderer() );
36 | tree.addMouseListener( new GLIssueListMouseAdapter( openIssueEditorAction, refreshProjectIssuesAction,
37 | deleteIssueAction, changeIssueStateAction, tree ) );
38 | // tree.setRootVisible( false );
39 |
40 | this.setLayout( new TableLayout(
41 | new double[] { TableLayout.FILL },
42 | new double[] { TableLayout.FILL }
43 | ) );
44 |
45 | final JBScrollPane scroller = new JBScrollPane( tree, JBScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JBScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED );
46 | this.add( scroller, new TableLayoutConstraints( 0, 0, 0, 0, TableLayout.FULL, TableLayout.FULL ) );
47 | }
48 |
49 | public < T > T[] getSelectedNodes( Class< T > clazz, Tree.NodeFilter< T > filter ) {
50 | return tree.getSelectedNodes( clazz, filter );
51 | }
52 |
53 | public void filter(final GIPUser author, final GIPUser assignee, final String filter, final boolean showClosedIssues, final boolean showEmptyNodes ) {
54 | filteredModel.setFilter( filter );
55 | filteredModel.setShowClosedIssues( showClosedIssues );
56 | filteredModel.setAuthor( author );
57 | filteredModel.setAssignee( assignee );
58 | filteredModel.setShowEmptyNodes( showEmptyNodes );
59 | ((DefaultTreeModel) filteredModel.getTreeModel() ).reload();
60 | }
61 |
62 |
63 |
64 | private final Map< Integer, GLGroupNode > groups = new HashMap<>();
65 |
66 | private final Map< Integer, GLProjectNode > projects = new HashMap<>();
67 |
68 |
69 | @Override
70 | public void onStartGroupsUpdate() {
71 | groups.clear();
72 | projects.clear();
73 |
74 | ( ( DefaultMutableTreeNode ) filteredModel.getTreeModel().getRoot() ).removeAllChildren();
75 | tree.treeDidChange();
76 | }
77 |
78 | @Override
79 | public void accept( GIPGroup group ) {
80 | LOGGER.info( "[group] " + group.id + ". " + group.name );
81 |
82 | DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel().getRoot();
83 | if ( group.parent_id != null ) {
84 | root = groups.get(group.parent_id);
85 | if ( root == null ) {
86 |
87 | GIPGroup temp = new GIPGroup();
88 | temp.id = group.parent_id;
89 |
90 | root = new GLGroupNode( temp );
91 |
92 | groups.put( temp.id, (GLGroupNode) root);
93 | }
94 | }
95 |
96 | GLGroupNode glGroupNode = new GLGroupNode(group);
97 | groups.put( group.id, glGroupNode );
98 |
99 | root.add(glGroupNode);
100 | }
101 |
102 | @Override
103 | public void onStartProjectUpdate( GIPProject project ) {
104 | GLProjectNode glProjectNode = projects.get(project.id);
105 | if ( glProjectNode == null ) {
106 | return ;
107 | }
108 |
109 | glProjectNode.removeAllChildren();
110 | tree.treeDidChange();
111 | }
112 |
113 | @Override
114 | public void accept( GIPProject project ) {
115 | LOGGER.info( "[project] " + project.id + ". " + project.name );
116 |
117 | GLGroupNode glGroupNode = groups.get(project.namespace.id);
118 | if ( glGroupNode != null ) {
119 | GLProjectNode glProjectNode = new GLProjectNode(project);
120 | projects.put( project.id, glProjectNode );
121 |
122 | glGroupNode.add(glProjectNode);
123 | }
124 | }
125 |
126 | @Override
127 | public void accept(GIPIssue issue) {
128 | LOGGER.info( "[issue] " + issue.id + ". " + issue.title );
129 |
130 | GLProjectNode glProjectNode = projects.get(issue.project_id);
131 | if ( glProjectNode == null ) {
132 | GIPProject temp = new GIPProject();
133 | temp.id = issue.project_id;
134 |
135 | glProjectNode = new GLProjectNode( temp );
136 |
137 | projects.put( issue.project_id, glProjectNode );
138 | }
139 |
140 | GLIssueNode glIssueNode = new GLIssueNode( issue );
141 |
142 | glProjectNode.add( glIssueNode );
143 | }
144 |
145 | }
146 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/view/toolwindow/GLIssuePopup.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.view.toolwindow;
2 |
3 | import com.intellij.icons.AllIcons;
4 | import com.intellij.ide.BrowserUtil;
5 | import com.intellij.openapi.diagnostic.Logger;
6 | import com.neon.intellij.plugins.gitlab.ChangeIssueStateAction;
7 | import com.neon.intellij.plugins.gitlab.DeleteIssueAction;
8 | import com.neon.intellij.plugins.gitlab.OpenIssueEditorAction;
9 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPIssue;
10 | import com.neon.intellij.plugins.gitlab.model.intellij.GLIssueNode;
11 |
12 | import javax.swing.*;
13 |
14 | public class GLIssuePopup extends JPopupMenu {
15 |
16 | private static final Logger LOG = Logger.getInstance("gitlab");
17 |
18 | public GLIssuePopup(final GLIssueNode node,
19 | final OpenIssueEditorAction openIssueEditorAction,
20 | final DeleteIssueAction deleteIssueAction,
21 | final ChangeIssueStateAction changeIssueStateAction ) {
22 | final GIPIssue issue = node.getUserObject();
23 |
24 | JMenuItem editItem = new JMenuItem( "Edit", AllIcons.Actions.Edit );
25 | editItem.addActionListener(e -> openIssueEditorAction.accept( issue ));
26 | this.add(editItem);
27 |
28 | JMenuItem delete = new JMenuItem( "Delete", AllIcons.Actions.Delete );
29 | delete.addActionListener(e -> {
30 | deleteIssueAction.accept(issue);
31 | });
32 | this.add( delete );
33 |
34 | JMenu statesMenu = new JMenu( "Change State To ..." );
35 |
36 | if ( "closed".equalsIgnoreCase( issue.state ) ) {
37 | JRadioButtonMenuItem reopen = new JRadioButtonMenuItem( "Re-Open", AllIcons.Actions.Resume );
38 | reopen.addActionListener(e -> {
39 | GIPIssue savedIssue = changeIssueStateAction.apply(issue, "reopen");
40 | issue.state = savedIssue.state;
41 | });
42 | statesMenu.add(reopen);
43 | } else {
44 | JRadioButtonMenuItem closed = new JRadioButtonMenuItem( "Close", AllIcons.Actions.Close );
45 | closed.addActionListener(e -> {
46 | GIPIssue savedIssue = changeIssueStateAction.apply(issue, "close");
47 | issue.state = savedIssue.state;
48 | });
49 | statesMenu.add(closed);
50 | }
51 |
52 | this.addSeparator();
53 | this.add( statesMenu );
54 |
55 | this.addSeparator();
56 | JMenuItem openBrowser = new JMenuItem( "Open in Browser", AllIcons.General.Web);
57 | openBrowser.addActionListener(e -> {
58 | if ( issue.web_url == null || issue.web_url.trim().isEmpty() ) {
59 | return ;
60 | }
61 |
62 | BrowserUtil.browse( issue.web_url );
63 | });
64 | openBrowser.setEnabled( issue.web_url != null && ! issue.web_url.trim().isEmpty() );
65 | this.add( openBrowser );
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/view/toolwindow/GLIssuesFilterView.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.view.toolwindow;
2 |
3 | import com.intellij.openapi.ui.ComboBox;
4 | import com.neon.intellij.plugins.gitlab.GIPUserObserver;
5 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPUser;
6 | import info.clearthought.layout.TableLayout;
7 | import info.clearthought.layout.TableLayoutConstraints;
8 |
9 | import javax.swing.*;
10 | import java.awt.*;
11 | import java.awt.event.ActionListener;
12 | import java.util.List;
13 |
14 | public class GLIssuesFilterView extends JPanel implements GIPUserObserver {
15 |
16 | private final JTextField textFilter = new JTextField();
17 |
18 | private final JCheckBox checkClosed = new JCheckBox( "Show closed issues", false );
19 | private final JCheckBox checkEmpty = new JCheckBox( "Show empty nodes", false );
20 |
21 | private final JLabel labelAuthor = new JLabel( "Author" );
22 | private final ComboBox< GIPUser > author = new ComboBox<>();
23 |
24 | private final JLabel labelAssignee = new JLabel( "Assignee" );
25 | private final ComboBox< GIPUser > assignee = new ComboBox<>();
26 |
27 |
28 | private final GLIssueListView listView;
29 |
30 | public GLIssuesFilterView(final GLIssueListView listView) {
31 | this.listView = listView;
32 |
33 | setupComponents();
34 | setupLayout();
35 | }
36 |
37 | private void setupComponents() {
38 | author.setPreferredSize( new Dimension( 200, 18 ) );
39 | assignee.setPreferredSize(new Dimension( 200, 18 ) );
40 | textFilter.setPreferredSize( new Dimension( 225, 18 ) );
41 |
42 | checkClosed.addItemListener(e -> filter());
43 | checkEmpty.addItemListener(e -> filter());
44 |
45 | ActionListener actionListener = e -> filter();
46 | textFilter.addActionListener( actionListener );
47 | author.addActionListener(actionListener);
48 | assignee.addActionListener(actionListener);
49 |
50 | author.setRenderer((list, value, index, isSelected, cellHasFocus) ->
51 | new JLabel( value == null ? " " : value.name != null && !value.name.trim().isEmpty() ? value.name :
52 | value.username != null && ! value.username.trim().isEmpty() ? value.username : " " ));
53 | assignee.setRenderer((list, value, index, isSelected, cellHasFocus) ->
54 | new JLabel( value == null ? " " : value.name != null && !value.name.trim().isEmpty() ? value.name :
55 | value.username != null && ! value.username.trim().isEmpty() ? value.username : " " ));
56 |
57 | }
58 |
59 | private void setupLayout() {
60 | TableLayout layout = new TableLayout(
61 | new double[] {
62 | TableLayout.MINIMUM, TableLayout.PREFERRED,
63 | TableLayout.MINIMUM, TableLayout.PREFERRED,
64 | TableLayout.MINIMUM
65 | },
66 | new double[] { TableLayout.FILL, TableLayout.FILL }
67 | );
68 | layout.setHGap( 5 );
69 | layout.setVGap( 5 );
70 | this.setLayout( layout );
71 |
72 | this.add( labelAuthor, new TableLayoutConstraints( 0, 0 ) );
73 | this.add( author, new TableLayoutConstraints( 1, 0 ) );
74 | this.add( labelAssignee, new TableLayoutConstraints( 2, 0 ) );
75 | this.add( assignee, new TableLayoutConstraints( 3, 0 ) );
76 |
77 | this.add( checkClosed, new TableLayoutConstraints( 4, 0 ) );
78 |
79 | this.add( new JLabel( "Filter" ), new TableLayoutConstraints( 0, 1 ) );
80 | this.add( textFilter, new TableLayoutConstraints( 1, 1, 3, 1 ) );
81 |
82 | this.add( checkEmpty, new TableLayoutConstraints( 4, 1 ) );
83 | }
84 |
85 |
86 | @Override
87 | public void onStartUsersUpdate() {
88 | author.removeAllItems();
89 | assignee.removeAllItems();
90 |
91 | author.addItem( new GIPUser() );
92 | assignee.addItem( new GIPUser() );
93 | }
94 |
95 | @Override
96 | public void accept(List users) {
97 | if ( users != null ) {
98 | users.forEach( user -> {
99 | author.addItem( user );
100 | assignee.addItem( user );
101 | } );
102 | }
103 | }
104 |
105 | private void filter() {
106 | GIPUser author = this.author.getModel().getElementAt(this.author.getSelectedIndex());
107 | GIPUser assignee = this.assignee.getModel().getElementAt(this.assignee.getSelectedIndex());
108 |
109 | listView.filter(
110 | author,
111 | assignee,
112 | textFilter.getText(),
113 | checkClosed.isSelected(),
114 | checkEmpty.isSelected() );
115 | }
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/src/main/java/com/neon/intellij/plugins/gitlab/view/toolwindow/GLProjectPopup.java:
--------------------------------------------------------------------------------
1 | package com.neon.intellij.plugins.gitlab.view.toolwindow;
2 |
3 | import com.intellij.icons.AllIcons;
4 | import com.neon.intellij.plugins.gitlab.OpenIssueEditorAction;
5 | import com.neon.intellij.plugins.gitlab.RefreshProjectIssuesAction;
6 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPIssue;
7 | import com.neon.intellij.plugins.gitlab.model.gitlab.GIPProject;
8 | import com.neon.intellij.plugins.gitlab.model.intellij.GLProjectNode;
9 |
10 | import javax.swing.*;
11 |
12 | public class GLProjectPopup extends JPopupMenu {
13 |
14 | public GLProjectPopup(final GLProjectNode node,
15 | final OpenIssueEditorAction openIssueEditorAction,
16 | final RefreshProjectIssuesAction refreshProjectIssuesAction ) {
17 | final GIPProject project = node.getUserObject();
18 |
19 | JMenuItem refresh = new JMenuItem( "Refresh Issues", AllIcons.Actions.Refresh );
20 | refresh.addActionListener(e -> {
21 | refreshProjectIssuesAction.accept( node );
22 | });
23 | this.add( refresh );
24 |
25 | JMenuItem create = new JMenuItem( "New Issue", AllIcons.General.Add );
26 | create.addActionListener(e -> {
27 | GIPIssue issue = new GIPIssue();
28 | issue.project_id = project.id;
29 | openIssueEditorAction.accept( issue );
30 | });
31 | this.add( create );
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 | com.neon.intellij.plugins.gitlab
3 | GitLab Integration Plugin
4 | 1.1.2
5 | Diogo Neves
6 |
7 |
9 |
10 | Lets you interact with gitlab from within your IDE.
11 |
12 | Features:
13 |
14 | - List projects (by namespaces) and their issues
15 | - Filter issues by author, assignee or custom text
16 | - Re-open / close issues (right click)
17 | - Edit issues (double click)
18 |
19 |
20 | Please, leave a comment or drop me an email with any issues/reports.
21 |
22 | After plugin install, go to IDE preferences, and look for Gitlab Integration.
23 | You'll need the host and your API key from your gitlab (which you can find under Profile Settings -> Account)
24 | ]]>
25 |
26 | v1.1.1
28 |
29 | - Minor improvements to the plugin initialization
30 | - Minor improvements to the plugin settings view
31 |
32 | ( 2018-11-13 ) v1.1.0
33 |
34 | - Redone plugin to use Gitlab API v4
35 |
36 | ( 2014-10-14 ) v1.0.6
37 |
38 | - Java 6 / 7 versions
39 | - displaying issue Iid instead of Id
40 |
41 | ( 2014-07-08 ) v1.0.5
42 |
43 | - Fixed possible ssl error (ignoring certificate errors by default)
44 |
45 | ( 2014-07-07 ) v1.0.4
46 |
49 | ( 2014-06-23 ) v1.0.3
50 |
51 | - Issues list filter
52 | - minor improvements
53 |
54 | ( 2014-04-29 ) v1.0.2
55 |
56 | - Set plugin dependency to build 133 (IntelliJ IDEA 13, PyCharm 3.1, WebStorm 7, PhpStorm 7) - intellij 13 was the first to support java7
57 |
58 | ( 2014-04-29 ) v1.0.1
59 |
60 | - Improves in the issue editing view
61 |
62 | ( 2014-04-25 ) v1.0
63 |
64 | - Plugin creation
65 |
66 | ]]>
67 |
68 |
69 |
70 |
71 |
72 |
74 | com.intellij.modules.lang
75 |
76 |
77 |
78 |
79 |
80 |
82 |
83 |
84 |
87 |
88 |
89 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | com.neon.intellij.plugins.gitlab.controller.editor.GLEditorProvider
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/src/main/resources/icons/gitlab.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dneves/gitlab-integration-plugin/12052867624585a713f274ff6b760817c73bbf9a/src/main/resources/icons/gitlab.jpg
--------------------------------------------------------------------------------