├── .gitignore ├── README.md ├── build.gradle.kts ├── compiler ├── build.gradle.kts └── src │ └── main │ └── java │ └── com │ └── tyron │ └── code │ └── compiler │ └── Compiler.java ├── completions ├── README.md ├── build.gradle.kts └── src │ ├── main │ └── java │ │ └── com │ │ └── tyron │ │ └── code │ │ └── java │ │ ├── ModuleFileManager.java │ │ ├── SourceFileObject.java │ │ ├── analysis │ │ ├── AnalysisResult.java │ │ ├── Analyzer.java │ │ └── ElementHandle.java │ │ ├── completion │ │ ├── ClassForImportCandidate.java │ │ ├── CompleteMemberSelectAction.java │ │ ├── CompleteSymbolAction.java │ │ ├── CompletionAction.java │ │ ├── CompletionArgs.java │ │ ├── CompletionCandidate.java │ │ ├── CompletionCandidateDecorator.java │ │ ├── CompletionCandidateListBuilder.java │ │ ├── CompletionCandidateWithMatchLevel.java │ │ ├── CompletionPrefixMatcher.java │ │ ├── CompletionPrefixUtils.java │ │ ├── CompletionResult.java │ │ ├── Completor.java │ │ ├── ContentWithLineMap.java │ │ ├── ElementBasedCompletionCandidate.java │ │ ├── ElementCompletionCandidate.java │ │ ├── ExecutableElementCompletionCandidate.java │ │ ├── FindCompletionsAt.java │ │ ├── KeywordCompletionCandidate.java │ │ ├── MethodCompletionCandidate.java │ │ ├── ScopeHelper.java │ │ ├── SimpleCompletionCandidate.java │ │ ├── TextEditOptions.java │ │ └── TextEdits.java │ │ ├── model │ │ ├── ResolveAction.java │ │ ├── ResolveActionParams.java │ │ ├── ResolveAddImportTextEditsParams.java │ │ └── ResolveData.java │ │ ├── parsing │ │ ├── AdjustedLineMap.java │ │ ├── FileContentFixer.java │ │ ├── Insertion.java │ │ ├── LineMapUtil.java │ │ ├── MethodBodyPruner.java │ │ ├── ParserContext.java │ │ └── PositionContext.java │ │ ├── protocol │ │ ├── Position.java │ │ └── Range.java │ │ └── util │ │ ├── ClassFileUtil.java │ │ ├── ElementUtils.java │ │ └── PrintUtil.java │ └── test │ ├── java │ └── com │ │ └── tyron │ │ └── code │ │ └── java │ │ ├── analysis │ │ └── ElementHandleTest.java │ │ └── completion │ │ ├── AutoImportCompletionTest.java │ │ ├── BaseCompletionTest.java │ │ ├── CancellationTest.java │ │ ├── CompletionVisibilityTest.java │ │ ├── PackageCompletionTests.java │ │ └── model │ │ └── ModuleTest.java │ └── resources │ └── android.jar ├── deskptop-test ├── README.md ├── android.jar ├── build.gradle.kts └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── tyron │ │ │ └── code │ │ │ └── desktop │ │ │ ├── CodeAssistApplication.kt │ │ │ ├── cdi │ │ │ └── module.kt │ │ │ ├── services │ │ │ └── navigation │ │ │ │ ├── Actions.java │ │ │ │ ├── Navigable.java │ │ │ │ ├── NavigationManager.java │ │ │ │ ├── SourceFileNavigable.java │ │ │ │ ├── UnsupportedContent.java │ │ │ │ └── UpdatableNavigable.java │ │ │ ├── ui │ │ │ ├── Main.kt │ │ │ ├── control │ │ │ │ ├── ActionButton.java │ │ │ │ ├── ActionMenuItem.java │ │ │ │ ├── BoundToggleIcon.java │ │ │ │ ├── FontIconView.java │ │ │ │ ├── IconView.java │ │ │ │ ├── Tooltipable.java │ │ │ │ ├── richtext │ │ │ │ │ ├── Editor.java │ │ │ │ │ ├── EditorComponent.java │ │ │ │ │ ├── ScrollbarPaddingUtil.java │ │ │ │ │ ├── bracket │ │ │ │ │ │ ├── BracketMatchGraphicFactory.java │ │ │ │ │ │ └── SelectedBracketTracking.java │ │ │ │ │ ├── completion │ │ │ │ │ │ ├── AutoCompletePopup.java │ │ │ │ │ │ └── CompletionListCell.java │ │ │ │ │ ├── linegraphics │ │ │ │ │ │ ├── AbstractLineGraphicFactory.java │ │ │ │ │ │ ├── LineContainer.java │ │ │ │ │ │ ├── LineGraphicFactory.java │ │ │ │ │ │ ├── LineNumberFactory.java │ │ │ │ │ │ └── RootLineGraphicFactory.java │ │ │ │ │ ├── problem │ │ │ │ │ │ ├── Problem.java │ │ │ │ │ │ ├── ProblemGraphicFactory.java │ │ │ │ │ │ ├── ProblemInvalidationListener.java │ │ │ │ │ │ ├── ProblemLevel.java │ │ │ │ │ │ ├── ProblemPhase.java │ │ │ │ │ │ └── ProblemTracking.java │ │ │ │ │ ├── search │ │ │ │ │ │ └── SearchBar.java │ │ │ │ │ ├── source │ │ │ │ │ │ └── CompletionProvider.java │ │ │ │ │ └── syntax │ │ │ │ │ │ ├── RegexLanguages.java │ │ │ │ │ │ ├── RegexRule.java │ │ │ │ │ │ ├── RegexSyntaxHighlighter.java │ │ │ │ │ │ ├── StyleResult.java │ │ │ │ │ │ ├── SyntaxHighlighter.java │ │ │ │ │ │ └── SyntaxUtil.java │ │ │ │ └── tree │ │ │ │ │ ├── FileTreeCell.java │ │ │ │ │ ├── FileTreeItem.java │ │ │ │ │ ├── FileTreeItemBuilder.java │ │ │ │ │ ├── FilterableTreeItem.java │ │ │ │ │ ├── TreeItems.java │ │ │ │ │ ├── WorkspaceTree.java │ │ │ │ │ └── WorkspaceTreeNode.java │ │ │ ├── docking │ │ │ │ ├── DockingManager.java │ │ │ │ ├── DockingRegion.java │ │ │ │ ├── DockingRegionFactory.java │ │ │ │ ├── DockingTab.java │ │ │ │ └── listener │ │ │ │ │ ├── TabClosureListener.java │ │ │ │ │ ├── TabCreationListener.java │ │ │ │ │ ├── TabMoveListener.java │ │ │ │ │ └── TabSelectionListener.java │ │ │ └── pane │ │ │ │ ├── LoggingPane.java │ │ │ │ ├── WelcomePane.java │ │ │ │ ├── WorkspaceExplorerPane.java │ │ │ │ ├── WorkspaceInformationPane.java │ │ │ │ ├── WorkspaceRootPane.kt │ │ │ │ └── editing │ │ │ │ ├── AbstractContentPane.java │ │ │ │ ├── JavaEditorPane.java │ │ │ │ ├── SourcePane.java │ │ │ │ └── TextPane.java │ │ │ └── util │ │ │ ├── ClassLoaderInternals.java │ │ │ ├── CollectionUtil.java │ │ │ ├── FxThreadUtils.java │ │ │ ├── Icons.java │ │ │ ├── IntRange.java │ │ │ ├── InternalPath.java │ │ │ ├── JavaVersion.java │ │ │ ├── Lang.java │ │ │ ├── NodeEvents.java │ │ │ ├── NumberUtil.java │ │ │ ├── RegexUtil.java │ │ │ ├── SelfReferenceUtil.java │ │ │ ├── SynchronizedSimpleStringProperty.java │ │ │ ├── SynchronizedStringBinding.java │ │ │ └── WorkspaceUtil.kt │ └── resources │ │ ├── fonts │ │ ├── JetBrainsMono-Bold.ttf │ │ ├── JetBrainsMono-BoldItalic.ttf │ │ ├── JetBrainsMono-Italic.ttf │ │ └── JetBrainsMono.ttf │ │ ├── icons │ │ ├── class │ │ │ └── class.png │ │ ├── file │ │ │ ├── file-config.png │ │ │ ├── folder-module.png │ │ │ ├── folder-package.png │ │ │ ├── folder-source.png │ │ │ └── folder.png │ │ └── member │ │ │ └── field.png │ │ └── style │ │ ├── code-editor.css │ │ ├── codeassist.css │ │ └── tweaks.css │ └── test │ ├── java │ └── com │ │ └── tyron │ │ └── code │ │ └── Test.java │ └── resources │ └── TestProject │ ├── app │ ├── libs │ │ └── guava-33.0.0-jre.jar │ ├── project.toml │ └── src │ │ └── main │ │ └── java │ │ └── test │ │ └── Main.java │ ├── module2 │ ├── project.toml │ └── src │ │ └── main │ │ └── java │ │ └── test │ │ └── Test.java │ └── project.toml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── javac ├── build.gradle.kts └── libs │ └── nb-javac-1.0-SNAPSHOT-all.jar ├── project-impl ├── build.gradle.kts ├── libs │ └── tomlconfig-0.2.4.jar └── src │ ├── main │ └── java │ │ └── com │ │ └── tyron │ │ └── code │ │ ├── info │ │ ├── BasicClassInfo.java │ │ ├── FileInfoImpl.java │ │ ├── JvmClassInfoImpl.java │ │ ├── SourceClassInfoImpl.java │ │ └── builder │ │ │ ├── AbstractClassInfoBuilder.java │ │ │ ├── FileInfoBuilder.java │ │ │ ├── JvmClassInfoBuilder.java │ │ │ └── SourceClassInfoBuilder.java │ │ ├── path │ │ └── impl │ │ │ ├── AbstractPathNode.java │ │ │ ├── DirectoryPathNode.java │ │ │ ├── FilePathNode.java │ │ │ ├── ModulePathNode.java │ │ │ ├── SourceClassPathNode.java │ │ │ └── WorkspacePathNode.java │ │ └── project │ │ └── impl │ │ ├── CodeAssistModuleManager.java │ │ ├── FileSystemModuleManager.java │ │ ├── ModuleInitializer.java │ │ ├── ProjectStructureParser.java │ │ ├── WorkspaceImpl.java │ │ ├── config │ │ └── ModuleConfig.java │ │ └── model │ │ ├── AbstractModule.java │ │ ├── ErroneousModuleImpl.java │ │ ├── ErroneousRootModuleImpl.java │ │ ├── JarModuleImpl.java │ │ ├── JavaModuleImpl.java │ │ ├── JdkModuleImpl.java │ │ ├── RootModuleImpl.java │ │ └── SourceModuleImpl.java │ └── test │ └── java │ └── com │ └── tyron │ └── code │ └── project │ └── impl │ ├── CodeAssistModuleManagerTest.java │ ├── FileSystemModuleManagerTest.java │ └── config │ └── ModuleConfigTest.java ├── project ├── build.gradle.kts └── src │ ├── main │ └── java │ │ └── com │ │ └── tyron │ │ └── code │ │ ├── diagnostic │ │ └── Diagnostic.java │ │ ├── info │ │ ├── ClassInfo.java │ │ ├── FileInfo.java │ │ ├── Info.java │ │ ├── JvmClassInfo.java │ │ ├── Named.java │ │ ├── SourceClassInfo.java │ │ └── properties │ │ │ ├── BasicProperty.java │ │ │ ├── BasicPropertyContainer.java │ │ │ ├── Property.java │ │ │ └── PropertyContainer.java │ │ ├── logging │ │ ├── CodeAssistLoggingFilter.java │ │ ├── DebuggingLogger.java │ │ ├── InterceptingLogger.java │ │ ├── LogConsumer.java │ │ └── Logging.java │ │ ├── path │ │ └── PathNode.java │ │ └── project │ │ ├── BasicWorkspaceManager.java │ │ ├── InitializationException.java │ │ ├── ModuleManager.java │ │ ├── Workspace.java │ │ ├── WorkspaceCloseCondition.java │ │ ├── WorkspaceCloseListener.java │ │ ├── WorkspaceManager.java │ │ ├── WorkspaceOpenListener.java │ │ ├── WorkspaceStateListener.java │ │ ├── dependency │ │ └── DependencyUtil.java │ │ ├── file │ │ ├── EditHistory.java │ │ ├── FileChangeListener.java │ │ ├── FileManager.java │ │ ├── FileManagerImpl.java │ │ ├── FileSnapshot.java │ │ ├── FileTextLocation.java │ │ ├── FileWatcher.java │ │ └── SimpleFileManager.java │ │ ├── graph │ │ ├── CompileProjectModuleBFS.java │ │ ├── GraphBFS.java │ │ ├── GraphPrinter.java │ │ ├── ModuleFileCollectorVisitor.java │ │ └── NodeVisitor.java │ │ ├── model │ │ ├── Build.java │ │ ├── Config.java │ │ ├── Dependency.java │ │ ├── DependencyType.java │ │ ├── JavaConfig.java │ │ ├── JavaFileInfo.java │ │ ├── PackageScope.java │ │ ├── ProjectError.java │ │ ├── Settings.java │ │ ├── TextPosition.java │ │ ├── TextRange.java │ │ ├── config │ │ │ └── Config.java │ │ └── module │ │ │ ├── ErroneousModule.java │ │ │ ├── ErroneousRootModule.java │ │ │ ├── JarModule.java │ │ │ ├── JavaModule.java │ │ │ ├── JdkModule.java │ │ │ ├── Module.java │ │ │ ├── RootModule.java │ │ │ └── SourceModule.java │ │ └── util │ │ ├── AccessPatcher.java │ │ ├── ClassNameUtils.java │ │ ├── IOUtil.java │ │ ├── JigsawUtil.java │ │ ├── LookupUtil.java │ │ ├── ModuleUtils.java │ │ ├── PathUtils.java │ │ ├── ReflectUtil.java │ │ ├── ResourceUtil.java │ │ ├── StringSearch.java │ │ ├── StringUtil.java │ │ ├── SystemInformation.java │ │ ├── ThreadLocals.java │ │ ├── ThreadPoolFactory.java │ │ ├── Unchecked.java │ │ ├── UncheckedBiConsumer.java │ │ ├── UncheckedBiFunction.java │ │ ├── UncheckedConsumer.java │ │ ├── UncheckedFunction.java │ │ ├── UncheckedRunnable.java │ │ ├── UncheckedSupplier.java │ │ ├── UnsafeIO.java │ │ └── UnsafeUtil.java │ └── test │ └── java │ └── com │ └── tyron │ └── code │ └── project │ └── util │ └── ClassNameUtilsTest.java └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea/modules.xml 9 | .idea/jarRepositories.xml 10 | .idea/compiler.xml 11 | .idea/libraries/ 12 | *.iws 13 | *.iml 14 | *.ipr 15 | out/ 16 | !**/src/main/**/out/ 17 | !**/src/test/**/out/ 18 | 19 | ### Eclipse ### 20 | .apt_generated 21 | .classpath 22 | .factorypath 23 | .project 24 | .settings 25 | .springBeans 26 | .sts4-cache 27 | bin/ 28 | !**/src/main/**/bin/ 29 | !**/src/test/**/bin/ 30 | 31 | ### NetBeans ### 32 | /nbproject/private/ 33 | /nbbuild/ 34 | /dist/ 35 | /nbdist/ 36 | /.nb-gradle/ 37 | 38 | ### VS Code ### 39 | .vscode/ 40 | 41 | ### Mac OS ### 42 | .DS_Store -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | allprojects { 2 | group = "com.tyron.code" 3 | version = "1.0-SNAPSHOT" 4 | } 5 | 6 | subprojects { 7 | repositories { 8 | mavenLocal() 9 | mavenCentral() 10 | google() 11 | maven { 12 | url = uri("https://jitpack.io") 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /compiler/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-library") 3 | } 4 | 5 | dependencies { 6 | testImplementation(platform("org.junit:junit-bom:5.9.1")) 7 | testImplementation("org.junit.jupiter:junit-jupiter") 8 | 9 | implementation(project(":project")) 10 | implementation("com.google.guava:guava:31.0.1-android") 11 | } 12 | 13 | tasks.test { 14 | useJUnitPlatform() 15 | } -------------------------------------------------------------------------------- /completions/README.md: -------------------------------------------------------------------------------- 1 | # Completion Module 2 | 3 | This module contains all the necessary logic for providing java completions 4 | in CodeAssist. 5 | 6 | ## Implementation Details 7 | (Not yet written) 8 | 9 | # Testing 10 | This repository contains a code completion testing framework for Java using JUnit 5. The base completion class BaseCompletionTest provides a foundation for testing code completions in Java code snippets. 11 | 12 | 1. **BaseCompletionTest Class:** 13 | - The **BaseCompletionTest** class serves as the foundation for code completion tests. 14 | - It sets up the testing environment, initializes required components, and provides a method for code completion. 15 | 2. **Creating Test Classes:** 16 | - Extend the **BaseCompletionTest** class to create test classes for specific completion scenarios. 17 | - Use the complete method to obtain completion suggestions for a given code snippet. 18 | 3. **Example Test:** 19 | - See the example test class PackageCompletionTests for reference. 20 | - Tests demonstrate completion scenarios inside method invocations, normal code blocks, imports, and static imports. 21 | ```java 22 | public class PackageCompletionTests extends BaseCompletionTest { 23 | 24 | @Test 25 | public void testPackageCompletionWorksOnStaticImports() { 26 | List complete = complete(""" 27 | import static java.lang.@complete 28 | """); 29 | assertThat(complete).isNotEmpty(); 30 | } 31 | } 32 | ``` 33 | 34 | 4. **Writing Test Methods** 35 | - Write test methods that contain Java code snippets with placeholders for completion. 36 | - Use the @complete tag to indicate the position where completion suggestions are expected. 37 | ```java 38 | @Test 39 | public void testPackageCompletionInsideMethodInvocations() { 40 | List completed = complete(""" 41 | // ... Java code snippet with @complete tag 42 | """); 43 | assertThat(completed).contains("ExpectedCompletion"); 44 | } 45 | ``` -------------------------------------------------------------------------------- /completions/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-library") 3 | } 4 | 5 | dependencies { 6 | testImplementation(platform("org.junit:junit-bom:5.9.1")) 7 | testImplementation("org.junit.jupiter:junit-jupiter") 8 | testImplementation("com.google.truth:truth:1.2.0") 9 | 10 | implementation("com.google.guava:guava:31.0.1-android") 11 | implementation("me.xdrop:fuzzywuzzy:1.2.0") 12 | implementation("org.slf4j:slf4j-api:2.0.10") 13 | implementation("org.jetbrains:annotations:24.1.0") 14 | 15 | implementation("com.google.auto.value:auto-value-annotations:1.8.2") 16 | testImplementation(project(mapOf("path" to ":project-impl"))) 17 | annotationProcessor("com.google.auto.value:auto-value:1.8.2") 18 | 19 | implementation(project(":project")) 20 | implementation(project(":javac")) 21 | 22 | } 23 | 24 | tasks.test { 25 | useJUnitPlatform() 26 | } -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/analysis/AnalysisResult.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.analysis; 2 | 3 | import com.tyron.code.project.model.module.JavaModule; 4 | import shadow.com.sun.source.tree.CompilationUnitTree; 5 | import shadow.com.sun.tools.javac.api.JavacTaskImpl; 6 | import shadow.javax.lang.model.element.Element; 7 | 8 | public record AnalysisResult(JavaModule module, 9 | JavacTaskImpl javacTask, 10 | CompilationUnitTree parsedTree, 11 | Iterable analyzed, Analyzer analyzer 12 | ) { 13 | 14 | } -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/completion/ClassForImportCandidate.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.completion; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import com.tyron.code.java.model.ResolveAction; 5 | import com.tyron.code.java.model.ResolveActionParams; 6 | import com.tyron.code.java.model.ResolveAddImportTextEditsParams; 7 | 8 | import java.nio.file.Path; 9 | import java.nio.file.Paths; 10 | import java.util.Map; 11 | import java.util.Optional; 12 | 13 | /** A candidate with import class edit actions. */ 14 | class ClassForImportCandidate implements CompletionCandidate { 15 | private final String fqn; 16 | private final String simpleName; 17 | private final Path filePath; 18 | 19 | ClassForImportCandidate(String fqn, String simpleName, String filePath) { 20 | this.fqn = fqn; 21 | this.simpleName = simpleName; 22 | this.filePath = Paths.get(filePath); 23 | } 24 | 25 | @Override 26 | public String getName() { 27 | return simpleName; 28 | } 29 | 30 | @Override 31 | public Kind getKind() { 32 | return Kind.CLASS; 33 | } 34 | 35 | @Override 36 | public Optional getDetail() { 37 | return Optional.of(fqn); 38 | } 39 | 40 | @Override 41 | public SortCategory getSortCategory() { 42 | return SortCategory.TO_IMPORT; 43 | } 44 | 45 | @Override 46 | public Map getResolveActions() { 47 | ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); 48 | ResolveAddImportTextEditsParams params = new ResolveAddImportTextEditsParams(); 49 | params.uri = filePath.toUri(); 50 | params.classFullName = fqn; 51 | builder.put(ResolveAction.ADD_IMPORT_TEXT_EDIT, params); 52 | return builder.build(); 53 | } 54 | 55 | @Override 56 | public String toString() { 57 | return "ClassForImportCandidate{" + 58 | "filePath=" + filePath + 59 | '}'; 60 | } 61 | } -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/completion/CompleteSymbolAction.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.completion; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.tyron.code.java.analysis.AnalysisResult; 5 | import com.tyron.code.project.model.module.JavaModule; 6 | import com.tyron.code.project.util.ModuleUtils; 7 | import me.xdrop.fuzzywuzzy.FuzzySearch; 8 | import shadow.com.sun.source.util.TreePath; 9 | import shadow.com.sun.source.util.Trees; 10 | import shadow.javax.lang.model.element.Element; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | public class CompleteSymbolAction implements CompletionAction { 16 | 17 | 18 | @Override 19 | public ImmutableList getCompletionCandidates(CompletionArgs args) { 20 | ImmutableList.Builder builder = ImmutableList.builder(); 21 | JavaModule module = args.module(); 22 | 23 | List list = ModuleUtils.getAllClasses(module).stream() 24 | .parallel() 25 | .filter(it -> FuzzySearch.partialRatio(args.prefix(), it.getName()) >= 85) 26 | .map(it -> new ClassForImportCandidate(String.join(".", it.getPackageNameParts()), it.getSimpleName(), it.getSourceFileName())) 27 | .toList(); 28 | builder.addAll(list); 29 | 30 | 31 | builder.addAll(completeUsingScope(args.currentAnalyzedPath(), args.analysisResult(), args.prefix())); 32 | return builder.build(); 33 | } 34 | 35 | private List completeUsingScope(TreePath treePath, AnalysisResult analysisResult, String prefix) { 36 | analysisResult.analyzer().checkCancelled(); 37 | List list = new ArrayList<>(); 38 | var trees = Trees.instance(analysisResult.javacTask()); 39 | var scope = trees.getScope(treePath); 40 | List elements = ScopeHelper.scopeMembers(analysisResult.javacTask(), scope, it -> it.toString().contains(prefix)); 41 | for (Element element : elements) { 42 | list.add(new ElementCompletionCandidate(element)); 43 | } 44 | return list; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/completion/CompletionAction.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.completion; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.tyron.code.java.analysis.AnalysisResult; 5 | import com.tyron.code.java.parsing.PositionContext; 6 | 7 | /** Action to perform the requested completion. */ 8 | interface CompletionAction { 9 | ImmutableList getCompletionCandidates(CompletionArgs args); 10 | } -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/completion/CompletionArgs.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.completion; 2 | 3 | import com.tyron.code.java.analysis.AnalysisResult; 4 | import com.tyron.code.java.parsing.PositionContext; 5 | import com.tyron.code.project.model.module.JavaModule; 6 | import shadow.com.sun.source.util.TreePath; 7 | 8 | public record CompletionArgs(@Deprecated PositionContext positionContext, JavaModule module, TreePath currentAnalyzedPath, AnalysisResult analysisResult, String prefix) { 9 | } 10 | -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/completion/CompletionCandidate.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.completion; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import com.tyron.code.java.model.ResolveAction; 5 | import com.tyron.code.java.model.ResolveActionParams; 6 | 7 | import java.util.Map; 8 | import java.util.Optional; 9 | 10 | public interface CompletionCandidate { 11 | enum Kind { 12 | UNKNOWN, 13 | CLASS, 14 | INTERFACE, 15 | ENUM, 16 | METHOD, 17 | VARIABLE, 18 | FIELD, 19 | PACKAGE, 20 | KEYWORD, 21 | } 22 | 23 | /** 24 | * The category a candidate belongs to for sorting purposes. 25 | * 26 | *

Candidates are sorted by SortCategory first, then by their other characteristics such as the 27 | * label. 28 | * 29 | *

