├── .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 | 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 | 26 | ( 2018-11-17 ) v1.1.1
27 | 31 | ( 2018-11-13 ) v1.1.0
32 | 35 | ( 2014-10-14 ) v1.0.6
36 | 40 | ( 2014-07-08 ) v1.0.5
41 | 44 | ( 2014-07-07 ) v1.0.4
45 | 48 | ( 2014-06-23 ) v1.0.3
49 | 53 | ( 2014-04-29 ) v1.0.2
54 | 57 | ( 2014-04-29 ) v1.0.1 58 | 61 | ( 2014-04-25 ) v1.0
62 | 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 |
    47 |
  • bug fixes
  • 48 |
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 --------------------------------------------------------------------------------