package ru.trylogic.idea.gitlab.integration.actions; import com.intellij.ide.BrowserUtil; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.SelectionModel; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.ui.popup.ListPopup; import com.intellij.openapi.vcs.VcsNotifier; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vcs.changes.ChangeListManager; import com.intellij.openapi.vfs.VirtualFile; import git4idea.GitLocalBranch; import git4idea.GitRemoteBranch; import git4idea.GitUtil; import git4idea.repo.GitRemote; import git4idea.repo.GitRepository; import git4idea.repo.GitRepositoryManager; import icons.GitlabIcons; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import ru.trylogic.idea.gitlab.integration.utils.GitlabUrlUtil; import java.util.ArrayList; import java.util.List; public class GitLabOpenInBrowserAction extends DumbAwareAction { public static final Logger LOG = Logger.getInstance("gitlab"); public static final String CANNOT_OPEN_IN_BROWSER = "Cannot open in browser"; protected GitLabOpenInBrowserAction() { super("Open on GitLab", "Open corresponding link in browser", GitlabIcons.Gitlab_icon); } static void setVisibleEnabled(AnActionEvent e, boolean visible, boolean enabled) { e.getPresentation().setVisible(visible); e.getPresentation().setEnabled(enabled); } static void showError(Project project, String title, String message) { showError(project, title, message, null); } static void showError(Project project, String title, String message, String debugInfo) { LOG.warn(title + "; " + message); if (debugInfo != null) { LOG.warn(debugInfo); } VcsNotifier.getInstance(project).notifyError(title, message); } @Nullable static String makeUrlToOpen(@Nullable Editor editor, @NotNull String relativePath, @NotNull String branch, @NotNull String remoteUrl) { final StringBuilder builder = new StringBuilder(); final String repoUrl = GitlabUrlUtil.makeRepoUrlFromRemoteUrl(remoteUrl); if (repoUrl == null) { return null; } builder.append(repoUrl).append("/blob/").append(branch).append(relativePath); if (editor != null && editor.getDocument().getLineCount() >= 1) { // lines are counted internally from 0, but from 1 on gitlab SelectionModel selectionModel = editor.getSelectionModel(); final int begin = editor.getDocument().getLineNumber(selectionModel.getSelectionStart()) + 1; final int selectionEnd = selectionModel.getSelectionEnd(); int end = editor.getDocument().getLineNumber(selectionEnd) + 1; if (editor.getDocument().getLineStartOffset(end - 1) == selectionEnd) { end -= 1; } builder.append("#L").append(begin).append('-').append(end); } return builder.toString(); } @Nullable public static String getBranchNameOnRemote(@NotNull Project project, @NotNull GitRepository repository) { GitLocalBranch currentBranch = repository.getCurrentBranch(); if (currentBranch == null) { showError(project, CANNOT_OPEN_IN_BROWSER, "Can't open the file on GitLab when repository is on detached HEAD. Please checkout a branch."); return null; } GitRemoteBranch tracked = currentBranch.findTrackedBranch(repository); if (tracked == null) { showError(project, CANNOT_OPEN_IN_BROWSER, "Can't open the file on GitLab when current branch doesn't have a tracked branch.", "Current branch: " + currentBranch + ", tracked info: " + repository.getBranchTrackInfos()); return null; } return tracked.getNameForRemoteOperations(); } @Override public void update(final AnActionEvent e) { Project project = e.getData(PlatformDataKeys.PROJECT); VirtualFile virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE); if (project == null || project.isDefault() || virtualFile == null) { setVisibleEnabled(e, false, false); return; } final GitRepository gitRepository = GitUtil.getRepositoryManager(project).getRepositoryForFile(virtualFile); if (gitRepository == null) { setVisibleEnabled(e, false, false); return; } ChangeListManager changeListManager = ChangeListManager.getInstance(project); if (changeListManager.isUnversioned(virtualFile)) { setVisibleEnabled(e, true, false); return; } Change change = changeListManager.getChange(virtualFile); if (change != null && change.getType() == Change.Type.NEW) { setVisibleEnabled(e, true, false); return; } setVisibleEnabled(e, true, true); } @Override public void actionPerformed(final AnActionEvent e) { final Project project = e.getData(PlatformDataKeys.PROJECT); final VirtualFile virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE); final Editor editor = e.getData(PlatformDataKeys.EDITOR); if (virtualFile == null || project == null || project.isDisposed()) { return; } GitRepositoryManager manager = GitUtil.getRepositoryManager(project); final GitRepository repository = manager.getRepositoryForFile(virtualFile); if (repository == null) { StringBuilder details = new StringBuilder("file: " + virtualFile.getPresentableUrl() + "; Git repositories: "); for (GitRepository repo : manager.getRepositories()) { details.append(repo.getPresentableUrl()).append("; "); } showError(project, CANNOT_OPEN_IN_BROWSER, "Can't find git repository", details.toString()); return; } final String rootPath = repository.getRoot().getPath(); final String path = virtualFile.getPath(); List<AnAction> remoteSelectedActions = new ArrayList<AnAction>(); for (GitRemote remote : repository.getRemotes()) { remoteSelectedActions.add(new RemoteSelectedAction(project, repository, editor, remote, rootPath, path)); } if (remoteSelectedActions.size() > 1) { DefaultActionGroup remotesActionGroup = new DefaultActionGroup(); remotesActionGroup.addAll(remoteSelectedActions); DataContext dataContext = e.getDataContext(); final ListPopup popup = JBPopupFactory.getInstance().createActionGroupPopup( "Select remote", remotesActionGroup, dataContext, JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, true); popup.showInBestPositionFor(dataContext); } else if (remoteSelectedActions.size() == 1) { remoteSelectedActions.get(0).actionPerformed(null); } else { showError(project, CANNOT_OPEN_IN_BROWSER, "Can't find gitlab remote"); } } } class RemoteSelectedAction extends AnAction { private final Editor editor; private final GitRemote remote; private final String rootPath; private final String path; private final Project project; private final GitRepository repository; public RemoteSelectedAction(@NotNull Project project, @NotNull GitRepository repository, @Nullable Editor editor, @NotNull GitRemote remote, @NotNull String rootPath, @NotNull String path) { super(remote.getName()); this.project = project; this.repository = repository; this.editor = editor; this.remote = remote; this.rootPath = rootPath; this.path = path; } @Override public void update(AnActionEvent e) { super.update(e); setEnabledInModalContext(true); e.getPresentation().setEnabled(true); } @Override public void actionPerformed(@Nullable AnActionEvent unused) { if (!path.startsWith(rootPath)) { GitLabOpenInBrowserAction.showError(project, GitLabOpenInBrowserAction.CANNOT_OPEN_IN_BROWSER, "File is not under repository root", "Root: " + rootPath + ", file: " + path); return; } String branch = GitLabOpenInBrowserAction.getBranchNameOnRemote(project, repository); if (branch == null) { return; } String remoteUrl = remote.getFirstUrl(); if (remoteUrl == null) { GitLabOpenInBrowserAction.showError(project, GitLabOpenInBrowserAction.CANNOT_OPEN_IN_BROWSER, "Can't obtain url for remote", remote.getName()); return; } String relativePath = path.substring(rootPath.length()); String urlToOpen = GitLabOpenInBrowserAction.makeUrlToOpen(editor, relativePath, branch, remoteUrl); if (urlToOpen == null) { GitLabOpenInBrowserAction.showError(project, GitLabOpenInBrowserAction.CANNOT_OPEN_IN_BROWSER, "Can't create properly url", remote.getFirstUrl()); return; } BrowserUtil.launchBrowser(urlToOpen); } }