SortCategory values are compared using their ordinal values. Do not change the order of the 30 | * values unless there is a good reason. 31 | */ 32 | enum SortCategory { 33 | /** A member name defined by the class of the instance. */ 34 | DIRECT_MEMBER, 35 | /** A symbol name that's visible in the given scope. */ 36 | ACCESSIBLE_SYMBOL, 37 | /** Other names in undefined categories that have the normal rank. */ 38 | UNKNOWN, 39 | /** All Java keywords. */ 40 | KEYWORD, 41 | /** Entity names that are not visible in the given scope. They need to be imported. */ 42 | TO_IMPORT, 43 | } 44 | 45 | String getName(); 46 | 47 | default Optional getNameDescription() { 48 | return Optional.empty(); 49 | } 50 | 51 | Kind getKind(); 52 | 53 | Optional getDetail(); 54 | 55 | default Optional getInsertPlainText(TextEditOptions textEditOptions) { 56 | return Optional.empty(); 57 | } 58 | 59 | default Optional getInsertSnippet(TextEditOptions textEditOptions) { 60 | return Optional.empty(); 61 | } 62 | 63 | default SortCategory getSortCategory() { 64 | return SortCategory.UNKNOWN; 65 | } 66 | 67 | default Map getResolveActions() { 68 | return ImmutableMap.of(); 69 | } 70 | } -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/completion/CompletionCandidateListBuilder.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.completion; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | 5 | import java.util.Collection; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.stream.Collectors; 9 | 10 | public class CompletionCandidateListBuilder { 11 | 12 | private final Map candidateMap; 13 | private final String completionPrefix; 14 | 15 | public CompletionCandidateListBuilder(String completionPrefix) { 16 | candidateMap = new HashMap<>(); 17 | this.completionPrefix = completionPrefix; 18 | } 19 | 20 | public boolean hasCandidateWithName(String name) { 21 | return candidateMap.containsKey(name); 22 | } 23 | 24 | // public CompletionCandidateListBuilder addEntities( 25 | // Multimap entities, CompletionCandidate.SortCategory sortCategory) { 26 | // for (Entity entity : entities.values()) { 27 | // addEntity(entity, sortCategory); 28 | // } 29 | // return this; 30 | // } 31 | 32 | public CompletionCandidateListBuilder addCandidates(Collection candidates) { 33 | for (CompletionCandidate candidate : candidates) { 34 | addCandidate(candidate); 35 | } 36 | return this; 37 | } 38 | 39 | // public CompletionCandidateListBuilder addEntity( 40 | // Entity entity, CompletionCandidate.SortCategory sortCategory) { 41 | // return this.addCandidate(new EntityCompletionCandidate(entity, sortCategory)); 42 | // } 43 | 44 | public CompletionCandidateListBuilder addCandidate(CompletionCandidate candidate) { 45 | String name = candidate.getName(); 46 | CompletionPrefixMatcher.MatchLevel matchLevel = 47 | CompletionPrefixMatcher.computeMatchLevel(name, completionPrefix); 48 | if (matchLevel == CompletionPrefixMatcher.MatchLevel.NOT_MATCH) { 49 | return this; 50 | } 51 | 52 | // if (!candidateMap.containsKey(name)) { 53 | // candidateMap.put(name, new EntityShadowingListBuilder<>(GET_ELEMENT_FUNCTION)); 54 | // } 55 | candidateMap.put(name, CompletionCandidateWithMatchLevel.create(candidate, matchLevel)); 56 | return this; 57 | } 58 | 59 | public ImmutableList build() { 60 | return candidateMap.values().stream() 61 | // .flatMap(EntityShadowingListBuilder::stream) 62 | .sorted() 63 | .map(CompletionCandidateWithMatchLevel::getCompletionCandidate) 64 | .collect(Collectors.collectingAndThen(Collectors.toList(), ImmutableList::copyOf)); 65 | } 66 | } -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/completion/CompletionCandidateWithMatchLevel.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.completion; 2 | 3 | import com.google.auto.value.AutoValue; 4 | import java.util.Comparator; 5 | 6 | /** A wrapper of {@link CompletionCandidate} and {@link CompletionPrefixMatcher#MatchLevel}. */ 7 | @AutoValue 8 | public abstract class CompletionCandidateWithMatchLevel 9 | implements Comparable { 10 | private static final Comparator COMPARATOR = 11 | Comparator.comparing( 12 | (CompletionCandidateWithMatchLevel candidateWithLevel) -> 13 | candidateWithLevel.getCompletionCandidate().getSortCategory().ordinal()) 14 | .thenComparing( 15 | candidateWithLevel -> candidateWithLevel.getMatchLevel().ordinal(), 16 | Comparator.reverseOrder()) 17 | .thenComparing( 18 | candidateWithLevel -> candidateWithLevel.getCompletionCandidate().getName()); 19 | 20 | public abstract CompletionCandidate getCompletionCandidate(); 21 | 22 | public abstract CompletionPrefixMatcher.MatchLevel getMatchLevel(); 23 | 24 | public static CompletionCandidateWithMatchLevel create( 25 | CompletionCandidate completionCandidate, CompletionPrefixMatcher.MatchLevel matchLevel) { 26 | return new AutoValue_CompletionCandidateWithMatchLevel(completionCandidate, matchLevel); 27 | } 28 | 29 | @Override 30 | public int compareTo(CompletionCandidateWithMatchLevel other) { 31 | return COMPARATOR.compare(this, other); 32 | } 33 | } -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/completion/CompletionPrefixMatcher.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.completion; 2 | 3 | /** Logic of matching a completion name with a given completion prefix. */ 4 | public class CompletionPrefixMatcher { 5 | 6 | /** 7 | * How well does the candidate name match the compleation prefix. 8 | * 9 | *

The ordinal values of the enum values imply the match level. The greater the ordinal value 10 | * is the better the candidate matches. It can be a key for sorting the matched candidates. New 11 | * value should be added to the right place to keep the ordinal value in order. 12 | */ 13 | public enum MatchLevel { 14 | NOT_MATCH, 15 | CASE_INSENSITIVE_PREFIX, 16 | CASE_SENSITIVE_PREFIX, 17 | CASE_INSENSITIVE_EQUAL, 18 | CASE_SENSITIVE_EQUAL, 19 | } 20 | 21 | /** Returns how well does {@code candidateName} match {@code completionPrefix}. */ 22 | public static MatchLevel computeMatchLevel(String candidateName, String completionPrefix) { 23 | if (candidateName.startsWith(completionPrefix)) { 24 | return candidateName.length() == completionPrefix.length() 25 | ? MatchLevel.CASE_SENSITIVE_EQUAL 26 | : MatchLevel.CASE_SENSITIVE_PREFIX; 27 | } 28 | 29 | if (candidateName.toLowerCase().startsWith(completionPrefix.toLowerCase())) { 30 | return candidateName.length() == completionPrefix.length() 31 | ? MatchLevel.CASE_INSENSITIVE_EQUAL 32 | : MatchLevel.CASE_INSENSITIVE_PREFIX; 33 | } 34 | 35 | return MatchLevel.NOT_MATCH; 36 | } 37 | 38 | /** Returns {@code true} if {@code candidateName} matches {@code completionPrefix}. */ 39 | public static boolean matches(String candidateName, String completionPrefix) { 40 | return computeMatchLevel(candidateName, completionPrefix) != MatchLevel.NOT_MATCH; 41 | } 42 | } -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/completion/CompletionPrefixUtils.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.completion; 2 | 3 | import me.xdrop.fuzzywuzzy.FuzzySearch; 4 | 5 | public class CompletionPrefixUtils { 6 | public static boolean prefixPartiallyMatch(String partial, String string) { 7 | // empty prefix means we need to match all 8 | if (partial.isEmpty()) { 9 | return true; 10 | } 11 | return FuzzySearch.partialRatio(partial, string) >= 85; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/completion/CompletionResult.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.completion; 2 | 3 | import com.google.auto.value.AutoValue; 4 | import com.google.common.collect.ImmutableList; 5 | import java.nio.file.Path; 6 | import java.util.List; 7 | 8 | /** Result of completion request. */ 9 | @AutoValue 10 | public abstract class CompletionResult { 11 | 12 | public abstract Path getFilePath(); 13 | 14 | public abstract int getLine(); 15 | 16 | public abstract int getColumn(); 17 | 18 | public abstract String getPrefix(); 19 | 20 | public abstract List getCompletionCandidates(); 21 | 22 | public abstract TextEditOptions getTextEditOptions(); 23 | 24 | public abstract Builder toBuilder(); 25 | 26 | public static Builder builder() { 27 | return new AutoValue_CompletionResult.Builder(); 28 | } 29 | 30 | /** 31 | * Check if the completor is processing a completion request that is an incremental completion of 32 | * the cached completion. 33 | */ 34 | boolean isIncrementalCompletion(Path filePath, int line, int column, String prefix) { 35 | if (!getFilePath().equals(filePath)) { 36 | return false; 37 | } 38 | if (getLine() != line) { 39 | return false; 40 | } 41 | if (getColumn() > column) { 42 | return false; 43 | } 44 | if (!prefix.startsWith(getPrefix())) { 45 | return false; 46 | } 47 | if (prefix.length() - getPrefix().length() != column - getColumn()) { 48 | // FIXME: This may break for complicated Unicodes. 49 | return false; 50 | } 51 | return true; 52 | } 53 | 54 | @AutoValue.Builder 55 | public abstract static class Builder { 56 | public abstract Builder setFilePath(Path filePath); 57 | 58 | public abstract Builder setLine(int line); 59 | 60 | public abstract Builder setColumn(int column); 61 | 62 | public abstract Builder setPrefix(String prefix); 63 | 64 | public abstract Builder setCompletionCandidates( 65 | ImmutableList completionCandidates); 66 | 67 | public abstract Builder setTextEditOptions(TextEditOptions textEditOptions); 68 | 69 | public abstract CompletionResult build(); 70 | } 71 | } -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/completion/ContentWithLineMap.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.completion; 2 | 3 | import com.google.auto.value.AutoValue; 4 | import com.tyron.code.java.parsing.FileContentFixer; 5 | import com.tyron.code.java.parsing.LineMapUtil; 6 | import com.tyron.code.java.parsing.PositionContext; 7 | import shadow.com.sun.source.tree.LineMap; 8 | import shadow.com.sun.tools.javac.tree.JCTree.JCCompilationUnit; 9 | import java.nio.file.Path; 10 | import java.util.logging.Logger; 11 | 12 | /** 13 | * Combines file content with line map for easier lookup. 14 | * 15 | *

The content and line map are from the original content, i.e. not modified by {@link 16 | * FileContentFixer}. 17 | */ 18 | @AutoValue 19 | abstract class ContentWithLineMap { 20 | public static ContentWithLineMap create(String content, LineMap lineMap, Path path) { 21 | return new AutoValue_ContentWithLineMap( 22 | content, 23 | lineMap, 24 | path 25 | ); 26 | } 27 | 28 | // private static final Logger logger = Logger.getLogger("main"); 29 | 30 | abstract CharSequence getContent(); 31 | 32 | abstract LineMap getLineMap(); 33 | 34 | abstract Path getFilePath(); 35 | 36 | /** Gets the content before cursor position (line, column) as prefix for completion. */ 37 | String extractCompletionPrefix(int position) { 38 | int start = position - 1; 39 | while (start >= 0 && Character.isJavaIdentifierPart(getContent().charAt(start))) { 40 | start--; 41 | } 42 | return getContent().subSequence(start + 1, position).toString(); 43 | } 44 | 45 | String substring(int line, int column, int length) { 46 | int position = LineMapUtil.getPositionFromZeroBasedLineAndColumn(getLineMap(), line, column); 47 | // if (position < 0) { 48 | // logger.warning( 49 | // "Position of (%s, %s): %s is negative when getting substring for file %s", 50 | // line, column, position, getFilePath()); 51 | // return ""; 52 | // } 53 | CharSequence content = getContent(); 54 | // if (content.length() < position) { 55 | // logger.warning( 56 | // "Position of (%s, %s): %s is greater than the length of the content %s when " 57 | // + "getting substring for file %s", 58 | // line, column, position, content.length(), getFilePath()); 59 | // return ""; 60 | // } 61 | return content.subSequence(position, Math.min(content.length(), position + length)).toString(); 62 | } 63 | } -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/completion/ElementBasedCompletionCandidate.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.completion; 2 | 3 | import shadow.javax.lang.model.element.Element; 4 | 5 | public abstract class ElementBasedCompletionCandidate implements CompletionCandidate{ 6 | 7 | private final T element; 8 | 9 | ElementBasedCompletionCandidate(T element) { 10 | this.element = element; 11 | } 12 | 13 | T getElement() { 14 | return element; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/completion/ElementCompletionCandidate.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.completion; 2 | 3 | import shadow.javax.lang.model.element.Element; 4 | import shadow.javax.lang.model.element.ElementKind; 5 | import shadow.javax.lang.model.element.ExecutableElement; 6 | import shadow.javax.lang.model.element.TypeElement; 7 | import shadow.javax.lang.model.type.TypeMirror; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | import java.util.Objects; 12 | import java.util.Optional; 13 | 14 | public class ElementCompletionCandidate extends ElementBasedCompletionCandidate { 15 | 16 | private final Map objectsMap = new HashMap<>(); 17 | 18 | ElementCompletionCandidate(Element element) { 19 | super(element); 20 | } 21 | 22 | @Override 23 | public String getName() { 24 | return getElement().getSimpleName().toString(); 25 | } 26 | 27 | @Override 28 | public Kind getKind() { 29 | return toCandidateKind(getElement().getKind()); 30 | } 31 | 32 | @Override 33 | public Optional getDetail() { 34 | return Optional.empty(); 35 | } 36 | 37 | public boolean getBoolean(String name) { 38 | Object o = objectsMap.get(name); 39 | if (o == null) { 40 | return false; 41 | } 42 | return (boolean) o; 43 | } 44 | 45 | public String getString(String name) { 46 | return (String) objectsMap.get(name); 47 | } 48 | 49 | public void putData(String name, T value) { 50 | objectsMap.put(name, value); 51 | } 52 | 53 | public static Kind toCandidateKind(ElementKind elementKind) { 54 | return switch (elementKind) { 55 | case CLASS -> Kind.CLASS; 56 | case ANNOTATION_TYPE, INTERFACE -> Kind.INTERFACE; 57 | case ENUM -> Kind.ENUM; 58 | case METHOD -> Kind.METHOD; 59 | case PARAMETER, LOCAL_VARIABLE -> Kind.VARIABLE; 60 | case FIELD -> Kind.FIELD; 61 | case PACKAGE -> Kind.PACKAGE; 62 | default -> Kind.UNKNOWN; 63 | }; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/completion/ExecutableElementCompletionCandidate.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.completion; 2 | 3 | import com.tyron.code.java.util.PrintUtil; 4 | import shadow.javax.lang.model.element.ExecutableElement; 5 | import shadow.javax.lang.model.element.VariableElement; 6 | import shadow.javax.lang.model.type.TypeKind; 7 | import shadow.javax.lang.model.type.TypeMirror; 8 | 9 | import java.util.List; 10 | import java.util.Optional; 11 | 12 | public class ExecutableElementCompletionCandidate extends ElementBasedCompletionCandidate { 13 | 14 | ExecutableElementCompletionCandidate(ExecutableElement element) { 15 | super(element); 16 | } 17 | 18 | @Override 19 | public String getName() { 20 | return getElement().getSimpleName().toString(); 21 | } 22 | 23 | @Override 24 | public Optional getNameDescription() { 25 | return Optional.of(PrintUtil.printMethodParameters(getElement())); 26 | } 27 | 28 | @Override 29 | public Kind getKind() { 30 | return Kind.METHOD; 31 | } 32 | 33 | @Override 34 | public Optional getDetail() { 35 | return Optional.of(PrintUtil.getSimpleName(getElement().getReturnType())); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/completion/KeywordCompletionCandidate.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.completion; 2 | 3 | import java.util.Optional; 4 | 5 | enum KeywordCompletionCandidate implements CompletionCandidate { 6 | ABSTRACT, 7 | ASSERT, 8 | BOOLEAN, 9 | BREAK, 10 | BYTE, 11 | CASE, 12 | CATCH, 13 | CHAR, 14 | CLASS, 15 | CONST, 16 | CONTINUE, 17 | DEFAULT, 18 | DO, 19 | DOUBLE, 20 | ELSE, 21 | ENUM, 22 | EXTENDS, 23 | FINAL, 24 | FINALLY, 25 | FLOAT, 26 | FOR, 27 | GOTO, 28 | IF, 29 | IMPLEMENTS, 30 | IMPORT, 31 | INSTANCEOF, 32 | INT, 33 | INTERFACE, 34 | LONG, 35 | NATIVE, 36 | NEW, 37 | PACKAGE, 38 | PRIVATE, 39 | PROTECTED, 40 | PUBLIC, 41 | RETURN, 42 | SHORT, 43 | STATIC, 44 | STRICTFP, 45 | SUPER, 46 | SWITCH, 47 | SYNCHRONIZED, 48 | THIS, 49 | THROW, 50 | THROWS, 51 | TRANSIENT, 52 | TRY, 53 | VOID, 54 | VOLATILE, 55 | WHILE, 56 | LENGTH; 57 | 58 | @Override 59 | public String getName() { 60 | return name().toLowerCase(); 61 | } 62 | 63 | @Override 64 | public Kind getKind() { 65 | return Kind.KEYWORD; 66 | } 67 | 68 | @Override 69 | public SortCategory getSortCategory() { 70 | return SortCategory.KEYWORD; 71 | } 72 | 73 | @Override 74 | public Optional getDetail() { 75 | return Optional.ofNullable(null); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/completion/MethodCompletionCandidate.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.completion; 2 | 3 | import com.tyron.code.java.util.PrintUtil; 4 | import shadow.javax.lang.model.element.ExecutableElement; 5 | import shadow.javax.lang.model.type.ExecutableType; 6 | import shadow.javax.lang.model.type.TypeKind; 7 | 8 | import java.util.Optional; 9 | 10 | public class MethodCompletionCandidate implements CompletionCandidate { 11 | private final ExecutableElement element; 12 | private final ExecutableType type; 13 | 14 | public MethodCompletionCandidate(ExecutableElement element, ExecutableType type) { 15 | this.element = element; 16 | this.type = type; 17 | } 18 | 19 | @Override 20 | public String getName() { 21 | return element.getSimpleName().toString(); 22 | } 23 | 24 | @Override 25 | public Optional getNameDescription() { 26 | return Optional.of(PrintUtil.printMethodType(type)); 27 | } 28 | 29 | @Override 30 | public Optional getInsertSnippet(TextEditOptions textEditOptions) { 31 | StringBuilder builder = new StringBuilder(); 32 | builder.append(element.getSimpleName()); 33 | builder.append("("); 34 | if (!element.getParameters().isEmpty()) { 35 | builder.append("$0"); 36 | } 37 | builder.append(")"); 38 | 39 | if (element.getReturnType().getKind() == TypeKind.VOID) { 40 | builder.append(";"); // Add semicolon if return type is void 41 | } 42 | return Optional.of(builder.toString()); 43 | } 44 | 45 | 46 | @Override 47 | public Kind getKind() { 48 | return Kind.METHOD; 49 | } 50 | 51 | @Override 52 | public Optional getDetail() { 53 | return Optional.empty(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/completion/SimpleCompletionCandidate.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.completion; 2 | 3 | import java.util.Optional; 4 | 5 | public class SimpleCompletionCandidate implements CompletionCandidate{ 6 | 7 | private final String name; 8 | 9 | public SimpleCompletionCandidate(String name) { 10 | this.name = name; 11 | } 12 | 13 | @Override 14 | public String getName() { 15 | return name; 16 | } 17 | 18 | @Override 19 | public Kind getKind() { 20 | return Kind.UNKNOWN; 21 | } 22 | 23 | @Override 24 | public Optional getDetail() { 25 | return Optional.empty(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/completion/TextEditOptions.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.completion; 2 | 3 | import com.google.auto.value.AutoValue; 4 | 5 | /** Options for customizing candidate-generated text edit content. */ 6 | @AutoValue 7 | public abstract class TextEditOptions { 8 | public static final TextEditOptions DEFAULT = 9 | TextEditOptions.builder().setAppendMethodArgumentSnippets(false).build(); 10 | 11 | /** 12 | * True if the text edit should contain snippets of method arguments when completing a method 13 | * candidate. 14 | */ 15 | public abstract boolean getAppendMethodArgumentSnippets(); 16 | 17 | public static Builder builder() { 18 | return new AutoValue_TextEditOptions.Builder(); 19 | } 20 | 21 | @AutoValue.Builder 22 | public abstract static class Builder { 23 | public abstract Builder setAppendMethodArgumentSnippets(boolean value); 24 | 25 | public abstract TextEditOptions build(); 26 | } 27 | } -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/model/ResolveAction.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.model; 2 | 3 | public enum ResolveAction { 4 | 5 | ADD_IMPORT_TEXT_EDIT 6 | } 7 | -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/model/ResolveActionParams.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.model; 2 | 3 | /** Marker interface for the actual type of {@link ResolveData#data}. */ 4 | public interface ResolveActionParams {} -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/model/ResolveAddImportTextEditsParams.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.model; 2 | 3 | import java.net.URI; 4 | 5 | /** 6 | * Resolve data for ADD_IMPORT_TEXT_EDIT action. 7 | */ 8 | public class ResolveAddImportTextEditsParams implements ResolveActionParams { 9 | 10 | /** 11 | * The text document's URI. 12 | */ 13 | public URI uri; 14 | /** 15 | * The full name of the class to be imported. 16 | */ 17 | public String classFullName; 18 | 19 | public ResolveAddImportTextEditsParams() { 20 | } 21 | 22 | public ResolveAddImportTextEditsParams(URI uri, String classFullName) { 23 | this.uri = uri; 24 | this.classFullName = classFullName; 25 | } 26 | } -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/model/ResolveData.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.model; 2 | 3 | public class ResolveData { 4 | /** The action for resolving the completion item. */ 5 | public ResolveAction action; 6 | /** Action-specific data. */ 7 | public String params; 8 | } -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/parsing/Insertion.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.parsing; 2 | 3 | import com.google.auto.value.AutoValue; 4 | import com.google.common.collect.Ordering; 5 | import java.util.List; 6 | 7 | @AutoValue 8 | public abstract class Insertion { 9 | private static final Ordering REVERSE_INSERTION = 10 | Ordering.natural().onResultOf(Insertion::getPos).reverse(); 11 | 12 | public abstract int getPos(); 13 | 14 | public abstract String getText(); 15 | 16 | public static Insertion create(int pos, String text) { 17 | return new AutoValue_Insertion(pos, text); 18 | } 19 | 20 | public static CharSequence applyInsertions(CharSequence content, List insertions) { 21 | List reverseInsertions = REVERSE_INSERTION.immutableSortedCopy(insertions); 22 | 23 | StringBuilder sb = new StringBuilder(content); 24 | 25 | for (Insertion insertion : reverseInsertions) { 26 | sb.insert(insertion.getPos(), insertion.getText()); 27 | } 28 | return sb; 29 | } 30 | } -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/parsing/LineMapUtil.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.parsing; 2 | 3 | import shadow.com.sun.source.tree.LineMap; 4 | 5 | /** Utility methods for working with {@link LineMap}. */ 6 | public final class LineMapUtil { 7 | private LineMapUtil() {} 8 | 9 | public static int getPositionFromZeroBasedLineAndColumn(LineMap lineMap, int line, int column) { 10 | // LineMap accepts 1-based line and column numbers. 11 | return (int) lineMap.getPosition(line + 1, column + 1); 12 | } 13 | } -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/parsing/MethodBodyPruner.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.parsing; 2 | 3 | import shadow.com.sun.source.tree.MethodTree; 4 | import shadow.com.sun.tools.javac.tree.JCTree; 5 | import shadow.com.sun.tools.javac.tree.TreeMaker; 6 | import shadow.com.sun.tools.javac.tree.TreeTranslator; 7 | import shadow.com.sun.tools.javac.util.Context; 8 | import shadow.com.sun.tools.javac.util.List; 9 | 10 | public class MethodBodyPruner extends TreeTranslator { 11 | 12 | private final TreeMaker treeMaker; 13 | public MethodBodyPruner() { 14 | treeMaker = TreeMaker.instance(new Context()); 15 | } 16 | 17 | @SuppressWarnings("unchecked") 18 | @Override 19 | public T translate(T tree) { 20 | if (tree instanceof JCTree.JCMethodDecl decl) { 21 | return (T) treeMaker.MethodDef( 22 | decl.mods, 23 | decl.name, 24 | decl.restype, 25 | decl.typarams, 26 | decl.params, 27 | decl.thrown, 28 | treeMaker.Block(0, List.nil()), 29 | decl.defaultValue 30 | ); 31 | } 32 | return super.translate(tree); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/parsing/ParserContext.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.parsing; 2 | 3 | import com.tyron.code.java.SourceFileObject; 4 | import shadow.com.sun.tools.javac.file.JavacFileManager; 5 | import shadow.com.sun.tools.javac.parser.JavacParser; 6 | import shadow.com.sun.tools.javac.parser.ParserFactory; 7 | import shadow.com.sun.tools.javac.parser.Scanner; 8 | import shadow.com.sun.tools.javac.parser.ScannerFactory; 9 | import shadow.com.sun.tools.javac.tree.JCTree; 10 | import shadow.com.sun.tools.javac.util.Context; 11 | import shadow.com.sun.tools.javac.util.Log; 12 | 13 | import java.io.OutputStream; 14 | import java.io.PrintWriter; 15 | import java.nio.file.Paths; 16 | 17 | import static com.google.common.base.Charsets.UTF_8; 18 | 19 | /** 20 | * Environment for using Javac Parser 21 | */ 22 | public class ParserContext { 23 | private final Context javacContext; 24 | private final JavacFileManager javacFileManager; 25 | 26 | public ParserContext() { 27 | javacContext = new Context(); 28 | javacFileManager = new JavacFileManager(javacContext, true /* register */, UTF_8); 29 | } 30 | 31 | /** 32 | * Set source file of the log. 33 | * 34 | *

This method should be called before parsing or lexing. If not set, IllegalArgumentException 35 | * will be thrown if the parser encounters errors. 36 | */ 37 | public void setupLoggingSource(String filename) { 38 | SourceFileObject sourceFileObject = new SourceFileObject(Paths.get(filename)); 39 | Log javacLog = Log.instance(javacContext); 40 | javacLog.setWriters(new PrintWriter(OutputStream.nullOutputStream())); 41 | javacLog.useSource(sourceFileObject); 42 | } 43 | 44 | /** 45 | * Parses the content of a Java file. 46 | * 47 | * @param filename the filename of the Java file 48 | * @param content the content of the Java file 49 | */ 50 | public JCTree.JCCompilationUnit parse(String filename, CharSequence content) { 51 | setupLoggingSource(filename); 52 | 53 | // Create a parser and start parsing. 54 | JavacParser parser = 55 | ParserFactory.instance(javacContext) 56 | .newParser( 57 | content, true /* keepDocComments */, true /* keepEndPos */, true /* keepLineMap */); 58 | return parser.parseCompilationUnit(); 59 | } 60 | 61 | public Scanner tokenize(CharSequence content, boolean keepDocComments) { 62 | return ScannerFactory.instance(javacContext).newScanner(content, keepDocComments); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/parsing/PositionContext.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.parsing; 2 | 3 | import com.google.auto.value.AutoValue; 4 | import com.tyron.code.project.file.FileManager; 5 | import com.tyron.code.project.model.JavaFileInfo; 6 | import com.tyron.code.project.model.module.JavaModule; 7 | import com.tyron.code.project.model.module.Module; 8 | import shadow.com.sun.source.tree.ErroneousTree; 9 | import shadow.com.sun.source.tree.LineMap; 10 | import shadow.com.sun.source.tree.Tree; 11 | import shadow.com.sun.source.util.TreePath; 12 | import shadow.com.sun.source.util.TreePathScanner; 13 | import shadow.com.sun.tools.javac.tree.EndPosTable; 14 | import shadow.com.sun.tools.javac.tree.JCTree; 15 | 16 | import java.nio.file.Path; 17 | import java.util.Optional; 18 | 19 | @AutoValue 20 | public abstract class PositionContext { 21 | 22 | public abstract Module getModule(); 23 | 24 | public abstract Path getPath(); 25 | 26 | public abstract TreePath getTreePath(); 27 | 28 | /** 29 | * Gets position of the parsed content of the file. 30 | * 31 | *

Because the content of a file may be modified by {@link FileContentFixer}, the parsed 32 | * content may be different from the original content of the file. So this position can not be 33 | * used in the context of the original position. 34 | */ 35 | public abstract long getPosition(); 36 | 37 | public abstract EndPosTable getEndPosTable(); 38 | 39 | public abstract CharSequence getContent(); 40 | 41 | } 42 | -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/protocol/Position.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.protocol; 2 | 3 | import com.tyron.code.project.model.TextPosition; 4 | 5 | import java.util.Objects; 6 | 7 | /** 8 | * Position in a text document expressed as zero-based line and character offset. A position is 9 | * between two characters like an 'insert' cursor in a editor. 10 | * 11 | *

See: ... 12 | */ 13 | public class Position extends TextPosition { 14 | private final int line; 15 | private final int character; 16 | 17 | // For GSON 18 | public Position() { 19 | this.line = 0; 20 | this.character = 0; 21 | } 22 | 23 | public Position(int line, int character) { 24 | this.line = line; 25 | this.character = character; 26 | } 27 | 28 | public static Position createFromTextPosition(TextPosition pos) { 29 | if (pos instanceof Position) { 30 | return (Position) pos; 31 | } 32 | return new Position(pos.getLine(), pos.getCharacter()); 33 | } 34 | 35 | @Override 36 | public int getLine() { 37 | return line; 38 | } 39 | 40 | @Override 41 | public int getCharacter() { 42 | return character; 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return String.format("(%d, %d)", line, character); 48 | } 49 | 50 | @Override 51 | public boolean equals(Object o) { 52 | if (!(o instanceof Position other)) { 53 | return false; 54 | } 55 | 56 | return this.line == other.line && this.character == other.character; 57 | } 58 | 59 | @Override 60 | public int hashCode() { 61 | return Objects.hash(line, character); 62 | } 63 | } -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/protocol/Range.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.protocol; 2 | 3 | import com.tyron.code.project.model.TextRange; 4 | 5 | import java.util.Objects; 6 | 7 | /** 8 | * A range in a text document expressed as (zero-based) start and end positions. 9 | * 10 | *

A range is comparable to a selection in an editor. Therefore the end position is exclusive. 11 | * 12 | *

NOTE: the start and end position are character offsets, not byte offsets. 13 | * 14 | *

This class corresponds to the Range type defined by Language Server Protocol: 15 | * https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#range 16 | */ 17 | public class Range extends TextRange { 18 | private final Position start; 19 | private final Position end; 20 | 21 | // For GSON 22 | public Range() { 23 | this.start = new Position(); 24 | this.end = new Position(); 25 | } 26 | 27 | public Range(Position start, Position end) { 28 | this.start = start; 29 | this.end = end; 30 | } 31 | 32 | public static Range createFromTextRange(TextRange textRange) { 33 | if (textRange instanceof Range) { 34 | return (Range) textRange; 35 | } 36 | 37 | return new Range( 38 | Position.createFromTextPosition(textRange.getStart()), 39 | Position.createFromTextPosition(textRange.getEnd())); 40 | } 41 | 42 | @Override 43 | public Position getStart() { 44 | return start; 45 | } 46 | 47 | @Override 48 | public Position getEnd() { 49 | return end; 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return String.format("{start=%s, end=%s}", start, end); 55 | } 56 | 57 | @Override 58 | public boolean equals(Object o) { 59 | if (!(o instanceof Range)) { 60 | return false; 61 | } 62 | 63 | Range other = (Range) o; 64 | return Objects.equals(this.start, other.start) && Objects.equals(this.end, other.end); 65 | } 66 | 67 | @Override 68 | public int hashCode() { 69 | return Objects.hash(start, end); 70 | } 71 | } -------------------------------------------------------------------------------- /completions/src/main/java/com/tyron/code/java/util/PrintUtil.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.util; 2 | 3 | import shadow.javax.lang.model.element.ExecutableElement; 4 | import shadow.javax.lang.model.type.ExecutableType; 5 | import shadow.javax.lang.model.type.TypeMirror; 6 | 7 | import java.util.stream.Collectors; 8 | 9 | public class PrintUtil { 10 | 11 | public static String printMethodParameters(ExecutableElement method) { 12 | StringBuilder builder = new StringBuilder(); 13 | builder.append("("); 14 | var params = method.getParameters().stream() 15 | .map(param -> getSimpleName(param.asType()) + " " + param.getSimpleName().toString()) 16 | .collect(Collectors.joining(", ")); 17 | builder.append(params); 18 | builder.append(")"); 19 | return builder.toString(); 20 | } 21 | 22 | public static String getSimpleName(TypeMirror typeMirror) { 23 | return getSimpleName(typeMirror.toString()); 24 | } 25 | 26 | public static String getSimpleName(String fullyQualifiedName) { 27 | int index = fullyQualifiedName.lastIndexOf("."); 28 | if (index == -1) { 29 | return fullyQualifiedName; 30 | } 31 | return fullyQualifiedName.substring(index + 1); 32 | } 33 | 34 | public static String printMethodType(ExecutableType type) { 35 | StringBuilder builder = new StringBuilder(); 36 | builder.append("("); 37 | var params = type.getParameterTypes().stream() 38 | .map(param -> getSimpleName(param) + " " + suggestName(param)) 39 | .collect(Collectors.joining(", ")); 40 | builder.append(params); 41 | builder.append(")"); 42 | builder.append(" -> "); 43 | builder.append(getSimpleName(type.getReturnType())); 44 | return builder.toString(); 45 | } 46 | 47 | public static String suggestName(TypeMirror type) { 48 | String name = getSimpleName(type); 49 | if (name.length() > 1) { 50 | return name.substring(0, 1).toLowerCase() + name.substring(1); 51 | } 52 | return name.toLowerCase(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /completions/src/test/java/com/tyron/code/java/completion/AutoImportCompletionTest.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.completion; 2 | 3 | import com.google.common.truth.Truth; 4 | import com.tyron.code.java.model.ResolveAction; 5 | import com.tyron.code.java.model.ResolveActionParams; 6 | import com.tyron.code.java.model.ResolveAddImportTextEditsParams; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import java.util.List; 10 | 11 | public class AutoImportCompletionTest extends BaseCompletionTest { 12 | 13 | @Test 14 | public void testAutoImportCompletion() { 15 | String text = """ 16 | class Main { 17 | public static void main(String[] args) { 18 | List.o@complete 19 | } 20 | } 21 | """; 22 | List complete = complete(text); 23 | for (CompletionCandidate completionCandidate : complete) { 24 | Truth.assertThat(completionCandidate.getResolveActions()).isNotEmpty(); 25 | Truth.assertThat(completionCandidate.getResolveActions()).containsKey(ResolveAction.ADD_IMPORT_TEXT_EDIT); 26 | 27 | ResolveActionParams resolveActionParams = completionCandidate.getResolveActions().get(ResolveAction.ADD_IMPORT_TEXT_EDIT); 28 | Truth.assertThat(resolveActionParams).isInstanceOf(ResolveAddImportTextEditsParams.class); 29 | Truth.assertThat(((ResolveAddImportTextEditsParams) resolveActionParams).classFullName).isEqualTo("java.util.List"); 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /completions/src/test/java/com/tyron/code/java/completion/CancellationTest.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.completion; 2 | 3 | import com.google.common.truth.Truth; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.nio.file.Paths; 7 | import java.util.concurrent.CancellationException; 8 | import java.util.concurrent.atomic.AtomicBoolean; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertThrows; 11 | 12 | public class CancellationTest extends BaseCompletionTest { 13 | 14 | @Test 15 | public void testOldAnalysisShouldBeCancelled() { 16 | assertThrows(CancellationException.class, this::actualTest); 17 | } 18 | 19 | private void actualTest() throws InterruptedException { 20 | final CancellationException[] cancellationException = {null}; 21 | Thread thread = new Thread(() -> { 22 | try { 23 | analyzer.analyze(Paths.get("test"), "", result -> { 24 | try { 25 | System.out.println("Running 1"); 26 | sleep(500); 27 | } catch (CancellationException e) { 28 | cancellationException[0] = e; 29 | } 30 | }); 31 | } catch (CancellationException e) { 32 | cancellationException[0] = e; 33 | } 34 | }, "First Thread"); 35 | thread.start(); 36 | 37 | // sleep(2); 38 | analyzer.analyze(Paths.get("test"), "", analysisResult -> { 39 | System.out.println("Running 2"); 40 | }); 41 | thread.join(); 42 | 43 | if (cancellationException[0] != null) { 44 | throw cancellationException[0]; 45 | } 46 | } 47 | 48 | private static void sleep(long millis) { 49 | try { 50 | Thread.sleep(millis); 51 | } catch (InterruptedException e) { 52 | throw new RuntimeException(e); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /completions/src/test/java/com/tyron/code/java/completion/CompletionVisibilityTest.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.completion; 2 | 3 | import com.google.common.truth.Truth; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.List; 7 | 8 | public class CompletionVisibilityTest extends BaseCompletionTest { 9 | 10 | @Test 11 | void testStaticCallsShouldNotContainInstanceMethods() { 12 | List completed = completeString(""" 13 | class Main { 14 | static void main() { 15 | Main.@complete 16 | } 17 | 18 | void instance(); 19 | } 20 | """); 21 | 22 | Truth.assertThat(completed).doesNotContain("instance"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /completions/src/test/java/com/tyron/code/java/completion/PackageCompletionTests.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.java.completion; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.List; 6 | 7 | import static com.google.common.truth.Truth.assertThat; 8 | 9 | public class PackageCompletionTests extends BaseCompletionTest{ 10 | 11 | @Test 12 | public void testPackageCompletionInsideMethodInvocations() { 13 | List completed = completeString(""" 14 | import java.util.*; 15 | 16 | class Main { 17 | void main() { 18 | new ArrayList().stream() 19 | .map(it -> it.toUpperCase()) 20 | .collect(java.util.stream.@complete 21 | } 22 | } 23 | """); 24 | assertThat(completed).contains("Collectors"); 25 | } 26 | 27 | @Test 28 | public void testPackageCompletionWorksNormally() { 29 | List completed = completeString(""" 30 | class Main { 31 | void main() { 32 | java.util.@complete 33 | } 34 | } 35 | """); 36 | assertThat(completed).isNotEmpty(); 37 | } 38 | 39 | @Test 40 | public void testPackageCompletionWorksOnImports() { 41 | List complete = completeString(""" 42 | import java.util.@complete 43 | """); 44 | assertThat(complete).contains("List"); 45 | } 46 | 47 | @Test 48 | public void testPackageCompletionWorksOnStaticImports() { 49 | List complete = completeString(""" 50 | import static java.lang.@complete 51 | """); 52 | assertThat(complete).isNotEmpty(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /completions/src/test/resources/android.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyron12233/CodeAssist-v3/ac37cbace6848b9f2c9343fc7f3a5355da6572b6/completions/src/test/resources/android.jar -------------------------------------------------------------------------------- /deskptop-test/README.md: -------------------------------------------------------------------------------- 1 | # Desktop Client 2 | 3 | Mostly based on [Recaf v4](https://github.com/Col-E/Recaf/tree/dev4) -------------------------------------------------------------------------------- /deskptop-test/android.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyron12233/CodeAssist-v3/ac37cbace6848b9f2c9343fc7f3a5355da6572b6/deskptop-test/android.jar -------------------------------------------------------------------------------- /deskptop-test/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | application 3 | kotlin("jvm") version "1.9.22" 4 | id("org.openjfx.javafxplugin") version "0.1.0" 5 | } 6 | 7 | val javaFxVersion = "21" 8 | val javaFxIncludeInDist = System.getProperty("skip.jfx.bundle") == null 9 | 10 | javafx { 11 | version = javaFxVersion 12 | modules("javafx.controls", "javafx.media") 13 | } 14 | 15 | dependencies { 16 | testImplementation(platform("org.junit:junit-bom:5.9.1")) 17 | testImplementation("org.junit.jupiter:junit-jupiter") 18 | 19 | implementation("io.github.mkpaz:atlantafx-base:2.0.1") 20 | 21 | implementation("io.insert-koin:koin-core:3.5.3") 22 | 23 | implementation("com.github.Col-E:tiwulfx-dock:1.2.3") 24 | implementation("ch.qos.logback:logback-classic:1.4.11") 25 | 26 | implementation("org.fxmisc.richtext:richtextfx:0.11.2") 27 | implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.15.2") 28 | implementation("com.github.tommyettinger:regexodus:0.1.15") 29 | implementation("org.kordamp.ikonli:ikonli-javafx:12.3.1") 30 | implementation("org.kordamp.ikonli:ikonli-carbonicons-pack:12.3.1") 31 | 32 | implementation("com.fifesoft:rsyntaxtextarea:3.3.4") 33 | implementation("com.fifesoft:autocomplete:3.3.1") 34 | implementation(project(":project")) 35 | implementation(project(":project-impl")) 36 | implementation(project(":completions")) 37 | implementation(kotlin("stdlib-jdk8")) 38 | } 39 | 40 | tasks.test { 41 | useJUnitPlatform() 42 | } 43 | kotlin { 44 | jvmToolchain(17) 45 | } -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/cdi/module.kt: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.cdi 2 | 3 | import com.tyron.code.desktop.services.navigation.Actions 4 | import com.tyron.code.desktop.services.navigation.NavigationManager 5 | import com.tyron.code.desktop.ui.docking.DockingManager 6 | import com.tyron.code.desktop.ui.pane.WelcomePane 7 | import com.tyron.code.desktop.ui.pane.WorkspaceInformationPane 8 | import com.tyron.code.desktop.ui.pane.WorkspaceRootPane 9 | import com.tyron.code.java.analysis.Analyzer 10 | import com.tyron.code.java.completion.Completor 11 | import com.tyron.code.project.BasicWorkspaceManager 12 | import com.tyron.code.project.ModuleManager 13 | import com.tyron.code.project.Workspace 14 | import com.tyron.code.project.WorkspaceManager 15 | import com.tyron.code.project.file.FileManager 16 | import com.tyron.code.project.file.FileManagerImpl 17 | import com.tyron.code.project.impl.CodeAssistModuleManager 18 | import com.tyron.code.project.impl.WorkspaceImpl 19 | import com.tyron.code.project.model.module.JavaModule 20 | import com.tyron.code.project.model.module.Module 21 | import org.koin.core.qualifier.named 22 | import org.koin.dsl.module 23 | import java.util.concurrent.Executors 24 | 25 | val mainModule = module { 26 | 27 | single { BasicWorkspaceManager() } 28 | single { DockingManager() } 29 | single { NavigationManager(get(), get()) } 30 | single { Actions(get(), get(), get()) } 31 | 32 | single { WorkspaceRootPane(get(), get()) } 33 | single { WelcomePane() } 34 | single { WorkspaceInformationPane(get()) } 35 | 36 | 37 | factory { 38 | get().current 39 | } 40 | 41 | scope { 42 | scoped { 43 | FileManagerImpl(get().root.toUri(), listOf(), Executors.newSingleThreadExecutor()) 44 | } 45 | 46 | scoped { 47 | CodeAssistModuleManager(get(), get().root) 48 | } 49 | } 50 | 51 | scope { 52 | scoped { 53 | Completor(get(), get()) 54 | } 55 | 56 | scoped { 57 | Analyzer(get(), get()) 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/services/navigation/SourceFileNavigable.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.services.navigation; 2 | 3 | import com.tyron.code.path.impl.SourceClassPathNode; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | public interface SourceFileNavigable extends Navigable { 7 | 8 | @NotNull 9 | @Override 10 | SourceClassPathNode getPath(); 11 | } 12 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/services/navigation/UnsupportedContent.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.services.navigation; 2 | 3 | import com.tyron.code.info.Info; 4 | 5 | /** 6 | * Exception used to denote a {@link Info} type couldn't be shown in the UI due to lack of support. 7 | * 8 | * @author Matt Coley 9 | */ 10 | public class UnsupportedContent extends RuntimeException { 11 | /** 12 | * @param message 13 | * Exception message. 14 | */ 15 | public UnsupportedContent(String message) { 16 | super(message); 17 | } 18 | } -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/services/navigation/UpdatableNavigable.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.services.navigation; 2 | 3 | import com.tyron.code.path.PathNode; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | /** 7 | * Navigable subtype with intent for content to be updated. 8 | * 9 | * @author Matt Coley 10 | */ 11 | public interface UpdatableNavigable extends Navigable { 12 | /** 13 | * Called when the underlying content of the path is updated. 14 | * The new content is wrapped in a new path, which is passed to this method. 15 | *
16 | * Implementations must update their internal {@link #getPath()} value 17 | * if the passed path type is the same as the one they represent. 18 | * For instance, a {@link ClassPane} will have this called when 19 | * a user modifies a class. It will then update its {@link ClassPane#getPath()} backing value. 20 | * It will then pass along the update message to its {@link ClassPane#getNavigableChildren()}. 21 | * Content displaying {@link ClassMember} values should re-fetch the member info from the new path 22 | * to maintain correctness. 23 | * 24 | * @param path 25 | * New path value. 26 | */ 27 | void onUpdatePath(@NotNull PathNode path); 28 | } -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/Main.kt: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui 2 | 3 | import com.tyron.code.desktop.CodeAssistApplication 4 | import com.tyron.code.desktop.cdi.mainModule 5 | import javafx.application.Application 6 | import org.koin.core.context.startKoin 7 | 8 | fun main() { 9 | 10 | startKoin { 11 | modules(mainModule) 12 | } 13 | 14 | Application.launch(CodeAssistApplication::class.java) 15 | } 16 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/control/ActionMenuItem.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.control; 2 | 3 | import javafx.beans.binding.StringBinding; 4 | import javafx.beans.value.ObservableValue; 5 | import javafx.scene.Node; 6 | import javafx.scene.control.MenuItem; 7 | 8 | /** 9 | * MenuItem with an on-click runnable action. 10 | * 11 | * @author Matt Coley 12 | */ 13 | public class ActionMenuItem extends MenuItem { 14 | /** 15 | * @param text 16 | * Menu item display text. 17 | * @param action 18 | * Action to run on-click. 19 | */ 20 | public ActionMenuItem(String text, Runnable action) { 21 | this(text, null, action); 22 | } 23 | 24 | /** 25 | * @param text 26 | * Menu item display text. 27 | * @param action 28 | * Action to run on-click. 29 | */ 30 | public ActionMenuItem(StringBinding text, Runnable action) { 31 | this(text, null, action); 32 | } 33 | 34 | /** 35 | * @param text 36 | * Menu item display text. 37 | * @param graphic 38 | * Menu item graphic. 39 | * @param action 40 | * Action to run on-click. 41 | */ 42 | public ActionMenuItem(String text, Node graphic, Runnable action) { 43 | super(text); 44 | setGraphic(graphic); 45 | setOnAction(e -> action.run()); 46 | } 47 | 48 | /** 49 | * @param text 50 | * Menu item display text. 51 | * @param graphic 52 | * Menu item graphic. 53 | * @param action 54 | * Action to run on-click. 55 | */ 56 | public ActionMenuItem(ObservableValue text, Node graphic, Runnable action) { 57 | textProperty().bind(text); 58 | setGraphic(graphic); 59 | setOnAction(e -> action.run()); 60 | } 61 | } -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/control/BoundToggleIcon.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.control; 2 | 3 | import javafx.beans.binding.Bindings; 4 | import javafx.beans.property.BooleanProperty; 5 | import javafx.scene.Node; 6 | import javafx.scene.control.Button; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | 10 | /** 11 | * Toggle button for a {@link BooleanProperty}. 12 | * 13 | * @author Amejonah 14 | * @author Matt Coley 15 | */ 16 | public class BoundToggleIcon extends Button implements Tooltipable { 17 | // /** 18 | // * @param graphic 19 | // * Button graphic path for {@link Icons#getIconView(String)}. 20 | // * @param property 21 | // * Property to bind to. 22 | // */ 23 | // public BoundToggleIcon(@NotNull String graphic, @NotNull BooleanProperty property) { 24 | // this(Icons.getIconView(graphic), property); 25 | // } 26 | 27 | /** 28 | * @param graphic 29 | * Button graphic. 30 | * @param property 31 | * Property to bind to. 32 | */ 33 | public BoundToggleIcon(@NotNull Node graphic, @NotNull BooleanProperty property) { 34 | setGraphic(graphic); 35 | setOnAction(e -> property.set(!property.get())); 36 | opacityProperty().bind( 37 | Bindings.when(property) 38 | .then(1.0) 39 | .otherwise(0.4) 40 | ); 41 | } 42 | 43 | // /** 44 | // * @param graphic 45 | // * Button graphic path for {@link Icons#getIconView(String)}. 46 | // * @param observable 47 | // * Observable to bind to. 48 | // */ 49 | // public BoundToggleIcon(@NotNull String graphic, @NotNull ObservableBoolean observable) { 50 | // this(Icons.getIconView(graphic), observable); 51 | // } 52 | 53 | // /** 54 | // * @param graphic 55 | // * Button graphic. 56 | // * @param observable 57 | // * Observable to bind to. 58 | // */ 59 | // public BoundToggleIcon(@NotNull Node graphic, @NotNull ObservableBoolean observable) { 60 | // setGraphic(graphic); 61 | // setOnAction(e -> observable.setValue(!observable.getValue())); 62 | // observable.addChangeListener((ob, old, cur) -> setOpacity(cur ? 1.0 : 0.4)); 63 | // } 64 | } -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/control/FontIconView.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.control; 2 | 3 | import javafx.scene.paint.Color; 4 | import javafx.scene.text.Font; 5 | import javafx.scene.text.Text; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | import org.kordamp.ikonli.Ikon; 9 | import org.kordamp.ikonli.IkonHandler; 10 | import org.kordamp.ikonli.javafx.FontIcon; 11 | import org.kordamp.ikonli.javafx.IkonResolver; 12 | 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | /** 17 | * Minimal reimplementation of {@link FontIcon} because its manual CSS writing instead of working with properties 18 | * prevents it from being used with AtlantaFX. 19 | * 20 | * @author Matt Coley 21 | */ 22 | public class FontIconView extends Text { 23 | private static final IkonResolver resolver = IkonResolver.getInstance(); 24 | private static final Map codeToHandler = new HashMap<>(); 25 | 26 | /** 27 | * @param icon 28 | * Icon to use. 29 | */ 30 | public FontIconView(Ikon icon) { 31 | this(icon, IconView.DEFAULT_ICON_SIZE, null); 32 | } 33 | 34 | /** 35 | * @param icon 36 | * Icon to use. 37 | * @param color 38 | * Color to use. 39 | */ 40 | public FontIconView(Ikon icon, @NotNull Color color) { 41 | this(icon, IconView.DEFAULT_ICON_SIZE, color); 42 | } 43 | 44 | /** 45 | * @param icon 46 | * Icon to use. 47 | * @param size 48 | * Size of icon in pixels. 49 | */ 50 | public FontIconView(Ikon icon, int size) { 51 | this(icon, size, null); 52 | } 53 | 54 | /** 55 | * @param icon 56 | * Icon to use. 57 | * @param size 58 | * Size of icon in pixels. 59 | * @param color 60 | * Optional color to use. 61 | */ 62 | public FontIconView(Ikon icon, int size, @Nullable Color color) { 63 | int code = icon.getCode(); 64 | IkonHandler ikonHandler = codeToHandler.computeIfAbsent(code, k -> resolver.resolve(icon.getDescription())); 65 | 66 | // Fetch expected font instance, configure size/color 67 | Font font = (Font) ikonHandler.getFont(); 68 | Font sizedFont = new Font(font.getFamily(), size); 69 | setFont(sizedFont); 70 | if (color != null) setFill(color); 71 | else setStyle("-fx-fill: -color-fg-default;"); 72 | 73 | // Set text to ikonli character 74 | if (code <= '\uFFFF') { 75 | setText(String.valueOf((char) code)); 76 | } else { 77 | char[] charPair = Character.toChars(code); 78 | String symbol = new String(charPair); 79 | setText(symbol); 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/control/IconView.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.control; 2 | 3 | import javafx.scene.image.Image; 4 | import javafx.scene.image.ImageView; 5 | 6 | /** 7 | * {@link ImageView} extension for icons. It is recommended to use use {@link Icons} 8 | * to fetch {@link Image} instances, and generally creating instances of this class. 9 | * 10 | * @author Matt Coley 11 | * @see Icons 12 | */ 13 | public class IconView extends ImageView { 14 | /** 15 | * Default icon size, 16x16. 16 | */ 17 | public static final int DEFAULT_ICON_SIZE = 16; 18 | 19 | /** 20 | * @param image 21 | * Image resource. 22 | */ 23 | public IconView(Image image) { 24 | this(image, DEFAULT_ICON_SIZE); 25 | } 26 | 27 | /** 28 | * @param image 29 | * Image resource. 30 | * @param size 31 | * Image width/height. 32 | */ 33 | public IconView(Image image, int size) { 34 | super(image); 35 | fitHeightProperty().set(size); 36 | fitWidthProperty().set(size); 37 | // Setting to false seems to make smaller images scale properly (like 10x10) 38 | setPreserveRatio(false); 39 | } 40 | } -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/control/Tooltipable.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.control; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import javafx.beans.value.ObservableValue; 5 | import javafx.scene.control.Control; 6 | import javafx.scene.control.Tooltip; 7 | 8 | /** 9 | * Support for tooltips with bindings to {@link Lang} for any supported control type. 10 | * 11 | * @author Matt Coley 12 | */ 13 | public interface Tooltipable { 14 | /** 15 | * Implemented by {@link Control#setTooltip(Tooltip)}. 16 | * 17 | * @param tooltip 18 | * Tooltip to assign. 19 | */ 20 | void setTooltip(Tooltip tooltip); 21 | 22 | /** 23 | * @param tooltipKey 24 | * Translation key for tooltip display. 25 | * 26 | * @return Self. 27 | */ 28 | @NotNull 29 | default T withTooltip(@NotNull String tooltipKey) { 30 | // return withTooltip(Lang.getBinding(tooltipKey)); 31 | throw new UnsupportedOperationException(); 32 | } 33 | 34 | /** 35 | * @param tooltipValue 36 | * Text binding value for tooltip display. 37 | * 38 | * @return Self. 39 | */ 40 | @NotNull 41 | @SuppressWarnings("unchecked") 42 | default T withTooltip(@NotNull ObservableValue tooltipValue) { 43 | Tooltip tooltip = new Tooltip(); 44 | tooltip.textProperty().bind(tooltipValue); 45 | setTooltip(tooltip); 46 | return (T) this; 47 | } 48 | } -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/control/richtext/EditorComponent.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.control.richtext; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | /** 6 | * Outline for all components that can be added to an {@link Editor}. 7 | * 8 | * @author Matt Coley 9 | */ 10 | public interface EditorComponent { 11 | /** 12 | * Called when the component is installed into the given editor. 13 | * 14 | * @param editor 15 | * Editor installed to. 16 | */ 17 | void install(@NotNull Editor editor); 18 | 19 | /** 20 | * Called when the component is removed from the given editor. 21 | * 22 | * @param editor 23 | * Editor removed from. 24 | */ 25 | void uninstall(@NotNull Editor editor); 26 | } 27 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/control/richtext/ScrollbarPaddingUtil.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.control.richtext; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import javafx.geometry.Insets; 5 | import javafx.scene.Node; 6 | import javafx.scene.layout.StackPane; 7 | 8 | /** 9 | * Used by {@link Node} values to be shown on an editor's {@link Editor#getPrimaryStack()}. 10 | * If a node is shown on the right, the padding will need to be adjusted when scrollbars are visible. 11 | * 12 | * @author Matt Coley 13 | * @see ProblemOverlay 14 | * @see AbstractDecompilerPaneConfigurator 15 | */ 16 | public class ScrollbarPaddingUtil { 17 | /** 18 | * When the {@link Editor#getVerticalScrollbar()} is visible, our {@link StackPane#setMargin(Node, Insets)} will cause 19 | * us to overlap with it. This doesn't look great, so when it is visible we will shift a bit over to the left so that we 20 | * do not overlap. 21 | * 22 | * @param node 23 | * Node to update. 24 | * @param currentlyVisible 25 | * Current visibility state of the editor's vertical scrollbar. 26 | */ 27 | public static void handleScrollbarVisibility(@NotNull Node node, boolean currentlyVisible) { 28 | StackPane.setMargin(node, new Insets(7, currentlyVisible ? 14 : 7, 7, 7)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/control/richtext/bracket/BracketMatchGraphicFactory.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.control.richtext.bracket; 2 | 3 | import com.tyron.code.desktop.ui.control.richtext.Editor; 4 | import com.tyron.code.desktop.ui.control.richtext.linegraphics.AbstractLineGraphicFactory; 5 | import com.tyron.code.desktop.ui.control.richtext.linegraphics.LineContainer; 6 | import org.jetbrains.annotations.NotNull; 7 | import javafx.geometry.Insets; 8 | import javafx.geometry.Orientation; 9 | import javafx.geometry.Pos; 10 | import javafx.scene.DepthTest; 11 | import javafx.scene.control.Separator; 12 | import javafx.scene.layout.StackPane; 13 | 14 | /** 15 | * Graphic factory that adds a line indicator to matched lines containing the 16 | * {@link SelectedBracketTracking#getRange() current bracket pair}. 17 | * 18 | * @author Matt Coley 19 | * @see SelectedBracketTracking 20 | */ 21 | public class BracketMatchGraphicFactory extends AbstractLineGraphicFactory { 22 | private Editor editor; 23 | 24 | /** 25 | * New graphic factory. 26 | */ 27 | public BracketMatchGraphicFactory() { 28 | super(P_BRACKET_MATCH); 29 | } 30 | 31 | @Override 32 | public void install(@NotNull Editor editor) { 33 | this.editor = editor; 34 | } 35 | 36 | @Override 37 | public void uninstall(@NotNull Editor editor) { 38 | this.editor = null; 39 | } 40 | 41 | 42 | @Override 43 | public void apply(@NotNull LineContainer container, int paragraph) { 44 | SelectedBracketTracking selectedBracketTracking = editor.getSelectedBracketTracking(); 45 | 46 | // Always null if no bracket tracking is registered for the editor. 47 | if (selectedBracketTracking == null) return; 48 | 49 | // Add brace line for selected. 50 | if (selectedBracketTracking.isSelectedParagraph(paragraph)) { 51 | SelectedLineSeparator separator = new SelectedLineSeparator(); 52 | StackPane.setAlignment(separator, Pos.CENTER_RIGHT); 53 | container.addTopLayer(separator); 54 | } 55 | } 56 | 57 | static class SelectedLineSeparator extends Separator { 58 | private SelectedLineSeparator() { 59 | super(Orientation.VERTICAL); 60 | getStyleClass().add("matched-brace-line"); 61 | setPadding(new Insets(0)); 62 | } 63 | 64 | @Override 65 | protected void setWidth(double value) { 66 | super.setWidth(value); 67 | // Keeps the vertical separator 'inline' with the parent container's edge 68 | setTranslateX(-value); 69 | } 70 | 71 | @Override 72 | protected void setHeight(double value) { 73 | super.setHeight(value + 1); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/control/richtext/linegraphics/AbstractLineGraphicFactory.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.control.richtext.linegraphics; 2 | 3 | /** 4 | * Base implementation of {@link LineGraphicFactory}. 5 | * 6 | * @author Matt Coley 7 | */ 8 | public abstract class AbstractLineGraphicFactory implements LineGraphicFactory { 9 | private final int priority; 10 | 11 | /** 12 | * @param priority 13 | * Priority dictating the order of graphics displayed in {@link RootLineGraphicFactory}. 14 | * See {@link LineGraphicFactory} for constants. 15 | */ 16 | protected AbstractLineGraphicFactory(int priority) { 17 | this.priority = priority; 18 | } 19 | 20 | @Override 21 | public int priority() { 22 | return priority; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/control/richtext/linegraphics/LineContainer.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.control.richtext.linegraphics; 2 | 3 | import javafx.geometry.Insets; 4 | import javafx.geometry.Pos; 5 | import javafx.scene.Node; 6 | import javafx.scene.layout.HBox; 7 | import javafx.scene.layout.StackPane; 8 | 9 | /** 10 | * Container for adding sub-graphics per-line graphic. 11 | * Created in {@link RootLineGraphicFactory}. 12 | * 13 | * @author Matt Coley 14 | */ 15 | public class LineContainer extends StackPane { 16 | private static final int LINE_V_PADDING = 1; 17 | private static final int LINE_H_PADDING = 5; 18 | private static final Insets PADDING = new Insets(LINE_V_PADDING, LINE_H_PADDING, LINE_V_PADDING, LINE_H_PADDING); 19 | private final HBox box = new HBox(); 20 | 21 | /** 22 | * Accessible only to local package. 23 | */ 24 | LineContainer() { 25 | box.setAlignment(Pos.CENTER_LEFT); 26 | box.setPadding(PADDING); 27 | getChildren().add(box); 28 | } 29 | 30 | /** 31 | * @param child 32 | * Child to add spanning horizontally in the container. 33 | * Appends to the left. 34 | */ 35 | public void addHorizontal(Node child) { 36 | box.getChildren().add(child); 37 | } 38 | 39 | /** 40 | * @param child 41 | * Child to add on top of the container. 42 | * This refers to Z-indexing, not north-south verticality. 43 | */ 44 | public void addTopLayer(Node child) { 45 | getChildren().add(child); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/control/richtext/linegraphics/LineGraphicFactory.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.control.richtext.linegraphics; 2 | 3 | import com.tyron.code.desktop.ui.control.richtext.EditorComponent; 4 | import com.tyron.code.desktop.ui.control.richtext.bracket.BracketMatchGraphicFactory; 5 | import com.tyron.code.desktop.ui.control.richtext.problem.ProblemGraphicFactory; 6 | import org.jetbrains.annotations.NotNull; 7 | import javafx.scene.Node; 8 | 9 | 10 | import java.util.function.IntFunction; 11 | 12 | /** 13 | * RichTextFX only uses {@link IntFunction} for graphics factories. We want to create our own system where 14 | * any number of graphic factories can be added, and the result will always be consistently displayed. 15 | *
16 | * To facilitate this, we have this type which also is comparable to other instances of the same type. 17 | * Each graphic factory has a {@link #priority()} which defines its placement in the {@link RootLineGraphicFactory}. 18 | * Lower values appear first. 19 | * 20 | * @author Matt Coley 21 | * @see AbstractLineGraphicFactory Base implementation of this type. 22 | * @see RootLineGraphicFactory The root implementation which managed displaying other {@link LineGraphicFactory} instances in order. 23 | */ 24 | public interface LineGraphicFactory extends EditorComponent, Comparable { 25 | /** 26 | * Priority for {@link LineNumberFactory}. 27 | */ 28 | int P_LINE_NUMBERS = 0; 29 | /** 30 | * Priority for {@link ProblemGraphicFactory}. 31 | */ 32 | int P_LINE_PROBLEMS = 100; 33 | /** 34 | * Priority for {@link BracketMatchGraphicFactory}. 35 | */ 36 | int P_BRACKET_MATCH = 1000; 37 | 38 | /** 39 | * @return Order priority for sorting in {@link RootLineGraphicFactory}. Lower values appear first. 40 | */ 41 | int priority(); 42 | 43 | /** 44 | * @param container 45 | * Container to add nodes to if a graphic needs to be generated. 46 | * Use {@link LineContainer#addHorizontal(Node)} and {@link LineContainer#addTopLayer(Node)}. 47 | * @param paragraph 48 | * Current paragraph index. 49 | * Line would be {@code paragraph + 1}. 50 | */ 51 | void apply(@NotNull LineContainer container, int paragraph); 52 | 53 | @Override 54 | default int compareTo(LineGraphicFactory o) { 55 | return Integer.compare(priority(), o.priority()); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/control/richtext/linegraphics/LineNumberFactory.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.control.richtext.linegraphics; 2 | 3 | import com.tyron.code.desktop.ui.control.richtext.Editor; 4 | import com.tyron.code.project.util.StringUtil; 5 | import org.jetbrains.annotations.NotNull; 6 | import javafx.scene.control.Label; 7 | import javafx.scene.layout.HBox; 8 | import javafx.scene.layout.Priority; 9 | import org.fxmisc.richtext.CodeArea; 10 | 11 | /** 12 | * Graphic factory to draw line numbers. 13 | * 14 | * @author Matt Coley 15 | */ 16 | public class LineNumberFactory extends AbstractLineGraphicFactory { 17 | private CodeArea codeArea; 18 | 19 | /** 20 | * New line number factory. 21 | */ 22 | public LineNumberFactory() { 23 | super(P_LINE_NUMBERS); 24 | } 25 | 26 | @Override 27 | public void install(@NotNull Editor editor) { 28 | codeArea = editor.getCodeArea(); 29 | } 30 | 31 | @Override 32 | public void uninstall(@NotNull Editor editor) { 33 | codeArea = null; 34 | } 35 | 36 | @Override 37 | public void apply(@NotNull LineContainer container, int paragraph) { 38 | if (codeArea == null) return; 39 | 40 | Label label = new Label(format(paragraph + 1, computeDigits(codeArea.getParagraphs().size()))); 41 | HBox.setHgrow(label, Priority.ALWAYS); 42 | container.addHorizontal(label); 43 | } 44 | 45 | private static String format(int line, int digits) { 46 | return String.format(StringUtil.fillLeft(digits, " ", String.valueOf(line))); 47 | } 48 | 49 | private static int computeDigits(int size) { 50 | return (int) Math.floor(Math.log10(size)) + 1; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/control/richtext/problem/ProblemInvalidationListener.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.control.richtext.problem; 2 | 3 | /** 4 | * Listener to receive updates when any change occurs in a {@link ProblemTracking}'s tracked problem map. 5 | * 6 | * @author Matt Coley 7 | */ 8 | public interface ProblemInvalidationListener { 9 | /** 10 | * Called when any changes to tracked problems occurs. 11 | */ 12 | void onProblemInvalidation(); 13 | } 14 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/control/richtext/problem/ProblemLevel.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.control.richtext.problem; 2 | 3 | /** 4 | * Severity level of a problem. 5 | * 6 | * @author Matt Coley 7 | */ 8 | public enum ProblemLevel { 9 | DEBUG, 10 | INFO, 11 | WARN, 12 | ERROR 13 | } 14 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/control/richtext/problem/ProblemPhase.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.control.richtext.problem; 2 | 3 | /** 4 | * Phase in logic where the problem occurred. 5 | * 6 | * @author Matt Coley 7 | */ 8 | public enum ProblemPhase { 9 | /** 10 | * Occurs prior to 'building' the content. 11 | *

    12 | *
  • For Java, this would be analyzing the syntax and ensuring it is complaint, before attempting to compile.
  • 13 | *
  • For bytecode, this would be building the AST model.
  • 14 | *
15 | */ 16 | LINT, 17 | /** 18 | * Occurs while 'building' the content. 19 | *
    20 | *
  • For Java, this would be logic within {@code javac} handling failures.
  • 21 | *
  • For bytecode, this would be converting the AST model to binary form.
  • 22 | *
23 | */ 24 | BUILD, 25 | /** 26 | * Occurs after content was built. 27 | */ 28 | POST_PROCESS 29 | } 30 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/control/richtext/source/CompletionProvider.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.control.richtext.source; 2 | 3 | import com.tyron.code.desktop.ui.control.richtext.Editor; 4 | import com.tyron.code.java.completion.CompletionResult; 5 | 6 | import java.util.List; 7 | 8 | public interface CompletionProvider { 9 | CompletionResult getCompletionSuggestions(Editor editor); 10 | } 11 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/control/richtext/syntax/RegexRule.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.control.richtext.syntax; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Regex rule data type. 9 | * 10 | * @param name 11 | * Name of rule. Must not contain any whitespaces or special characters. 12 | * @param regex 13 | * Regex pattern. 14 | * @param classes 15 | * Style classes to apply to matched ranges within a {@link SyntaxHighlighter}. 16 | * @param subRules 17 | * Sub-rules which can be used to create sub-matches within ranges matched by this rule. 18 | * @param backtrackMark 19 | * Used for rules that are variable length. Indicates the start text of such matches. 20 | * See {@link RegexSyntaxHighlighter#expandRange(String, int, int)} for usage. 21 | * @param advanceMark 22 | * Used for rules that are variable length. Indicates the end text of such matches. 23 | * See {@link RegexSyntaxHighlighter#expandRange(String, int, int)} for usage. 24 | * 25 | * @author Matt Coley 26 | * @see RegexSyntaxHighlighter 27 | */ 28 | public record RegexRule(@JsonProperty("name") String name, 29 | @JsonProperty("regex") String regex, 30 | @JsonProperty("classes") List classes, 31 | @JsonProperty("sub-rules") List subRules, 32 | @JsonProperty("backtrack-mark") String backtrackMark, 33 | @JsonProperty("advance-mark") String advanceMark) { 34 | } 35 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/control/richtext/syntax/StyleResult.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.control.richtext.syntax; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.fxmisc.richtext.model.StyleSpans; 5 | 6 | import java.util.Collection; 7 | 8 | /** 9 | * Wrapper of created style-spans and starting position. 10 | * 11 | * @author Matt Coley 12 | */ 13 | public record StyleResult(@NotNull StyleSpans> spans, int position) { 14 | } 15 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/control/tree/FileTreeItem.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.control.tree; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | 8 | public class FileTreeItem extends FilterableTreeItem implements Comparable{ 9 | 10 | private final Path path; 11 | 12 | public FileTreeItem(Path path) { 13 | this.path = path; 14 | setValue(path); 15 | } 16 | 17 | public Path getPath() { 18 | return path; 19 | } 20 | 21 | @Override 22 | public int compareTo(@NotNull FileTreeItem fileTreeItem) { 23 | // display directories first 24 | if (Files.isDirectory(path) && !Files.isDirectory(fileTreeItem.getPath())) { 25 | return -1; 26 | } 27 | if (!Files.isDirectory(path) && Files.isDirectory(fileTreeItem.getPath())) { 28 | return 1; 29 | } 30 | 31 | 32 | 33 | return path.compareTo(fileTreeItem.getPath()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/control/tree/FileTreeItemBuilder.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.control.tree; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.*; 5 | import java.nio.file.attribute.BasicFileAttributes; 6 | 7 | public class FileTreeItemBuilder { 8 | 9 | public static void main(String[] args) throws IOException { 10 | Path path = Paths.get("/home/tyronscott/IdeaProjects/CodeAssistCompletions/deskptop-test"); 11 | FileTreeItem build = build(path); 12 | System.out.println(); 13 | } 14 | 15 | public static FileTreeItem build(Path root) throws IOException { 16 | FileVisitor visitor = new FileVisitor(); 17 | Files.walkFileTree(root, visitor); 18 | return visitor.getRoot(); 19 | } 20 | 21 | private static class FileVisitor extends SimpleFileVisitor { 22 | private FileTreeItem current; 23 | private FileTreeItem root; 24 | 25 | @Override 26 | public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { 27 | if (current == null) { 28 | current = new FileTreeItem(dir); 29 | root = current; 30 | } else { 31 | FileTreeItem item = new FileTreeItem(dir); 32 | current.addAndSortChild(item); 33 | current = item; 34 | } 35 | return FileVisitResult.CONTINUE; 36 | } 37 | 38 | @Override 39 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 40 | FileTreeItem item = new FileTreeItem(file); 41 | current.addAndSortChild(item); 42 | return FileVisitResult.CONTINUE; 43 | } 44 | 45 | @Override 46 | public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 47 | current = (FileTreeItem) current.getParent(); 48 | return FileVisitResult.CONTINUE; 49 | } 50 | 51 | public FileTreeItem getRoot() { 52 | return root; 53 | } 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/control/tree/TreeItems.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.control.tree; 2 | 3 | import com.tyron.code.project.util.Unchecked; 4 | import org.jetbrains.annotations.NotNull; 5 | import javafx.scene.control.MultipleSelectionModel; 6 | import javafx.scene.control.TreeItem; 7 | import javafx.scene.control.TreeView; 8 | 9 | /** 10 | * Utilities for {@link TreeItem} types. 11 | * 12 | * @author Matt Coley 13 | */ 14 | public class TreeItems { 15 | /** 16 | * Expand all parents to this item. 17 | */ 18 | public static void expandParents(@NotNull TreeItem item) { 19 | while ((item = item.getParent()) != null) 20 | item.setExpanded(true); 21 | } 22 | 23 | /** 24 | * Opens children recursively as long as only as there is only a path of single children. 25 | * 26 | * @param item 27 | * Item to recursively open. 28 | */ 29 | public static void recurseOpen(@NotNull TreeItem item) { 30 | item.setExpanded(true); 31 | if (item.getChildren().size() == 1) 32 | recurseOpen(item.getChildren().get(0)); 33 | } 34 | 35 | /** 36 | * Closes children recursively. 37 | * 38 | * @param tree 39 | * Tree containing the item. 40 | * @param item 41 | * Item to recursively close. 42 | */ 43 | public static void recurseClose(@NotNull TreeView tree, @NotNull TreeItem item) { 44 | MultipleSelectionModel> selectionModel = Unchecked.cast(tree.getSelectionModel()); 45 | boolean wasSelected = selectionModel.getSelectedItem() == item; 46 | recurseClose(item); 47 | 48 | // For some reason closing a tree item screws with selection in weird ways. 49 | // So we'll re-select the item afterward if it was previously the selected item. 50 | if (wasSelected) selectionModel.select(item); 51 | } 52 | 53 | /** 54 | * Closes children recursively. 55 | * 56 | * @param item 57 | * Item to recursively close. 58 | */ 59 | private static void recurseClose(@NotNull TreeItem item) { 60 | if (!item.isLeaf() && item.isExpanded()) { 61 | item.setExpanded(false); 62 | item.getChildren().forEach(TreeItems::recurseClose); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/docking/DockingRegionFactory.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.docking; 2 | 3 | import com.panemu.tiwulfx.control.dock.DetachableTabPane; 4 | import com.panemu.tiwulfx.control.dock.DetachableTabPaneFactory; 5 | import javafx.scene.Scene; 6 | import org.jetbrains.annotations.NotNull; 7 | import javafx.scene.control.TabPane; 8 | import javafx.stage.WindowEvent; 9 | 10 | /** 11 | * Factory for {@link DockingRegion}. 12 | * Handles callbacks back to {@link DockingManager}. 13 | * 14 | * @author Matt Coley 15 | */ 16 | public class DockingRegionFactory extends DetachableTabPaneFactory { 17 | private static final int SCENE_WIDTH = 600; 18 | private static final int SCENE_HEIGHT = 400; 19 | private final DockingManager manager; 20 | 21 | /** 22 | * @param manager Associated docking manager. 23 | */ 24 | public DockingRegionFactory(@NotNull DockingManager manager) { 25 | this.manager = manager; 26 | } 27 | 28 | @Override 29 | protected DockingRegion create() { 30 | return new DockingRegion(manager); 31 | } 32 | 33 | @Override 34 | protected void init(DetachableTabPane newTabPane) { 35 | newTabPane.setSceneFactory(param -> new Scene(param, SCENE_WIDTH, SCENE_HEIGHT)); 36 | newTabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.ALL_TABS); 37 | newTabPane.setCloseIfEmpty(true); 38 | newTabPane.setDetachableTabPaneFactory(this); 39 | 40 | // Register closure callbacks 41 | if (newTabPane instanceof DockingRegion region) { 42 | region.setOnRemove(pane -> { 43 | // We can ignore the return value of 'onRegionClose' since by this point 44 | // the docking region should have no tabs. That is why its being removed. 45 | if (region.isCloseIfEmpty()) { 46 | manager.onRegionClose(region); 47 | } 48 | }); 49 | 50 | // When the containing window is closed, trigger the region closure callback. 51 | region.sceneProperty().addListener((obS, oldScene, newScene) -> { 52 | if (newScene != null) { 53 | newScene.windowProperty().addListener((obW, oldWindow, newWindow) -> 54 | newWindow.addEventFilter(WindowEvent.WINDOW_CLOSE_REQUEST, e -> { 55 | if (!manager.onRegionClose(region)) { 56 | e.consume(); 57 | } 58 | })); 59 | } 60 | }); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/docking/listener/TabClosureListener.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.docking.listener; 2 | 3 | import com.tyron.code.desktop.ui.docking.DockingRegion; 4 | import com.tyron.code.desktop.ui.docking.DockingTab; 5 | 6 | /** 7 | * Listener for {@link DockingTab} being closed. 8 | * 9 | * @author Matt Coley 10 | */ 11 | public interface TabClosureListener { 12 | /** 13 | * @param parent 14 | * Parent region the tab belonged to. 15 | * @param tab 16 | * Tab closed. 17 | */ 18 | void onClose(DockingRegion parent, DockingTab tab); 19 | } -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/docking/listener/TabCreationListener.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.docking.listener; 2 | 3 | import com.tyron.code.desktop.ui.docking.DockingRegion; 4 | import com.tyron.code.desktop.ui.docking.DockingTab; 5 | 6 | public interface TabCreationListener { 7 | void onCreate(DockingRegion parent, DockingTab tab); 8 | } 9 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/docking/listener/TabMoveListener.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.docking.listener; 2 | 3 | import com.tyron.code.desktop.ui.docking.DockingRegion; 4 | import com.tyron.code.desktop.ui.docking.DockingTab; 5 | 6 | public interface TabMoveListener { 7 | void onMove(DockingRegion oldRegion, DockingRegion newRegion, DockingTab tab); 8 | } 9 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/docking/listener/TabSelectionListener.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.docking.listener; 2 | 3 | import com.tyron.code.desktop.ui.docking.DockingRegion; 4 | import com.tyron.code.desktop.ui.docking.DockingTab; 5 | 6 | public interface TabSelectionListener { 7 | void onSelection(DockingRegion parent, DockingTab tab); 8 | } 9 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/pane/WelcomePane.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.pane; 2 | 3 | import javafx.scene.control.Label; 4 | import javafx.scene.layout.BorderPane; 5 | 6 | public class WelcomePane extends BorderPane { 7 | 8 | public WelcomePane() { 9 | setCenter(new Label("Welcome")); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/pane/WorkspaceExplorerPane.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.pane; 2 | 3 | import com.tyron.code.desktop.ui.control.tree.WorkspaceTree; 4 | import com.tyron.code.project.Workspace; 5 | import javafx.scene.layout.BorderPane; 6 | 7 | public class WorkspaceExplorerPane extends BorderPane { 8 | 9 | private final WorkspaceTree workspaceTree; 10 | 11 | public WorkspaceExplorerPane(Workspace workspace) { 12 | workspaceTree = new WorkspaceTree(); 13 | 14 | setCenter(workspaceTree); 15 | 16 | workspaceTree.createWorkspaceRoot(workspace); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/pane/WorkspaceInformationPane.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.pane; 2 | 3 | import atlantafx.base.theme.Styles; 4 | import com.tyron.code.project.Workspace; 5 | import com.tyron.code.project.graph.GraphPrinter; 6 | import com.tyron.code.project.model.module.RootModule; 7 | import javafx.geometry.Insets; 8 | import javafx.scene.control.Label; 9 | import javafx.scene.control.ScrollPane; 10 | import javafx.scene.layout.BorderPane; 11 | import javafx.scene.layout.ColumnConstraints; 12 | import javafx.scene.layout.GridPane; 13 | import javafx.scene.layout.VBox; 14 | 15 | public class WorkspaceInformationPane extends BorderPane { 16 | 17 | public WorkspaceInformationPane(Workspace workspace) { 18 | Grid content = new Grid(); 19 | content.setPadding(new Insets(5)); 20 | content.prefWidthProperty().bind(widthProperty()); 21 | ScrollPane scroll = new ScrollPane(content); 22 | setCenter(scroll); 23 | getStyleClass().add("background"); 24 | 25 | RootModule module = workspace.getModule(); 26 | Label title = new Label(module.getName()); 27 | Label subtitle = new Label(String.format("%d included modules", module.getIncludedModules().size())); 28 | title.getStyleClass().add(Styles.TITLE_4); 29 | subtitle.getStyleClass().add(Styles.TEXT_SUBTLE); 30 | VBox wrapper = new VBox(title, subtitle); 31 | content.add(wrapper, 0, content.getRowCount(), 2, 2); 32 | } 33 | 34 | private static class Grid extends GridPane { 35 | private Grid() { 36 | setVgap(5); 37 | setHgap(5); 38 | ColumnConstraints column1 = new ColumnConstraints(); 39 | ColumnConstraints column2 = new ColumnConstraints(); 40 | column1.setPercentWidth(25); 41 | column2.setPercentWidth(75); 42 | getColumnConstraints().addAll(column1, column2); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/pane/WorkspaceRootPane.kt: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.pane 2 | 3 | import com.panemu.tiwulfx.control.dock.DetachableTab 4 | import com.tyron.code.desktop.ui.control.FontIconView 5 | import com.tyron.code.desktop.ui.control.richtext.Editor 6 | import com.tyron.code.desktop.ui.control.richtext.source.CompletionProvider 7 | import com.tyron.code.desktop.ui.docking.DockingManager 8 | import com.tyron.code.desktop.ui.docking.DockingRegion 9 | import com.tyron.code.java.analysis.Analyzer 10 | import com.tyron.code.project.Workspace 11 | import com.tyron.code.project.file.FileManager 12 | import com.tyron.code.project.model.module.JavaModule 13 | import javafx.scene.control.SplitPane 14 | import javafx.scene.layout.BorderPane 15 | import org.koin.java.KoinJavaComponent 16 | import org.koin.mp.KoinPlatform.getKoin 17 | import org.kordamp.ikonli.carbonicons.CarbonIcons 18 | import java.util.concurrent.atomic.AtomicReference 19 | 20 | 21 | class WorkspaceRootPane(private val dockingManager: DockingManager, workspace: Workspace) : BorderPane() { 22 | 23 | private val lastTreeRegion = AtomicReference() 24 | private val lastPrimaryRegion = AtomicReference() 25 | 26 | init { 27 | styleClass.add("bg-inset") 28 | 29 | println(workspace) 30 | workspace.addWorkspaceStateChangeListener { 31 | if (it == Workspace.State.INITIALIZED) { 32 | handleWorkspaceInitialized(workspace) 33 | } 34 | } 35 | } 36 | 37 | private fun handleWorkspaceInitialized(workspace: Workspace) { 38 | val dockTree: DockingRegion = dockingManager.newRegion() 39 | val dockPrimary: DockingRegion = dockingManager.primaryRegion 40 | 41 | createWorkspaceExplorerTab(dockTree, workspace); 42 | createPrimaryTab(dockPrimary, workspace); 43 | 44 | val split = SplitPane(dockTree, dockPrimary) 45 | SplitPane.setResizableWithParent(dockTree, false) 46 | split.setDividerPositions(0.333) 47 | center = split 48 | 49 | 50 | lastTreeRegion.set(dockTree); 51 | lastPrimaryRegion.set(dockPrimary); 52 | } 53 | 54 | private fun createWorkspaceExplorerTab(region: DockingRegion, workspace: Workspace) { 55 | val tab = region.createTab("Workspace", WorkspaceExplorerPane(workspace)) 56 | tab.graphic = FontIconView(CarbonIcons.TREE_VIEW) 57 | tab.isClosable = false 58 | tab.isDetachable = false 59 | } 60 | 61 | private fun createPrimaryTab(region: DockingRegion, workspace: Workspace) { 62 | val infoTab: DetachableTab = region.createTab("Workspace Info", WorkspaceInformationPane(workspace)) 63 | infoTab.graphic = FontIconView(CarbonIcons.INFORMATION) 64 | } 65 | } -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/pane/editing/SourcePane.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.pane.editing; 2 | 3 | import com.tyron.code.desktop.services.navigation.SourceFileNavigable; 4 | import com.tyron.code.desktop.services.navigation.UpdatableNavigable; 5 | import com.tyron.code.path.PathNode; 6 | import com.tyron.code.path.impl.SourceClassPathNode; 7 | import com.tyron.code.project.model.module.JavaModule; 8 | import com.tyron.code.project.model.module.Module; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | public class SourcePane extends AbstractContentPane implements SourceFileNavigable { 12 | private final Module module; 13 | 14 | public SourcePane(Module module) { 15 | this.module = module; 16 | } 17 | 18 | @NotNull 19 | @Override 20 | public SourceClassPathNode getPath() { 21 | return path; 22 | } 23 | 24 | @Override 25 | public void onUpdatePath(@NotNull PathNode path) { 26 | if (path instanceof SourceClassPathNode sourceClassPathNode) { 27 | this.path = sourceClassPathNode; 28 | pathUpdateListeners.forEach(listener -> listener.accept(sourceClassPathNode)); 29 | 30 | if (getCenter() == null) { 31 | generateDisplay(); 32 | } 33 | 34 | // Notify children of change. 35 | getNavigableChildren().forEach(child -> { 36 | if (child instanceof UpdatableNavigable updatable) { 37 | updatable.onUpdatePath(path); 38 | } 39 | }); 40 | } 41 | } 42 | 43 | @Override 44 | protected void generateDisplay() { 45 | setDisplay(new JavaEditorPane(((JavaModule) module))); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/ui/pane/editing/TextPane.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.ui.pane.editing; 2 | 3 | import com.tyron.code.desktop.ui.control.richtext.Editor; 4 | import com.tyron.code.desktop.ui.control.richtext.bracket.BracketMatchGraphicFactory; 5 | import com.tyron.code.desktop.ui.control.richtext.bracket.SelectedBracketTracking; 6 | import com.tyron.code.desktop.ui.control.richtext.problem.ProblemGraphicFactory; 7 | import javafx.scene.layout.BorderPane; 8 | 9 | import java.util.concurrent.atomic.AtomicBoolean; 10 | 11 | public class TextPane extends BorderPane { 12 | 13 | protected final AtomicBoolean updateLock = new AtomicBoolean(); 14 | protected final Editor editor; 15 | 16 | public TextPane() { 17 | 18 | // Configure the editor 19 | editor = new Editor(); 20 | editor.setSelectedBracketTracking(new SelectedBracketTracking()); 21 | editor.getRootLineGraphicFactory().addLineGraphicFactories( 22 | new BracketMatchGraphicFactory(), 23 | new ProblemGraphicFactory() 24 | ); 25 | 26 | setCenter(editor); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/util/ClassLoaderInternals.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.util; 2 | 3 | import com.tyron.code.project.util.ReflectUtil; 4 | 5 | import java.lang.reflect.Field; 6 | import java.lang.reflect.Method; 7 | import java.net.URL; 8 | import java.util.Collections; 9 | import java.util.List; 10 | 11 | /** 12 | * Hacky code to get internal classloader state. 13 | * 14 | * @author xDark 15 | * @author Matt Coley 16 | */ 17 | public class ClassLoaderInternals { 18 | /** 19 | * @return {@link jdk.internal.loader.URLClassPath} instance. 20 | * @throws ReflectiveOperationException When the internals change and the reflective look-ups fail. 21 | */ 22 | public static Object getUcp() throws ReflectiveOperationException { 23 | // Fetch UCP of application's ClassLoader 24 | // - ((ClassLoaders.AppClassLoader) ClassLoaders.appClassLoader()).ucp 25 | Class clsClassLoaders = Class.forName("jdk.internal.loader.ClassLoaders"); 26 | Object appClassLoader = ReflectUtil.quietInvoke(clsClassLoaders, null, "appClassLoader", 27 | new Class[0], new Object[0]); 28 | Class ucpOwner = appClassLoader.getClass(); 29 | 30 | // Field removed in 16, but still exists in parent class "BuiltinClassLoader" 31 | if (JavaVersion.get() >= 16) { 32 | ucpOwner = ucpOwner.getSuperclass(); 33 | } 34 | 35 | Field fieldUCP = ReflectUtil.getDeclaredField(ucpOwner, "ucp"); 36 | return fieldUCP.get(appClassLoader); 37 | } 38 | 39 | /** 40 | * @param ucp See {@link #getUcp()}. 41 | * @return The contents of the UCP. 42 | * @throws ReflectiveOperationException When the internals change and the reflective look-ups fail. 43 | */ 44 | @SuppressWarnings("unchecked") 45 | public static List getUcpPathList(Object ucp) throws ReflectiveOperationException { 46 | if (ucp == null) 47 | return Collections.emptyList(); 48 | Class ucpClass = ucp.getClass(); 49 | Field path = ReflectUtil.getDeclaredField(ucpClass, "path"); 50 | return (List) path.get(ucp); 51 | } 52 | 53 | /** 54 | * @param ucp See {@link #getUcp()}. 55 | * @param url URL to add to the search path for directories and Jar files of the UCP. 56 | * @throws ReflectiveOperationException When the internals change and the reflective look-ups fail. 57 | */ 58 | public static void appendToUcpPath(Object ucp, URL url) throws ReflectiveOperationException { 59 | if (ucp == null) { 60 | return; 61 | } 62 | Class ucpClass = ucp.getClass(); 63 | Method addURL = ReflectUtil.getDeclaredMethod(ucpClass, "addURL", URL.class); 64 | addURL.invoke(ucp, url); 65 | } 66 | } -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/util/FxThreadUtils.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.util; 2 | 3 | import javafx.application.Platform; 4 | 5 | import java.util.concurrent.CompletableFuture; 6 | import java.util.concurrent.ScheduledExecutorService; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | import static com.tyron.code.project.util.ThreadPoolFactory.newScheduledThreadPool; 10 | 11 | public class FxThreadUtils { 12 | private static final ScheduledExecutorService scheduledService = newScheduledThreadPool("CodeAssist misc"); 13 | 14 | public static void run(Runnable runnable) { 15 | if (Platform.isFxApplicationThread()) { 16 | runnable.run(); 17 | } else { 18 | Platform.runLater(runnable); 19 | } 20 | } 21 | 22 | public static void delayedRun(long delayMs, Runnable action) { 23 | CompletableFuture future = new CompletableFuture<>(); 24 | scheduledService.schedule(() -> { 25 | try { 26 | action.run(); 27 | future.complete(null); 28 | } catch (Throwable t) { 29 | future.completeExceptionally(t); 30 | } 31 | }, delayMs, TimeUnit.MILLISECONDS); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/util/IntRange.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.util; 2 | 3 | /** 4 | * Representation of a range. 5 | * 6 | * @param start 7 | * Start value. 8 | * @param end 9 | * End value. 10 | * 11 | * @author Matt Coley 12 | */ 13 | public record IntRange(int start, int end) implements Comparable { 14 | /** 15 | * Constant range of 0 to 0. 16 | */ 17 | public static final IntRange EMPTY = new IntRange(0, 0); 18 | 19 | /** 20 | * @return Length of the range. 21 | */ 22 | public int length() { 23 | return end() - start(); 24 | } 25 | 26 | /** 27 | * @return {@code true} when {@link #length()} is {@code 0}. 28 | */ 29 | public boolean empty() { 30 | return length() == 0; 31 | } 32 | 33 | /** 34 | * @param startInclusive 35 | * Flag to indicate inclusive range check against {@link #start()}. 36 | * @param endInclusive 37 | * Flag to indicate inclusive range check against {@link #end()}. 38 | * @param value 39 | * Position to check. 40 | * 41 | * @return {@code true} when the position is within this range. 42 | */ 43 | public boolean isBetween(boolean startInclusive, boolean endInclusive, int value) { 44 | if (startInclusive ? value >= start : value > start) 45 | return endInclusive ? value <= end : value < end; 46 | return false; 47 | } 48 | 49 | /** 50 | * Shorthand for {@code text.substring(range.start(), range.end())}. 51 | * 52 | * @param text 53 | * Text to cut section out of. 54 | * 55 | * @return Section of text of this range. 56 | */ 57 | public String sectionOfText(String text) { 58 | return text.substring(start, end); 59 | } 60 | 61 | /** 62 | * @param length 63 | * Extension size. 64 | * 65 | * @return Copy of range, with end offset forwards by length. 66 | */ 67 | public IntRange extendForwards(int length) { 68 | return new IntRange(start(), end() + length); 69 | } 70 | 71 | /** 72 | * @param length 73 | * Extension size. 74 | * 75 | * @return Copy of range, with start offset backwards by length. 76 | */ 77 | public IntRange extendBackwards(int length) { 78 | return new IntRange(Math.max(0, start() - length), end()); 79 | } 80 | 81 | @Override 82 | public int compareTo(IntRange o) { 83 | return Integer.compare(start, o.start); 84 | } 85 | } -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/util/InternalPath.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.util; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.net.URL; 6 | 7 | /** 8 | * Wrapper for a resource to assist in handling of mixed classpath and non-classpath items. 9 | * 10 | * @author Matt Coley 11 | */ 12 | public class InternalPath { 13 | private final String path; 14 | private final boolean internal; 15 | 16 | /** 17 | * Create a resource wrapper. 18 | * 19 | * @param path 20 | * Path to resource. 21 | * @param internal 22 | * {@code true} if the resource is in the classpath, {@code false} if it is external. 23 | */ 24 | private InternalPath(String path, boolean internal) { 25 | this.path = path; 26 | this.internal = internal; 27 | } 28 | 29 | /** 30 | * Create an internal resource. 31 | * 32 | * @param path 33 | * Path to resource. 34 | * 35 | * @return Internal resource wrapper. 36 | */ 37 | public static InternalPath internal(String path) { 38 | return new InternalPath(path, true); 39 | } 40 | 41 | /** 42 | * Create an external resource. 43 | * 44 | * @param path 45 | * Path to resource. 46 | * 47 | * @return External resource wrapper. 48 | */ 49 | public static InternalPath external(String path) { 50 | return new InternalPath(path, false); 51 | } 52 | 53 | /** 54 | * @return Resource path. 55 | */ 56 | public String getPath() { 57 | return path; 58 | } 59 | 60 | /** 61 | * @return {@code true} if the resource is in the classpath, {@code false} if it is external. 62 | */ 63 | public boolean isInternal() { 64 | return internal; 65 | } 66 | 67 | /** 68 | * @return Name of resource file. 69 | */ 70 | public String getFileName() { 71 | String name = getPath(); 72 | int sep = name.lastIndexOf('/'); 73 | if (sep > 0) 74 | name = name.substring(sep + 1); 75 | return name; 76 | } 77 | 78 | /** 79 | * Creates a URL path to resource, 80 | * 81 | * @return URL path to resource. 82 | * 83 | * @throws IOException 84 | * When the path cannot be created. 85 | */ 86 | public URL getURL() throws IOException { 87 | if (internal) { 88 | return ClassLoader.getSystemClassLoader().getResource(getPath()); 89 | } else { 90 | return new File(getPath()).toURI().toURL(); 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/util/JavaVersion.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.util; 2 | 3 | import com.tyron.code.logging.Logging; 4 | import org.slf4j.Logger; 5 | 6 | /** 7 | * Supported Java version of the current JVM. 8 | * 9 | * @author Matt Coley 10 | */ 11 | public class JavaVersion { 12 | /** 13 | * The offset from which a version and the version constant value is. For example, Java 8 is 52 (44 + 8). 14 | */ 15 | public static final int VERSION_OFFSET = 44; 16 | private static final String JAVA_CLASS_VERSION = "java.class.version"; 17 | private static final String JAVA_VM_SPEC_VERSION = "java.vm.specification.version"; 18 | private static final int FALLBACK_VERSION = 11; 19 | private static final Logger logger = Logging.get(JavaVersion.class); 20 | private static int version = -1; 21 | 22 | /** 23 | * Get the supported Java version of the current JVM. 24 | * 25 | * @return Version. 26 | */ 27 | public static int get() { 28 | if (version < 0) { 29 | // Check for class version 30 | String property = System.getProperty(JAVA_CLASS_VERSION, ""); 31 | if (!property.isEmpty()) 32 | return version = (int) (Float.parseFloat(property) - VERSION_OFFSET); 33 | 34 | // Odd, not found. Try the spec version 35 | logger.warn("Property '{}' not found, using '{}' as fallback", 36 | JAVA_CLASS_VERSION, 37 | JAVA_VM_SPEC_VERSION 38 | ); 39 | property = System.getProperty(JAVA_VM_SPEC_VERSION, ""); 40 | if (property.contains(".")) 41 | return version = (int) Float.parseFloat(property.substring(property.indexOf('.') + 1)); 42 | else if (!property.isEmpty()) 43 | return version = Integer.parseInt(property); 44 | logger.warn("Property '{}' not found, using '{}' as fallback", 45 | JAVA_VM_SPEC_VERSION, FALLBACK_VERSION 46 | ); 47 | 48 | // Very odd 49 | return FALLBACK_VERSION; 50 | } 51 | return version; 52 | } 53 | 54 | /** 55 | * Adapts the class file spec version to the familiar release versions. 56 | * For example, 52 becomes Java 8. 57 | * 58 | * @param version 59 | * Class file version, such as from {@link JvmClassInfo#getVersion()}. 60 | * 61 | * @return Version. 62 | */ 63 | public static int adaptFromClassFileVersion(int version) { 64 | return version - VERSION_OFFSET; 65 | } 66 | } -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/util/SynchronizedSimpleStringProperty.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.util; 2 | 3 | import javafx.beans.InvalidationListener; 4 | import javafx.beans.property.Property; 5 | import javafx.beans.property.SimpleStringProperty; 6 | import javafx.beans.value.ChangeListener; 7 | import javafx.beans.value.ObservableValue; 8 | import javafx.util.StringConverter; 9 | 10 | import java.text.Format; 11 | 12 | /** 13 | * Synchronized implementation of {@link SimpleStringProperty}. 14 | * 15 | * @author xDark 16 | */ 17 | public class SynchronizedSimpleStringProperty extends SimpleStringProperty { 18 | /** 19 | * @param initialValue 20 | * Initial value. 21 | */ 22 | public SynchronizedSimpleStringProperty(String initialValue) { 23 | super(initialValue); 24 | } 25 | 26 | @Override 27 | public synchronized void addListener(ChangeListener listener) { 28 | super.addListener(listener); 29 | } 30 | 31 | @Override 32 | public synchronized void addListener(InvalidationListener listener) { 33 | super.addListener(listener); 34 | } 35 | 36 | @Override 37 | public synchronized void removeListener(ChangeListener listener) { 38 | super.removeListener(listener); 39 | } 40 | 41 | @Override 42 | public synchronized void removeListener(InvalidationListener listener) { 43 | super.removeListener(listener); 44 | } 45 | 46 | @Override 47 | public synchronized void bind(ObservableValue newObservable) { 48 | super.bind(newObservable); 49 | } 50 | 51 | @Override 52 | public synchronized void bindBidirectional(Property other) { 53 | super.bindBidirectional(other); 54 | } 55 | 56 | @Override 57 | public synchronized void bindBidirectional(Property other, Format format) { 58 | super.bindBidirectional(other, format); 59 | } 60 | 61 | @Override 62 | protected synchronized void invalidated() { 63 | super.invalidated(); 64 | } 65 | 66 | @Override 67 | public synchronized String get() { 68 | return super.get(); 69 | } 70 | 71 | @Override 72 | public synchronized void set(String newValue) { 73 | super.set(newValue); 74 | } 75 | 76 | @Override 77 | public synchronized void unbind() { 78 | super.unbind(); 79 | } 80 | 81 | @Override 82 | public synchronized void bindBidirectional(Property other, StringConverter converter) { 83 | super.bindBidirectional(other, converter); 84 | } 85 | 86 | @Override 87 | public synchronized void unbindBidirectional(Property other) { 88 | super.unbindBidirectional(other); 89 | } 90 | 91 | @Override 92 | public synchronized void unbindBidirectional(Object other) { 93 | super.unbindBidirectional(other); 94 | } 95 | } -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/util/SynchronizedStringBinding.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.util; 2 | 3 | import javafx.beans.InvalidationListener; 4 | import javafx.beans.binding.StringBinding; 5 | import javafx.beans.value.ChangeListener; 6 | 7 | /** 8 | * Synchronized implementation of {@link StringBinding}. 9 | * 10 | * @author xDark 11 | */ 12 | public abstract class SynchronizedStringBinding extends StringBinding { 13 | @Override 14 | public synchronized String getValue() { 15 | return super.getValue(); 16 | } 17 | 18 | @Override 19 | protected synchronized void onInvalidating() { 20 | super.onInvalidating(); 21 | } 22 | 23 | @Override 24 | public synchronized void addListener(ChangeListener listener) { 25 | super.addListener(listener); 26 | } 27 | 28 | @Override 29 | public synchronized void addListener(InvalidationListener listener) { 30 | super.addListener(listener); 31 | } 32 | 33 | @Override 34 | public synchronized void removeListener(ChangeListener listener) { 35 | super.removeListener(listener); 36 | } 37 | 38 | @Override 39 | public synchronized void removeListener(InvalidationListener listener) { 40 | super.removeListener(listener); 41 | } 42 | } -------------------------------------------------------------------------------- /deskptop-test/src/main/java/com/tyron/code/desktop/util/WorkspaceUtil.kt: -------------------------------------------------------------------------------- 1 | package com.tyron.code.desktop.util 2 | 3 | import com.tyron.code.project.Workspace 4 | import org.koin.core.component.getScopeId 5 | import org.koin.core.scope.get 6 | import org.koin.mp.KoinPlatform.getKoin 7 | 8 | object WorkspaceUtil { 9 | /** 10 | * Get the workspace scoped instance of the given class. 11 | */ 12 | @JvmStatic 13 | fun Workspace.getScoped(clazz : Class) : T { 14 | return getKoin().getScope(this.getScopeId()).get(clazz) 15 | } 16 | } -------------------------------------------------------------------------------- /deskptop-test/src/main/resources/fonts/JetBrainsMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyron12233/CodeAssist-v3/ac37cbace6848b9f2c9343fc7f3a5355da6572b6/deskptop-test/src/main/resources/fonts/JetBrainsMono-Bold.ttf -------------------------------------------------------------------------------- /deskptop-test/src/main/resources/fonts/JetBrainsMono-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyron12233/CodeAssist-v3/ac37cbace6848b9f2c9343fc7f3a5355da6572b6/deskptop-test/src/main/resources/fonts/JetBrainsMono-BoldItalic.ttf -------------------------------------------------------------------------------- /deskptop-test/src/main/resources/fonts/JetBrainsMono-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyron12233/CodeAssist-v3/ac37cbace6848b9f2c9343fc7f3a5355da6572b6/deskptop-test/src/main/resources/fonts/JetBrainsMono-Italic.ttf -------------------------------------------------------------------------------- /deskptop-test/src/main/resources/fonts/JetBrainsMono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyron12233/CodeAssist-v3/ac37cbace6848b9f2c9343fc7f3a5355da6572b6/deskptop-test/src/main/resources/fonts/JetBrainsMono.ttf -------------------------------------------------------------------------------- /deskptop-test/src/main/resources/icons/class/class.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyron12233/CodeAssist-v3/ac37cbace6848b9f2c9343fc7f3a5355da6572b6/deskptop-test/src/main/resources/icons/class/class.png -------------------------------------------------------------------------------- /deskptop-test/src/main/resources/icons/file/file-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyron12233/CodeAssist-v3/ac37cbace6848b9f2c9343fc7f3a5355da6572b6/deskptop-test/src/main/resources/icons/file/file-config.png -------------------------------------------------------------------------------- /deskptop-test/src/main/resources/icons/file/folder-module.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyron12233/CodeAssist-v3/ac37cbace6848b9f2c9343fc7f3a5355da6572b6/deskptop-test/src/main/resources/icons/file/folder-module.png -------------------------------------------------------------------------------- /deskptop-test/src/main/resources/icons/file/folder-package.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyron12233/CodeAssist-v3/ac37cbace6848b9f2c9343fc7f3a5355da6572b6/deskptop-test/src/main/resources/icons/file/folder-package.png -------------------------------------------------------------------------------- /deskptop-test/src/main/resources/icons/file/folder-source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyron12233/CodeAssist-v3/ac37cbace6848b9f2c9343fc7f3a5355da6572b6/deskptop-test/src/main/resources/icons/file/folder-source.png -------------------------------------------------------------------------------- /deskptop-test/src/main/resources/icons/file/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyron12233/CodeAssist-v3/ac37cbace6848b9f2c9343fc7f3a5355da6572b6/deskptop-test/src/main/resources/icons/file/folder.png -------------------------------------------------------------------------------- /deskptop-test/src/main/resources/icons/member/field.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyron12233/CodeAssist-v3/ac37cbace6848b9f2c9343fc7f3a5355da6572b6/deskptop-test/src/main/resources/icons/member/field.png -------------------------------------------------------------------------------- /deskptop-test/src/test/java/com/tyron/code/Test.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code; 2 | 3 | public class Test { 4 | 5 | public static void main(String[] args) { 6 | 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /deskptop-test/src/test/resources/TestProject/app/libs/guava-33.0.0-jre.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyron12233/CodeAssist-v3/ac37cbace6848b9f2c9343fc7f3a5355da6572b6/deskptop-test/src/test/resources/TestProject/app/libs/guava-33.0.0-jre.jar -------------------------------------------------------------------------------- /deskptop-test/src/test/resources/TestProject/app/project.toml: -------------------------------------------------------------------------------- 1 | name = "" 2 | moduleType = "JAVA" 3 | 4 | [[dependencies]] 5 | notation = "module2" 6 | type = "PROJECT" 7 | 8 | [[dependencies]] 9 | notation = "libs/guava-33.0.0-jre.jar" 10 | type = "FILE" 11 | -------------------------------------------------------------------------------- /deskptop-test/src/test/resources/TestProject/app/src/main/java/test/Main.java: -------------------------------------------------------------------------------- 1 | package test; 2 | class Main { 3 | public static void main(String[] args) { 4 | System.out.println("Hello World!"); 5 | Test test = new Test(); 6 | test.test(); 7 | } 8 | } -------------------------------------------------------------------------------- /deskptop-test/src/test/resources/TestProject/module2/project.toml: -------------------------------------------------------------------------------- 1 | name = "" 2 | moduleType = "JAVA" 3 | -------------------------------------------------------------------------------- /deskptop-test/src/test/resources/TestProject/module2/src/main/java/test/Test.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | public class Test { 4 | 5 | public static void staticMethod() { 6 | 7 | } 8 | 9 | public void test() { 10 | 11 | } 12 | } -------------------------------------------------------------------------------- /deskptop-test/src/test/resources/TestProject/project.toml: -------------------------------------------------------------------------------- 1 | name = "" 2 | 3 | includedModules = [ 4 | "app", 5 | "module2" 6 | ] 7 | 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyron12233/CodeAssist-v3/ac37cbace6848b9f2c9343fc7f3a5355da6572b6/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Dec 20 18:35:26 PST 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /javac/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-library") 3 | } 4 | 5 | dependencies { 6 | testImplementation(platform("org.junit:junit-bom:5.9.1")) 7 | testImplementation("org.junit.jupiter:junit-jupiter") 8 | 9 | api(files("libs/nb-javac-1.0-SNAPSHOT-all.jar")) 10 | } 11 | 12 | tasks.test { 13 | useJUnitPlatform() 14 | } -------------------------------------------------------------------------------- /javac/libs/nb-javac-1.0-SNAPSHOT-all.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyron12233/CodeAssist-v3/ac37cbace6848b9f2c9343fc7f3a5355da6572b6/javac/libs/nb-javac-1.0-SNAPSHOT-all.jar -------------------------------------------------------------------------------- /project-impl/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java") 3 | } 4 | 5 | 6 | dependencies { 7 | testImplementation(platform("org.junit:junit-bom:5.9.1")) 8 | testImplementation("org.junit.jupiter:junit-jupiter") 9 | 10 | implementation(files("libs/tomlconfig-0.2.4.jar")) 11 | implementation("com.google.guava:guava:31.0.1-android") 12 | implementation("org.slf4j:slf4j-api:2.0.10") 13 | implementation("org.jetbrains:annotations:24.1.0") 14 | implementation("org.ow2.asm:asm:9.6") 15 | 16 | 17 | 18 | implementation(project(":project")) 19 | } 20 | 21 | tasks.test { 22 | useJUnitPlatform() 23 | } -------------------------------------------------------------------------------- /project-impl/libs/tomlconfig-0.2.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyron12233/CodeAssist-v3/ac37cbace6848b9f2c9343fc7f3a5355da6572b6/project-impl/libs/tomlconfig-0.2.4.jar -------------------------------------------------------------------------------- /project-impl/src/main/java/com/tyron/code/info/FileInfoImpl.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.info; 2 | 3 | import com.tyron.code.info.builder.FileInfoBuilder; 4 | import com.tyron.code.info.properties.Property; 5 | import com.tyron.code.info.properties.PropertyContainer; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import javax.annotation.Nonnull; 9 | import java.util.Arrays; 10 | import java.util.Map; 11 | 12 | public class FileInfoImpl implements FileInfo { 13 | 14 | private final PropertyContainer properties; 15 | private final String name; 16 | private final byte[] rawContent; 17 | 18 | /** 19 | * @param name 20 | * File name/path. 21 | * @param rawContent 22 | * Raw contents of file. 23 | * @param properties 24 | * Assorted properties. 25 | */ 26 | public FileInfoImpl(String name, byte[] rawContent, PropertyContainer properties) { 27 | this.name = name; 28 | this.rawContent = rawContent; 29 | this.properties = properties; 30 | } 31 | 32 | public FileInfoImpl(FileInfoBuilder builder) { 33 | this.name = builder.getName(); 34 | this.properties = builder.getProperties(); 35 | this.rawContent = builder.getRawContent(); 36 | } 37 | 38 | @Override 39 | public byte @NotNull [] getRawContent() { 40 | return rawContent; 41 | } 42 | 43 | @Nonnull 44 | @Override 45 | public String getName() { 46 | return name; 47 | } 48 | 49 | @Override 50 | public boolean equals(Object o) { 51 | if (this == o) return true; 52 | if (o == null || getClass() != o.getClass()) return false; 53 | 54 | FileInfoImpl other = (FileInfoImpl) o; 55 | 56 | if (!name.equals(other.name)) return false; 57 | return Arrays.equals(rawContent, other.rawContent); 58 | } 59 | 60 | @Override 61 | public int hashCode() { 62 | int result = name.hashCode(); 63 | result = 31 * result + Arrays.hashCode(rawContent); 64 | return result; 65 | } 66 | 67 | @Override 68 | public void setProperty(Property property) { 69 | properties.setProperty(property); 70 | } 71 | 72 | @Override 73 | public void removeProperty(String key) { 74 | properties.removeProperty(key); 75 | } 76 | 77 | @Nonnull 78 | @Override 79 | public Map> getProperties() { 80 | return properties.getProperties(); 81 | } 82 | 83 | @Override 84 | public String toString() { 85 | return name; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /project-impl/src/main/java/com/tyron/code/info/JvmClassInfoImpl.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.info; 2 | 3 | import com.tyron.code.info.builder.JvmClassInfoBuilder; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.objectweb.asm.ClassReader; 6 | 7 | public class JvmClassInfoImpl extends BasicClassInfo implements JvmClassInfo { 8 | 9 | private final int version; 10 | 11 | private final byte[] bytecode; 12 | private ClassReader reader; 13 | 14 | public JvmClassInfoImpl(JvmClassInfoBuilder builder) { 15 | super(builder); 16 | 17 | this.version = builder.getVersion(); 18 | this.bytecode = builder.getBytecode(); 19 | } 20 | 21 | @Override 22 | public int getVersion() { 23 | return version; 24 | } 25 | 26 | @Override 27 | public byte @NotNull [] getBytecode() { 28 | return bytecode; 29 | } 30 | 31 | @Override 32 | public @NotNull ClassReader getClassReader() { 33 | if (reader == null) { 34 | reader = new ClassReader(bytecode); 35 | } 36 | return reader; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /project-impl/src/main/java/com/tyron/code/info/SourceClassInfoImpl.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.info; 2 | 3 | import com.tyron.code.info.builder.AbstractClassInfoBuilder; 4 | import com.tyron.code.info.builder.SourceClassInfoBuilder; 5 | 6 | import java.nio.file.Path; 7 | 8 | public class SourceClassInfoImpl extends BasicClassInfo implements SourceClassInfo{ 9 | 10 | private Path path; 11 | 12 | public SourceClassInfoImpl(SourceClassInfoBuilder builder) { 13 | super(builder); 14 | 15 | path = builder.getPath(); 16 | } 17 | 18 | @Override 19 | public Path getPath() { 20 | return path; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /project-impl/src/main/java/com/tyron/code/info/builder/SourceClassInfoBuilder.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.info.builder; 2 | 3 | import com.google.common.io.Files; 4 | import com.tyron.code.info.ClassInfo; 5 | import com.tyron.code.info.SourceClassInfo; 6 | import com.tyron.code.info.SourceClassInfoImpl; 7 | import com.tyron.code.project.util.ClassNameUtils; 8 | import com.tyron.code.project.util.StringSearch; 9 | 10 | import java.nio.file.Path; 11 | 12 | public class SourceClassInfoBuilder extends AbstractClassInfoBuilder{ 13 | 14 | private Path path; 15 | 16 | public SourceClassInfoBuilder() { 17 | super(); 18 | } 19 | 20 | public SourceClassInfoBuilder(SourceClassInfo info) { 21 | withPath(info.getPath()); 22 | } 23 | 24 | public SourceClassInfoBuilder(Path path) { 25 | withPath(path); 26 | withSourceFileName(Files.getNameWithoutExtension(path.getFileName().toString())); 27 | withName(StringSearch.packageName(path).replace('.', '/') + "/" + getSourceFileName()); 28 | } 29 | 30 | public SourceClassInfoBuilder withPath(Path path) { 31 | this.path = path; 32 | return this; 33 | } 34 | 35 | @Override 36 | public SourceClassInfo build() { 37 | return new SourceClassInfoImpl(this); 38 | } 39 | 40 | public Path getPath() { 41 | return path; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /project-impl/src/main/java/com/tyron/code/path/impl/FilePathNode.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.path.impl; 2 | 3 | import com.tyron.code.info.FileInfo; 4 | import com.tyron.code.path.PathNode; 5 | import com.tyron.code.project.model.module.Module; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import java.util.Set; 10 | 11 | public class FilePathNode extends AbstractPathNode { 12 | 13 | /** 14 | * Type identifier for file nodes. 15 | */ 16 | public static final String TYPE_ID = "file"; 17 | 18 | 19 | /** 20 | * Node without parent. 21 | * 22 | * @param info 23 | * File value. 24 | */ 25 | public FilePathNode(@NotNull FileInfo info) { 26 | this(null, info); 27 | } 28 | 29 | /** 30 | * Node with parent. 31 | * 32 | * @param parent 33 | * Parent node. 34 | * @param info 35 | * File value. 36 | * 37 | * @see DirectoryPathNode#child(FileInfo) 38 | */ 39 | public FilePathNode(@Nullable DirectoryPathNode parent, @NotNull FileInfo info) { 40 | super(TYPE_ID, parent, FileInfo.class, info); 41 | } 42 | 43 | @Override 44 | public DirectoryPathNode getParent() { 45 | return (DirectoryPathNode) super.getParent(); 46 | } 47 | 48 | @NotNull 49 | @Override 50 | public Set directParentTypeIds() { 51 | return Set.of(DirectoryPathNode.TYPE_ID, ModulePathNode.TYPE_ID); 52 | } 53 | 54 | @Override 55 | public int localCompare(PathNode o) { 56 | if (o instanceof FilePathNode fileNode) { 57 | String name = getValue().getName(); 58 | String otherName = fileNode.getValue().getName(); 59 | return String.CASE_INSENSITIVE_ORDER.compare(name, otherName); 60 | } 61 | return 0; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /project-impl/src/main/java/com/tyron/code/path/impl/ModulePathNode.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.path.impl; 2 | 3 | import com.tyron.code.path.PathNode; 4 | import com.tyron.code.project.model.module.Module; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.util.Collections; 8 | import java.util.Set; 9 | 10 | public class ModulePathNode extends AbstractPathNode{ 11 | 12 | public static final String TYPE_ID = "module"; 13 | 14 | public ModulePathNode(Module module) { 15 | super(TYPE_ID, null, Module.class, module); 16 | } 17 | 18 | @Override 19 | public @NotNull Set directParentTypeIds() { 20 | return Collections.emptySet(); 21 | } 22 | 23 | @Override 24 | public boolean isDescendantOf(@NotNull PathNode other) { 25 | // Workspace is the root of all paths. 26 | // Only other workspace paths with the same value should count here. 27 | if (typeId().equals(other.typeId())) { 28 | return getValue().equals(other.getValue()); 29 | } 30 | 31 | // We have no parents. 32 | return false; 33 | } 34 | 35 | @Override 36 | public int localCompare(PathNode o) { 37 | return 0; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /project-impl/src/main/java/com/tyron/code/path/impl/SourceClassPathNode.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.path.impl; 2 | 3 | import com.tyron.code.info.SourceClassInfo; 4 | import com.tyron.code.path.PathNode; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import java.util.Set; 9 | 10 | public class SourceClassPathNode extends AbstractPathNode { 11 | 12 | public static final String TYPE_ID = "source-class-info"; 13 | 14 | /** 15 | * Node with parent. 16 | * 17 | * @param parent 18 | * Parent node. 19 | * @param info 20 | * Class value. 21 | * 22 | * @see DirectoryPathNode#child(ClassInfo) 23 | */ 24 | public SourceClassPathNode(@Nullable DirectoryPathNode parent, @NotNull SourceClassInfo info) { 25 | super(TYPE_ID, parent, SourceClassInfo.class, info); 26 | } 27 | 28 | @Override 29 | public DirectoryPathNode getParent() { 30 | return (DirectoryPathNode) super.getParent(); 31 | } 32 | 33 | @Override 34 | public @NotNull Set directParentTypeIds() { 35 | return Set.of(DirectoryPathNode.TYPE_ID); 36 | } 37 | 38 | @Override 39 | public int localCompare(PathNode o) { 40 | if (o instanceof SourceClassPathNode classPathNode) { 41 | String name = getValue().getName(); 42 | String otherName = classPathNode.getValue().getName(); 43 | return String.CASE_INSENSITIVE_ORDER.compare(name, otherName); 44 | } 45 | return 0; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /project-impl/src/main/java/com/tyron/code/path/impl/WorkspacePathNode.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.path.impl; 2 | 3 | import com.tyron.code.path.PathNode; 4 | import com.tyron.code.project.Workspace; 5 | import com.tyron.code.project.model.module.RootModule; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.util.Collections; 9 | import java.util.Set; 10 | 11 | public class WorkspacePathNode extends AbstractPathNode { 12 | 13 | /** 14 | * Type identifier for workspace nodes. 15 | */ 16 | public static final String TYPE_ID = "workspace"; 17 | 18 | 19 | /** 20 | * Node without parent. 21 | * 22 | * @param value Workspace value. 23 | */ 24 | public WorkspacePathNode(@NotNull RootModule value) { 25 | super(TYPE_ID, null, RootModule.class, value); 26 | } 27 | 28 | 29 | @NotNull 30 | @Override 31 | public Set directParentTypeIds() { 32 | return Collections.emptySet(); 33 | } 34 | 35 | @Override 36 | public boolean isDescendantOf(@NotNull PathNode other) { 37 | // Workspace is the root of all paths. 38 | // Only other workspace paths with the same value should count here. 39 | if (typeId().equals(other.typeId())) { 40 | return getValue().equals(other.getValue()); 41 | } 42 | 43 | // We have no parents. 44 | return false; 45 | } 46 | 47 | @Override 48 | public int localCompare(PathNode o) { 49 | return 0; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /project-impl/src/main/java/com/tyron/code/project/impl/WorkspaceImpl.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.impl; 2 | 3 | import com.tyron.code.project.Workspace; 4 | import com.tyron.code.project.WorkspaceStateListener; 5 | import com.tyron.code.project.model.module.RootModule; 6 | 7 | import java.nio.file.Path; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class WorkspaceImpl implements Workspace { 12 | 13 | private State currentState; 14 | 15 | private final Path root; 16 | 17 | private final List stateListeners; 18 | private RootModule rootModule; 19 | 20 | public WorkspaceImpl(Path root) { 21 | this.currentState = State.UNINITIALIZED; 22 | this.root = root; 23 | this.stateListeners = new ArrayList<>(); 24 | } 25 | 26 | @Override 27 | public void close() { 28 | 29 | } 30 | 31 | @Override 32 | public Path getRoot() { 33 | return root; 34 | } 35 | 36 | @Override 37 | public State getState() { 38 | return currentState; 39 | } 40 | 41 | @Override 42 | public void addWorkspaceStateChangeListener(WorkspaceStateListener listener) { 43 | stateListeners.add(listener); 44 | } 45 | 46 | @Override 47 | public void removeWorkspaceStateChangeListener(WorkspaceStateListener listener) { 48 | stateListeners.remove(listener); 49 | } 50 | 51 | public void setState(State state) { 52 | currentState = state; 53 | for (WorkspaceStateListener listener : List.copyOf(stateListeners)) { 54 | listener.onWorkspaceStateChanged(state); 55 | } 56 | } 57 | 58 | @Override 59 | public RootModule getModule() { 60 | return rootModule; 61 | } 62 | 63 | public void setRoot(RootModule module) { 64 | this.rootModule = module; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /project-impl/src/main/java/com/tyron/code/project/impl/config/ModuleConfig.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.impl.config; 2 | 3 | import com.google.common.annotations.VisibleForTesting; 4 | import red.jackf.tomlconfig.annotations.Config; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class ModuleConfig implements Config { 10 | 11 | @Comment("The name that will be used when referencing this from another project") 12 | @Comment("Empty means use the name of the folder this config is located") 13 | public String name = ""; 14 | 15 | @Comment("If this module contains child modules, it should be listed here") 16 | public List includedModules = new ArrayList<>(); 17 | 18 | @Comment("Defines the type of module this project will be") 19 | @Comment("Will determine what compiler to use") 20 | public ModuleType moduleType = ModuleType.DEFAULT; 21 | 22 | public List dependencies = new ArrayList<>(); 23 | 24 | public enum ModuleType { 25 | DEFAULT, 26 | JAVA 27 | } 28 | 29 | @Transitive 30 | public static class Dependency { 31 | 32 | public DependencyScope scope = DependencyScope.IMPLEMENTATION; 33 | 34 | public String notation; 35 | 36 | public DependencyType type = DependencyType.MAVEN; 37 | 38 | public Dependency() { 39 | 40 | } 41 | 42 | @VisibleForTesting 43 | public Dependency(String notation) { 44 | this(DependencyScope.IMPLEMENTATION, notation, DependencyType.MAVEN); 45 | } 46 | 47 | @VisibleForTesting 48 | public Dependency(DependencyScope scope, String notation, DependencyType type) { 49 | this.scope = scope; 50 | this.notation = notation; 51 | this.type = type; 52 | } 53 | 54 | 55 | public enum DependencyScope { 56 | IMPLEMENTATION, 57 | RUNTIME_ONLY, 58 | COMPILE_ONLY 59 | } 60 | 61 | public enum DependencyType { 62 | FILE, 63 | MAVEN, 64 | PROJECT 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /project-impl/src/main/java/com/tyron/code/project/impl/model/AbstractModule.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.impl.model; 2 | 3 | import com.tyron.code.info.FileInfo; 4 | import com.tyron.code.project.ModuleManager; 5 | import com.tyron.code.project.model.module.Module; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.nio.file.Path; 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.List; 12 | import java.util.Set; 13 | 14 | public class AbstractModule implements Module { 15 | 16 | private final ModuleManager moduleManager; 17 | private final Path root; 18 | 19 | private String name; 20 | private final List dependencies; 21 | 22 | private final List files; 23 | 24 | public AbstractModule(ModuleManager moduleManager, Path rootDirectory) { 25 | this.moduleManager = moduleManager; 26 | this.root = rootDirectory; 27 | this.dependencies = new ArrayList<>(); 28 | this.files = new ArrayList<>(); 29 | } 30 | 31 | @Override 32 | public @NotNull ModuleManager getModuleManager() { 33 | return moduleManager; 34 | } 35 | 36 | @Override 37 | public @NotNull String getName() { 38 | return name; 39 | } 40 | 41 | @Override 42 | public @NotNull List getDependencies() { 43 | return Collections.unmodifiableList(dependencies); 44 | } 45 | 46 | @Override 47 | public @NotNull Path getRootDirectory() { 48 | return root; 49 | } 50 | 51 | public void setName(String name) { 52 | this.name = name; 53 | } 54 | 55 | public void addDependency(Module dependency) { 56 | dependencies.add(dependency.getName()); 57 | } 58 | 59 | @Override 60 | public @NotNull List getFiles() { 61 | return files; 62 | } 63 | 64 | public void addFile(FileInfo file) { 65 | files.add(file); 66 | } 67 | 68 | public void addFiles(List files) { 69 | this.files.addAll(files); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /project-impl/src/main/java/com/tyron/code/project/impl/model/ErroneousModuleImpl.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.impl.model; 2 | 3 | import com.tyron.code.project.ModuleManager; 4 | import com.tyron.code.project.model.ProjectError; 5 | import com.tyron.code.project.model.module.ErroneousModule; 6 | 7 | import java.nio.file.Path; 8 | import java.util.List; 9 | 10 | public class ErroneousModuleImpl extends AbstractModule implements ErroneousModule { 11 | 12 | private final List errors; 13 | 14 | public ErroneousModuleImpl(ModuleManager manager, Path rootDirectory, List errors) { 15 | super(manager, rootDirectory); 16 | this.errors = errors; 17 | } 18 | 19 | @Override 20 | public List getErrors() { 21 | return errors; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /project-impl/src/main/java/com/tyron/code/project/impl/model/ErroneousRootModuleImpl.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.impl.model; 2 | 3 | import com.tyron.code.project.ModuleManager; 4 | import com.tyron.code.project.model.ProjectError; 5 | import com.tyron.code.project.model.module.ErroneousRootModule; 6 | import com.tyron.code.project.model.module.Module; 7 | 8 | import java.nio.file.Path; 9 | import java.util.List; 10 | 11 | public class ErroneousRootModuleImpl extends RootModuleImpl implements ErroneousRootModule { 12 | 13 | private final List errors; 14 | 15 | public ErroneousRootModuleImpl(ModuleManager moduleManager, Path rootDirectory, List includedModules, List errors) { 16 | super(moduleManager, rootDirectory, includedModules); 17 | this.errors = errors; 18 | } 19 | 20 | @Override 21 | public List getErrors() { 22 | return errors; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /project-impl/src/main/java/com/tyron/code/project/impl/model/JarModuleImpl.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.impl.model; 2 | 3 | import com.tyron.code.info.JvmClassInfo; 4 | import com.tyron.code.project.ModuleManager; 5 | import com.tyron.code.project.model.module.JarModule; 6 | 7 | import java.nio.file.Path; 8 | import java.util.Set; 9 | 10 | public class JarModuleImpl extends SourceModuleImpl implements JarModule { 11 | private final Path path; 12 | 13 | public JarModuleImpl(ModuleManager moduleManager, Path path) { 14 | super(moduleManager, path); 15 | this.path = path; 16 | } 17 | 18 | @Override 19 | public Path getPath() { 20 | return path; 21 | } 22 | 23 | @Override 24 | public Set getClasses() { 25 | return getSourceFiles(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /project-impl/src/main/java/com/tyron/code/project/impl/model/JdkModuleImpl.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.impl.model; 2 | 3 | import com.tyron.code.project.ModuleManager; 4 | import com.tyron.code.project.model.module.JdkModule; 5 | 6 | import java.nio.file.Path; 7 | 8 | public class JdkModuleImpl extends JarModuleImpl implements JdkModule { 9 | private final String jdkVersion; 10 | 11 | public JdkModuleImpl(ModuleManager moduleManager, Path jarPath, String jdkVersion) { 12 | super(moduleManager, jarPath); 13 | setName("JDK-" + jdkVersion); 14 | this.jdkVersion = jdkVersion; 15 | } 16 | 17 | @Override 18 | public String getJdkVersion() { 19 | return jdkVersion; 20 | } 21 | 22 | 23 | } -------------------------------------------------------------------------------- /project-impl/src/main/java/com/tyron/code/project/impl/model/RootModuleImpl.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.impl.model; 2 | 3 | import com.tyron.code.project.ModuleManager; 4 | import com.tyron.code.project.model.module.Module; 5 | import com.tyron.code.project.model.module.RootModule; 6 | 7 | import java.nio.file.Path; 8 | import java.util.List; 9 | 10 | public class RootModuleImpl extends AbstractModule implements RootModule { 11 | 12 | private final List includedModules; 13 | 14 | public RootModuleImpl(ModuleManager moduleManager, Path rootDirectory, List includedModules) { 15 | super(moduleManager, rootDirectory); 16 | this.includedModules = includedModules; 17 | setName(rootDirectory.getFileName().toString()); 18 | } 19 | 20 | public List getIncludedModules() { 21 | return includedModules; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /project-impl/src/main/java/com/tyron/code/project/impl/model/SourceModuleImpl.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.impl.model; 2 | 3 | import com.tyron.code.info.ClassInfo; 4 | import com.tyron.code.project.ModuleManager; 5 | import com.tyron.code.project.model.module.SourceModule; 6 | 7 | import java.nio.file.Path; 8 | import java.util.HashSet; 9 | import java.util.Set; 10 | 11 | public class SourceModuleImpl extends AbstractModule implements SourceModule { 12 | private final Set classInfos; 13 | 14 | public SourceModuleImpl(ModuleManager moduleManager, Path root) { 15 | super(moduleManager, root); 16 | this.classInfos = new HashSet<>(); 17 | } 18 | 19 | public void addClass(T info) { 20 | classInfos.add(info); 21 | } 22 | 23 | @Override 24 | public Set getSourceFiles() { 25 | return classInfos; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /project-impl/src/test/java/com/tyron/code/project/impl/FileSystemModuleManagerTest.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.impl; 2 | 3 | import com.tyron.code.info.SourceClassInfo; 4 | import com.tyron.code.project.file.SimpleFileManager; 5 | import com.tyron.code.project.impl.model.JavaModuleImpl; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import java.io.IOException; 10 | import java.nio.file.Files; 11 | import java.nio.file.Path; 12 | import java.util.Set; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | 16 | class FileSystemModuleManagerTest { 17 | 18 | private FileSystemModuleManager manager; 19 | private JavaModuleImpl module; 20 | 21 | private Path root; 22 | 23 | @BeforeEach 24 | public void setup() throws IOException { 25 | root = Files.createTempDirectory("project"); 26 | SimpleFileManager fileManager = new SimpleFileManager(); 27 | manager = new FileSystemModuleManager(fileManager, root); 28 | module = (JavaModuleImpl) manager.getRootModule(); 29 | } 30 | 31 | @Test 32 | public void testAddOrUpdateFile() throws IOException { 33 | Path testJavaFile = root.resolve("Test.java"); 34 | Files.createFile(testJavaFile); 35 | 36 | Set files = module.getSourceFiles(); 37 | assertEquals(0, files.size()); 38 | 39 | manager.addOrUpdateFile(testJavaFile); 40 | files = module.getSourceFiles(); 41 | assertEquals(1, files.size()); 42 | 43 | SourceClassInfo file = files.iterator().next(); 44 | assertEquals(testJavaFile, file.getPath()); 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /project-impl/src/test/java/com/tyron/code/project/impl/config/ModuleConfigTest.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.impl.config; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import red.jackf.tomlconfig.TOMLConfig; 5 | 6 | import java.io.PrintWriter; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | 10 | import static org.junit.jupiter.api.Assertions.*; 11 | 12 | class ModuleConfigTest { 13 | 14 | @Test 15 | public void test() throws Exception { 16 | Path tempDirectory = Files.createTempDirectory("module"); 17 | Path config = Files.createFile(tempDirectory.resolve("project.config")); 18 | 19 | TOMLConfig tomlConfig = TOMLConfig.get(); 20 | ModuleConfig moduleConfig = tomlConfig.readConfig(ModuleConfig.class, config); 21 | moduleConfig.dependencies.add(new ModuleConfig.Dependency("com.tyron.test:2020:123")); 22 | 23 | tomlConfig.writeConfig(moduleConfig, new PrintWriter(System.out)); 24 | System.out.println(); 25 | 26 | } 27 | } -------------------------------------------------------------------------------- /project/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-library") 3 | } 4 | 5 | dependencies { 6 | testImplementation(platform("org.junit:junit-bom:5.9.1")) 7 | testImplementation("org.junit.jupiter:junit-jupiter") 8 | 9 | implementation(project(":javac")) 10 | 11 | implementation("com.google.auto.value:auto-value-annotations:1.8.2") 12 | annotationProcessor("com.google.auto.value:auto-value:1.8.2") 13 | 14 | implementation("org.slf4j:slf4j-api:2.0.10") 15 | implementation("ch.qos.logback:logback-classic:1.4.11") 16 | 17 | implementation("org.jetbrains:annotations:24.1.0") 18 | 19 | implementation("org.ow2.asm:asm:9.6") 20 | 21 | implementation("com.moandjiezana.toml:toml4j:0.7.2") 22 | 23 | implementation("com.google.guava:guava:31.0.1-android") 24 | } 25 | 26 | tasks.test { 27 | useJUnitPlatform() 28 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/diagnostic/Diagnostic.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.diagnostic; 2 | 3 | public record Diagnostic(Kind kind, String message, int start, int end, int line, int column) { 4 | 5 | public static Diagnostic from(shadow.javax.tools.Diagnostic diagnostic) { 6 | return new Diagnostic( 7 | from(diagnostic.getKind()), 8 | diagnostic.getMessage(null), 9 | (int) diagnostic.getStartPosition(), 10 | (int) diagnostic.getEndPosition(), 11 | (int) diagnostic.getLineNumber(), 12 | (int) diagnostic.getColumnNumber() 13 | ); 14 | } 15 | 16 | private static Kind from(shadow.javax.tools.Diagnostic.Kind kind) { 17 | return switch (kind) { 18 | case ERROR -> Kind.ERROR; 19 | case WARNING -> Kind.WARNING; 20 | case MANDATORY_WARNING -> Kind.MANDATORY_WARNING; 21 | case NOTE -> Kind.NOTE; 22 | default -> Kind.OTHER; 23 | }; 24 | } 25 | 26 | public enum Kind { 27 | ERROR, 28 | WARNING, 29 | MANDATORY_WARNING, 30 | NOTE, 31 | OTHER, 32 | ; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/info/ClassInfo.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.info; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Outline of a compiled class file. 10 | */ 11 | public interface ClassInfo extends Info { 12 | 13 | @Nullable 14 | String getSourceFileName(); 15 | 16 | @NotNull 17 | List getInterfaces(); 18 | 19 | @Nullable 20 | String getSuperName(); 21 | 22 | @NotNull 23 | default String getSimpleName() { 24 | String className = getName(); 25 | int packageIndex = className.lastIndexOf('/'); 26 | if (packageIndex <= 0) { 27 | return className; 28 | } 29 | return className.substring(packageIndex + 1); 30 | } 31 | 32 | @Nullable 33 | default String getPackageName() { 34 | String className = getName(); 35 | int packageIndex = className.lastIndexOf('/'); 36 | if (packageIndex <= 0) { 37 | return null; 38 | } 39 | return className.substring(0, packageIndex); 40 | } 41 | 42 | default String[] getPackageNameParts() { 43 | String packageName = getPackageName(); 44 | if (packageName == null) { 45 | return new String[0]; 46 | } 47 | return packageName.split("/"); 48 | } 49 | 50 | default boolean isInDefaultPackage() { 51 | return getPackageName() == null; 52 | } 53 | 54 | String getSignature(); 55 | 56 | /** 57 | * @return Name of outer class that this is declared in, if this is an inner class. 58 | * {@code null} when this class is not an inner class. 59 | */ 60 | @Nullable 61 | String getOuterClassName(); 62 | 63 | /** 64 | * @return Name of the outer method that this is declared in, as an anonymous inner class. 65 | * {@code null} when this class is not an inner anonymous class. 66 | * 67 | * @see #getOuterMethodDescriptor() Descriptor of outer method 68 | */ 69 | @Nullable 70 | String getOuterMethodName(); 71 | 72 | /** 73 | * @return Descriptor of the outer method that this is declared in, as an anonymous inner class. 74 | * {@code null} when this class is not an inner anonymous class. 75 | * 76 | * @see #getOuterMethodName() Name of outer method. 77 | */ 78 | @Nullable 79 | String getOuterMethodDescriptor(); 80 | 81 | 82 | } 83 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/info/FileInfo.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.info; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | public interface FileInfo extends Info, Named { 7 | 8 | /** 9 | * @return Raw bytes of file content. 10 | */ 11 | 12 | byte @NotNull [] getRawContent(); 13 | 14 | /** 15 | * @return Directory the file resides in. 16 | * May be {@code null} for files in the root directory. 17 | */ 18 | @Nullable 19 | default String getDirectoryName() { 20 | String fileName = getName(); 21 | int directoryIndex = fileName.lastIndexOf('/'); 22 | if (directoryIndex <= 0) { 23 | return null; 24 | } 25 | return fileName.substring(0, directoryIndex); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/info/Info.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.info; 2 | 3 | import com.tyron.code.info.properties.PropertyContainer; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * Outline of all info types. 10 | */ 11 | public interface Info extends PropertyContainer, Serializable { 12 | 13 | @NotNull 14 | String getName(); 15 | } 16 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/info/JvmClassInfo.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.info; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.objectweb.asm.ClassReader; 5 | 6 | /** 7 | * Outline of a JVM Class 8 | */ 9 | public interface JvmClassInfo extends ClassInfo { 10 | 11 | /** 12 | * Denotes the base version offset. 13 | *
    14 | *
  • For version 1 of you would use {@code BASE_VERSION + 1}.
  • 15 | *
  • For version 2 of you would use {@code BASE_VERSION + 2}.
  • 16 | *
  • ...
  • 17 | *
  • For version N of you would use {@code BASE_VERSION + N}.
  • 18 | *
19 | */ 20 | int BASE_VERSION = 44; 21 | 22 | /** 23 | * @return Java class file version. 24 | */ 25 | int getVersion(); 26 | 27 | /** 28 | * @return Bytecode of class. 29 | */ 30 | byte @NotNull [] getBytecode(); 31 | 32 | /** 33 | * @return Class reader of {@link #getBytecode()}. 34 | */ 35 | @NotNull 36 | ClassReader getClassReader(); 37 | } 38 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/info/Named.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.info; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | /** 6 | * Outline of a type that can be identified by name. 7 | */ 8 | public interface Named { 9 | /** 10 | * @return Identifying name. 11 | */ 12 | @NotNull 13 | String getName(); 14 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/info/SourceClassInfo.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.info; 2 | 3 | import java.nio.file.Path; 4 | 5 | /** 6 | * Outline of a source file 7 | */ 8 | public interface SourceClassInfo extends ClassInfo { 9 | 10 | /** 11 | * @return Path to the source file. 12 | */ 13 | Path getPath(); 14 | } 15 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/info/properties/BasicProperty.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.info.properties; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.util.Objects; 6 | 7 | /** 8 | * Basic property implementation. 9 | * 10 | * @param 11 | * Value type. 12 | * 13 | * @author Matt Coley 14 | */ 15 | public class BasicProperty implements Property { 16 | private final String key; 17 | private final V value; 18 | 19 | /** 20 | * @param key 21 | * Property key. 22 | * @param value 23 | * Property value. 24 | */ 25 | public BasicProperty(String key, V value) { 26 | this.key = key; 27 | this.value = value; 28 | } 29 | 30 | @NotNull 31 | @Override 32 | public String key() { 33 | return key; 34 | } 35 | 36 | @Override 37 | public V value() { 38 | return value; 39 | } 40 | 41 | @Override 42 | public boolean equals(Object o) { 43 | if (this == o) return true; 44 | if (o == null || getClass() != o.getClass()) return false; 45 | 46 | BasicProperty that = (BasicProperty) o; 47 | 48 | if (!Objects.equals(key, that.key)) return false; 49 | return Objects.equals(value, that.value); 50 | } 51 | 52 | @Override 53 | public int hashCode() { 54 | int result = key != null ? key.hashCode() : 0; 55 | result = 31 * result + (value != null ? value.hashCode() : 0); 56 | return result; 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return "SimpleProperty{" + 62 | "key='" + key + '\'' + 63 | ", value=" + value + 64 | '}'; 65 | } 66 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/info/properties/BasicPropertyContainer.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.info.properties; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.util.Collections; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * Basic implementation of property container. 11 | * 12 | * @author Matt Coley 13 | */ 14 | public class BasicPropertyContainer implements PropertyContainer { 15 | private Map> properties = new HashMap<>(); 16 | 17 | /** 18 | * Container with empty map. 19 | */ 20 | public BasicPropertyContainer() { 21 | this(Collections.emptyMap()); 22 | } 23 | 24 | /** 25 | * @param properties Pre-defined property map. 26 | */ 27 | public BasicPropertyContainer(Map> properties) { 28 | this.properties.putAll(properties); 29 | } 30 | 31 | @Override 32 | public void setProperty(Property property) { 33 | if (properties == null) { 34 | // Memory optimization to keep null by default 35 | properties = new HashMap<>(); 36 | } 37 | properties.put(property.key(), property); 38 | } 39 | 40 | @Override 41 | public void removeProperty(String key) { 42 | if (properties != null) { 43 | properties.remove(key); 44 | } 45 | } 46 | 47 | @NotNull 48 | public Map> getProperties() { 49 | if (properties == null) { 50 | return Collections.emptyMap(); 51 | } 52 | // Disallow modification 53 | return Collections.unmodifiableMap(properties); 54 | } 55 | 56 | @Override 57 | public boolean equals(Object o) { 58 | if (this == o) { 59 | return true; 60 | } 61 | if (o == null || getClass() != o.getClass()) { 62 | return false; 63 | } 64 | 65 | BasicPropertyContainer other = (BasicPropertyContainer) o; 66 | 67 | return properties.equals(other.properties); 68 | } 69 | 70 | @Override 71 | public int hashCode() { 72 | return properties.hashCode(); 73 | } 74 | 75 | @Override 76 | public String toString() { 77 | return "BasicPropertyContainer[" + properties.size() + " items]"; 78 | } 79 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/info/properties/Property.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.info.properties; 2 | 3 | 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | /** 8 | * Basic property outline. 9 | * 10 | * @param 11 | * Value type. 12 | * 13 | * @author Matt Coley 14 | */ 15 | public interface Property { 16 | /** 17 | * @return Property key. 18 | */ 19 | @NotNull 20 | String key(); 21 | 22 | /** 23 | * @return Property value. 24 | */ 25 | @Nullable 26 | V value(); 27 | 28 | /** 29 | * Generally, when an info type is updated in a workspace it will be copying state from the original value. 30 | * When that occurs properties of the original info instance get copied to the new one. Not all properties should 31 | * be copied though. Consider these two cases: 32 | *
    33 | *
  • {@link software.coley.recaf.info.properties.builtin.ZipCommentProperty} - 34 | * Is based on the input file content. Will never change based on info state, thus 35 | * should be copied between info instances.
  • 36 | *
  • {@link software.coley.recaf.info.properties.builtin.CachedDecompileProperty} - 37 | * Caches the decompiled code of a class file to prevent duplicate work. Changes when info state 38 | * (bytecode) is updated, thus should NOT be copied between info instances
  • 39 | *
40 | * By default, this returns {@code true}. 41 | * 42 | * @return {@code true} to represent content that should be persisted across instances. 43 | * {@code false} to represent content that should be dropped between instances. 44 | */ 45 | default boolean persistent() { 46 | return true; 47 | } 48 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/logging/CodeAssistLoggingFilter.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.logging; 2 | 3 | import ch.qos.logback.core.filter.Filter; 4 | import ch.qos.logback.core.spi.FilterReply; 5 | import org.slf4j.event.LoggingEvent; 6 | 7 | public class CodeAssistLoggingFilter extends Filter { 8 | 9 | @Override 10 | public FilterReply decide(LoggingEvent event) { 11 | if (event.getLoggerName().startsWith("com.tyron.")) { 12 | return FilterReply.ACCEPT; 13 | } 14 | return FilterReply.DENY; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/logging/DebuggingLogger.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.logging; 2 | 3 | import org.slf4j.Logger; 4 | 5 | import java.util.function.Consumer; 6 | 7 | /** 8 | * Used for verbose logging that we normally would not want to capture due to expressiveness. 9 | * But in the case where we want to enable it for local testing, its available. 10 | * 11 | * @author Matt Coley 12 | */ 13 | public interface DebuggingLogger extends Logger { 14 | boolean DEBUG = System.getenv("RECAF_DEBUG") != null; 15 | 16 | /** 17 | * Only do the given action when manual debugging is enabled. 18 | * 19 | * @param action 20 | * Call onto self. 21 | */ 22 | default void debugging(Consumer action) { 23 | if (DEBUG) 24 | action.accept(this); 25 | } 26 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/logging/LogConsumer.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.logging; 2 | 3 | import org.slf4j.event.Level; 4 | 5 | /** 6 | * Triple-argument consumer for taking in log messages. 7 | * 8 | * @param 9 | * Log content. 10 | * 11 | * @author Matt Coley 12 | */ 13 | public interface LogConsumer { 14 | /** 15 | * @param loggerName 16 | * Name of logger message applies to. 17 | * @param level 18 | * Log level of message. 19 | * @param messageContent 20 | * Content of message. 21 | */ 22 | void accept(String loggerName, Level level, T messageContent); 23 | 24 | /** 25 | * @param loggerName 26 | * Name of logger message applies to. 27 | * @param level 28 | * Log level of message. 29 | * @param messageContent 30 | * Content of message. 31 | * @param throwable 32 | * Associated thrown exception. 33 | */ 34 | void accept(String loggerName, Level level, T messageContent, Throwable throwable); 35 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/InitializationException.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project; 2 | 3 | /** 4 | * Thrown when there's an exception during project initialization such as 5 | * missing dependencies, error on configs, etc. 6 | */ 7 | public class InitializationException extends RuntimeException { 8 | 9 | public InitializationException() { 10 | 11 | } 12 | 13 | public InitializationException(String reason) { 14 | super(reason); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/ModuleManager.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project; 2 | 3 | import com.tyron.code.project.model.JavaFileInfo; 4 | import com.tyron.code.project.model.module.Module; 5 | import com.tyron.code.project.model.module.RootModule; 6 | 7 | import java.nio.file.Path; 8 | import java.util.Optional; 9 | 10 | public interface ModuleManager { 11 | 12 | /** 13 | * Initialize the module manager. The module manager can start index files and building modules. 14 | * The module manager may also choose to defer indexing files to optimize for large repository. 15 | */ 16 | void initialize(); 17 | 18 | Optional getFileItem(Path path); 19 | 20 | default void addOrUpdateFile(Path path) { 21 | throw new UnsupportedOperationException(); 22 | } 23 | 24 | /** Remove a file from modules. */ 25 | default void removeFile(Path path) { 26 | throw new UnsupportedOperationException(); 27 | } 28 | 29 | /** Add a module that all modules loaded by the module manager depends on. */ 30 | default void addDependingModule(Module module) { 31 | throw new UnsupportedOperationException(); 32 | } 33 | 34 | RootModule getRootModule(); 35 | 36 | /** 37 | * Find the module that contains the given file. 38 | * @param file The file to find the module for. 39 | * @return The module that contains the given file, or empty if no module contains the file. 40 | */ 41 | default Optional findModuleByFile(Path file) { 42 | return Optional.empty(); 43 | } 44 | 45 | /** 46 | * Find a module by its name. 47 | * @param name The name of the module to find. 48 | * @return The module with the given name, or empty if no module with the given name exists. 49 | */ 50 | default Optional findModuleByName(String name) { 51 | return Optional.empty(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/Workspace.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project; 2 | 3 | import com.tyron.code.project.model.module.RootModule; 4 | 5 | import java.nio.file.Path; 6 | 7 | public interface Workspace { 8 | 9 | enum State { 10 | UNINITIALIZED, 11 | 12 | INITIALIZING, 13 | 14 | INITIALIZED 15 | } 16 | 17 | void close(); 18 | 19 | Path getRoot(); 20 | 21 | State getState(); 22 | 23 | void addWorkspaceStateChangeListener(WorkspaceStateListener listener); 24 | 25 | void removeWorkspaceStateChangeListener(WorkspaceStateListener listener); 26 | 27 | RootModule getModule(); 28 | } 29 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/WorkspaceCloseCondition.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project; 2 | 3 | import com.tyron.code.project.model.module.Module; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import javax.annotation.Nonnull; 7 | 8 | /** 9 | * Condition applied to {@link WorkspaceManager} to prevent closure of an active workspace for when 10 | * {@link WorkspaceManager#setCurrent(Workspace)} is called. 11 | */ 12 | public interface WorkspaceCloseCondition { 13 | /** 14 | * @param current Current workspace. 15 | * @return {@code true} when the operation is allowed. 16 | */ 17 | boolean canClose(@NotNull Workspace current); 18 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/WorkspaceCloseListener.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project; 2 | 3 | import com.tyron.code.project.model.module.Module; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import javax.annotation.Nonnull; 7 | 8 | /** 9 | * Listener for when old workspaces are closed. 10 | */ 11 | public interface WorkspaceCloseListener { 12 | /** 13 | * Called when {@link WorkspaceManager#setCurrent(Workspace)} passes and a prior workspace is removed. 14 | * 15 | * @param workspace New workspace module assigned. 16 | */ 17 | void onWorkspaceClosed(@NotNull Workspace workspace); 18 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/WorkspaceOpenListener.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import javax.annotation.Nonnull; 6 | 7 | /** 8 | * Listener for when new workspaces are opened. 9 | */ 10 | public interface WorkspaceOpenListener { 11 | /** 12 | * Called when {@link WorkspaceManager#setCurrent(Workspace)} passes. 13 | * 14 | * @param workspace New workspace assigned. 15 | */ 16 | void onWorkspaceOpened(@NotNull Workspace workspace); 17 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/WorkspaceStateListener.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project; 2 | 3 | public interface WorkspaceStateListener { 4 | 5 | void onWorkspaceStateChanged(Workspace.State newState); 6 | } 7 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/dependency/DependencyUtil.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.dependency; 2 | 3 | import com.google.common.graph.GraphBuilder; 4 | import com.google.common.graph.MutableGraph; 5 | import com.tyron.code.project.ModuleManager; 6 | import com.tyron.code.project.model.module.Module; 7 | 8 | import java.util.*; 9 | 10 | @SuppressWarnings("UnstableApiUsage") 11 | public class DependencyUtil { 12 | 13 | public static MutableGraph buildDependencyGraph(Module module) { 14 | MutableGraph graph = GraphBuilder.directed() 15 | .allowsSelfLoops(false) 16 | .build(); 17 | 18 | Deque queue = new LinkedList<>(); 19 | Set visitedModules = new HashSet<>(); 20 | queue.addLast(module); 21 | 22 | while (!queue.isEmpty()) { 23 | Module current = queue.removeFirst(); 24 | 25 | graph.addNode(current); 26 | 27 | visitedModules.add(current); 28 | 29 | List dependencies = current.getDependencies(); 30 | for (String dependencyName : dependencies) { 31 | ModuleManager moduleManager = module.getModuleManager(); 32 | Optional dependency = moduleManager.findModuleByName(dependencyName); 33 | if (dependency.isEmpty()) { 34 | throw new IllegalStateException("Module " + current.getName() + " depends on module " + dependencyName + " which does not exist"); 35 | } 36 | if (!visitedModules.contains(dependency.get())) { 37 | graph.putEdge(current, dependency.get()); 38 | queue.add(dependency.get()); 39 | } 40 | } 41 | } 42 | 43 | return graph; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/file/EditHistory.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.file; 2 | 3 | 4 | import com.google.auto.value.AutoValue; 5 | import com.google.common.collect.ImmutableList; 6 | import com.tyron.code.project.model.TextRange; 7 | 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | @AutoValue 12 | public abstract class EditHistory { 13 | public abstract String getOriginalContent(); 14 | 15 | public abstract ImmutableList getAppliedEdits(); 16 | 17 | public static EditHistory create(String originalContent, List appliedEdits) { 18 | return new AutoValue_EditHistory(originalContent, ImmutableList.copyOf(appliedEdits)); 19 | } 20 | 21 | @AutoValue 22 | public abstract static class AppliedEdit { 23 | public abstract TextRange getTextRange(); 24 | 25 | public abstract Optional getRangeLength(); 26 | 27 | public abstract String getNewText(); 28 | 29 | public static AppliedEdit create( 30 | TextRange textRange, Optional rangeLength, String newText) { 31 | return new AutoValue_EditHistory_AppliedEdit(textRange, rangeLength, newText); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/file/FileChangeListener.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.file; 2 | 3 | import java.nio.file.Path; 4 | import java.nio.file.WatchEvent; 5 | 6 | public interface FileChangeListener { 7 | void onFileChange(Path filePath, WatchEvent.Kind eventKind); 8 | } 9 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/file/FileTextLocation.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.file; 2 | 3 | import com.google.auto.value.AutoValue; 4 | import com.tyron.code.project.model.TextRange; 5 | 6 | import java.nio.file.Path; 7 | 8 | /** Represents a range in a file. */ 9 | @AutoValue 10 | public abstract class FileTextLocation { 11 | 12 | public abstract Path getFilePath(); 13 | 14 | public abstract TextRange getRange(); 15 | 16 | public static FileTextLocation create(Path filePath, TextRange range) { 17 | return new AutoValue_FileTextLocation(filePath, range); 18 | } 19 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/graph/CompileProjectModuleBFS.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.graph; 2 | 3 | import com.tyron.code.project.model.module.JavaModule; 4 | import com.tyron.code.project.model.module.Module; 5 | 6 | import java.util.Collection; 7 | import java.util.Collections; 8 | import java.util.stream.Stream; 9 | 10 | public class CompileProjectModuleBFS extends GraphBFS { 11 | 12 | public CompileProjectModuleBFS(Module startingModule) { 13 | super(startingModule); 14 | } 15 | 16 | @Override 17 | protected Collection getChildren(Module node) { 18 | if (node instanceof JavaModule javaModule) { 19 | return javaModule.getCompileOnlyDependencies(); 20 | } 21 | return Collections.emptyList(); 22 | } 23 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/graph/GraphBFS.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.graph; 2 | 3 | import java.util.*; 4 | 5 | public abstract class GraphBFS { 6 | 7 | private final T startingNode; 8 | 9 | public GraphBFS(T startingNode) { 10 | this.startingNode = startingNode; 11 | } 12 | 13 | public void traverse(NodeVisitor visitor) { 14 | Deque queue = new LinkedList<>(); 15 | Set visitedSet = new HashSet<>(); 16 | 17 | queue.addLast(startingNode); 18 | visitedSet.add(startingNode); 19 | 20 | while (!queue.isEmpty()) { 21 | T currentNode = queue.removeFirst(); 22 | visitor.visit(currentNode); 23 | 24 | for (T childNode : getChildren(currentNode)) { 25 | if (!visitedSet.contains(childNode)) { 26 | queue.addLast(childNode); 27 | visitedSet.add(childNode); 28 | } 29 | } 30 | } 31 | } 32 | 33 | protected abstract Collection getChildren(T node); 34 | } 35 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/graph/GraphPrinter.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.graph; 2 | 3 | import com.google.common.graph.Graph; 4 | 5 | @SuppressWarnings("UnstableApiUsage") 6 | public class GraphPrinter { 7 | 8 | public static void printGraphAsTree(Graph graph, T root, String prefix) { 9 | if (!graph.nodes().contains(root)) { 10 | System.err.println("Node '" + root + "' not found!"); 11 | return; 12 | } 13 | 14 | System.out.println(prefix + root); 15 | 16 | for (T child : graph.successors(root)) { 17 | printGraphAsTree(graph, child, prefix + (isLastChild(graph, root, child) ? " " : "| ")); 18 | } 19 | } 20 | 21 | private static boolean isLastChild(Graph graph, T parent, T child) { 22 | // Check if there are no more successors of the parent after this child 23 | return graph.successors(parent).stream().noneMatch(node -> node.equals(child)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/graph/ModuleFileCollectorVisitor.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.graph; 2 | 3 | import com.tyron.code.info.ClassInfo; 4 | import com.tyron.code.project.model.module.JarModule; 5 | import com.tyron.code.project.model.module.JavaModule; 6 | import com.tyron.code.project.model.module.Module; 7 | 8 | import java.util.HashSet; 9 | import java.util.Set; 10 | 11 | public class ModuleFileCollectorVisitor implements NodeVisitor { 12 | 13 | private final Set allFiles = new HashSet<>(); 14 | 15 | public ModuleFileCollectorVisitor() { 16 | 17 | } 18 | 19 | public Set getAllFiles() { 20 | return allFiles; 21 | } 22 | 23 | @Override 24 | public void visit(Module module) { 25 | if (module instanceof JavaModule projectModule) { 26 | allFiles.addAll(projectModule.getSourceFiles()); 27 | } else if (module instanceof JarModule jarModule) { 28 | allFiles.addAll(jarModule.getSourceFiles()); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/graph/NodeVisitor.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.graph; 2 | 3 | public interface NodeVisitor { 4 | 5 | 6 | void visit(T currentNode); 7 | } 8 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/model/Build.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.model; 2 | 3 | import java.util.List; 4 | 5 | public class Build { 6 | 7 | public List dependencies; 8 | } 9 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/model/Config.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.model; 2 | 3 | public class Config { 4 | 5 | public String type = "default"; 6 | 7 | public JavaConfig javaConfig = new JavaConfig(); 8 | 9 | public Build build = new Build(); 10 | 11 | public Settings settings = new Settings(); 12 | } 13 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/model/Dependency.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.model; 2 | 3 | public class Dependency { 4 | 5 | public String notation; 6 | 7 | public String scope; 8 | 9 | public String type; 10 | } 11 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/model/DependencyType.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.model; 2 | 3 | public enum DependencyType { 4 | RUNTIME, 5 | COMPILE_TIME 6 | } 7 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/model/JavaConfig.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.model; 2 | 3 | public class JavaConfig { 4 | 5 | public String sourceRoot = "src/main/java"; 6 | } 7 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/model/JavaFileInfo.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.model; 2 | 3 | import com.tyron.code.info.ClassInfo; 4 | import com.tyron.code.info.properties.Property; 5 | import com.tyron.code.project.model.module.Module; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import java.nio.file.Path; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | public record JavaFileInfo( 14 | Module module, 15 | Path path, 16 | String fileName, 17 | List qualifiers 18 | ) implements ClassInfo { 19 | 20 | @Override 21 | public @Nullable String getSourceFileName() { 22 | return fileName; 23 | } 24 | 25 | @Override 26 | public @NotNull List getInterfaces() { 27 | return null; 28 | } 29 | 30 | @Override 31 | public @Nullable String getSuperName() { 32 | return null; 33 | } 34 | 35 | @Override 36 | public String getSignature() { 37 | return null; 38 | } 39 | 40 | @Override 41 | public @Nullable String getOuterClassName() { 42 | return null; 43 | } 44 | 45 | @Override 46 | public @Nullable String getOuterMethodName() { 47 | return null; 48 | } 49 | 50 | @Override 51 | public @Nullable String getOuterMethodDescriptor() { 52 | return null; 53 | } 54 | 55 | @Override 56 | public @NotNull String getName() { 57 | return null; 58 | } 59 | 60 | @Override 61 | public void setProperty(Property property) { 62 | 63 | } 64 | 65 | @Override 66 | public void removeProperty(String key) { 67 | 68 | } 69 | 70 | @Override 71 | public @NotNull Map> getProperties() { 72 | return null; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/model/PackageScope.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.model; 2 | 3 | import com.google.common.collect.ArrayListMultimap; 4 | import com.google.common.collect.ImmutableList; 5 | import com.google.common.collect.Multimap; 6 | 7 | import java.util.HashSet; 8 | import java.util.List; 9 | import java.util.Set; 10 | 11 | public class PackageScope { 12 | 13 | private final Multimap subPackages; 14 | 15 | private final Set files; 16 | 17 | private final String simpleName; 18 | 19 | private final PackageScope parent; 20 | 21 | public PackageScope(String simpleName) { 22 | this(null, simpleName); 23 | } 24 | 25 | public PackageScope(PackageScope parent, String simpleName) { 26 | this.parent = parent; 27 | this.simpleName = simpleName; 28 | this.subPackages = ArrayListMultimap.create(); 29 | this.files = new HashSet<>(); 30 | 31 | } 32 | 33 | public List getSubPackages(String name) { 34 | return ImmutableList.copyOf(subPackages.get(name)); 35 | } 36 | 37 | public List getSubPackages() { 38 | return ImmutableList.copyOf(subPackages.values()); 39 | } 40 | 41 | public void addPackage(PackageScope newPackageScope) { 42 | subPackages.put(newPackageScope.getSimpleName(), newPackageScope); 43 | } 44 | 45 | public void removePackage(PackageScope packageScope) { 46 | subPackages.remove(packageScope.getSimpleName(), packageScope); 47 | } 48 | 49 | public String getSimpleName() { 50 | return this.simpleName; 51 | } 52 | 53 | public Set getFiles() { 54 | return files; 55 | } 56 | 57 | public void addFile(JavaFileInfo file) { 58 | files.add(file); 59 | } 60 | 61 | public PackageScope getParent() { 62 | return parent; 63 | } 64 | 65 | public void removeFile(JavaFileInfo file) { 66 | files.remove(file); 67 | } 68 | 69 | public boolean hasChildren() { 70 | return !(subPackages.isEmpty() && files.isEmpty()); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/model/ProjectError.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.model; 2 | 3 | public record ProjectError(String message) { 4 | } 5 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/model/Settings.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.model; 2 | 3 | import java.util.List; 4 | 5 | public class Settings { 6 | 7 | public String projectName; 8 | 9 | public List include = List.of(); 10 | } 11 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/model/TextPosition.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.model; 2 | 3 | import com.google.auto.value.AutoValue; 4 | 5 | /** 6 | * Position in a text document expressed as zero-based line and character offset. A position is 7 | * between two characters like an 'insert' cursor in a editor. 8 | */ 9 | @AutoValue 10 | public abstract class TextPosition { 11 | 12 | /** Gets the line position in a document (zero-based). */ 13 | public abstract int getLine(); 14 | 15 | /** Gets the character offset on a line in a document (zero-based). */ 16 | public abstract int getCharacter(); 17 | 18 | public static TextPosition create(int line, int character) { 19 | return new AutoValue_TextPosition(line, character); 20 | } 21 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/model/TextRange.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.model; 2 | 3 | import com.google.auto.value.AutoValue; 4 | 5 | /** 6 | * A range in a text document expressed as (zero-based) start and end positions. 7 | * 8 | *

A range is comparable to a selection in an editor. Therefore the end position is exclusive. 9 | */ 10 | @AutoValue 11 | public abstract class TextRange { 12 | /** @return the range's start position, inclusive. */ 13 | public abstract TextPosition getStart(); 14 | 15 | /** @return the range's end position, exclusive. */ 16 | public abstract TextPosition getEnd(); 17 | 18 | public static TextRange create(TextPosition start, TextPosition end) { 19 | return new AutoValue_TextRange(start, end); 20 | } 21 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/model/config/Config.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.model.config; 2 | 3 | public interface Config { 4 | 5 | 6 | } 7 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/model/module/ErroneousModule.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.model.module; 2 | 3 | import com.tyron.code.project.model.ProjectError; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * A module that has errors. 9 | */ 10 | public interface ErroneousModule extends Module { 11 | 12 | List getErrors(); 13 | } 14 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/model/module/ErroneousRootModule.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.model.module; 2 | 3 | import com.tyron.code.project.model.ProjectError; 4 | 5 | import java.util.List; 6 | 7 | public interface ErroneousRootModule extends RootModule { 8 | 9 | List getErrors(); 10 | } 11 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/model/module/JarModule.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.model.module; 2 | 3 | import com.tyron.code.info.JvmClassInfo; 4 | 5 | import java.nio.file.Path; 6 | import java.util.List; 7 | import java.util.Set; 8 | 9 | public interface JarModule extends SourceModule { 10 | Path getPath(); 11 | 12 | Set getClasses(); 13 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/model/module/JavaModule.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.model.module; 2 | 3 | import com.tyron.code.info.SourceClassInfo; 4 | 5 | import java.nio.file.Path; 6 | import java.util.List; 7 | import java.util.Set; 8 | 9 | public interface JavaModule extends SourceModule { 10 | 11 | Set getRuntimeOnlyDependencies(); 12 | 13 | Set getCompileOnlyDependencies(); 14 | 15 | Path getSourceDirectory(); 16 | 17 | /** 18 | * 19 | * @return the JDK jar this project depends on 20 | */ 21 | JdkModule getJdkModule(); 22 | 23 | Set getSourceFiles(); 24 | } 25 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/model/module/JdkModule.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.model.module; 2 | 3 | public interface JdkModule extends JarModule { 4 | String getJdkVersion(); 5 | } 6 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/model/module/Module.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.model.module; 2 | 3 | import com.tyron.code.info.FileInfo; 4 | import com.tyron.code.project.ModuleManager; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.io.Serializable; 8 | import java.nio.file.Path; 9 | import java.util.List; 10 | 11 | /** 12 | * Represents a module in a project 13 | */ 14 | public interface Module extends Serializable { 15 | 16 | String CONFIG_NAME = "project.toml"; 17 | 18 | /** 19 | * @return the path to the module configuration file 20 | */ 21 | default Path getModuleConfig() { 22 | return getRootDirectory().resolve(CONFIG_NAME); 23 | } 24 | 25 | /** 26 | * @return the module manager object that manages this module 27 | */ 28 | @NotNull 29 | ModuleManager getModuleManager(); 30 | 31 | /** 32 | * @return the unique name of this module 33 | */ 34 | @NotNull 35 | String getName(); 36 | 37 | /** 38 | * @return a list of module names that this module depends on 39 | */ 40 | @NotNull 41 | List getDependencies(); 42 | 43 | /** 44 | * @return the root directory of this module 45 | */ 46 | @NotNull 47 | Path getRootDirectory(); 48 | 49 | /** 50 | * @return a list of files that belong to this module, this may not 51 | * return all files in the module directory, as some files are considered 52 | * as part of the module, but not part of the source code 53 | * see {@link SourceModule#getSourceFiles()} 54 | */ 55 | @NotNull 56 | default List getFiles() { 57 | return List.of(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/model/module/RootModule.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.model.module; 2 | 3 | import java.util.List; 4 | 5 | public interface RootModule extends Module { 6 | 7 | List getIncludedModules(); 8 | } 9 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/model/module/SourceModule.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.model.module; 2 | 3 | import com.tyron.code.info.ClassInfo; 4 | 5 | import java.util.Set; 6 | 7 | public interface SourceModule extends Module { 8 | 9 | Set getSourceFiles(); 10 | } 11 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/util/ClassNameUtils.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.util; 2 | 3 | import com.google.common.annotations.VisibleForTesting; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | public class ClassNameUtils { 11 | 12 | public static String getPackageOnly(String fqn) { 13 | if (fqn.endsWith(".class")) { 14 | fqn = fqn.substring(0, fqn.length() - ".class".length()); 15 | } 16 | 17 | String className = getClassName(fqn); 18 | return fqn.substring(0, fqn.length() - className.length() - 1); 19 | } 20 | 21 | /** 22 | * Returns the Fully Qualified Name of a classpath 23 | */ 24 | @VisibleForTesting 25 | public static String getFqn(String path) { 26 | String classPath = path.replace("/", "."); 27 | if (classPath.startsWith(".")) { 28 | classPath = classPath.substring(1); 29 | } 30 | return classPath.substring(0, classPath.length() - ".class".length()); 31 | } 32 | 33 | @VisibleForTesting 34 | public static String getClassName(String fqn) { 35 | if (!fqn.contains(".")) { 36 | return fqn; 37 | } 38 | 39 | return fqn.substring(fqn.lastIndexOf('.') + 1); 40 | } 41 | 42 | public static List getAsQualifierList(String className) { 43 | List qualifiers = Arrays.stream(className.split("\\.")).toList(); 44 | if (qualifiers.isEmpty()) { 45 | return Collections.emptyList(); 46 | } 47 | List validQualifiers = new ArrayList<>(qualifiers.size()); 48 | for (String qualifier : qualifiers) { 49 | if (qualifier.isEmpty()) { 50 | continue; 51 | } 52 | 53 | validQualifiers.add(qualifier); 54 | } 55 | return validQualifiers; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/util/LookupUtil.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.util; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.lang.invoke.MethodHandles.Lookup; 6 | import java.lang.reflect.Field; 7 | 8 | /** 9 | * Utility class that holds the unrestricted {@link Lookup} instance. 10 | * 11 | * @author xDark 12 | */ 13 | public final class LookupUtil { 14 | private static final Lookup LOOKUP; 15 | 16 | /** 17 | * Deny public constructions. 18 | */ 19 | private LookupUtil() { 20 | } 21 | 22 | /** 23 | * @return {@link Lookup} instance. 24 | */ 25 | @NotNull 26 | public static Lookup lookup() { 27 | return LOOKUP; 28 | } 29 | 30 | static { 31 | try { 32 | Field field = ReflectUtil.getDeclaredField(Lookup.class, "IMPL_LOOKUP"); 33 | LOOKUP = (Lookup) field.get(null); 34 | } catch (NoSuchFieldException | IllegalAccessException ex) { 35 | throw new ExceptionInInitializerError(ex); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/util/ResourceUtil.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.util; 2 | 3 | import java.io.InputStream; 4 | 5 | /** 6 | * Utility for classpath resources. 7 | * 8 | * @author Matt Coley 9 | */ 10 | public class ResourceUtil { 11 | /** 12 | * Check if a resource exists in the current classpath. 13 | * 14 | * @param path 15 | * Path to resource. 16 | * 17 | * @return {@code true} if resource exists. {@code false} otherwise. 18 | */ 19 | public static boolean resourceExists(String path) { 20 | if (!path.startsWith("/")) 21 | path = "/" + path; 22 | return ResourceUtil.class.getResource(path) != null; 23 | } 24 | 25 | /** 26 | * Fetch a resource as a stream in the current classpath. 27 | * 28 | * @param path 29 | * Path to resource. 30 | * 31 | * @return Stream of resource. 32 | */ 33 | public static InputStream resource(String path) { 34 | if (!path.startsWith("/")) 35 | path = "/" + path; 36 | return ResourceUtil.class.getResourceAsStream(path); 37 | } 38 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/util/StringSearch.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.util; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.StringReader; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | import java.util.regex.Pattern; 9 | 10 | public class StringSearch { 11 | 12 | public static String packageName(String contents) { 13 | return packageName(new BufferedReader(new StringReader(contents))); 14 | } 15 | 16 | public static String packageName(Path file) { 17 | try { 18 | return packageName(Files.newBufferedReader(file)); 19 | } catch (IOException e) { 20 | throw new RuntimeException(e); 21 | } 22 | } 23 | 24 | public static String packageName(BufferedReader reader) { 25 | var packagePattern = Pattern.compile("^package +(.*);"); 26 | var startOfClass = Pattern.compile("^[\\w ]*class +\\w+"); 27 | try (reader) { 28 | for (var line = reader.readLine(); line != null; line = reader.readLine()) { 29 | if (startOfClass.matcher(line).find()) return ""; 30 | var matchPackage = packagePattern.matcher(line); 31 | if (matchPackage.matches()) { 32 | return matchPackage.group(1); 33 | } 34 | } 35 | } catch (IOException e) { 36 | throw new RuntimeException(e); 37 | } 38 | // TODO fall back on parsing file 39 | return ""; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/util/ThreadLocals.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.util; 2 | 3 | /** 4 | * Some useful {@link ThreadLocal}s. 5 | * 6 | * @author xDark 7 | */ 8 | public final class ThreadLocals { 9 | private static final ThreadLocal BYTE_BUFFER = ThreadLocal.withInitial(IOUtil::newByteBuffer); 10 | 11 | /** 12 | * Deny all constructions. 13 | */ 14 | private ThreadLocals() { 15 | } 16 | 17 | /** 18 | * @return Thread-local byte buffer. 19 | */ 20 | public static byte[] getByteBuffer() { 21 | return BYTE_BUFFER.get(); 22 | } 23 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/util/UncheckedBiConsumer.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.util; 2 | 3 | import java.util.function.BiConsumer; 4 | 5 | /** 6 | * Its {@link BiConsumer} but can throw an exception. 7 | * 8 | * @author xDark 9 | */ 10 | @FunctionalInterface 11 | public interface UncheckedBiConsumer extends BiConsumer { 12 | @Override 13 | default void accept(T t, U u) { 14 | try { 15 | uncheckedAccept(t, u); 16 | } catch (Throwable th) { 17 | ReflectUtil.propagate(th); 18 | } 19 | } 20 | 21 | void uncheckedAccept(T t, U u) throws Throwable; 22 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/util/UncheckedBiFunction.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.util; 2 | 3 | import java.util.function.BiFunction; 4 | 5 | /** 6 | * Its {@link BiFunction} but can throw an exception. 7 | * 8 | * @author xDark 9 | */ 10 | @FunctionalInterface 11 | public interface UncheckedBiFunction extends BiFunction { 12 | @Override 13 | default R apply(T t, U u) { 14 | try { 15 | return uncheckedApply(t, u); 16 | } catch (Throwable th) { 17 | ReflectUtil.propagate(th); 18 | return null; 19 | } 20 | } 21 | 22 | /** 23 | * @param t 24 | * First input. 25 | * @param u 26 | * Second input. 27 | * 28 | * @return The function result. 29 | * 30 | * @throws Throwable 31 | * Whenever. 32 | */ 33 | R uncheckedApply(T t, U u) throws Throwable; 34 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/util/UncheckedConsumer.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.util; 2 | 3 | import java.util.function.Consumer; 4 | 5 | /** 6 | * Its {@link Consumer} but can throw an exception. 7 | * 8 | * @author Matt Coley 9 | */ 10 | @FunctionalInterface 11 | public interface UncheckedConsumer extends Consumer { 12 | @Override 13 | default void accept(T t) { 14 | try { 15 | uncheckedAccept(t); 16 | } catch (Throwable th) { 17 | ReflectUtil.propagate(th); 18 | } 19 | } 20 | 21 | /** 22 | * @param input 23 | * Consumer input. 24 | * 25 | * @throws Throwable 26 | * Whenever. 27 | */ 28 | void uncheckedAccept(T input) throws Throwable; 29 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/util/UncheckedFunction.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.util; 2 | 3 | import java.util.function.Function; 4 | 5 | /** 6 | * Its {@link Function} but can throw an exception. 7 | * 8 | * @author xDark 9 | */ 10 | @FunctionalInterface 11 | public interface UncheckedFunction extends Function { 12 | @Override 13 | default R apply(T t) { 14 | try { 15 | return uncheckedApply(t); 16 | } catch (Throwable th) { 17 | ReflectUtil.propagate(th); 18 | return null; 19 | } 20 | } 21 | 22 | /** 23 | * @param input 24 | * Function input. 25 | * 26 | * @return The function result. 27 | * 28 | * @throws Throwable 29 | * Whenever. 30 | */ 31 | R uncheckedApply(T input) throws Throwable; 32 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/util/UncheckedRunnable.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.util; 2 | 3 | /** 4 | * Its {@link Runnable} but can throw an exception. 5 | * 6 | * @author Matt Coley 7 | */ 8 | @FunctionalInterface 9 | public interface UncheckedRunnable extends Runnable { 10 | @Override 11 | default void run() { 12 | try { 13 | uncheckedRun(); 14 | } catch (Throwable t) { 15 | ReflectUtil.propagate(t); 16 | } 17 | } 18 | 19 | /** 20 | * @throws Throwable 21 | * Whenever. 22 | */ 23 | void uncheckedRun() throws Throwable; 24 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/util/UncheckedSupplier.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.util; 2 | 3 | import java.util.function.Supplier; 4 | 5 | /** 6 | * Its {@link Supplier} but can throw an exception. 7 | * 8 | * @author Matt Coley 9 | */ 10 | @FunctionalInterface 11 | public interface UncheckedSupplier extends Supplier { 12 | 13 | @Override 14 | default T get() { 15 | try { 16 | return uncheckedGet(); 17 | } catch (Throwable t) { 18 | ReflectUtil.propagate(t); 19 | return null; 20 | } 21 | } 22 | 23 | /** 24 | * @return Supplier output. 25 | * 26 | * @throws Throwable 27 | * Whenever. 28 | */ 29 | T uncheckedGet() throws Throwable; 30 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/util/UnsafeIO.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.util; 2 | 3 | import com.tyron.code.logging.Logging; 4 | import org.slf4j.Logger; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.BufferedWriter; 8 | import java.lang.invoke.MethodHandle; 9 | import java.lang.invoke.MethodHandles.Lookup; 10 | 11 | /** 12 | * Unsafe IO utilities. 13 | * 14 | * @author xDark 15 | */ 16 | public final class UnsafeIO { 17 | private static final Logger logger = Logging.get(UnsafeIO.class); 18 | private static final MethodHandle BR_SET_BUFFER; 19 | private static final MethodHandle BW_SET_BUFFER; 20 | private static final MethodHandle BW_SET_NCHARS; 21 | 22 | /** 23 | * Deny all constructions. 24 | */ 25 | private UnsafeIO() { 26 | } 27 | 28 | /** 29 | * Updates underlying buffer of a given {@link BufferedReader}. 30 | * 31 | * @param reader 32 | * Reader to update. 33 | * @param buffer 34 | * Buffer to set. 35 | */ 36 | public static void setReaderBuffer(BufferedReader reader, char[] buffer) { 37 | try { 38 | BR_SET_BUFFER.invokeExact(reader, buffer); 39 | } catch (Throwable t) { 40 | logger.error("Failed to set buffer for reader", t); 41 | throw new AssertionError(t); 42 | } 43 | } 44 | 45 | /** 46 | * Updates underlying buffer of a given {@link BufferedWriter}. 47 | * 48 | * @param writer 49 | * Writer to update. 50 | * @param buffer 51 | * Buffer to set. 52 | */ 53 | public static void setWriterBuffer(BufferedWriter writer, char[] buffer) { 54 | try { 55 | BW_SET_BUFFER.invokeExact(writer, buffer); 56 | BW_SET_NCHARS.invokeExact(writer, buffer.length); 57 | } catch (Throwable t) { 58 | logger.error("Failed to set buffer for writer", t); 59 | throw new AssertionError(t); 60 | } 61 | } 62 | 63 | static { 64 | try { 65 | Lookup lookup = LookupUtil.lookup(); 66 | BR_SET_BUFFER = lookup.findSetter(BufferedReader.class, "cb", char[].class); 67 | BW_SET_BUFFER = lookup.findSetter(BufferedWriter.class, "cb", char[].class); 68 | BW_SET_NCHARS = lookup.findSetter(BufferedWriter.class, "nChars", Integer.TYPE); 69 | } catch (NoSuchFieldException | IllegalAccessException ex) { 70 | throw new ExceptionInInitializerError(ex); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /project/src/main/java/com/tyron/code/project/util/UnsafeUtil.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.util; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import sun.misc.Unsafe; 5 | 6 | import java.lang.reflect.Field; 7 | 8 | /** 9 | * Util to access {@link Unsafe}. 10 | * 11 | * @author xDark 12 | */ 13 | public class UnsafeUtil { 14 | private static final Unsafe UNSAFE; 15 | 16 | /** 17 | * @return The unsafe instance. 18 | */ 19 | @NotNull 20 | public static Unsafe get() { 21 | return UNSAFE; 22 | } 23 | 24 | static { 25 | try { 26 | Unsafe unsafe = null; 27 | for (Field field : Unsafe.class.getDeclaredFields()) { 28 | if (Unsafe.class == field.getType()) { 29 | field.setAccessible(true); 30 | unsafe = (Unsafe) field.get(null); 31 | break; 32 | } 33 | } 34 | if (unsafe == null) 35 | throw new IllegalStateException("Unable to locate unsafe instance"); 36 | UNSAFE = unsafe; 37 | } catch (IllegalAccessException ex) { 38 | throw new ExceptionInInitializerError(ex); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /project/src/test/java/com/tyron/code/project/util/ClassNameUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.tyron.code.project.util; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.List; 6 | 7 | import static com.tyron.code.project.util.ClassNameUtils.*; 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | 10 | class ClassNameUtilsTest { 11 | 12 | @Test 13 | public void testGetFqn() { 14 | assertEquals("com.tyron.test.Test", getFqn("/com/tyron/test/Test.class")); 15 | assertEquals("com.tyron.test.Test", getFqn("com/tyron/test/Test.class")); 16 | assertEquals( "Test", getFqn("Test.class")); 17 | } 18 | 19 | @Test 20 | public void testGetClassName() { 21 | assertEquals( "Test", getClassName("com.tyron.test.Test")); 22 | assertEquals("Test", getClassName("Test")); 23 | } 24 | 25 | @Test 26 | public void testGetQualifiers() { 27 | assertEquals( 28 | List.of("com", "tyron", "test", "Test"), 29 | getAsQualifierList("com.tyron.test.Test") 30 | ); 31 | assertEquals( 32 | List.of("Test"), 33 | getAsQualifierList("Test") 34 | ); 35 | assertEquals( 36 | List.of("Test"), 37 | getAsQualifierList(".Test") 38 | ); 39 | assertEquals( 40 | List.of("com", "tyron", "test"), 41 | getAsQualifierList( 42 | "com.tyron.test.Test.class".substring( 43 | 0, 44 | "com.tyron.test.Test.class".length() - ("Test".length() + ".class".length()) 45 | ) 46 | ) 47 | ); 48 | } 49 | 50 | @Test 51 | public void testGetPackageOnly() { 52 | assertEquals( 53 | "com.tyron.test", 54 | getPackageOnly("com.tyron.test.Test") 55 | ); 56 | } 57 | } -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" 3 | } 4 | rootProject.name = "CodeAssist" 5 | include("completions") 6 | include("project") 7 | include("compiler") 8 | include("deskptop-test") 9 | include("javac") 10 | include("project-impl") 11 | --------------------------------------------------------------------------------