diff --git a/src/main/java/ee/carlrobert/codegpt/util/ApplicationUtil.java b/src/main/java/ee/carlrobert/codegpt/util/ApplicationUtil.java deleted file mode 100644 index 96dfae018..000000000 --- a/src/main/java/ee/carlrobert/codegpt/util/ApplicationUtil.java +++ /dev/null @@ -1,43 +0,0 @@ -package ee.carlrobert.codegpt.util; - -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectManager; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.openapi.wm.IdeFrame; -import org.jetbrains.annotations.Nullable; - -public class ApplicationUtil { - - private ApplicationUtil() { - } - - public static boolean isUnitTestingMode() { - Application app = ApplicationManager.getApplication(); - return app != null && app.isUnitTestMode(); - } - - @Nullable - public static Project findCurrentProject() { - IdeFrame frame = IdeFocusManager.getGlobalInstance().getLastFocusedFrame(); - Project project = frame != null ? frame.getProject() : null; - if (isValidProject(project)) { - return project; - } - return findProjectFromOpenProjects(); - } - - private static Project findProjectFromOpenProjects() { - for (Project project : ProjectManager.getInstance().getOpenProjects()) { - if (isValidProject(project)) { - return project; - } - } - return null; - } - - private static boolean isValidProject(@Nullable Project project) { - return project != null && !project.isDisposed() && !project.isDefault(); - } -} diff --git a/src/main/java/ee/carlrobert/codegpt/util/BaseConverter.java b/src/main/java/ee/carlrobert/codegpt/util/BaseConverter.java deleted file mode 100644 index 0a4f29505..000000000 --- a/src/main/java/ee/carlrobert/codegpt/util/BaseConverter.java +++ /dev/null @@ -1,38 +0,0 @@ -package ee.carlrobert.codegpt.util; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.intellij.util.xmlb.Converter; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public abstract class BaseConverter extends Converter { - - private final TypeReference typeReference; - private final ObjectMapper objectMapper = new ObjectMapper() - .registerModule(new Jdk8Module()) - .registerModule(new JavaTimeModule()); - - public BaseConverter(TypeReference typeReference) { - this.typeReference = typeReference; - } - - public @Nullable T fromString(@NotNull String value) { - try { - return objectMapper.readValue(value, typeReference); - } catch (JsonProcessingException e) { - throw new RuntimeException("Unable to deserialize conversations", e); - } - } - - public @Nullable String toString(@NotNull T value) { - try { - return objectMapper.writeValueAsString(value); - } catch (JsonProcessingException e) { - throw new RuntimeException("Unable to serialize conversations", e); - } - } -} diff --git a/src/main/java/ee/carlrobert/codegpt/util/EditorUtil.java b/src/main/java/ee/carlrobert/codegpt/util/EditorUtil.java deleted file mode 100644 index 8f2c17448..000000000 --- a/src/main/java/ee/carlrobert/codegpt/util/EditorUtil.java +++ /dev/null @@ -1,152 +0,0 @@ -package ee.carlrobert.codegpt.util; - -import static java.lang.String.format; - -import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.PathManager; -import com.intellij.openapi.command.WriteCommandAction; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.EditorFactory; -import com.intellij.openapi.editor.EditorKind; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.fileEditor.FileEditor; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.fileEditor.TextEditor; -import com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.codeStyle.CodeStyleManager; -import com.intellij.testFramework.LightVirtualFile; -import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public final class EditorUtil { - - private EditorUtil() { - } - - public static Editor createEditor(@NotNull Project project, String fileExtension, String code) { - var timestamp = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now()); - var fileName = "temp_" + timestamp + fileExtension; - var lightVirtualFile = new LightVirtualFile( - format("%s/%s", PathManager.getTempPath(), fileName), - code); - var existingDocument = FileDocumentManager.getInstance().getDocument(lightVirtualFile); - var document = existingDocument != null - ? existingDocument - : EditorFactory.getInstance().createDocument(code); - - disableHighlighting(project, document); - - return EditorFactory.getInstance().createEditor( - document, - project, - lightVirtualFile, - true, - EditorKind.MAIN_EDITOR); - } - - public static void updateEditorDocument(Editor editor, String content) { - var document = editor.getDocument(); - var application = ApplicationManager.getApplication(); - Runnable updateDocumentRunnable = () -> application.runWriteAction(() -> - WriteCommandAction.runWriteCommandAction(editor.getProject(), () -> { - document.replaceString(0, document.getTextLength(), content); - editor.getComponent().repaint(); - editor.getComponent().revalidate(); - })); - - if (application.isUnitTestMode()) { - application.invokeAndWait(updateDocumentRunnable); - } else { - application.invokeLater(updateDocumentRunnable); - } - } - - public static boolean hasSelection(@Nullable Editor editor) { - return editor != null && editor.getSelectionModel().hasSelection(); - } - - public static @Nullable Editor getSelectedEditor(@NotNull Project project) { - FileEditorManager editorManager = FileEditorManager.getInstance(project); - return editorManager != null ? editorManager.getSelectedTextEditor() : null; - } - - public static @Nullable String getSelectedEditorSelectedText(@NotNull Project project) { - var selectedEditor = EditorUtil.getSelectedEditor(project); - if (selectedEditor != null) { - return selectedEditor.getSelectionModel().getSelectedText(); - } - return null; - } - - public static boolean isSelectedEditor(Editor editor) { - Project project = editor.getProject(); - if (project != null && !project.isDisposed()) { - FileEditorManager editorManager = FileEditorManager.getInstance(project); - if (editorManager == null) { - return false; - } - if (editorManager instanceof FileEditorManagerImpl) { - Editor current = ((FileEditorManagerImpl) editorManager).getSelectedTextEditor(true); - return current != null && current.equals(editor); - } - FileEditor current = editorManager.getSelectedEditor(); - return current instanceof TextEditor && editor.equals(((TextEditor) current).getEditor()); - } - return false; - } - - public static boolean isMainEditorTextSelected(@NotNull Project project) { - return hasSelection(getSelectedEditor(project)); - } - - public static void replaceMainEditorSelection(@NotNull Project project, @NotNull String text) { - var application = ApplicationManager.getApplication(); - application.invokeLater(() -> - application.runWriteAction(() -> WriteCommandAction.runWriteCommandAction(project, () -> { - var editor = getSelectedEditor(project); - if (editor != null) { - var selectionModel = editor.getSelectionModel(); - int startOffset = selectionModel.getSelectionStart(); - int endOffset = selectionModel.getSelectionEnd(); - var document = editor.getDocument(); - - document.replaceString(startOffset, endOffset, text); - - if (ConfigurationSettings.getCurrentState().isAutoFormattingEnabled()) { - reformatDocument(project, document, startOffset, endOffset); - } - - editor.getContentComponent().requestFocus(); - selectionModel.removeSelection(); - } - }))); - } - - public static void reformatDocument( - @NotNull Project project, - @NotNull Document document, - int startOffset, - int endOffset) { - var psiDocumentManager = PsiDocumentManager.getInstance(project); - psiDocumentManager.commitDocument(document); - var psiFile = psiDocumentManager.getPsiFile(document); - if (psiFile != null) { - CodeStyleManager.getInstance(project) - .reformatText(psiFile, startOffset, endOffset); - } - } - - public static void disableHighlighting(@NotNull Project project, Document document) { - var psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document); - if (psiFile != null) { - DaemonCodeAnalyzer.getInstance(project).setHighlightingEnabled(psiFile, false); - } - } -} diff --git a/src/main/java/ee/carlrobert/codegpt/util/MapConverter.java b/src/main/java/ee/carlrobert/codegpt/util/MapConverter.java deleted file mode 100644 index e7de065d1..000000000 --- a/src/main/java/ee/carlrobert/codegpt/util/MapConverter.java +++ /dev/null @@ -1,11 +0,0 @@ -package ee.carlrobert.codegpt.util; - -import com.fasterxml.jackson.core.type.TypeReference; -import java.util.Map; - -public class MapConverter extends BaseConverter> { - - public MapConverter() { - super(new TypeReference<>() {}); - } -} diff --git a/src/main/java/ee/carlrobert/codegpt/util/MarkdownUtil.java b/src/main/java/ee/carlrobert/codegpt/util/MarkdownUtil.java deleted file mode 100644 index 1d7ced580..000000000 --- a/src/main/java/ee/carlrobert/codegpt/util/MarkdownUtil.java +++ /dev/null @@ -1,47 +0,0 @@ -package ee.carlrobert.codegpt.util; - -import com.vladsch.flexmark.html.HtmlRenderer; -import com.vladsch.flexmark.parser.Parser; -import com.vladsch.flexmark.util.data.MutableDataSet; -import ee.carlrobert.codegpt.toolwindow.chat.ResponseNodeRenderer; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class MarkdownUtil { - - private MarkdownUtil() { - } - - /** - * Splits a given string into a list of strings where each element is either a code block - * surrounded by triple backticks or a non-code block text. - * - * @param inputMarkdown The input markdown formatted string to be split. - * @return A list of strings where each element is a code block or a non-code block text from the - * input string. - */ - public static List splitCodeBlocks(String inputMarkdown) { - List result = new ArrayList<>(); - Pattern pattern = Pattern.compile("(?s)```.*?```"); - Matcher matcher = pattern.matcher(inputMarkdown); - int start = 0; - while (matcher.find()) { - result.add(inputMarkdown.substring(start, matcher.start())); - result.add(matcher.group()); - start = matcher.end(); - } - result.add(inputMarkdown.substring(start)); - return result.stream().filter(item -> !item.isBlank()).toList(); - } - - public static String convertMdToHtml(String message) { - MutableDataSet options = new MutableDataSet(); - var document = Parser.builder(options).build().parse(message); - return HtmlRenderer.builder(options) - .nodeRendererFactory(new ResponseNodeRenderer.Factory()) - .build() - .render(document); - } -} diff --git a/src/main/java/ee/carlrobert/codegpt/util/file/FileExtensionLanguageDetails.java b/src/main/java/ee/carlrobert/codegpt/util/file/FileExtensionLanguageDetails.java deleted file mode 100644 index 412d0f203..000000000 --- a/src/main/java/ee/carlrobert/codegpt/util/file/FileExtensionLanguageDetails.java +++ /dev/null @@ -1,23 +0,0 @@ -package ee.carlrobert.codegpt.util.file; - -public class FileExtensionLanguageDetails { - - private String extension; - private String value; - - public String getExtension() { - return extension; - } - - public void setExtension(String extension) { - this.extension = extension; - } - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } -} diff --git a/src/main/java/ee/carlrobert/codegpt/util/file/FileUtil.java b/src/main/java/ee/carlrobert/codegpt/util/file/FileUtil.java deleted file mode 100644 index 13df0f540..000000000 --- a/src/main/java/ee/carlrobert/codegpt/util/file/FileUtil.java +++ /dev/null @@ -1,195 +0,0 @@ -package ee.carlrobert.codegpt.util.file; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.vfs.VirtualFile; -import ee.carlrobert.codegpt.CodeGPTPlugin; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.Writer; -import java.net.URL; -import java.nio.ByteBuffer; -import java.nio.channels.Channels; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.text.DecimalFormat; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.jetbrains.annotations.NotNull; - -public class FileUtil { - - private FileUtil() { - } - - private static final Logger LOG = Logger.getInstance(FileUtil.class); - - public static File createFile(String directoryPath, String fileName, String fileContent) { - try { - tryCreateDirectory(directoryPath); - return Files.writeString( - Path.of(directoryPath, fileName), - fileContent, - StandardOpenOption.CREATE).toFile(); - } catch (IOException e) { - throw new RuntimeException("Failed to create file", e); - } - } - - public static void copyFileWithProgress( - String fileName, - URL url, - long[] bytesRead, - long fileSize, - ProgressIndicator indicator) throws IOException { - FileUtil.tryCreateDirectory(CodeGPTPlugin.getLlamaModelsPath()); - - try ( - var readableByteChannel = Channels.newChannel(url.openStream()); - var fileOutputStream = new FileOutputStream( - CodeGPTPlugin.getLlamaModelsPath() + File.separator + fileName)) { - var buffer = ByteBuffer.allocateDirect(1024 * 10); - - while (readableByteChannel.read(buffer) != -1) { - if (indicator.isCanceled()) { - readableByteChannel.close(); - break; - } - buffer.flip(); - bytesRead[0] += fileOutputStream.getChannel().write(buffer); - buffer.clear(); - indicator.setFraction((double) bytesRead[0] / fileSize); - } - } - } - - public static VirtualFile getEditorFile(@NotNull Editor editor) { - return FileDocumentManager.getInstance().getFile(editor.getDocument()); - } - - public static void tryCreateDirectory(String directoryPath) { - try { - if (!com.intellij.openapi.util.io.FileUtil.exists(directoryPath)) { - if (!com.intellij.openapi.util.io.FileUtil.createDirectory( - Path.of(directoryPath).toFile())) { - throw new IOException("Failed to create directory: " + directoryPath); - } - } - } catch (IOException e) { - throw new RuntimeException("Failed to create directory", e); - } - } - - - public static String getFileExtension(String filename) { - Pattern pattern = Pattern.compile("[^.]+$"); - Matcher matcher = pattern.matcher(filename); - - if (matcher.find()) { - return matcher.group(); - } - return ""; - } - - public static Map.Entry findLanguageExtensionMapping(String language) { - var defaultValue = Map.entry("Text", ".txt"); - var mapper = new ObjectMapper(); - - List extensionToLanguageMappings; - List languageToExtensionMappings; - try { - extensionToLanguageMappings = mapper.readValue( - getResourceContent("/fileExtensionLanguageMappings.json"), new TypeReference<>() { - }); - languageToExtensionMappings = mapper.readValue( - getResourceContent("/languageFileExtensionMappings.json"), new TypeReference<>() { - }); - } catch (JsonProcessingException e) { - LOG.error("Unable to extract file extension", e); - return defaultValue; - } - - return findFirstExtension(languageToExtensionMappings, language) - .or(() -> extensionToLanguageMappings.stream() - .filter(it -> it.getExtension().equalsIgnoreCase(language)) - .findFirst() - .flatMap(it -> findFirstExtension(languageToExtensionMappings, it.getValue())) - ).orElse(defaultValue); - } - - public static boolean isUtf8File(String filePath) { - var path = Paths.get(filePath); - try (var reader = Files.newBufferedReader(path)) { - int c = reader.read(); - if (c >= 0) { - reader.transferTo(Writer.nullWriter()); - } - return true; - } catch (Exception e) { - return false; - } - } - - public static String getImageMediaType(String fileName) { - var fileExtension = getFileExtension(fileName); - return switch (fileExtension) { - case "png" -> "image/png"; - case "jpg", "jpeg" -> "image/jpeg"; - default -> throw new IllegalArgumentException("Unsupported image type: " + fileExtension); - }; - } - - public static String getResourceContent(String name) { - try (var stream = Objects.requireNonNull(FileUtil.class.getResourceAsStream(name))) { - return new String(stream.readAllBytes(), StandardCharsets.UTF_8); - } catch (IOException e) { - throw new RuntimeException("Unable to read resource", e); - } - } - - public static String convertFileSize(long fileSizeInBytes) { - String[] units = {"B", "KB", "MB", "GB"}; - int unitIndex = 0; - double fileSize = fileSizeInBytes; - - while (fileSize >= 1024 && unitIndex < units.length - 1) { - fileSize /= 1024; - unitIndex++; - } - - return new DecimalFormat("#.##").format(fileSize) + " " + units[unitIndex]; - } - - public static String convertLongValue(long value) { - if (value >= 1_000_000) { - return value / 1_000_000 + "M"; - } - if (value >= 1_000) { - return value / 1_000 + "K"; - } - - return String.valueOf(value); - } - - private static Optional> findFirstExtension( - List languageFileExtensionMappings, - String language) { - return languageFileExtensionMappings.stream() - .filter(item -> language.equalsIgnoreCase(item.getName())) - .findFirst() - .map(it -> Map.entry(it.getName(), it.getExtensions().get(0))); - } -} diff --git a/src/main/java/ee/carlrobert/codegpt/util/file/LanguageFileExtensionDetails.java b/src/main/java/ee/carlrobert/codegpt/util/file/LanguageFileExtensionDetails.java deleted file mode 100644 index b5574be71..000000000 --- a/src/main/java/ee/carlrobert/codegpt/util/file/LanguageFileExtensionDetails.java +++ /dev/null @@ -1,34 +0,0 @@ -package ee.carlrobert.codegpt.util.file; - -import java.util.List; - -public class LanguageFileExtensionDetails { - - private String name; - private String type; - private List extensions; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public List getExtensions() { - return extensions; - } - - public void setExtensions(List extensions) { - this.extensions = extensions; - } -} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/util/ApplicationUtil.kt b/src/main/kotlin/ee/carlrobert/codegpt/util/ApplicationUtil.kt new file mode 100644 index 000000000..b024e3ffe --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/util/ApplicationUtil.kt @@ -0,0 +1,37 @@ +package ee.carlrobert.codegpt.util + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.ProjectManager +import com.intellij.openapi.wm.IdeFocusManager + +object ApplicationUtil { + @JvmStatic + fun isUnitTestingMode(): Boolean { + val app = ApplicationManager.getApplication() + return app != null && app.isUnitTestMode + } + + @JvmStatic + fun findCurrentProject(): Project? { + val frame = IdeFocusManager.getGlobalInstance().lastFocusedFrame + val project = frame?.project + if (isValidProject(project)) { + return project + } + return findProjectFromOpenProjects() + } + + private fun findProjectFromOpenProjects(): Project? { + for (project in ProjectManager.getInstance().openProjects) { + if (isValidProject(project)) { + return project + } + } + return null + } + + private fun isValidProject(project: Project?): Boolean { + return project != null && !project.isDisposed && !project.isDefault + } +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/util/BaseConverter.kt b/src/main/kotlin/ee/carlrobert/codegpt/util/BaseConverter.kt new file mode 100644 index 000000000..67e2ea445 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/util/BaseConverter.kt @@ -0,0 +1,30 @@ +package ee.carlrobert.codegpt.util + +import com.fasterxml.jackson.core.JsonProcessingException +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import com.intellij.util.xmlb.Converter + +abstract class BaseConverter protected constructor(private val typeReference: TypeReference) : Converter() { + private val objectMapper: ObjectMapper = ObjectMapper() + .registerModule(Jdk8Module()) + .registerModule(JavaTimeModule()) + + override fun fromString(value: String): T? { + try { + return objectMapper.readValue(value, typeReference) + } catch (e: JsonProcessingException) { + throw RuntimeException("Unable to deserialize conversations", e) + } + } + + override fun toString(value: T & Any): String? { + try { + return objectMapper.writeValueAsString(value) + } catch (e: JsonProcessingException) { + throw RuntimeException("Unable to serialize conversations", e) + } + } +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/util/EditorUtil.kt b/src/main/kotlin/ee/carlrobert/codegpt/util/EditorUtil.kt new file mode 100644 index 000000000..71471f6bd --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/util/EditorUtil.kt @@ -0,0 +1,152 @@ +package ee.carlrobert.codegpt.util + +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.PathManager +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.editor.Document +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.EditorFactory +import com.intellij.openapi.editor.EditorKind +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.fileEditor.TextEditor +import com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.codeStyle.CodeStyleManager +import com.intellij.testFramework.LightVirtualFile +import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +object EditorUtil { + @JvmStatic + fun createEditor(project: Project, fileExtension: String, code: String): Editor { + val timestamp = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now()) + val fileName = "temp_$timestamp$fileExtension" + val lightVirtualFile = LightVirtualFile( + String.format("%s/%s", PathManager.getTempPath(), fileName), + code + ) + val existingDocument = FileDocumentManager.getInstance().getDocument(lightVirtualFile) + val document = existingDocument ?: EditorFactory.getInstance().createDocument(code) + + disableHighlighting(project, document) + + return EditorFactory.getInstance().createEditor( + document, + project, + lightVirtualFile, + true, + EditorKind.MAIN_EDITOR + ) + } + + @JvmStatic + fun updateEditorDocument(editor: Editor, content: String) { + val document = editor.document + val application = ApplicationManager.getApplication() + val updateDocumentRunnable = Runnable { + application.runWriteAction { + WriteCommandAction.runWriteCommandAction(editor.project) { + document.replaceString(0, document.textLength, content) + editor.component.repaint() + editor.component.revalidate() + } + } + } + + if (application.isUnitTestMode) { + application.invokeAndWait(updateDocumentRunnable) + } else { + application.invokeLater(updateDocumentRunnable) + } + } + + @JvmStatic + fun hasSelection(editor: Editor?): Boolean { + return editor?.selectionModel?.hasSelection() == true + } + + @JvmStatic + fun getSelectedEditor(project: Project): Editor? { + val editorManager = FileEditorManager.getInstance(project) + return editorManager?.selectedTextEditor + } + + @JvmStatic + fun getSelectedEditorSelectedText(project: Project): String? { + val selectedEditor = getSelectedEditor(project) + return selectedEditor?.selectionModel?.selectedText + } + + @JvmStatic + fun isSelectedEditor(editor: Editor): Boolean { + val project = editor.project + if (project != null && !project.isDisposed) { + val editorManager = FileEditorManager.getInstance(project) ?: return false + if (editorManager is FileEditorManagerImpl) { + return editor == editorManager.getSelectedTextEditor(true) + } + val current = editorManager.selectedEditor + return (current is TextEditor) && editor == current.editor + } + return false + } + + @JvmStatic + fun isMainEditorTextSelected(project: Project): Boolean { + return hasSelection(getSelectedEditor(project)) + } + + @JvmStatic + fun replaceMainEditorSelection(project: Project, text: String) { + val application = ApplicationManager.getApplication() + application.invokeLater { + application.runWriteAction { + WriteCommandAction.runWriteCommandAction(project) { + val editor = getSelectedEditor(project) + editor?.let { + val selectionModel = editor.selectionModel + val startOffset = selectionModel.selectionStart + val endOffset = selectionModel.selectionEnd + val document = editor.document + + document.replaceString(startOffset, endOffset, text) + + if (ConfigurationSettings.getCurrentState().isAutoFormattingEnabled) { + reformatDocument(project, document, startOffset, endOffset) + } + + editor.contentComponent.requestFocus() + selectionModel.removeSelection() + } + } + } + } + } + + @JvmStatic + fun reformatDocument( + project: Project, + document: Document, + startOffset: Int, + endOffset: Int + ) { + val psiDocumentManager = PsiDocumentManager.getInstance(project) + psiDocumentManager.commitDocument(document) + val psiFile = psiDocumentManager.getPsiFile(document) + psiFile?.let { + CodeStyleManager.getInstance(project).reformatText(psiFile, startOffset, endOffset) + } + } + + @JvmStatic + fun disableHighlighting(project: Project, document: Document) { + val psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document) + psiFile?.let { + DaemonCodeAnalyzer.getInstance(project).setHighlightingEnabled(psiFile, false) + } + } +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/util/MapConverter.kt b/src/main/kotlin/ee/carlrobert/codegpt/util/MapConverter.kt new file mode 100644 index 000000000..753d9f6a0 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/util/MapConverter.kt @@ -0,0 +1,5 @@ +package ee.carlrobert.codegpt.util + +import com.fasterxml.jackson.core.type.TypeReference + +class MapConverter : BaseConverter?>(object : TypeReference?>() {}) diff --git a/src/main/kotlin/ee/carlrobert/codegpt/util/MarkdownUtil.kt b/src/main/kotlin/ee/carlrobert/codegpt/util/MarkdownUtil.kt new file mode 100644 index 000000000..e0907289f --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/util/MarkdownUtil.kt @@ -0,0 +1,42 @@ +package ee.carlrobert.codegpt.util + +import com.vladsch.flexmark.html.HtmlRenderer +import com.vladsch.flexmark.parser.Parser +import com.vladsch.flexmark.util.data.MutableDataSet +import ee.carlrobert.codegpt.toolwindow.chat.ResponseNodeRenderer +import java.util.regex.Pattern + +object MarkdownUtil { + /** + * Splits a given string into a list of strings where each element is either a code block + * surrounded by triple backticks or a non-code block text. + * + * @param inputMarkdown The input markdown formatted string to be split. + * @return A list of strings where each element is a code block or a non-code block text from the + * input string. + */ + @JvmStatic + fun splitCodeBlocks(inputMarkdown: String): List { + val result: MutableList = ArrayList() + val pattern = Pattern.compile("(?s)```.*?```") + val matcher = pattern.matcher(inputMarkdown) + var start = 0 + while (matcher.find()) { + result.add(inputMarkdown.substring(start, matcher.start())) + result.add(matcher.group()) + start = matcher.end() + } + result.add(inputMarkdown.substring(start)) + return result.stream().filter(String::isNotBlank).toList() + } + + @JvmStatic + fun convertMdToHtml(message: String?): String { + val options = MutableDataSet() + val document = Parser.builder(options).build().parse(message!!) + return HtmlRenderer.builder(options) + .nodeRendererFactory(ResponseNodeRenderer.Factory()) + .build() + .render(document) + } +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/util/file/FileExtensionLanguageDetails.kt b/src/main/kotlin/ee/carlrobert/codegpt/util/file/FileExtensionLanguageDetails.kt new file mode 100644 index 000000000..06c1a5ef0 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/util/file/FileExtensionLanguageDetails.kt @@ -0,0 +1,4 @@ +package ee.carlrobert.codegpt.util.file + +@JvmRecord +data class FileExtensionLanguageDetails(val extension: String, val value: String) diff --git a/src/main/kotlin/ee/carlrobert/codegpt/util/file/FileUtil.kt b/src/main/kotlin/ee/carlrobert/codegpt/util/file/FileUtil.kt new file mode 100644 index 000000000..b55c704bd --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/util/file/FileUtil.kt @@ -0,0 +1,213 @@ +package ee.carlrobert.codegpt.util.file + +import com.fasterxml.jackson.core.JsonProcessingException +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.databind.ObjectMapper +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.vfs.VirtualFile +import ee.carlrobert.codegpt.CodeGPTPlugin +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.Writer +import java.net.URL +import java.nio.ByteBuffer +import java.nio.channels.Channels +import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.nio.file.StandardOpenOption +import java.text.DecimalFormat +import java.util.Objects +import java.util.Optional +import java.util.regex.Pattern + +object FileUtil { + private val LOG = Logger.getInstance(FileUtil::class.java) + + @JvmStatic + fun createFile(directoryPath: String, fileName: String?, fileContent: String?): File { + try { + tryCreateDirectory(directoryPath) + return Files.writeString( + Path.of(directoryPath, fileName), + fileContent, + StandardOpenOption.CREATE + ).toFile() + } catch (e: IOException) { + throw RuntimeException("Failed to create file", e) + } + } + + @JvmStatic + @Throws(IOException::class) + fun copyFileWithProgress( + fileName: String, + url: URL, + bytesRead: LongArray, + fileSize: Long, + indicator: ProgressIndicator + ) { + tryCreateDirectory(CodeGPTPlugin.getLlamaModelsPath()) + + Channels.newChannel(url.openStream()).use { readableByteChannel -> + FileOutputStream( + CodeGPTPlugin.getLlamaModelsPath() + File.separator + fileName + ).use { fileOutputStream -> + val buffer = ByteBuffer.allocateDirect(1024 * 10) + while (readableByteChannel.read(buffer) != -1) { + if (indicator.isCanceled) { + readableByteChannel.close() + break + } + buffer.flip() + bytesRead[0] += fileOutputStream.channel.write(buffer).toLong() + buffer.clear() + indicator.fraction = bytesRead[0].toDouble() / fileSize + } + } + } + } + + @JvmStatic + fun getEditorFile(editor: Editor): VirtualFile? { + return FileDocumentManager.getInstance().getFile(editor.document) + } + + private fun tryCreateDirectory(directoryPath: String) { + try { + if (!com.intellij.openapi.util.io.FileUtil.exists(directoryPath)) { + if (!com.intellij.openapi.util.io.FileUtil.createDirectory( + Path.of(directoryPath).toFile() + ) + ) { + throw IOException("Failed to create directory: $directoryPath") + } + } + } catch (e: IOException) { + throw RuntimeException("Failed to create directory", e) + } + } + + + @JvmStatic + fun getFileExtension(filename: String?): String { + val pattern = Pattern.compile("[^.]+$") + val matcher = filename?.let { pattern.matcher(it) } + + if (matcher?.find() == true) { + return matcher.group() + } + return "" + } + + @JvmStatic + fun findLanguageExtensionMapping(language: String): Map.Entry { + val defaultValue = mapOf("Text" to ".txt").entries.first() + val mapper = ObjectMapper() + + val extensionToLanguageMappings: List + val languageToExtensionMappings: List + try { + extensionToLanguageMappings = mapper.readValue( + getResourceContent("/fileExtensionLanguageMappings.json"), + object : TypeReference>() { + }) + languageToExtensionMappings = mapper.readValue( + getResourceContent("/languageFileExtensionMappings.json"), + object : TypeReference>() { + }) + } catch (e: JsonProcessingException) { + LOG.error("Unable to extract file extension", e) + return defaultValue + } + + return findFirstExtension(languageToExtensionMappings, language) + .or { + extensionToLanguageMappings.stream() + .filter { it.extension.equals(language, ignoreCase = true) } + .findFirst() + .flatMap { findFirstExtension(languageToExtensionMappings, it.value) } + }.orElse(defaultValue) + } + + fun isUtf8File(filePath: String?): Boolean { + val path = filePath?.let { Paths.get(it) } + try { + Files.newBufferedReader(path).use { reader -> + val c = reader.read() + if (c >= 0) { + reader.transferTo(Writer.nullWriter()) + } + return true + } + } catch (e: Exception) { + return false + } + } + + @JvmStatic + fun getImageMediaType(fileName: String?): String { + return when (val fileExtension = getFileExtension(fileName)) { + "png" -> "image/png" + "jpg", "jpeg" -> "image/jpeg" + else -> throw IllegalArgumentException("Unsupported image type: $fileExtension") + } + } + + @JvmStatic + fun getResourceContent(name: String?): String { + try { + Objects.requireNonNull(name?.let { FileUtil::class.java.getResourceAsStream(it) }).use { stream -> + return String(stream.readAllBytes(), StandardCharsets.UTF_8) + } + } catch (e: IOException) { + throw RuntimeException("Unable to read resource", e) + } + } + + @JvmStatic + fun convertFileSize(fileSizeInBytes: Long): String { + val units = arrayOf("B", "KB", "MB", "GB") + var unitIndex = 0 + var fileSize = fileSizeInBytes.toDouble() + + while (fileSize >= 1024 && unitIndex < units.size - 1) { + fileSize /= 1024.0 + unitIndex++ + } + + return DecimalFormat("#.##").format(fileSize) + " " + units[unitIndex] + } + + @JvmStatic + fun convertLongValue(value: Long): String { + if (value >= 1000000) { + return (value / 1000000).toString() + "M" + } + if (value >= 1000) { + return (value / 1000).toString() + "K" + } + + return value.toString() + } + + @JvmStatic + fun findFirstExtension( + languageFileExtensionMappings: List, + language: String + ): Optional> { + return languageFileExtensionMappings.stream() + .filter { language.equals(it.name, ignoreCase = true) + && it.extensions != null + && it.extensions.stream().anyMatch(String::isNotBlank) } + .findFirst() + .map { java.util.Map.entry(it.name, + it.extensions?.stream()?.filter(String::isNotBlank)?.findFirst()?.orElse("") ?: "" + ) } + } +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/util/file/LanguageFileExtensionDetails.kt b/src/main/kotlin/ee/carlrobert/codegpt/util/file/LanguageFileExtensionDetails.kt new file mode 100644 index 000000000..07f770b7a --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/util/file/LanguageFileExtensionDetails.kt @@ -0,0 +1,4 @@ +package ee.carlrobert.codegpt.util.file + +@JvmRecord +data class LanguageFileExtensionDetails(val name: String, val type: String, val extensions: List?)