diff --git a/README.md b/README.md index 13f1469..4141134 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ java -jar phoenix-0.3.3.jar После запуска приложения в конфигураторе нажимаем в модуле с кодом: * `CTRL` + `I` - анализ кода на замечания. * `CTRL` + `K` - форматирование кода. +* `CTRL` + `J` - замена не канонически написанных ключевых слов (**работает только после анализа на замечания**). Так же стоит отметить, что анализ и форматирование работает по выделенному коду. diff --git a/docs/index.md b/docs/index.md index 63f60a1..bff68fd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -41,6 +41,7 @@ java -jar phoenix-0.3.3.jar После запуска приложения в конфигураторе нажимаем в модуле с кодом: * `CTRL` + `I` - анализ кода на замечания. * `CTRL` + `K` - форматирование кода. +* `CTRL` + `J` - замена не канонически написанных ключевых слов (**работает только после анализа на замечания**). Так же стоит отметить, что анализ и форматирование работает по выделенному коду. diff --git a/src/main/java/org/github/otymko/phoenixbsl/LauncherApp.java b/src/main/java/org/github/otymko/phoenixbsl/LauncherApp.java index 45ac0e0..e17c631 100644 --- a/src/main/java/org/github/otymko/phoenixbsl/LauncherApp.java +++ b/src/main/java/org/github/otymko/phoenixbsl/LauncherApp.java @@ -31,6 +31,9 @@ private static void runApp() { // инициализация настроек app.initConfiguration(); + // инициализация базовых настроек BSL LS + app.initBSLConfiguration(); + // запускаем главную форму MainApplicationThread mainApplicationThread = new MainApplicationThread(); mainApplicationThread.start(); diff --git a/src/main/java/org/github/otymko/phoenixbsl/core/PhoenixAPI.java b/src/main/java/org/github/otymko/phoenixbsl/core/PhoenixAPI.java index deb795e..67533c8 100644 --- a/src/main/java/org/github/otymko/phoenixbsl/core/PhoenixAPI.java +++ b/src/main/java/org/github/otymko/phoenixbsl/core/PhoenixAPI.java @@ -104,7 +104,7 @@ public static void setTextInClipboard(String text) { private static void clearClipboard() { LOGGER.debug("clearClipboard"); var stringSelection = new StringSelection(""); - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(stringSelection, null); // FIXME: падает буфер обмена } private static String getFromClipboard() { @@ -134,4 +134,5 @@ public static int getProcessId() { public static void showMessageDialog(String message) { JOptionPane.showMessageDialog(new JFrame(), message); } + } diff --git a/src/main/java/org/github/otymko/phoenixbsl/core/PhoenixApp.java b/src/main/java/org/github/otymko/phoenixbsl/core/PhoenixApp.java index b98d6b1..1579b81 100644 --- a/src/main/java/org/github/otymko/phoenixbsl/core/PhoenixApp.java +++ b/src/main/java/org/github/otymko/phoenixbsl/core/PhoenixApp.java @@ -2,11 +2,17 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.sun.jna.platform.win32.WinDef; +import lombok.Data; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import org.eclipse.lsp4j.CodeAction; +import org.eclipse.lsp4j.Command; +import org.eclipse.lsp4j.Diagnostic; +import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.github.otymko.phoenixbsl.events.EventListener; import org.github.otymko.phoenixbsl.events.EventManager; import org.github.otymko.phoenixbsl.lsp.BSLBinding; +import org.github.otymko.phoenixbsl.lsp.BSLConfiguration; import org.github.otymko.phoenixbsl.lsp.BSLLanguageClient; import org.github.otymko.phoenixbsl.utils.ProcessHelper; import org.github.otymko.phoenixbsl.views.Toolbar; @@ -15,22 +21,25 @@ import java.io.IOException; import java.net.URI; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.jar.Attributes; import java.util.jar.Manifest; +import java.util.stream.Collectors; @Slf4j +@Data public class PhoenixApp implements EventListener { private static final PhoenixApp INSTANCE = new PhoenixApp(); - private static final Path pathToFolderLog = Path.of("app", "logs"); - // TODO: лучше файл настроек хранить не в каталоге с app, а в пользовательском каталоге - // при переустановке app тогда настройки сохраняться, с другой стороны - может сменится структура настроек - private static final Path pathToConfiguration = Path.of("app", "Configuration.json").toAbsolutePath(); - - public static final URI fakeUri = new File("C:/BSL/fake.bsl").toPath().toAbsolutePath().toUri(); + private static final Path pathToFolderLog = createPathToLog(); + private static final Path pathToConfiguration = createPathToConfiguration(); + private static final Path pathToBSLConfiguration = + Path.of(System.getProperty("user.home"), "phoenixbsl", ".bsl-language-server.json"); + public static final URI fakeUri = Path.of("fake.bsl").toUri(); private EventManager events; private WinDef.HWND focusForm; @@ -39,6 +48,7 @@ public class PhoenixApp implements EventListener { private ConfigurationApp configuration; + private List diagnosticList = new ArrayList<>(); public int currentOffset = 0; @@ -47,11 +57,13 @@ private PhoenixApp() { events = new EventManager( EventManager.EVENT_INSPECTION, EventManager.EVENT_FORMATTING, + EventManager.EVENT_FIX_ALL, EventManager.EVENT_UPDATE_ISSUES, EventManager.SHOW_ISSUE_STAGE, EventManager.SHOW_SETTING_STAGE); events.subscribe(EventManager.EVENT_INSPECTION, this); events.subscribe(EventManager.EVENT_FORMATTING, this); + events.subscribe(EventManager.EVENT_FIX_ALL, this); configuration = new ConfigurationApp(); @@ -69,7 +81,130 @@ public void initProcessBSL() { } } + + // EventListener + // + + @Override + public void inspection() { + + LOGGER.debug("Событие: анализ кода"); + + if (processBSLIsRunning() && PhoenixAPI.isWindowsForm1S()) { + updateFocusForm(); + } else { + return; + } + + if (bslBinding == null) { + return; + } + + currentOffset = 0; + var textForCheck = ""; + var textModuleSelected = PhoenixAPI.getTextSelected(); + if (textModuleSelected.length() > 0) { + // получем номер строки + textForCheck = textModuleSelected; + currentOffset = PhoenixAPI.getCurrentLineNumber(); + } else { + textForCheck = PhoenixAPI.getTextAll(); + } + + bslBinding.textDocumentDidChange(fakeUri, textForCheck); + bslBinding.textDocumentDidSave(fakeUri); + + } + + @Override + public void formatting() { + + LOGGER.debug("Событие: форматирование"); + + if (!(processBSLIsRunning() && PhoenixAPI.isWindowsForm1S())) { + return; + } + + var textForFormatting = ""; + var isSelected = false; + var textModuleSelected = PhoenixAPI.getTextSelected(); + if (textModuleSelected.length() > 0) { + textForFormatting = textModuleSelected; + isSelected = true; + } else { + textForFormatting = PhoenixAPI.getTextAll(); + } + + // DidChange + bslBinding.textDocumentDidChange(fakeUri, textForFormatting); + + // Formatting + var result = bslBinding.textDocumentFormatting(fakeUri); + + try { + PhoenixAPI.insetTextOnForm(result.get().get(0).getNewText(), isSelected); + } catch (InterruptedException e) { + LOGGER.error(e.getMessage()); + } catch (ExecutionException e) { + LOGGER.error(e.getMessage()); + } + + } + + @Override + public void fixAll() { + + LOGGER.debug("Событие: обработка квикфиксов"); + + if (!(processBSLIsRunning() && PhoenixAPI.isWindowsForm1S())) { + return; + } + + var separator = "\n"; + var textForQF = PhoenixAPI.getTextAll(); + + // найдем все диагностики подсказки + var listQF = diagnosticList.stream() + .filter(diagnostic -> diagnostic.getCode().equalsIgnoreCase("CanonicalSpellingKeywords")) + .collect(Collectors.toList()); + + List> codeActions = new ArrayList<>(); + try { + codeActions = bslBinding.textDocumentCodeAction(fakeUri, listQF); + } catch (ExecutionException | InterruptedException e) { + LOGGER.error(e.getMessage()); + + } + LOGGER.debug("Квикфиксов найдено: " + codeActions); + String[] strings = textForQF.split(separator); + + codeActions.forEach(diagnostic -> { + CodeAction codeAction = diagnostic.getRight(); + codeAction.getEdit().getChanges().forEach((s, textEdits) -> { + textEdits.forEach(textEdit -> { + var range = textEdit.getRange(); + var currentLine = range.getStart().getLine(); + var newText = textEdit.getNewText(); + var currentString = strings[currentLine]; + var newString = + currentString.substring(0, range.getStart().getCharacter()) + + newText + + currentString.substring(range.getEnd().getCharacter()); + strings[currentLine] = newString; + }); + }); + }); + + if (!codeActions.isEmpty()) { + var text = String.join(separator, strings); + PhoenixAPI.insetTextOnForm(text, false); + } + + } + + public void createProcessBSLLS() { + processBSL = null; var pathToBSLLS = Path.of(configuration.getPathToBSLLS()).toAbsolutePath(); @@ -79,6 +214,14 @@ public void createProcessBSLLS() { } var arguments = ProcessHelper.getArgumentsRunProcessBSLLS(configuration); + + if (pathToBSLLS.toFile().exists()) { + arguments.add("--configuration"); + arguments.add(pathToBSLLS.toString()); + } + + LOGGER.debug("Строка запуска BSL LS {}", String.join(" ", arguments)); + try { processBSL = new ProcessBuilder() .command(arguments.toArray(new String[0])) @@ -91,9 +234,11 @@ public void createProcessBSLLS() { } catch (IOException e) { LOGGER.error("Не удалалось запустить процесс с BSL LS", e); } + } public void connectToBSLLSProcess() { + BSLLanguageClient bslClient = new BSLLanguageClient(); BSLBinding bslBinding = new BSLBinding( bslClient, @@ -110,6 +255,7 @@ public void connectToBSLLSProcess() { // откроем фейковый документ bslBinding.textDocumentDidOpen(getFakeUri(), ""); + } public void sleepCurrentThread(long value) { @@ -159,72 +305,6 @@ public EventManager getEventManager() { return events; } - @Override - public void inspection() { - - LOGGER.debug("Событие: анализ кода"); - - if (processBSLIsRunning() && PhoenixAPI.isWindowsForm1S()) { - updateFocusForm(); - } else { - return; - } - - if (bslBinding == null) { - return; - } - - currentOffset = 0; - var textForCheck = ""; - var textModuleSelected = PhoenixAPI.getTextSelected(); - if (textModuleSelected.length() > 0) { - // получем номер строки - textForCheck = textModuleSelected; - currentOffset = PhoenixAPI.getCurrentLineNumber(); - } else { - textForCheck = PhoenixAPI.getTextAll(); - } - - bslBinding.textDocumentDidChange(fakeUri, textForCheck); - bslBinding.textDocumentDidSave(fakeUri); - - } - - @Override - public void formatting() { - - LOGGER.debug("Событие: форматирование"); - - if (!(processBSLIsRunning() && PhoenixAPI.isWindowsForm1S())) { - return; - } - - var textForFormatting = ""; - var isSelected = false; - var textModuleSelected = PhoenixAPI.getTextSelected(); - if (textModuleSelected.length() > 0) { - textForFormatting = textModuleSelected; - isSelected = true; - } else { - textForFormatting = PhoenixAPI.getTextAll(); - } - - // DidChange - bslBinding.textDocumentDidChange(fakeUri, textForFormatting); - - // Formatting - var result = bslBinding.textDocumentFormatting(fakeUri); - - try { - PhoenixAPI.insetTextOnForm(result.get().get(0).getNewText(), isSelected); - } catch (InterruptedException e) { - LOGGER.error(e.getMessage()); - } catch (ExecutionException e) { - LOGGER.error(e.getMessage()); - } - - } - public void stopBSL() { if (bslBinding == null) { return; @@ -245,7 +325,6 @@ public URI getFakeUri() { return fakeUri; } - @Override public void showIssuesStage() { events.notify(EventManager.SHOW_ISSUE_STAGE); } @@ -264,7 +343,7 @@ public String getVersionApp() { } var version = "dev"; - if (manifest.getMainAttributes().get(Attributes.Name.MAIN_CLASS) == null){ + if (manifest.getMainAttributes().get(Attributes.Name.MAIN_CLASS) == null) { return version; } version = manifest.getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION); @@ -272,7 +351,6 @@ public String getVersionApp() { } - @Override public void showSettingStage() { events.notify(EventManager.SHOW_SETTING_STAGE); @@ -297,10 +375,6 @@ public void initConfiguration() { } - public ConfigurationApp getConfiguration() { - return configuration; - } - public void writeConfiguration(ConfigurationApp configurationApp, File fileConfiguration) { // запишем ее в файл ObjectMapper mapper = new ObjectMapper(); @@ -315,7 +389,6 @@ public void writeConfiguration(ConfigurationApp configurationApp) { writeConfiguration(configurationApp, pathToConfiguration.toFile()); } - @SneakyThrows public String getVersionBSLLS() { var result = "<Неопределено>"; @@ -329,5 +402,41 @@ public String getVersionBSLLS() { return result; } + public void initBSLConfiguration() { + createBSLConfigurationFile(); + } + + public void createBSLConfigurationFile() { + var bslConfiguration = new BSLConfiguration(); + bslConfiguration.setDiagnosticLanguage("ru"); + bslConfiguration.setShowCognitiveComplexityCodeLens(false); + bslConfiguration.setShowCyclomaticComplexityCodeLens(false); + bslConfiguration.setComputeDiagnosticsTrigger("onSave"); + bslConfiguration.setComputeDiagnosticsSkipSupport("withSupportLocked"); + bslConfiguration.setConfigurationRoot("src"); + + pathToBSLConfiguration.getParent().toFile().mkdirs(); + + ObjectMapper mapper = new ObjectMapper(); + try { + mapper.writeValue(pathToBSLConfiguration.toFile(), bslConfiguration); + } catch (IOException e) { + LOGGER.error("Не удалось записать файл конфигурации BSL LS", e); + } + + } + + private static Path createPathToConfiguration() { + var path = Path.of(System.getProperty("user.home"), "phoenixbsl", "Configuration.json").toAbsolutePath(); + path.getParent().toFile().mkdirs(); + return path; + } + + private static Path createPathToLog() { + var path = Path.of(System.getProperty("user.home"), "phoenixbsl", "logs").toAbsolutePath(); + path.toFile().mkdirs(); + return path; + } + -} \ No newline at end of file +} diff --git a/src/main/java/org/github/otymko/phoenixbsl/events/EventListener.java b/src/main/java/org/github/otymko/phoenixbsl/events/EventListener.java index b99dbea..114cbde 100644 --- a/src/main/java/org/github/otymko/phoenixbsl/events/EventListener.java +++ b/src/main/java/org/github/otymko/phoenixbsl/events/EventListener.java @@ -11,6 +11,8 @@ default void formatting() { default void inspection() { } + default void fixAll() { } + default void updateIssues(List diagnostics) { } diff --git a/src/main/java/org/github/otymko/phoenixbsl/events/EventManager.java b/src/main/java/org/github/otymko/phoenixbsl/events/EventManager.java index 2329c41..c349cce 100644 --- a/src/main/java/org/github/otymko/phoenixbsl/events/EventManager.java +++ b/src/main/java/org/github/otymko/phoenixbsl/events/EventManager.java @@ -16,6 +16,7 @@ public class EventManager { public static final int EVENT_UPDATE_ISSUES = 3; public static final int SHOW_ISSUE_STAGE = 4; public static final int SHOW_SETTING_STAGE = 5; + public static final int EVENT_FIX_ALL = 6; Map> listeners = new HashMap<>(); @@ -46,6 +47,8 @@ public synchronized void notify(int eventType) { listener.showIssuesStage(); } else if (eventType == SHOW_SETTING_STAGE) { listener.showSettingStage(); + } else if (eventType == EVENT_FIX_ALL) { + listener.fixAll(); } } } diff --git a/src/main/java/org/github/otymko/phoenixbsl/lsp/BSLBinding.java b/src/main/java/org/github/otymko/phoenixbsl/lsp/BSLBinding.java index 76ac378..9d031a0 100644 --- a/src/main/java/org/github/otymko/phoenixbsl/lsp/BSLBinding.java +++ b/src/main/java/org/github/otymko/phoenixbsl/lsp/BSLBinding.java @@ -4,6 +4,7 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.lsp4j.*; import org.eclipse.lsp4j.jsonrpc.Launcher; +import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.eclipse.lsp4j.launch.LSPLauncher; import org.eclipse.lsp4j.services.LanguageServer; import org.github.otymko.phoenixbsl.core.PhoenixAPI; @@ -12,6 +13,7 @@ import java.io.OutputStream; import java.net.URI; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -42,7 +44,6 @@ public void startInThread() { @VisibleForTesting private void start() { - launcher = LSPLauncher.createClientLauncher(client, in, out); Future future = launcher.startListening(); @@ -64,9 +65,21 @@ private void start() { public CompletableFuture initialize() { var params = new InitializeParams(); params.setProcessId(PhoenixAPI.getProcessId()); - params.setTrace("messages"); + params.setTrace("verbose"); ClientCapabilities serverCapabilities = new ClientCapabilities(); + + TextDocumentClientCapabilities textDocument = new TextDocumentClientCapabilities(); + CodeActionCapabilities codeActionCapabilities = new CodeActionCapabilities(true); + textDocument.setCodeAction(codeActionCapabilities); + textDocument + .setCodeAction( + new CodeActionCapabilities( + new CodeActionLiteralSupportCapabilities( + new CodeActionKindCapabilities(Arrays.asList("", CodeActionKind.QuickFix))), + false)); + serverCapabilities.setTextDocument(textDocument); params.setCapabilities(serverCapabilities); + return server.initialize(params); } @@ -102,6 +115,21 @@ public void textDocumentDidSave(URI uri) { server.getTextDocumentService().didSave(paramsSave); } + public List> textDocumentCodeAction(URI uri, List listDiagnostic) throws ExecutionException, InterruptedException { + CodeActionParams params = new CodeActionParams(); + + TextDocumentIdentifier textDocumentIdentifier = new TextDocumentIdentifier(); + textDocumentIdentifier.setUri(uri.toString()); + + var context = new CodeActionContext(); + context.setDiagnostics(listDiagnostic); + + params.setRange(new Range()); + params.setTextDocument(textDocumentIdentifier); + params.setContext(context); + return server.getTextDocumentService().codeAction(params).get(); + } + public CompletableFuture> textDocumentFormatting(URI uri) { var paramsFormatting = new DocumentFormattingParams(); var identifier = new TextDocumentIdentifier(); diff --git a/src/main/java/org/github/otymko/phoenixbsl/lsp/BSLConfiguration.java b/src/main/java/org/github/otymko/phoenixbsl/lsp/BSLConfiguration.java new file mode 100644 index 0000000..fc723b2 --- /dev/null +++ b/src/main/java/org/github/otymko/phoenixbsl/lsp/BSLConfiguration.java @@ -0,0 +1,20 @@ +package org.github.otymko.phoenixbsl.lsp; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class BSLConfiguration { + + private String diagnosticLanguage; + private boolean showCognitiveComplexityCodeLens; + private boolean showCyclomaticComplexityCodeLens; + private String computeDiagnosticsTrigger; + private String computeDiagnosticsSkipSupport; + private String traceLog; + private String configurationRoot; + +} diff --git a/src/main/java/org/github/otymko/phoenixbsl/lsp/BSLLanguageClient.java b/src/main/java/org/github/otymko/phoenixbsl/lsp/BSLLanguageClient.java index d91f334..e4d6a70 100644 --- a/src/main/java/org/github/otymko/phoenixbsl/lsp/BSLLanguageClient.java +++ b/src/main/java/org/github/otymko/phoenixbsl/lsp/BSLLanguageClient.java @@ -35,9 +35,15 @@ public void telemetryEvent(Object o) { @Override public void publishDiagnostics(PublishDiagnosticsParams publishDiagnosticsParams) { + var app = PhoenixApp.getInstance(); + + var diagnosticList = app.getDiagnosticList(); + diagnosticList.clear(); + diagnosticList.addAll(publishDiagnosticsParams.getDiagnostics()); + PhoenixApp.getInstance().getEventManager().notify( EventManager.EVENT_UPDATE_ISSUES, - publishDiagnosticsParams.getDiagnostics() + diagnosticList ); } diff --git a/src/main/java/org/github/otymko/phoenixbsl/threads/GlobalKeyListenerThread.java b/src/main/java/org/github/otymko/phoenixbsl/threads/GlobalKeyListenerThread.java index 748da15..d06fd1b 100644 --- a/src/main/java/org/github/otymko/phoenixbsl/threads/GlobalKeyListenerThread.java +++ b/src/main/java/org/github/otymko/phoenixbsl/threads/GlobalKeyListenerThread.java @@ -42,6 +42,9 @@ public void keyPressed(GlobalKeyEvent event) { if (event.getVirtualKeyCode() == GlobalKeyEvent.VK_K) { PhoenixApp.getInstance().getEventManager().notify(EventManager.EVENT_FORMATTING); } + if (event.getVirtualKeyCode() == GlobalKeyEvent.VK_J) { + PhoenixApp.getInstance().getEventManager().notify(EventManager.EVENT_FIX_ALL); + } } } diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 1d854d9..6b5c325 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -1,8 +1,6 @@ - - @@ -12,7 +10,7 @@ - ${HOME_LOG} + ${user.home}/phoenixbsl/logs/app.log app.%d{yyyy-MM-dd}.log 7