diff --git a/changelog/@unreleased/pr-33.v2.yml b/changelog/@unreleased/pr-33.v2.yml new file mode 100644 index 0000000..e1f55e2 --- /dev/null +++ b/changelog/@unreleased/pr-33.v2.yml @@ -0,0 +1,5 @@ +type: improvement +improvement: + description: button to trigger writeVersionsLock + links: + - https://github.com/palantir/gradle-consistent-versions-idea-plugin/pull/33 diff --git a/gradle-consistent-versions-idea-plugin/build.gradle b/gradle-consistent-versions-idea-plugin/build.gradle index 916cd87..b08105e 100644 --- a/gradle-consistent-versions-idea-plugin/build.gradle +++ b/gradle-consistent-versions-idea-plugin/build.gradle @@ -30,6 +30,7 @@ dependencies { compileOnly 'org.immutables:value::annotations' testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation 'org.assertj:assertj-core' + testImplementation 'org.mockito:mockito-core' } grammarKit { diff --git a/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/ProjectRefreshUtils.java b/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/ProjectRefreshUtils.java new file mode 100644 index 0000000..d5b5ada --- /dev/null +++ b/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/ProjectRefreshUtils.java @@ -0,0 +1,97 @@ +/* + * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.gradle.versions.intellij; + +import com.intellij.execution.executors.DefaultRunExecutor; +import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder; +import com.intellij.openapi.externalSystem.model.execution.ExternalSystemTaskExecutionSettings; +import com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode; +import com.intellij.openapi.externalSystem.task.TaskCallback; +import com.intellij.openapi.externalSystem.util.ExternalSystemUtil; +import com.intellij.openapi.project.Project; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Collections; +import org.jetbrains.plugins.gradle.util.GradleConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ProjectRefreshUtils { + private static final Logger log = LoggerFactory.getLogger(ProjectRefreshUtils.class); + + private ProjectRefreshUtils() { + // Utility class + } + + public static void runWriteVersionsLock(Project project) { + String taskName = "writeVersionsLock"; + if (hasBuildSrc(project)) { + runTaskThenRefresh(project, taskName); + } else { + refreshProjectWithTask(project, taskName); + } + } + + private static void runTaskThenRefresh(Project project, String taskName) { + log.debug("Running task {} on project {}", taskName, project.getName()); + TaskCallback callback = new TaskCallback() { + @Override + public void onSuccess() { + log.debug("Task {} successfully executed", taskName); + refreshProject(project); + } + + @Override + public void onFailure() { + log.error("Task {} failed", taskName); + } + }; + ExternalSystemTaskExecutionSettings settings = createExecutionSettings(project, taskName); + ExternalSystemUtil.runTask( + settings, + DefaultRunExecutor.EXECUTOR_ID, + project, + GradleConstants.SYSTEM_ID, + callback, + ProgressExecutionMode.IN_BACKGROUND_ASYNC); + } + + private static ExternalSystemTaskExecutionSettings createExecutionSettings(Project project, String taskName) { + ExternalSystemTaskExecutionSettings settings = new ExternalSystemTaskExecutionSettings(); + settings.setExternalProjectPath(project.getBasePath()); + settings.setTaskNames(Collections.singletonList(taskName)); + settings.setExternalSystemIdString(GradleConstants.SYSTEM_ID.toString()); + return settings; + } + + private static void refreshProjectWithTask(Project project, String taskName) { + log.debug("Refreshing project {} with task {}", project.getName(), taskName); + refreshProject(project, new ImportSpecBuilder(project, GradleConstants.SYSTEM_ID).withArguments(taskName)); + } + + private static void refreshProject(Project project) { + refreshProject(project, new ImportSpecBuilder(project, GradleConstants.SYSTEM_ID)); + } + + private static void refreshProject(Project project, ImportSpecBuilder importSpec) { + ExternalSystemUtil.refreshProject(project.getBasePath(), importSpec); + } + + private static boolean hasBuildSrc(Project project) { + return Files.exists(Paths.get(project.getBasePath(), "buildSrc")); + } +} diff --git a/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsAppSettings.java b/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsAppSettings.java new file mode 100644 index 0000000..b970951 --- /dev/null +++ b/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsAppSettings.java @@ -0,0 +1,68 @@ +/* + * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.gradle.versions.intellij; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.Service; +import com.intellij.openapi.components.Service.Level; +import com.intellij.openapi.components.Storage; +import org.jetbrains.annotations.Nullable; + +@Service(Level.APP) +@com.intellij.openapi.components.State( + name = "AppSettings", + storages = {@Storage("gcv-plugin-application-settings.xml")}) +public final class VersionPropsAppSettings implements PersistentStateComponent { + + public static final class State { + private boolean enabled = true; + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean getEnabled() { + return this.enabled; + } + } + + private State state = new State(); + + @Nullable + @Override + public State getState() { + return state; + } + + @Override + public void loadState(State status) { + this.state = status; + } + + public boolean isEnabled() { + return state.enabled; + } + + public void setEnabled(boolean enabled) { + state.enabled = enabled; + } + + public static VersionPropsAppSettings getInstance() { + return ApplicationManager.getApplication().getService(VersionPropsAppSettings.class); + } +} diff --git a/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsAppSettingsPage.java b/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsAppSettingsPage.java new file mode 100644 index 0000000..60aa4ad --- /dev/null +++ b/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsAppSettingsPage.java @@ -0,0 +1,70 @@ +/* + * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.gradle.versions.intellij; + +import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.options.ConfigurationException; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JPanel; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.Nullable; + +public final class VersionPropsAppSettingsPage implements Configurable { + private JCheckBox enabledCheckbox; + + private final VersionPropsAppSettings settings; + + public VersionPropsAppSettingsPage() { + settings = VersionPropsAppSettings.getInstance(); + } + + @Nls + @Override + public String getDisplayName() { + return "Gradle Consistent Versions"; + } + + @Nullable + @Override + public JComponent createComponent() { + JPanel rootPanel = new JPanel(); + enabledCheckbox = new JCheckBox("Enable writeVersionsLock on save"); + rootPanel.add(enabledCheckbox); + return rootPanel; + } + + @Override + public boolean isModified() { + return enabledCheckbox.isSelected() != settings.isEnabled(); + } + + @Override + public void apply() throws ConfigurationException { + settings.setEnabled(enabledCheckbox.isSelected()); + } + + @Override + public void reset() { + enabledCheckbox.setSelected(settings.isEnabled()); + } + + @Override + public void disposeUIResources() { + // No resources to dispose + } +} diff --git a/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsCloseAction.java b/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsCloseAction.java new file mode 100644 index 0000000..7e5bb2d --- /dev/null +++ b/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsCloseAction.java @@ -0,0 +1,47 @@ +/* + * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.gradle.versions.intellij; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import java.util.Optional; + +public class VersionPropsCloseAction extends AnAction { + + public VersionPropsCloseAction() { + super("Close"); + } + + @Override + public final void actionPerformed(AnActionEvent event) { + DataContext dataContext = event.getDataContext(); + Editor editor = dataContext.getData(CommonDataKeys.EDITOR); + Project project = event.getProject(); + + if (editor != null && project != null) { + VirtualFile file = editor.getVirtualFile(); + if (file != null) { + VersionPropsToolbar.getInstance().hideToolbarForFile(file.getPath(), project, Optional.empty()); + } + } + } +} diff --git a/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsDocumentListener.java b/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsDocumentListener.java new file mode 100644 index 0000000..8d256f6 --- /dev/null +++ b/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsDocumentListener.java @@ -0,0 +1,93 @@ +/* + * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.palantir.gradle.versions.intellij; + +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.openapi.editor.event.DocumentListener; +import com.intellij.openapi.editor.toolbar.floating.FloatingToolbarComponent; +import com.intellij.openapi.fileEditor.FileEditor; +import com.intellij.openapi.vfs.VirtualFile; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import javax.swing.SwingUtilities; + +public class VersionPropsDocumentListener implements DocumentListener { + private final FileEditor fileEditor; + private final Editor editor; + private final Map originalContent; + private final Map filesToolbarComponents; + private static final String FILE_NAME = "versions.props"; + private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + + public VersionPropsDocumentListener( + FileEditor fileEditor, + Editor editor, + Map originalContent, + Map filesToolbarComponents) { + this.fileEditor = fileEditor; + this.editor = editor; + this.originalContent = originalContent; + this.filesToolbarComponents = filesToolbarComponents; + } + + @Override + public void beforeDocumentChange(DocumentEvent event) {} + + @Override + public final void documentChanged(DocumentEvent event) { + VirtualFile file = fileEditor.getFile(); + if (file != null && FILE_NAME.equals(file.getName())) { + VersionPropsProjectSettings projectSettings = + VersionPropsProjectSettings.getInstance(Objects.requireNonNull(editor.getProject())); + VersionPropsAppSettings appSettings = VersionPropsAppSettings.getInstance(); + if (!projectSettings.isEnabled() || appSettings.isEnabled()) { + scheduleUpdate(() -> updateFileUnchanged(file.getPath())); + return; + } + + String currentContent = editor.getDocument().getText(); + + // This requires debouncing to so that the toolbar actually shows up + if (!originalContent.get(file.getPath()).equals(currentContent)) { + scheduleUpdate(() -> updateFileChanged(file.getPath())); + } else { + scheduleUpdate(() -> updateFileUnchanged(file.getPath())); + } + } + } + + private void scheduleUpdate(Runnable updateTask) { + scheduler.schedule(() -> SwingUtilities.invokeLater(updateTask), 300, TimeUnit.MILLISECONDS); + } + + private void updateFileChanged(String filePath) { + FloatingToolbarComponent toolbarComponent = filesToolbarComponents.get(filePath); + if (toolbarComponent != null) { + toolbarComponent.scheduleShow(); + } + } + + private void updateFileUnchanged(String filePath) { + FloatingToolbarComponent toolbarComponent = filesToolbarComponents.get(filePath); + if (toolbarComponent != null) { + toolbarComponent.scheduleHide(); + } + } +} diff --git a/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsFileListener.java b/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsFileListener.java index c8bdc13..eb9bd43 100644 --- a/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsFileListener.java +++ b/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsFileListener.java @@ -16,13 +16,7 @@ package com.palantir.gradle.versions.intellij; -import com.intellij.execution.executors.DefaultRunExecutor; import com.intellij.openapi.components.ComponentManager; -import com.intellij.openapi.externalSystem.importing.ImportSpecBuilder; -import com.intellij.openapi.externalSystem.model.execution.ExternalSystemTaskExecutionSettings; -import com.intellij.openapi.externalSystem.service.execution.ProgressExecutionMode; -import com.intellij.openapi.externalSystem.task.TaskCallback; -import com.intellij.openapi.externalSystem.util.ExternalSystemUtil; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.vfs.AsyncFileListener; @@ -32,14 +26,10 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.util.PsiTreeUtil; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.function.Predicate; import org.jetbrains.annotations.Nullable; -import org.jetbrains.plugins.gradle.util.GradleConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -74,69 +64,16 @@ public ChangeApplier prepareChange(List events) { public void afterVfsChange() { projectsAffected.forEach(project -> { VersionPropsProjectSettings settings = VersionPropsProjectSettings.getInstance(project); - if (!settings.isEnabled()) { + VersionPropsAppSettings appSettings = VersionPropsAppSettings.getInstance(); + if (!settings.isEnabled() || !appSettings.isEnabled()) { return; } - - if (hasBuildSrc(project)) { - runTaskThenRefresh(project); - } else { - refreshProjectWithTask(project); - } + ProjectRefreshUtils.runWriteVersionsLock(project); }); } }; } - private boolean hasBuildSrc(Project project) { - return Files.exists(Paths.get(project.getBasePath(), "buildSrc")); - } - - private void runTaskThenRefresh(Project project) { - log.debug("Running task {} on project {}", TASK_NAME, project.getName()); - TaskCallback callback = new TaskCallback() { - @Override - public void onSuccess() { - log.debug("Task {} successfully executed", TASK_NAME); - refreshProject(project); - } - - @Override - public void onFailure() { - log.error("Task {} failed", TASK_NAME); - } - }; - ExternalSystemTaskExecutionSettings settings = createExecutionSettings(project); - ExternalSystemUtil.runTask( - settings, - DefaultRunExecutor.EXECUTOR_ID, - project, - GradleConstants.SYSTEM_ID, - callback, - ProgressExecutionMode.IN_BACKGROUND_ASYNC); - } - - private ExternalSystemTaskExecutionSettings createExecutionSettings(Project project) { - ExternalSystemTaskExecutionSettings settings = new ExternalSystemTaskExecutionSettings(); - settings.setExternalProjectPath(project.getBasePath()); - settings.setTaskNames(Collections.singletonList(TASK_NAME)); - settings.setExternalSystemIdString(GradleConstants.SYSTEM_ID.toString()); - return settings; - } - - private void refreshProjectWithTask(Project project) { - log.debug("Refreshing project {} with task {}", project.getName(), TASK_NAME); - refreshProject(project, new ImportSpecBuilder(project, GradleConstants.SYSTEM_ID).withArguments(TASK_NAME)); - } - - private void refreshProject(Project project) { - refreshProject(project, new ImportSpecBuilder(project, GradleConstants.SYSTEM_ID)); - } - - private void refreshProject(Project project, ImportSpecBuilder importSpec) { - ExternalSystemUtil.refreshProject(project.getBasePath(), importSpec); - } - private static boolean isFileMalformed(Project project, VirtualFile file) { PsiFile psiFile = PsiManager.getInstance(project).findFile(file); diff --git a/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsSettingsPage.java b/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsProjectSettingsPage.java similarity index 93% rename from gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsSettingsPage.java rename to gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsProjectSettingsPage.java index 4accaf9..bac0afa 100644 --- a/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsSettingsPage.java +++ b/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsProjectSettingsPage.java @@ -25,12 +25,12 @@ import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.Nullable; -public final class VersionPropsSettingsPage implements Configurable { +public final class VersionPropsProjectSettingsPage implements Configurable { private JCheckBox enabledCheckbox; private final VersionPropsProjectSettings settings; - public VersionPropsSettingsPage(Project project) { + public VersionPropsProjectSettingsPage(Project project) { settings = VersionPropsProjectSettings.getInstance(project); } diff --git a/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsToolbar.java b/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsToolbar.java new file mode 100644 index 0000000..044ac6e --- /dev/null +++ b/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsToolbar.java @@ -0,0 +1,113 @@ +/* + * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.palantir.gradle.versions.intellij; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.toolbar.floating.AbstractFloatingToolbarProvider; +import com.intellij.openapi.editor.toolbar.floating.FloatingToolbarComponent; +import com.intellij.openapi.fileEditor.FileEditor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class VersionPropsToolbar extends AbstractFloatingToolbarProvider { + private static VersionPropsToolbar instance; + private final Map> projectFilesToolbarComponents = new HashMap<>(); + private final Map> projectOriginalContent = new HashMap<>(); + + public VersionPropsToolbar() { + super("VersionPropsActionGroup"); + instance = this; + } + + public static VersionPropsToolbar getInstance() { + return instance; + } + + @Override + public final boolean getAutoHideable() { + return false; + } + + @Override + public final void register( + DataContext dataContext, FloatingToolbarComponent component, Disposable parentDisposable) { + super.register(dataContext, component, parentDisposable); + + FileEditor fileEditor = dataContext.getData(PlatformDataKeys.FILE_EDITOR); + Project project = dataContext.getData(PlatformDataKeys.PROJECT); + + if (fileEditor == null || fileEditor.getFile() == null || project == null) { + return; + } + + projectFilesToolbarComponents.computeIfAbsent(project, k -> new HashMap<>()); + projectOriginalContent.computeIfAbsent(project, k -> new HashMap<>()); + + registerDocumentListener(dataContext, fileEditor, parentDisposable, project); + + projectFilesToolbarComponents.get(project).put(fileEditor.getFile().getPath(), component); + } + + private void registerDocumentListener( + DataContext dataContext, FileEditor fileEditor, Disposable parentDisposable, Project project) { + Editor editor = dataContext.getData(CommonDataKeys.EDITOR); + if (editor != null) { + VirtualFile file = fileEditor.getFile(); + if (file != null) { + projectOriginalContent + .get(project) + .put(file.getPath(), editor.getDocument().getText()); + } + editor.getDocument() + .addDocumentListener( + new VersionPropsDocumentListener( + fileEditor, + editor, + projectOriginalContent.get(project), + projectFilesToolbarComponents.get(project)), + parentDisposable); + } + } + + public final void hideToolbarForFile(String filePath, Project project, Optional editor) { + FloatingToolbarComponent toolbarComponent = projectFilesToolbarComponents + .getOrDefault(project, new HashMap<>()) + .get(filePath); + if (toolbarComponent != null) { + toolbarComponent.scheduleHide(); + } + + editor.ifPresent(value -> projectOriginalContent + .get(project) + .put(filePath, value.getDocument().getText())); + } + + // getter methods used for tests + public final Map> getProjectFilesToolbarComponents() { + return projectFilesToolbarComponents; + } + + public final Map> getProjectOriginalContent() { + return projectOriginalContent; + } +} diff --git a/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsWriteAction.java b/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsWriteAction.java new file mode 100644 index 0000000..7cc17d9 --- /dev/null +++ b/gradle-consistent-versions-idea-plugin/src/main/java/com/palantir/gradle/versions/intellij/VersionPropsWriteAction.java @@ -0,0 +1,52 @@ +/* + * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.gradle.versions.intellij; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class VersionPropsWriteAction extends AnAction { + private static final Logger log = LoggerFactory.getLogger(VersionPropsWriteAction.class); + + public VersionPropsWriteAction() { + super("Write Versions Lock"); + } + + @Override + public final void actionPerformed(AnActionEvent event) { + DataContext dataContext = event.getDataContext(); + Editor editor = dataContext.getData(CommonDataKeys.EDITOR); + Project project = event.getProject(); + + if (editor != null && project != null) { + ProjectRefreshUtils.runWriteVersionsLock(project); + + VirtualFile file = editor.getVirtualFile(); + if (file != null) { + VersionPropsToolbar.getInstance().hideToolbarForFile(file.getPath(), project, Optional.of(editor)); + } + } + } +} diff --git a/gradle-consistent-versions-idea-plugin/src/main/resources/META-INF/plugin.xml b/gradle-consistent-versions-idea-plugin/src/main/resources/META-INF/plugin.xml index b70dec5..df819fb 100644 --- a/gradle-consistent-versions-idea-plugin/src/main/resources/META-INF/plugin.xml +++ b/gradle-consistent-versions-idea-plugin/src/main/resources/META-INF/plugin.xml @@ -10,18 +10,29 @@ com.intellij.modules.platform org.jetbrains.plugins.gradle + + + + + + + - + + + + + implementationClass="com.palantir.gradle.versions.intellij.VersionCompletionContributor"/> + \ No newline at end of file diff --git a/gradle-consistent-versions-idea-plugin/src/test/java/com/palantir/gradle/versions/intellij/VersionPropsToolbarTest.java b/gradle-consistent-versions-idea-plugin/src/test/java/com/palantir/gradle/versions/intellij/VersionPropsToolbarTest.java new file mode 100644 index 0000000..c496f36 --- /dev/null +++ b/gradle-consistent-versions-idea-plugin/src/test/java/com/palantir/gradle/versions/intellij/VersionPropsToolbarTest.java @@ -0,0 +1,101 @@ +/* + * (c) Copyright 2024 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.gradle.versions.intellij; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.toolbar.floating.FloatingToolbarComponent; +import com.intellij.openapi.fileEditor.FileEditor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class VersionPropsToolbarTest { + + @Test + public void testGetInstance() { + VersionPropsToolbar toolbar1 = new VersionPropsToolbar(); + VersionPropsToolbar toolbar2 = VersionPropsToolbar.getInstance(); + assertThat(toolbar1).isEqualTo(toolbar2); + } + + @Test + public void testGetAutoHideable() { + VersionPropsToolbar toolbar = new VersionPropsToolbar(); + assertThat(toolbar.getAutoHideable()).isFalse(); + } + + @Test + public void testRegister() { + VersionPropsToolbar toolbar = new VersionPropsToolbar(); + DataContext dataContext = mock(DataContext.class); + FloatingToolbarComponent component = mock(FloatingToolbarComponent.class); + Disposable parentDisposable = mock(Disposable.class); + FileEditor fileEditor = mock(FileEditor.class); + Project project = mock(Project.class); + VirtualFile virtualFile = mock(VirtualFile.class); + + when(dataContext.getData(PlatformDataKeys.FILE_EDITOR)).thenReturn(fileEditor); + when(dataContext.getData(PlatformDataKeys.PROJECT)).thenReturn(project); + when(fileEditor.getFile()).thenReturn(virtualFile); + when(virtualFile.getPath()).thenReturn("test-path"); + + toolbar.register(dataContext, component, parentDisposable); + + Map> projectFilesToolbarComponents = + toolbar.getProjectFilesToolbarComponents(); + assertThat(projectFilesToolbarComponents).containsKey(project); + assertThat(projectFilesToolbarComponents.get(project)).containsKey("test-path"); + } + + @Test + public void testHideToolbarForFile() { + VersionPropsToolbar toolbar = new VersionPropsToolbar(); + Project project = mock(Project.class); + Editor editor = mock(Editor.class); + Document document = mock(Document.class); + FloatingToolbarComponent component = mock(FloatingToolbarComponent.class); + + when(editor.getDocument()).thenReturn(document); + when(document.getText()).thenReturn("new"); + + Map fileToolbarMap = new HashMap<>(); + fileToolbarMap.put("test-path", component); + toolbar.getProjectFilesToolbarComponents().put(project, fileToolbarMap); + + Map fileContentMap = new HashMap<>(); + fileContentMap.put("test-path", "original"); + toolbar.getProjectOriginalContent().put(project, fileContentMap); + + toolbar.hideToolbarForFile("test-path", project, Optional.of(editor)); + + Mockito.verify(component).scheduleHide(); + assertThat(toolbar.getProjectOriginalContent().get(project).get("test-path")) + .isEqualTo("new"); + } +} diff --git a/versions.props b/versions.props index f3ab5d5..503a3f2 100644 --- a/versions.props +++ b/versions.props @@ -6,7 +6,8 @@ org.immutables:value = 2.10.1 org.junit.jupiter:junit-jupiter = 5.11.2 org.jetbrains:annotations = 25.0.0 com.github.ben-manes.caffeine:caffeine = 3.1.8 +org.mockito:mockito-core = 5.14.1 # Unnecessary once we have lock files com.google.code.findbugs:jsr305 = 3.0.2 -com.google.errorprone:error_prone_annotations = 2.11.0 +com.google.errorprone:error_prone_annotations = 2.11.0 \ No newline at end of file