Repository: phodal/shire Branch: main Commit: ef768073548a Files: 834 Total size: 2.2 MB Directory structure: gitextract_jk0y9og_/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── custom.md │ │ └── feature_request.md │ ├── dependabot.yml │ └── workflows/ │ ├── build.yml │ ├── release.yml │ └── run-ui-tests.yml ├── .gitignore ├── .run/ │ ├── Build Plugin.run.xml │ ├── PublishPlugin.run.xml │ ├── Run IDE for UI Tests.run.xml │ ├── Run Plugin.run.xml │ ├── Run Qodana.run.xml │ ├── Run Tests.run.xml │ └── Run Verifications.run.xml ├── .shire/ │ └── docs/ │ ├── context_variable.shire │ ├── hobbit-hole.shire │ ├── on-streamin-done.shire │ └── shire-command.shire ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.gradle.kts ├── core/ │ ├── README.md │ └── src/ │ ├── embeddings/ │ │ └── LocalEmbedding.kt │ ├── main/ │ │ ├── kotlin/ │ │ │ └── com/ │ │ │ └── phodal/ │ │ │ └── shirecore/ │ │ │ ├── ProjectUtil.kt │ │ │ ├── ShireConstants.kt │ │ │ ├── ShireCoreBundle.kt │ │ │ ├── ShireCoroutineScope.kt │ │ │ ├── ShirelangNotifications.kt │ │ │ ├── agent/ │ │ │ │ ├── CustomAgent.kt │ │ │ │ ├── CustomAgentResponseAction.kt │ │ │ │ └── agenttool/ │ │ │ │ ├── AgentToolContext.kt │ │ │ │ ├── AgentToolResult.kt │ │ │ │ ├── browse/ │ │ │ │ │ ├── BrowseTool.kt │ │ │ │ │ ├── DocumentCleaner.kt │ │ │ │ │ └── DocumentContent.kt │ │ │ │ └── ua/ │ │ │ │ ├── BrowserType.kt │ │ │ │ ├── DeviceType.kt │ │ │ │ ├── Pattern.kt │ │ │ │ ├── RandomUserAgent.kt │ │ │ │ ├── Seeds.kt │ │ │ │ └── UserAgentException.kt │ │ │ ├── ast/ │ │ │ │ ├── ComplexityPoint.kt │ │ │ │ ├── ComplexitySink.kt │ │ │ │ ├── ComplexityVisitor.kt │ │ │ │ └── PsiSyntaxCheckingVisitor.kt │ │ │ ├── completion/ │ │ │ │ └── ShireLookupElement.kt │ │ │ ├── config/ │ │ │ │ ├── InteractionType.kt │ │ │ │ ├── ShireActionLocation.kt │ │ │ │ └── interaction/ │ │ │ │ ├── EditorInteractionProvider.kt │ │ │ │ ├── PostFunction.kt │ │ │ │ ├── dto/ │ │ │ │ │ └── CodeCompletionRequest.kt │ │ │ │ └── task/ │ │ │ │ ├── ChatCompletionTask.kt │ │ │ │ ├── CodeCompletionTask.kt │ │ │ │ ├── FileGenerateTask.kt │ │ │ │ ├── InsertUtil.kt │ │ │ │ └── ShireInteractionTask.kt │ │ │ ├── diff/ │ │ │ │ ├── DiffStreamHandler.kt │ │ │ │ ├── DiffStreamService.kt │ │ │ │ ├── EditorComponentInlaysManager.kt │ │ │ │ ├── LICENSE │ │ │ │ ├── VerticalDiffBlock.kt │ │ │ │ └── model/ │ │ │ │ └── StreamDiff.kt │ │ │ ├── function/ │ │ │ │ ├── guard/ │ │ │ │ │ ├── base/ │ │ │ │ │ │ ├── LocalScanner.kt │ │ │ │ │ │ └── ScanResult.kt │ │ │ │ │ ├── model/ │ │ │ │ │ │ ├── SecretPattern.kt │ │ │ │ │ │ └── SecretPatterns.kt │ │ │ │ │ └── scanner/ │ │ │ │ │ └── SecretPatternsScanner.kt │ │ │ │ └── shireql/ │ │ │ │ └── JvmShireQLFuncType.kt │ │ │ ├── llm/ │ │ │ │ ├── ChatMessage.kt │ │ │ │ ├── LlmConfig.kt │ │ │ │ └── LlmProvider.kt │ │ │ ├── middleware/ │ │ │ │ ├── builtin/ │ │ │ │ │ ├── AppendProcessor.kt │ │ │ │ │ ├── DiffProcessor.kt │ │ │ │ │ ├── FormatCodeProcessor.kt │ │ │ │ │ ├── InsertCodeProcessor.kt │ │ │ │ │ ├── InsertNewlineProcessor.kt │ │ │ │ │ ├── OpenFileProcessor.kt │ │ │ │ │ ├── OpenWebpageProcessor.kt │ │ │ │ │ ├── ParseCodeProcessor.kt │ │ │ │ │ ├── ParseCommentProcessor.kt │ │ │ │ │ ├── PatchProcessor.kt │ │ │ │ │ ├── RunCodeProcessor.kt │ │ │ │ │ ├── SaveFileProcessor.kt │ │ │ │ │ ├── ShowWebviewProcessor.kt │ │ │ │ │ ├── TimeMetricProcessor.kt │ │ │ │ │ ├── UpdateEditorTextProcessor.kt │ │ │ │ │ ├── VerifyCodeProcessor.kt │ │ │ │ │ └── ui/ │ │ │ │ │ ├── ActionableWebView.kt │ │ │ │ │ └── WebViewWindow.kt │ │ │ │ ├── post/ │ │ │ │ │ ├── PostProcessor.kt │ │ │ │ │ ├── PostProcessorContext.kt │ │ │ │ │ └── PostProcessorType.kt │ │ │ │ └── select/ │ │ │ │ ├── DefaultPsiElementStrategy.kt │ │ │ │ ├── PsiElementStrategy.kt │ │ │ │ └── SelectElementStrategy.kt │ │ │ ├── project/ │ │ │ │ └── ProjectFileUtil.kt │ │ │ ├── provider/ │ │ │ │ ├── TestingService.kt │ │ │ │ ├── action/ │ │ │ │ │ ├── CustomActionLocationExecutor.kt │ │ │ │ │ ├── TerminalLocationExecutor.kt │ │ │ │ │ └── terminal/ │ │ │ │ │ └── TerminalHandler.kt │ │ │ │ ├── agent/ │ │ │ │ │ └── AgentTool.kt │ │ │ │ ├── codeedit/ │ │ │ │ │ └── CodeModifier.kt │ │ │ │ ├── codemodel/ │ │ │ │ │ ├── ClassStructureProvider.kt │ │ │ │ │ ├── FileStructureProvider.kt │ │ │ │ │ ├── MethodStructureProvider.kt │ │ │ │ │ ├── StructureProvider.kt │ │ │ │ │ ├── VariableStructureProvider.kt │ │ │ │ │ ├── base/ │ │ │ │ │ │ └── FormatableElement.kt │ │ │ │ │ └── model/ │ │ │ │ │ ├── ClassStructure.kt │ │ │ │ │ ├── DirectoryStructure.kt │ │ │ │ │ ├── FileStructure.kt │ │ │ │ │ ├── MethodStructure.kt │ │ │ │ │ └── VariableStructure.kt │ │ │ │ ├── complexity/ │ │ │ │ │ └── ComplexityProvider.kt │ │ │ │ ├── context/ │ │ │ │ │ ├── ActionLocationEditor.kt │ │ │ │ │ ├── BuildSystemProvider.kt │ │ │ │ │ ├── BuildTool.kt │ │ │ │ │ └── LanguageToolchainProvider.kt │ │ │ │ ├── function/ │ │ │ │ │ └── ToolchainFunctionProvider.kt │ │ │ │ ├── http/ │ │ │ │ │ └── HttpHandler.kt │ │ │ │ ├── ide/ │ │ │ │ │ ├── InlineChatProvider.kt │ │ │ │ │ ├── LocationInteractionContext.kt │ │ │ │ │ ├── LocationInteractionProvider.kt │ │ │ │ │ └── ShirePromptBuilder.kt │ │ │ │ ├── impl/ │ │ │ │ │ └── MarkdownPsiContextVariableProvider.kt │ │ │ │ ├── psi/ │ │ │ │ │ ├── PsiCapture.kt │ │ │ │ │ ├── PsiElementDataBuilder.kt │ │ │ │ │ ├── PsiElementStrategyBuilder.kt │ │ │ │ │ └── RelatedClassesProvider.kt │ │ │ │ ├── shire/ │ │ │ │ │ ├── FileCreateService.kt │ │ │ │ │ ├── FileRunService.kt │ │ │ │ │ ├── ProjectRunService.kt │ │ │ │ │ ├── RefactoringTool.kt │ │ │ │ │ ├── RevisionProvider.kt │ │ │ │ │ ├── ShireQLDataProvider.kt │ │ │ │ │ └── ShireSymbolProvider.kt │ │ │ │ ├── sketch/ │ │ │ │ │ └── LanguageSketchProvider.kt │ │ │ │ ├── streaming/ │ │ │ │ │ ├── LoggingStreamingService.kt │ │ │ │ │ ├── OnStreamingService.kt │ │ │ │ │ ├── ProfilingStreamingService.kt │ │ │ │ │ ├── StreamingServiceProvider.kt │ │ │ │ │ └── TimingStreamingService.kt │ │ │ │ └── variable/ │ │ │ │ ├── PsiContextVariableProvider.kt │ │ │ │ ├── ShireQLInterpreter.kt │ │ │ │ ├── ToolchainVariableProvider.kt │ │ │ │ ├── VariableProvider.kt │ │ │ │ ├── impl/ │ │ │ │ │ └── DefaultPsiContextVariableProvider.kt │ │ │ │ └── model/ │ │ │ │ ├── ConditionPsiVariable.kt │ │ │ │ ├── ContextVariable.kt │ │ │ │ ├── PsiContextVariable.kt │ │ │ │ ├── SystemInfoVariable.kt │ │ │ │ ├── ToolchainVariable.kt │ │ │ │ ├── Variable.kt │ │ │ │ └── toolchain/ │ │ │ │ ├── BuildToolchainVariable.kt │ │ │ │ ├── DatabaseToolchainVariable.kt │ │ │ │ ├── SonarqubeVariable.kt │ │ │ │ ├── TerminalToolchainVariable.kt │ │ │ │ └── VcsToolchainVariable.kt │ │ │ ├── psi/ │ │ │ │ ├── CodeSmellCollector.kt │ │ │ │ └── PsiErrorCollector.kt │ │ │ ├── runner/ │ │ │ │ ├── ConfigurationRunner.kt │ │ │ │ ├── RunContext.kt │ │ │ │ ├── RunServiceExt.kt │ │ │ │ ├── RunServiceTask.kt │ │ │ │ ├── RunnerResult.kt │ │ │ │ ├── RunnerResultSeverity.kt │ │ │ │ ├── RunnerStatus.kt │ │ │ │ ├── ShireProcessHandler.kt │ │ │ │ └── console/ │ │ │ │ ├── CustomFlowWrapper.kt │ │ │ │ └── ShireConsoleViewBase.kt │ │ │ ├── schema/ │ │ │ │ ├── SecretPatternFileProvider.kt │ │ │ │ ├── ShireCustomAgentSchemaFileProvider.kt │ │ │ │ ├── ShireEnvFileProvider.kt │ │ │ │ └── ShireJsonSchemaProviderFactory.kt │ │ │ ├── search/ │ │ │ │ ├── algorithm/ │ │ │ │ │ ├── BM25Similarity.kt │ │ │ │ │ ├── JaccardSimilarity.kt │ │ │ │ │ ├── Similarity.kt │ │ │ │ │ └── TfIdf.kt │ │ │ │ ├── function/ │ │ │ │ │ ├── ScoreText.kt │ │ │ │ │ ├── SemanticService.kt │ │ │ │ │ └── SemanticStorageType.kt │ │ │ │ ├── indices/ │ │ │ │ │ ├── DiskSynchronizedEmbeddingSearchIndex.kt │ │ │ │ │ ├── EmbeddingSearchIndex.kt │ │ │ │ │ ├── InMemoryEmbeddingSearchIndex.kt │ │ │ │ │ ├── LocalEmbeddingIndexFileManager.kt │ │ │ │ │ └── LockedSequenceWrapper.kt │ │ │ │ ├── rank/ │ │ │ │ │ ├── LlmReRanker.kt │ │ │ │ │ ├── LostInTheMiddleRanker.kt │ │ │ │ │ └── Reranker.kt │ │ │ │ ├── similar/ │ │ │ │ │ ├── SimilarChunkContext.kt │ │ │ │ │ └── SimilarChunkSearcher.kt │ │ │ │ └── tokenizer/ │ │ │ │ ├── CodeNamingTokenizer.kt │ │ │ │ ├── RegexpTokenizer.kt │ │ │ │ ├── StopwordsBasedTokenizer.kt │ │ │ │ ├── TermSplitter.kt │ │ │ │ └── Tokenizer.kt │ │ │ ├── sketch/ │ │ │ │ ├── LangSketch.kt │ │ │ │ ├── highlight/ │ │ │ │ │ ├── CodeHighlightSketch.kt │ │ │ │ │ ├── EditorFragment.kt │ │ │ │ │ └── toolbar/ │ │ │ │ │ ├── ShireCopyToClipboardAction.kt │ │ │ │ │ ├── ShireInsertCodeAction.kt │ │ │ │ │ ├── ShireLanguageLabelAction.kt │ │ │ │ │ └── ShireRunCodeAction.kt │ │ │ │ ├── lint/ │ │ │ │ │ ├── SketchCodeInspection.kt │ │ │ │ │ └── SketchInspectionError.kt │ │ │ │ └── patch/ │ │ │ │ ├── DiffLangSketch.kt │ │ │ │ ├── DiffLangSketchProvider.kt │ │ │ │ ├── MyApplyPatchFromClipboardDialog.kt │ │ │ │ └── SingleFileDiffView.kt │ │ │ ├── sse/ │ │ │ │ ├── CustomAgentSSEExecutor.kt │ │ │ │ ├── CustomSSEHandler.kt │ │ │ │ └── io/ │ │ │ │ ├── JSONBodyResponseCallback.kt │ │ │ │ ├── OpenAIDto.kt │ │ │ │ ├── ResponseBodyCallback.kt │ │ │ │ ├── SSE.kt │ │ │ │ ├── SSEFormatException.kt │ │ │ │ └── ShireHttpException.kt │ │ │ ├── task/ │ │ │ │ └── Graph.kt │ │ │ ├── ui/ │ │ │ │ ├── CustomProgressBar.kt │ │ │ │ ├── ShirePanelView.kt │ │ │ │ └── input/ │ │ │ │ ├── DarculaNewUIUtil.kt │ │ │ │ ├── ShireChatBoxInput.kt │ │ │ │ ├── ShireCoolBorder.kt │ │ │ │ ├── ShireInputListener.kt │ │ │ │ ├── ShireInputLookupManagerListener.kt │ │ │ │ ├── ShireInputSection.kt │ │ │ │ ├── ShireInputTextField.kt │ │ │ │ └── ShireInputTrigger.kt │ │ │ ├── utils/ │ │ │ │ └── markdown/ │ │ │ │ ├── CodeFence.kt │ │ │ │ ├── CodeFenceLanguage.kt │ │ │ │ ├── MarkdownUtil.kt │ │ │ │ └── PostCodeProcessor.kt │ │ │ └── variable/ │ │ │ ├── frontend/ │ │ │ │ ├── Component.kt │ │ │ │ └── ComponentProvider.kt │ │ │ ├── template/ │ │ │ │ ├── TemplateContext.kt │ │ │ │ └── VariableActionEventDataHolder.kt │ │ │ ├── toolchain/ │ │ │ │ ├── buildsystem/ │ │ │ │ │ └── BuildSystemContext.kt │ │ │ │ ├── refactoring/ │ │ │ │ │ └── RefactorInstElement.kt │ │ │ │ └── unittest/ │ │ │ │ └── AutoTestingPromptContext.kt │ │ │ └── vcs/ │ │ │ ├── ShireFileBranch.kt │ │ │ ├── ShireFileCommit.kt │ │ │ └── ShireGitCommit.kt │ │ └── resources/ │ │ ├── com.phodal.shirecore.xml │ │ ├── messages/ │ │ │ └── ShireCoreBundle.properties │ │ ├── schemas/ │ │ │ ├── shireCustomAgent.schema.json │ │ │ ├── shireEnv.schema.json │ │ │ └── shireSecretPattern.schema.json │ │ ├── secrets/ │ │ │ └── default.shireSecretPattern.yml │ │ └── tokenizers-engine.properties │ └── test/ │ ├── kotlin/ │ │ └── com/ │ │ └── phodal/ │ │ └── shirecore/ │ │ ├── diff/ │ │ │ └── model/ │ │ │ └── StreamDiffTest.kt │ │ ├── guard/ │ │ │ └── model/ │ │ │ └── SecretPatternsScannerTest.kt │ │ ├── middleware/ │ │ │ └── builtin/ │ │ │ └── SaveFileProcessorTest.kt │ │ ├── provider/ │ │ │ └── impl/ │ │ │ └── MarkdownPsiContextVariableProviderTest.kt │ │ ├── search/ │ │ │ ├── TfIdfTest.kt │ │ │ ├── algorithm/ │ │ │ │ ├── BM25SimilarityTest.kt │ │ │ │ └── JaccardSimilarityTest.kt │ │ │ ├── function/ │ │ │ │ └── LocalEmbeddingTest.kt │ │ │ └── tokenizer/ │ │ │ └── TermSplitterTest.kt │ │ ├── task/ │ │ │ └── GraphTest.kt │ │ └── utils/ │ │ └── markdown/ │ │ └── CodeFenceTest.kt │ └── resources/ │ └── META-INF/ │ └── plugin.xml ├── docs/ │ ├── CNAME │ ├── _config.yml │ ├── _includes/ │ │ ├── head_custom.html │ │ └── js/ │ │ └── custom.js │ ├── _sass/ │ │ └── custom/ │ │ └── custom.scss │ ├── cloud/ │ │ ├── cloud.md │ │ ├── http-api-tool.md │ │ └── remote-agent.md │ ├── data-privacy/ │ │ ├── data-privacy.md │ │ ├── guarding-functions.md │ │ └── pipeline-guarding.md │ ├── development/ │ │ ├── design-principle.md │ │ ├── development.md │ │ ├── ide-note.md │ │ ├── language-spec.md │ │ └── shire-sketch.md │ ├── examples/ │ │ ├── auto-test.md │ │ ├── batch-execute.md │ │ ├── cli-copilot.md │ │ ├── code-comment.md │ │ ├── code-refactoring.md │ │ ├── commit-message-gen.md │ │ ├── examples.md │ │ ├── inline-chat.md │ │ ├── multiple-file-edit.md │ │ ├── on-paste-modify.md │ │ └── search.md │ ├── faq.md │ ├── index.md │ ├── lifecycle/ │ │ ├── after-streaming.md │ │ ├── before-streaming.md │ │ ├── lifecycle.md │ │ ├── on-streaming-done.md │ │ └── on-streaming.md │ ├── quick-start.md │ ├── scene/ │ │ ├── ai-for-doc.md │ │ ├── scene.md │ │ └── secondary-research.md │ ├── shire/ │ │ ├── shire-builtin-variable.md │ │ ├── shire-custom-variable.md │ │ ├── shire-env.md │ │ ├── shire-foreign-function.md │ │ ├── shire-hobbit-hole.md │ │ ├── shire-lang.md │ │ ├── shire-template.md │ │ ├── shire-toolchain-function.md │ │ ├── shire-toolchain-variable.md │ │ ├── shire.md │ │ └── when.md │ ├── shireql/ │ │ ├── shire-database.md │ │ ├── shire-ql-basic.md │ │ ├── shire-ql-dependency.md │ │ ├── shire-ql-git.md │ │ ├── shire-ql-psi.md │ │ └── shire-ql.md │ └── workflow/ │ ├── custom-ai-agent.md │ ├── rag-flow.md │ ├── remote-ai-agent.md │ ├── response-routing-function.md │ └── workflow.md ├── gradle/ │ ├── libs.versions.toml │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle-241.properties ├── gradle-243.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── languages/ │ ├── README.md │ ├── shire-go/ │ │ └── src/ │ │ └── main/ │ │ ├── kotlin/ │ │ │ └── com/ │ │ │ └── phodal/ │ │ │ └── shirelang/ │ │ │ └── go/ │ │ │ ├── codemodel/ │ │ │ │ ├── GoClassStructureProvider.kt │ │ │ │ ├── GoFileStructureProvider.kt │ │ │ │ ├── GoMethodStructureProvider.kt │ │ │ │ └── GoVariableStructureProvider.kt │ │ │ ├── util/ │ │ │ │ └── GoPsiUtil.kt │ │ │ └── variable/ │ │ │ ├── GoLanguageProvider.kt │ │ │ └── GoPsiContextVariableProvider.kt │ │ └── resources/ │ │ └── com.phodal.shirelang.go.xml │ ├── shire-java/ │ │ └── src/ │ │ ├── main/ │ │ │ ├── kotlin/ │ │ │ │ └── com/ │ │ │ │ └── phodal/ │ │ │ │ └── shirelang/ │ │ │ │ └── java/ │ │ │ │ ├── archmeta/ │ │ │ │ │ ├── SpringLayerCharacteristic.kt │ │ │ │ │ └── SpringLibrary.kt │ │ │ │ ├── codeedit/ │ │ │ │ │ ├── JavaAutoTestingService.kt │ │ │ │ │ └── JavaCodeModifier.kt │ │ │ │ ├── codemodel/ │ │ │ │ │ ├── JavaClassStructureProvider.kt │ │ │ │ │ ├── JavaFileStructureProvider.kt │ │ │ │ │ ├── JavaMethodStructureProvider.kt │ │ │ │ │ └── JavaVariableStructureProvider.kt │ │ │ │ ├── complexity/ │ │ │ │ │ ├── JavaComplexityProvider.kt │ │ │ │ │ └── JavaLanguageVisitor.kt │ │ │ │ ├── impl/ │ │ │ │ │ ├── JavaElementStrategyBuilder.kt │ │ │ │ │ ├── JavaPsiElementDataBuilder.kt │ │ │ │ │ ├── JavaRefactoringTool.kt │ │ │ │ │ ├── JavaShireQLInterpreter.kt │ │ │ │ │ ├── JavaSymbolProvider.kt │ │ │ │ │ └── JvmBuildSystemProvider.kt │ │ │ │ ├── provider/ │ │ │ │ │ └── JavaRelatedClassesProvider.kt │ │ │ │ ├── toolchain/ │ │ │ │ │ ├── GradleBuildTool.kt │ │ │ │ │ ├── JvmLanguageDetector.kt │ │ │ │ │ ├── JvmRunProjectService.kt │ │ │ │ │ └── MavenBuildTool.kt │ │ │ │ ├── util/ │ │ │ │ │ ├── JavaContextCollection.kt │ │ │ │ │ ├── JavaTestHelper.kt │ │ │ │ │ ├── JavaTypeResolver.kt │ │ │ │ │ └── SimpleClassStructure.kt │ │ │ │ └── variable/ │ │ │ │ ├── JavaLanguageToolchainProvider.kt │ │ │ │ ├── JavaPsiContextVariableProvider.kt │ │ │ │ └── JavaVariableProvider.kt │ │ │ └── resources/ │ │ │ └── com.phodal.shirelang.java.xml │ │ └── test/ │ │ └── kotlin/ │ │ └── com/ │ │ └── phodal/ │ │ └── shirelang/ │ │ └── java/ │ │ ├── complexity/ │ │ │ └── JavaComplexityProviderTest.kt │ │ ├── impl/ │ │ │ ├── JavaBuildSystemProviderTest.kt │ │ │ └── JavaPsiQLInterpreterTest.kt │ │ ├── toolchain/ │ │ │ └── SpringLayerCharacteristicTest.kt │ │ └── variable/ │ │ └── JavaTestHelperTest.kt │ ├── shire-javascript/ │ │ └── src/ │ │ └── main/ │ │ ├── kotlin/ │ │ │ └── com/ │ │ │ └── phodal/ │ │ │ └── shirelang/ │ │ │ └── javascript/ │ │ │ ├── JSTypeResolver.kt │ │ │ ├── codeedit/ │ │ │ │ ├── JSAutoTestingService.kt │ │ │ │ ├── JSFileRunService.kt │ │ │ │ ├── JavaScriptTestCodeModifier.kt │ │ │ │ └── JestCodeModifier.kt │ │ │ ├── codemodel/ │ │ │ │ ├── JavaScriptClassStructureProvider.kt │ │ │ │ ├── JavaScriptFileStructureProvider.kt │ │ │ │ ├── JavaScriptMethodStructureProvider.kt │ │ │ │ └── JavaScriptVariableStructureProvider.kt │ │ │ ├── framework/ │ │ │ │ ├── ReactPage.kt │ │ │ │ └── ReactPsiUtil.kt │ │ │ ├── impl/ │ │ │ │ ├── JavaScriptBuildSystemProvider.kt │ │ │ │ └── TypeScriptRefactoringTool.kt │ │ │ ├── provider/ │ │ │ │ └── JavaScriptRelatedClassesProvider.kt │ │ │ ├── util/ │ │ │ │ ├── JSPsiUtil.kt │ │ │ │ ├── JsDependeciesSnapshot.kt │ │ │ │ └── LanguageApplicationUtil.kt │ │ │ └── variable/ │ │ │ ├── JSPsiContextVariableProvider.kt │ │ │ ├── JavaScriptFrameworks.kt │ │ │ └── JavaScriptLanguageToolchainProvider.kt │ │ └── resources/ │ │ └── com.phodal.shirelang.javascript.xml │ ├── shire-json/ │ │ └── src/ │ │ ├── main/ │ │ │ ├── kotlin/ │ │ │ │ └── com/ │ │ │ │ └── phodal/ │ │ │ │ └── shire/ │ │ │ │ └── json/ │ │ │ │ ├── PsiJsonUtil.kt │ │ │ │ ├── ShireEnvReader.kt │ │ │ │ ├── ShireEnvVariableFiller.kt │ │ │ │ ├── ShireEnvironmentIndex.kt │ │ │ │ ├── ShireEnvironmentInputFilter.kt │ │ │ │ ├── ShireStringsExternalizer.kt │ │ │ │ └── llm/ │ │ │ │ └── LlmEnv.kt │ │ │ └── resources/ │ │ │ └── com.phodal.shire.json.xml │ │ └── test/ │ │ └── kotlin/ │ │ └── com/ │ │ └── phodal/ │ │ └── shire/ │ │ └── json/ │ │ └── ShireEnvVariableFillerTest.kt │ ├── shire-kotlin/ │ │ └── src/ │ │ └── main/ │ │ ├── kotlin/ │ │ │ └── com/ │ │ │ └── phodal/ │ │ │ └── shirelang/ │ │ │ └── kotlin/ │ │ │ ├── KotlinContextCollector.kt │ │ │ ├── KotlinPsiUtil.kt │ │ │ ├── KotlinTypeResolver.kt │ │ │ ├── codemodel/ │ │ │ │ ├── KotlinClassStructureProvider.kt │ │ │ │ ├── KotlinFileStructureProvider.kt │ │ │ │ ├── KotlinMethodStructureProvider.kt │ │ │ │ └── KotlinVariableStructureProvider.kt │ │ │ ├── complexity/ │ │ │ │ ├── KotlinComplexityProvider.kt │ │ │ │ └── KotlinLanguageVisitor.kt │ │ │ ├── impl/ │ │ │ │ └── KotlinRefactoringTool.kt │ │ │ ├── provider/ │ │ │ │ ├── KotlinAutoTestService.kt │ │ │ │ ├── KotlinPsiElementDataBuilder.kt │ │ │ │ └── KotlinRelatedClassesProvider.kt │ │ │ └── variable/ │ │ │ ├── KotlinLanguageToolchainProvider.kt │ │ │ └── KotlinPsiContextVariableProvider.kt │ │ └── resources/ │ │ └── com.phodal.shirelang.kotlin.xml │ ├── shire-markdown/ │ │ └── src/ │ │ ├── main/ │ │ │ ├── kotlin/ │ │ │ │ └── com/ │ │ │ │ └── phodal/ │ │ │ │ └── shirelang/ │ │ │ │ └── markdown/ │ │ │ │ ├── MarkdownNode.kt │ │ │ │ └── MarkdownPsiCapture.kt │ │ │ └── resources/ │ │ │ └── com.phodal.shirelang.markdown.xml │ │ └── test/ │ │ └── kotlin/ │ │ └── com/ │ │ └── phodal/ │ │ └── shirelang/ │ │ └── markdown/ │ │ └── MarkdownPsiCaptureTest.kt │ ├── shire-proto/ │ │ └── src/ │ │ └── main/ │ │ ├── kotlin/ │ │ │ └── com/ │ │ │ └── phodal/ │ │ │ └── shirelang/ │ │ │ └── proto/ │ │ │ ├── codemodel/ │ │ │ │ ├── ProtoClassStructureProvider.kt │ │ │ │ └── ProtoFileStructureProvider.kt │ │ │ ├── provider/ │ │ │ │ ├── ShireProtoPsiVariableProvider.kt │ │ │ │ └── ShireProtoUtils.kt │ │ │ └── variable/ │ │ │ └── ProtobufToolchainProvider.kt │ │ └── resources/ │ │ └── com.phodal.shirelang.proto.xml │ └── shire-python/ │ └── src/ │ ├── main/ │ │ ├── kotlin/ │ │ │ └── com/ │ │ │ └── phodal/ │ │ │ └── shirelang/ │ │ │ └── python/ │ │ │ ├── provider/ │ │ │ │ ├── ShirePythonAutoTesting.kt │ │ │ │ ├── ShirePythonPsiVariableProvider.kt │ │ │ │ └── ShirePythonRunService.kt │ │ │ └── util/ │ │ │ ├── PyTestUtil.kt │ │ │ └── PythonPsiUtil.kt │ │ └── resources/ │ │ └── com.phodal.shirelang.python.xml │ └── test/ │ ├── kotlin/ │ │ └── com/ │ │ └── phodal/ │ │ └── shirelang/ │ │ └── python/ │ │ └── util/ │ │ ├── PyTestUtilTest.kt │ │ └── PythonPsiUtilTest.kt │ └── resources/ │ └── META-INF/ │ └── plugin.xml ├── qodana.yml ├── settings.gradle.kts ├── shirelang/ │ ├── .gitignore │ ├── README.md │ ├── editor/ │ │ └── ShireVariablePanel.kt │ └── src/ │ ├── main/ │ │ ├── grammar/ │ │ │ ├── ShireLexer.flex │ │ │ └── ShireParser.bnf │ │ ├── kotlin/ │ │ │ └── com/ │ │ │ └── phodal/ │ │ │ └── shirelang/ │ │ │ ├── ShireActionStartupActivity.kt │ │ │ ├── ShireAstFactory.kt │ │ │ ├── ShireBundle.kt │ │ │ ├── ShireFileType.kt │ │ │ ├── ShireIcons.kt │ │ │ ├── ShireInCommentInjector.kt │ │ │ ├── ShireLanguage.kt │ │ │ ├── ShireLanguageInjector.kt │ │ │ ├── ShireTypedHandler.kt │ │ │ ├── actions/ │ │ │ │ ├── GlobalShireFileChangesProvider.kt │ │ │ │ ├── ShireFileChangesProvider.kt │ │ │ │ ├── ShireFileModifier.kt │ │ │ │ ├── ShireRunFileAction.kt │ │ │ │ ├── base/ │ │ │ │ │ ├── DynamicShireActionConfig.kt │ │ │ │ │ ├── DynamicShireActionService.kt │ │ │ │ │ └── validator/ │ │ │ │ │ └── WhenConditionValidator.kt │ │ │ │ ├── console/ │ │ │ │ │ └── ShireConsoleAction.kt │ │ │ │ ├── context/ │ │ │ │ │ ├── ShireContextMenuAction.kt │ │ │ │ │ └── ShireContextMenuActionGroup.kt │ │ │ │ ├── copyPaste/ │ │ │ │ │ └── ShireCopyPastePreProcessor.kt │ │ │ │ ├── database/ │ │ │ │ │ └── ShireDatabaseAction.kt │ │ │ │ ├── external/ │ │ │ │ │ └── ShireSonarLintAction.kt │ │ │ │ ├── input/ │ │ │ │ │ ├── ShireInputBoxAction.kt │ │ │ │ │ └── inlay/ │ │ │ │ │ ├── ComponentInlaysContainer.kt │ │ │ │ │ ├── CustomInputBox.kt │ │ │ │ │ ├── InlayPanel.kt │ │ │ │ │ └── InlayRenderer.kt │ │ │ │ ├── intention/ │ │ │ │ │ ├── ShireIntentionAction.kt │ │ │ │ │ ├── ShireIntentionHelper.kt │ │ │ │ │ ├── ShireIntentionsActionGroup.kt │ │ │ │ │ └── ui/ │ │ │ │ │ └── CustomPopupStep.kt │ │ │ │ ├── template/ │ │ │ │ │ └── NewShireFileAction.kt │ │ │ │ ├── terminal/ │ │ │ │ │ └── ShireTerminalAction.kt │ │ │ │ └── vcs/ │ │ │ │ ├── ShireVcsActionGroup.kt │ │ │ │ ├── ShireVcsLogAction.kt │ │ │ │ └── ShireVcsSingleAction.kt │ │ │ ├── comment/ │ │ │ │ └── ShireCommenter.kt │ │ │ ├── compiler/ │ │ │ │ ├── ast/ │ │ │ │ │ ├── ExpressionBuiltInMethod.kt │ │ │ │ │ ├── ForeignFunction.kt │ │ │ │ │ ├── FrontMatterType.kt │ │ │ │ │ ├── LineInfo.kt │ │ │ │ │ ├── ShireExpression.kt │ │ │ │ │ ├── ShirePsiQueryStatement.kt │ │ │ │ │ ├── TaskRoutes.kt │ │ │ │ │ ├── action/ │ │ │ │ │ │ ├── DirectAction.kt │ │ │ │ │ │ ├── PatternAction.kt │ │ │ │ │ │ └── RuleBasedPatternAction.kt │ │ │ │ │ ├── hobbit/ │ │ │ │ │ │ ├── HobbitHole.kt │ │ │ │ │ │ └── base/ │ │ │ │ │ │ └── Smials.kt │ │ │ │ │ └── patternaction/ │ │ │ │ │ ├── PatternActionFunc.kt │ │ │ │ │ ├── PatternActionFuncDef.kt │ │ │ │ │ ├── PatternProcessor.kt │ │ │ │ │ └── VariableTransform.kt │ │ │ │ ├── execute/ │ │ │ │ │ ├── FunctionStatementProcessor.kt │ │ │ │ │ ├── PatternActionProcessor.kt │ │ │ │ │ ├── PatternFuncProcessor.kt │ │ │ │ │ ├── command/ │ │ │ │ │ │ ├── BrowseShireCommand.kt │ │ │ │ │ │ ├── CommitShireCommand.kt │ │ │ │ │ │ ├── DatabaseShireCommand.kt │ │ │ │ │ │ ├── DirShireCommand.kt │ │ │ │ │ │ ├── FileFuncShireCommand.kt │ │ │ │ │ │ ├── FileShireCommand.kt │ │ │ │ │ │ ├── GotoShireCommand.kt │ │ │ │ │ │ ├── LocalSearchShireCommand.kt │ │ │ │ │ │ ├── OpenShireCommand.kt │ │ │ │ │ │ ├── PatchShireCommand.kt │ │ │ │ │ │ ├── PrintShireCommand.kt │ │ │ │ │ │ ├── RefactorShireCommand.kt │ │ │ │ │ │ ├── RelatedSymbolInsCommand.kt │ │ │ │ │ │ ├── RevShireCommand.kt │ │ │ │ │ │ ├── RipgrepSearchShireCommand.kt │ │ │ │ │ │ ├── RunShireCommand.kt │ │ │ │ │ │ ├── ShellShireCommand.kt │ │ │ │ │ │ ├── ShireCommand.kt │ │ │ │ │ │ ├── StructureShireCommand.kt │ │ │ │ │ │ ├── SymbolShireCommand.kt │ │ │ │ │ │ ├── WriteShireCommand.kt │ │ │ │ │ │ ├── search/ │ │ │ │ │ │ │ ├── RipgrepOutputProcessor.kt │ │ │ │ │ │ │ └── RipgrepSearcher.kt │ │ │ │ │ │ └── status/ │ │ │ │ │ │ ├── ShireCommandStatus.kt │ │ │ │ │ │ └── ShireCommandStatusListener.kt │ │ │ │ │ ├── processor/ │ │ │ │ │ │ ├── ApprovalExecuteProcessor.kt │ │ │ │ │ │ ├── BatchProcessor.kt │ │ │ │ │ │ ├── CaptureProcessor.kt │ │ │ │ │ │ ├── CrawlProcessor.kt │ │ │ │ │ │ ├── ExecuteProcessor.kt │ │ │ │ │ │ ├── ForeignFunctionProcessor.kt │ │ │ │ │ │ ├── JsonPathProcessor.kt │ │ │ │ │ │ ├── RedactProcessor.kt │ │ │ │ │ │ ├── ThreadProcessor.kt │ │ │ │ │ │ ├── TokenizerProcessor.kt │ │ │ │ │ │ ├── shell/ │ │ │ │ │ │ │ └── ShireShellCommandRunner.kt │ │ │ │ │ │ └── ui/ │ │ │ │ │ │ └── PendingApprovalPanel.kt │ │ │ │ │ ├── searcher/ │ │ │ │ │ │ └── PatternSearcher.kt │ │ │ │ │ ├── shireql/ │ │ │ │ │ │ ├── ShireDateSchema.kt │ │ │ │ │ │ ├── ShireQLProcessor.kt │ │ │ │ │ │ └── ShireQLSchema.kt │ │ │ │ │ └── variable/ │ │ │ │ │ ├── ShireQLFromType.kt │ │ │ │ │ ├── ShireQLVariableBuilder.kt │ │ │ │ │ └── VariableEvaluator.kt │ │ │ │ ├── parser/ │ │ │ │ │ ├── HobbitHoleParser.kt │ │ │ │ │ ├── ShireAstQLParser.kt │ │ │ │ │ ├── ShireError.kt │ │ │ │ │ ├── ShireParsedResult.kt │ │ │ │ │ └── ShireSyntaxAnalyzer.kt │ │ │ │ ├── template/ │ │ │ │ │ ├── ShireVariableTemplateCompiler.kt │ │ │ │ │ └── TemplateCompiler.kt │ │ │ │ └── variable/ │ │ │ │ ├── CompositeVariableProvider.kt │ │ │ │ ├── VariableTable.kt │ │ │ │ └── resolver/ │ │ │ │ ├── CompositeVariableResolver.kt │ │ │ │ ├── ContextVariableResolver.kt │ │ │ │ ├── PsiContextVariableResolver.kt │ │ │ │ ├── SystemInfoVariableResolver.kt │ │ │ │ ├── ToolchainVariableResolver.kt │ │ │ │ ├── UserCustomVariableResolver.kt │ │ │ │ └── base/ │ │ │ │ ├── VariableResolver.kt │ │ │ │ └── VariableResolverContext.kt │ │ │ ├── completion/ │ │ │ │ ├── ShireCompletionContributor.kt │ │ │ │ ├── UserCustomCompletionContributor.kt │ │ │ │ ├── dataprovider/ │ │ │ │ │ ├── BuiltinCommand.kt │ │ │ │ │ ├── BuiltinRefactorCommand.kt │ │ │ │ │ ├── CustomCommand.kt │ │ │ │ │ ├── FileFunc.kt │ │ │ │ │ └── ToolHubVariable.kt │ │ │ │ └── provider/ │ │ │ │ ├── AgentToolOverviewCompletion.kt │ │ │ │ ├── BuiltinCommandCompletion.kt │ │ │ │ ├── CodeFenceLanguageCompletion.kt │ │ │ │ ├── CustomAgentCompletion.kt │ │ │ │ ├── CustomCommandCompletion.kt │ │ │ │ ├── FileFunctionProvider.kt │ │ │ │ ├── FileReferenceLanguageProvider.kt │ │ │ │ ├── HobbitHoleKeyCompletion.kt │ │ │ │ ├── HobbitHoleValueCompletion.kt │ │ │ │ ├── PostProcessorCompletion.kt │ │ │ │ ├── ProjectRunProvider.kt │ │ │ │ ├── QueryStatementCompletion.kt │ │ │ │ ├── RefactoringFuncProvider.kt │ │ │ │ ├── RevisionReferenceLanguageProvider.kt │ │ │ │ ├── SymbolReferenceLanguageProvider.kt │ │ │ │ ├── VariableCompletionProvider.kt │ │ │ │ └── WhenConditionCompletionProvider.kt │ │ │ ├── debugger/ │ │ │ │ ├── ShireBreakpoint.kt │ │ │ │ ├── ShireDebugProcess.kt │ │ │ │ ├── ShireDebugRunner.kt │ │ │ │ ├── ShireDebugSettings.kt │ │ │ │ ├── ShireDebuggerEditorsProvider.kt │ │ │ │ ├── ShireStackFrame.kt │ │ │ │ ├── ShireSuspendContext.kt │ │ │ │ └── snapshot/ │ │ │ │ ├── ShireFileSnapshot.kt │ │ │ │ ├── UserCustomVariableSnapshot.kt │ │ │ │ └── VariableSnapshotRecorder.kt │ │ │ ├── documentation/ │ │ │ │ └── ShireDocumentationProvider.kt │ │ │ ├── editor/ │ │ │ │ ├── FileFilterPopup.kt │ │ │ │ ├── ShireFileEditorWithPreview.kt │ │ │ │ ├── ShirePreviewEditor.kt │ │ │ │ ├── ShirePreviewEditorProvider.kt │ │ │ │ ├── ShireSnapshotViewPanel.kt │ │ │ │ ├── ShireSplitEditorProvider.kt │ │ │ │ └── ShireVariableViewPanel.kt │ │ │ ├── folding/ │ │ │ │ └── ShireFoldingBuilder.kt │ │ │ ├── formatter/ │ │ │ │ └── ShireFormattingModelBuilder.kt │ │ │ ├── highlight/ │ │ │ │ ├── ShireErrorFilter.kt │ │ │ │ ├── ShireHighlightingAnnotator.kt │ │ │ │ ├── ShireSyntaxHighlighter.kt │ │ │ │ ├── ShireSyntaxHighlighterFactory.kt │ │ │ │ └── braces/ │ │ │ │ ├── ShireBraceMatcher.kt │ │ │ │ └── ShireQuoteHandler.kt │ │ │ ├── index/ │ │ │ │ └── ShireIdentifierIndex.kt │ │ │ ├── lexer/ │ │ │ │ ├── ShireLexerAdapter.kt │ │ │ │ └── ShireTokenType.kt │ │ │ ├── lints/ │ │ │ │ └── ShireDuplicateAgentInspection.kt │ │ │ ├── markdown/ │ │ │ │ └── CodeFenceLanguageAliases.kt │ │ │ ├── navigation/ │ │ │ │ └── ShireGotoDeclarationHandler.kt │ │ │ ├── parser/ │ │ │ │ ├── CodeBlockElement.kt │ │ │ │ ├── CodeBlockLiteralTextEscaper.kt │ │ │ │ ├── PatternElement.kt │ │ │ │ ├── ShireGrepFuncCall.kt │ │ │ │ ├── ShireParserDefinition.kt │ │ │ │ └── ShireTokenTypeSets.kt │ │ │ ├── provider/ │ │ │ │ ├── ChatBoxShireFileCreateService.kt │ │ │ │ ├── ShireActionPromptBuilder.kt │ │ │ │ ├── ShireLanguageToolchainProvider.kt │ │ │ │ ├── ShirePsiVariableProvider.kt │ │ │ │ └── ShireToolchainFunctionProvider.kt │ │ │ ├── psi/ │ │ │ │ ├── ShireElementType.kt │ │ │ │ ├── ShireFile.kt │ │ │ │ └── ShireFileStub.kt │ │ │ ├── run/ │ │ │ │ ├── ShireBeforeRunProviderDelegate.kt │ │ │ │ ├── ShireConfiguration.kt │ │ │ │ ├── ShireConfigurationType.kt │ │ │ │ ├── ShireConsoleView.kt │ │ │ │ ├── ShirePluginDisposable.kt │ │ │ │ ├── ShireProcessAdapter.kt │ │ │ │ ├── ShireProgramRunner.kt │ │ │ │ ├── ShireRunConfigurationProducer.kt │ │ │ │ ├── ShireRunConfigurationProfileState.kt │ │ │ │ ├── ShireRunLineMarkersProvider.kt │ │ │ │ ├── ShireRunListener.kt │ │ │ │ ├── ShireSettingsEditor.kt │ │ │ │ ├── ShireSyntaxLineMarkerProvider.kt │ │ │ │ ├── executor/ │ │ │ │ │ ├── CustomRemoteAgentLlmExecutor.kt │ │ │ │ │ ├── ShireDefaultLlmExecutor.kt │ │ │ │ │ └── ShireLlmExecutor.kt │ │ │ │ ├── flow/ │ │ │ │ │ ├── ShireConversationService.kt │ │ │ │ │ ├── ShireProcessContext.kt │ │ │ │ │ └── ShireProcessProcessor.kt │ │ │ │ └── runner/ │ │ │ │ ├── ShireRunner.kt │ │ │ │ └── ShireRunnerContext.kt │ │ │ ├── runner/ │ │ │ │ ├── ShellFileRunService.kt │ │ │ │ └── ShireFileRunService.kt │ │ │ └── thirdparty/ │ │ │ └── ShireSonarLintToolWindowListener.kt │ │ └── resources/ │ │ ├── com.phodal.shirelang.xml │ │ ├── docs/ │ │ │ └── agentExamples/ │ │ │ ├── browse.shire │ │ │ ├── commit.shire │ │ │ ├── file-func.shire │ │ │ ├── file.shire │ │ │ ├── patch.shire │ │ │ ├── refactor.shire │ │ │ ├── rev.shire │ │ │ ├── run.shire │ │ │ ├── shell.shire │ │ │ ├── symbol.shire │ │ │ └── write.shire │ │ ├── fileTemplates/ │ │ │ └── internal/ │ │ │ └── Shire Action.shire.ft │ │ ├── inspectionDescriptions/ │ │ │ └── ShireDuplicateAgent.html │ │ └── messages/ │ │ └── ShireBundle.properties │ └── test/ │ ├── kotlin/ │ │ └── com/ │ │ └── phodal/ │ │ └── shirelang/ │ │ ├── ParsingNormalTest.kt │ │ ├── ParsingRealWorldTest.kt │ │ ├── ShireCompileTest.kt │ │ ├── ShireLifecycleTest.kt │ │ ├── ShirePatternPipelineTest.kt │ │ ├── ShireQueryExpressionTest.kt │ │ ├── compiler/ │ │ │ ├── hobbit/ │ │ │ │ └── execute/ │ │ │ │ ├── CrawlProcessorTest.kt │ │ │ │ ├── JsonPathProcessorTest.kt │ │ │ │ └── ShireShellRunnerTest.kt │ │ │ └── parser/ │ │ │ └── HobbitHoleParserTest.kt │ │ ├── impl/ │ │ │ └── DefaultShireSymbolProvider.kt │ │ ├── regression/ │ │ │ ├── ShireCompileTest.kt │ │ │ └── ShireTokenizerTest.kt │ │ └── run/ │ │ └── ShireConfigurationTest.kt │ ├── resources/ │ │ └── META-INF/ │ │ └── plugin.xml │ └── testData/ │ ├── parser/ │ │ ├── AfterStream.shire │ │ ├── AfterStream.txt │ │ ├── AutoCommand.shire │ │ ├── AutoCommand.txt │ │ ├── AutoRefactor.shire │ │ ├── AutoRefactor.txt │ │ ├── BasicTest.shire │ │ ├── BasicTest.txt │ │ ├── BlockStartOnly.shire │ │ ├── BlockStartOnly.txt │ │ ├── BrowseWeb.shire │ │ ├── BrowseWeb.txt │ │ ├── CommandAndSymbol.shire │ │ ├── CommandAndSymbol.txt │ │ ├── ComplexLangId.shire │ │ ├── ComplexLangId.txt │ │ ├── CustomFunctions.shire │ │ ├── CustomFunctions.txt │ │ ├── EmptyCodeFence.shire │ │ ├── EmptyCodeFence.txt │ │ ├── FrontMatter.shire │ │ ├── FrontMatter.txt │ │ ├── IfExpression.shire │ │ ├── IfExpression.txt │ │ ├── JavaAnnotation.shire │ │ ├── JavaAnnotation.txt │ │ ├── JavaHelloWorld.shire │ │ ├── JavaHelloWorld.txt │ │ ├── MarkdownCompatible.shire │ │ ├── MarkdownCompatible.txt │ │ ├── MultipleFMVariable.shire │ │ ├── MultipleFMVariable.txt │ │ ├── PatternAction.shire │ │ ├── PatternAction.txt │ │ ├── PatternCaseAction.shire │ │ ├── PatternCaseAction.txt │ │ ├── ShireFmObject.shire │ │ ├── ShireFmObject.txt │ │ ├── ShirePsiQueryExpression.shire │ │ ├── ShirePsiQueryExpression.txt │ │ ├── SingleComment.shire │ │ ├── SingleComment.txt │ │ ├── VariableAccess.shire │ │ ├── VariableAccess.txt │ │ ├── WhenCondition.shire │ │ └── WhenCondition.txt │ └── realworld/ │ ├── AfterStreamingOnly.shire │ ├── AfterStreamingOnly.txt │ ├── Autotest.shire │ ├── Autotest.txt │ ├── ContentTee.shire │ ├── ContentTee.txt │ ├── LifeCycle.shire │ ├── LifeCycle.txt │ ├── LoginCommit.shire │ ├── LoginCommit.txt │ ├── OnPaste.shire │ ├── OnPaste.txt │ ├── OutputInVariable.shire │ ├── OutputInVariable.txt │ ├── WhenAfterStreaming.shire │ └── WhenAfterStreaming.txt ├── src/ │ ├── description.html │ ├── main/ │ │ ├── kotlin/ │ │ │ └── com/ │ │ │ └── phodal/ │ │ │ └── shire/ │ │ │ ├── ShireIdeaIcons.kt │ │ │ ├── ShireMainBundle.kt │ │ │ ├── inline/ │ │ │ │ ├── ShireGutterIconRenderer.kt │ │ │ │ ├── ShireInlineChatPanel.kt │ │ │ │ ├── ShireInlineChatProvider.kt │ │ │ │ └── ShireInlineChatService.kt │ │ │ ├── llm/ │ │ │ │ └── OpenAILikeProvider.kt │ │ │ ├── marketplace/ │ │ │ │ ├── ShireToolWindowFactory.kt │ │ │ │ ├── model/ │ │ │ │ │ └── ShirePackage.kt │ │ │ │ ├── ui/ │ │ │ │ │ ├── IconButtonTableCellEditor.kt │ │ │ │ │ ├── IconButtonTableCellRenderer.kt │ │ │ │ │ ├── MarketplaceView.kt │ │ │ │ │ └── ShireMarketplaceTableView.kt │ │ │ │ └── util/ │ │ │ │ └── ShireDownloader.kt │ │ │ └── settings/ │ │ │ ├── ShireLlmSettingsConfigurable.kt │ │ │ ├── ShireSettingUi.kt │ │ │ └── ShireSettingsState.kt │ │ └── resources/ │ │ ├── META-INF/ │ │ │ ├── docker-contrib.xml │ │ │ ├── json-contrib.xml │ │ │ ├── openrewrite-contrib.xml │ │ │ ├── plugin.xml │ │ │ └── wiremock-contrib.xml │ │ ├── intentionDescriptions/ │ │ │ ├── ShireIntention/ │ │ │ │ ├── after.txt.template │ │ │ │ ├── before.txt.template │ │ │ │ └── description.html │ │ │ └── ShireIntentionHelper/ │ │ │ ├── after.txt.template │ │ │ ├── before.txt.template │ │ │ └── description.html │ │ └── messages/ │ │ └── ShireMainBundle.properties │ └── test/ │ └── testData/ │ └── rename/ │ ├── foo.xml │ └── foo_after.xml └── toolsets/ ├── README.md ├── database/ │ └── src/ │ └── main/ │ ├── kotlin/ │ │ └── com/ │ │ └── phodal/ │ │ └── shire/ │ │ └── database/ │ │ ├── DatabaseSchemaAssistant.kt │ │ ├── SQLExecutor.kt │ │ ├── SqlContextBuilder.kt │ │ └── provider/ │ │ ├── DatabaseFunctionProvider.kt │ │ ├── DatabaseToolchainProvider.kt │ │ └── DatabaseVariableProvider.kt │ └── resources/ │ └── com.phodal.shire.database.xml ├── docker/ │ └── src/ │ └── main/ │ ├── kotlin/ │ │ └── com/ │ │ └── phodal/ │ │ └── shire/ │ │ └── docker/ │ │ └── DockerContextProvider.kt │ └── resources/ │ └── com.phodal.shire.docker.xml ├── git/ │ └── src/ │ └── main/ │ ├── kotlin/ │ │ └── com/ │ │ └── phodal/ │ │ └── shire/ │ │ └── git/ │ │ ├── DiffSimplifier.kt │ │ ├── VcsPrompting.kt │ │ └── provider/ │ │ ├── GitActionLocationEditor.kt │ │ ├── GitDataContext.kt │ │ ├── GitFunctionProvider.kt │ │ ├── GitQLDataProvider.kt │ │ ├── GitRepositoryCommitter.kt │ │ ├── GitRevisionProvider.kt │ │ └── GitToolchainVariableProvider.kt │ └── resources/ │ └── com.phodal.shire.git.xml ├── httpclient/ │ ├── README.md │ └── src/ │ ├── main/ │ │ ├── kotlin/ │ │ │ └── com/ │ │ │ └── phodal/ │ │ │ └── shire/ │ │ │ └── httpclient/ │ │ │ ├── HttpClientFileRunService.kt │ │ │ ├── converter/ │ │ │ │ ├── CUrlConverter.kt │ │ │ │ └── RestClientUtil.kt │ │ │ └── handler/ │ │ │ └── CUrlHttpHandler.kt │ │ └── resources/ │ │ └── com.phodal.shire.httpclient.xml │ └── test/ │ ├── kotlin/ │ │ └── com/ │ │ └── phodal/ │ │ ├── shire/ │ │ │ └── httpclient/ │ │ │ └── converter/ │ │ │ └── CUrlConverterTest.kt │ │ └── shirecore/ │ │ └── agent/ │ │ └── agenttool/ │ │ └── browse/ │ │ └── BrowseToolTest.kt │ └── resources/ │ └── META-INF/ │ └── plugin.xml ├── mermaid/ │ └── src/ │ └── main/ │ ├── kotlin/ │ │ └── com/ │ │ └── phodal/ │ │ └── shire/ │ │ └── mermaid/ │ │ └── provider/ │ │ └── MermaidSketchProvider.kt │ └── resources/ │ └── com.phodal.shire.mermaid.xml ├── mock/ │ ├── README.md │ └── src/ │ └── main/ │ ├── kotlin/ │ │ └── com/ │ │ └── phodal/ │ │ └── shire/ │ │ └── mock/ │ │ └── provider/ │ │ ├── WiremockFunction.kt │ │ └── WiremockFunctionProvider.kt │ └── resources/ │ └── com.phodal.shire.mock.xml ├── openrewrite/ │ ├── README.md │ └── src/ │ └── main/ │ ├── kotlin/ │ │ └── com/ │ │ └── phodal/ │ │ └── shire/ │ │ └── openrewrite/ │ │ └── OpenRewriteFileRunService.kt │ └── resources/ │ └── com.phodal.shire.openrewrite.xml ├── plantuml/ │ ├── README.md │ └── src/ │ └── main/ │ ├── kotlin/ │ │ └── com/ │ │ └── phodal/ │ │ └── shire/ │ │ └── plantuml/ │ │ ├── PlantUmlSketchProvider.kt │ │ └── PlantUmlToolchainProvider.kt │ └── resources/ │ └── com.phodal.shire.plantuml.xml ├── sonarqube/ │ ├── README.md │ └── src/ │ └── main/ │ ├── kotlin/ │ │ └── com/ │ │ └── phodal/ │ │ └── shire/ │ │ └── sonarqube/ │ │ ├── SonarLintProvider.kt │ │ └── SonarLintVariableProvider.kt │ └── resources/ │ └── com.phodal.shire.sonarqube.xml ├── terminal/ │ └── src/ │ └── main/ │ ├── kotlin/ │ │ └── com/ │ │ └── phodal/ │ │ └── shire/ │ │ └── terminal/ │ │ ├── ShireTerminalExecutor.kt │ │ ├── TerminalToolchainVariableProvider.kt │ │ └── sketch/ │ │ ├── ShellUtil.kt │ │ └── TerminalLangSketchProvider.kt │ └── resources/ │ └── com.phodal.shire.terminal.xml └── uitest/ └── src/ └── main/ ├── kotlin/ │ └── com/ │ └── phodal/ │ └── shire/ │ └── uitest/ │ └── provider/ │ └── PlaywrightFileRunService.kt └── resources/ └── com.phodal.shire.uitest.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari] - Version [e.g. 22] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/custom.md ================================================ --- name: Custom issue template about: Describe this issue template's purpose here. title: '' labels: '' assignees: '' --- ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/dependabot.yml ================================================ # Dependabot configuration: # https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: # Maintain dependencies for Gradle dependencies - package-ecosystem: "gradle" directory: "/" schedule: interval: "daily" # Maintain dependencies for GitHub Actions - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" ================================================ FILE: .github/workflows/build.yml ================================================ # GitHub Actions Workflow is created for testing and preparing the plugin release in the following steps: # - Validate Gradle Wrapper. # - Run 'test' and 'verifyPlugin' tasks. # - Run Qodana inspections. # - Run the 'buildPlugin' task and prepare artifact for further tests. # - Run the 'runPluginVerifier' task. # - Create a draft release. # # The workflow is triggered on push and pull_request events. # # GitHub Actions reference: https://help.github.com/en/actions # ## JBIJPPTPL name: Build on: # Trigger the workflow on pushes to only the 'main' branch (this avoids duplicate checks being run e.g., for dependabot pull requests) push: branches: [ main ] # Trigger the workflow on any pull request pull_request: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: # Prepare environment and build the plugin build: name: Build runs-on: ubuntu-latest outputs: version: ${{ steps.properties.outputs.version }} changelog: ${{ steps.properties.outputs.changelog }} pluginVerifierHomeDir: ${{ steps.properties.outputs.pluginVerifierHomeDir }} steps: # Free GitHub Actions Environment Disk Space - name: Maximize Build Space uses: jlumbroso/free-disk-space@main with: tool-cache: false large-packages: false # Check out the current repository - name: Fetch Sources uses: actions/checkout@v4 # Validate wrapper - name: Gradle Wrapper Validation uses: gradle/actions/wrapper-validation@v4 # Set up Java environment for the next steps - name: Setup Java uses: actions/setup-java@v4 with: distribution: jetbrains java-version: 17 # Setup Gradle - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 # Set environment variables - name: Export Properties id: properties shell: bash run: | PROPERTIES="$(./gradlew properties --console=plain -q)" VERSION="$(echo "$PROPERTIES" | grep "^version:" | cut -f2- -d ' ')" CHANGELOG="$(./gradlew getChangelog --unreleased --no-header --console=plain -q)" echo "version=$VERSION" >> $GITHUB_OUTPUT echo "pluginVerifierHomeDir=~/.pluginVerifier" >> $GITHUB_OUTPUT echo "changelog<> $GITHUB_OUTPUT echo "$CHANGELOG" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT # Build plugin - name: Build plugin run: ./gradlew buildPlugin # Prepare plugin archive content for creating artifact - name: Prepare Plugin Artifact id: artifact shell: bash run: | cd ${{ github.workspace }}/build/distributions FILENAME=`ls *.zip` unzip "$FILENAME" -d content echo "filename=${FILENAME:0:-4}" >> $GITHUB_OUTPUT # Store already-built plugin as an artifact for downloading - name: Upload artifact uses: actions/upload-artifact@v4 with: name: ${{ steps.artifact.outputs.filename }} path: ./build/distributions/content/*/* # Run tests and upload a code coverage report test: name: Test needs: [ build ] runs-on: ubuntu-latest steps: # Check out the current repository - name: Fetch Sources uses: actions/checkout@v4 # Set up Java environment for the next steps - name: Setup Java uses: actions/setup-java@v4 with: distribution: jetbrains java-version: 17 # Setup Gradle - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 with: gradle-home-cache-cleanup: true # Run tests - name: Run Tests run: ./gradlew check # Collect Tests Result of failed tests - name: Collect Tests Result if: ${{ failure() }} uses: actions/upload-artifact@v4 with: name: tests-result path: ${{ github.workspace }}/build/reports/tests # Upload the Kover report to CodeCov - name: Upload Code Coverage Report uses: codecov/codecov-action@v4 with: files: ${{ github.workspace }}/build/reports/kover/report.xml # Run plugin structure verification along with IntelliJ Plugin Verifier verify: name: Verify plugin needs: [ build ] runs-on: ubuntu-latest steps: # Free GitHub Actions Environment Disk Space - name: Maximize Build Space uses: jlumbroso/free-disk-space@main with: tool-cache: false large-packages: false # Check out the current repository - name: Fetch Sources uses: actions/checkout@v4 # Set up Java environment for the next steps - name: Setup Java uses: actions/setup-java@v4 with: distribution: zulu java-version: 17 # Setup Gradle - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 with: gradle-home-cache-cleanup: true # Cache Plugin Verifier IDEs - name: Setup Plugin Verifier IDEs Cache uses: actions/cache@v4 with: path: ${{ needs.build.outputs.pluginVerifierHomeDir }}/ides key: plugin-verifier-${{ hashFiles('build/listProductsReleases.txt') }} # Run Verify Plugin task and IntelliJ Plugin Verifier tool - name: Run Plugin Verification tasks run: ./gradlew verifyPlugin -Dplugin.verifier.home.dir=${{ needs.build.outputs.pluginVerifierHomeDir }} # Collect Plugin Verifier Result - name: Collect Plugin Verifier Result if: ${{ always() }} uses: actions/upload-artifact@v4 with: name: pluginVerifier-result path: ${{ github.workspace }}/build/reports/pluginVerifier # Prepare a draft release for GitHub Releases page for the manual verification # If accepted and published, release workflow would be triggered releaseDraft: name: Release draft if: github.event_name != 'pull_request' needs: [ build, test, verify ] runs-on: ubuntu-latest permissions: contents: write steps: # Check out the current repository - name: Fetch Sources uses: actions/checkout@v4 # Remove old release drafts by using the curl request for the available releases with a draft flag - name: Remove Old Release Drafts env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh api repos/{owner}/{repo}/releases \ --jq '.[] | select(.draft == true) | .id' \ | xargs -I '{}' gh api -X DELETE repos/{owner}/{repo}/releases/{} # Create a new release draft which is not publicly visible and requires manual acceptance - name: Create Release Draft env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh release create "v${{ needs.build.outputs.version }}" \ --draft \ --title "v${{ needs.build.outputs.version }}" \ --notes "$(cat << 'EOM' ${{ needs.build.outputs.changelog }} EOM )" ================================================ FILE: .github/workflows/release.yml ================================================ # GitHub Actions Workflow created for handling the release process based on the draft release prepared with the Build workflow. # Running the publishPlugin task requires all following secrets to be provided: PUBLISH_TOKEN, PRIVATE_KEY, PRIVATE_KEY_PASSWORD, CERTIFICATE_CHAIN. # See https://plugins.jetbrains.com/docs/intellij/plugin-signing.html for more information. name: Publish JetBrains on: release: types: [prereleased, released] jobs: # Prepare and publish the plugin to JetBrains Marketplace repository release: name: Publish Plugin runs-on: ubuntu-latest strategy: fail-fast: true matrix: platform-version: [ 241 ] env: ORG_GRADLE_PROJECT_platformVersion: ${{ matrix.platform-version }} permissions: contents: write pull-requests: write steps: # Free GitHub Actions Environment Disk Space - name: Maximize Build Space uses: jlumbroso/free-disk-space@main with: tool-cache: false large-packages: false # Check out current repository - name: Fetch Sources uses: actions/checkout@v4 with: submodules: 'true' ref: ${{ github.event.release.tag_name }} # Set up Java environment for the next steps - name: Setup Java uses: actions/setup-java@v4 with: distribution: zulu java-version: 17 # Setup Gradle - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 with: gradle-home-cache-cleanup: true # Set environment variables - name: Export Properties id: properties shell: bash run: | CHANGELOG="$(cat << 'EOM' | sed -e 's/^[[:space:]]*$//g' -e '/./,$!d' ${{ github.event.release.body }} EOM )" echo "changelog<> $GITHUB_OUTPUT echo "$CHANGELOG" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT # Update Unreleased section with the current release note - name: Patch Changelog if: ${{ steps.properties.outputs.changelog != '' }} env: CHANGELOG: ${{ steps.properties.outputs.changelog }} run: | ./gradlew patchChangelog --release-note="$CHANGELOG" # Publish the plugin to JetBrains Marketplace - name: Publish Plugin env: PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} CERTIFICATE_CHAIN: ${{ secrets.CERTIFICATE_CHAIN }} PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} PRIVATE_KEY_PASSWORD: ${{ secrets.PRIVATE_KEY_PASSWORD }} run: ./gradlew :publishPlugin # Upload artifact as a release asset - name: Upload Release Asset env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: gh release upload ${{ github.event.release.tag_name }} ./build/distributions/* ================================================ FILE: .github/workflows/run-ui-tests.yml ================================================ # GitHub Actions Workflow for launching UI tests on Linux, Windows, and Mac in the following steps: # - Prepare and launch IDE with your plugin and robot-server plugin, which is needed to interact with the UI. # - Wait for IDE to start. # - Run UI tests with a separate Gradle task. # # Please check https://github.com/JetBrains/intellij-ui-test-robot for information about UI tests with IntelliJ Platform. # # Workflow is triggered manually. name: Run UI Tests on: workflow_dispatch jobs: testUI: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: include: - os: ubuntu-latest runIde: | export DISPLAY=:99.0 Xvfb -ac :99 -screen 0 1920x1080x16 & gradle runIdeForUiTests & - os: windows-latest runIde: start gradlew.bat :plugin:runIdeForUiTests - os: macos-latest runIde: ./gradlew :plugin:runIdeForUiTests & steps: # Check out current repository - name: Fetch Sources uses: actions/checkout@v4 # Set up Java environment for the next steps - name: Setup Java uses: actions/setup-java@v4 with: distribution: zulu java-version: 17 # Setup Gradle - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 with: gradle-home-cache-cleanup: true # Run IDEA prepared for UI testing - name: Run IDE run: ${{ matrix.runIde }} # Wait for IDEA to be started - name: Health Check uses: jtalk/url-health-check-action@v4 with: url: http://127.0.0.1:8082 max-attempts: 15 retry-delay: 30s # Run tests - name: Tests run: ./gradlew test ================================================ FILE: .gitignore ================================================ .gradle .idea .qodana build .kotlin/ .shire-output .intellijPlatform recording.jsonl **/bin/ ================================================ FILE: .run/Build Plugin.run.xml ================================================ false true false false ================================================ FILE: .run/PublishPlugin.run.xml ================================================ true true false false ================================================ FILE: .run/Run IDE for UI Tests.run.xml ================================================ true true false false ================================================ FILE: .run/Run Plugin.run.xml ================================================ true true false false ================================================ FILE: .run/Run Qodana.run.xml ================================================ true true false false ================================================ FILE: .run/Run Tests.run.xml ================================================ true true false true ================================================ FILE: .run/Run Verifications.run.xml ================================================ true true false ================================================ FILE: .shire/docs/context_variable.shire ================================================ --- name: "Context Variable" description: "Here is a description of the action." interaction: RunPanel variables: "contextVariable": /ContextVariable\.kt/ { cat } "psiContextVariable": /PsiContextVariable\.kt/ { cat } onStreamingEnd: { parseCode | saveFile("docs/shire/shire-builtin-variable.md") } --- 根据如下的信息,编写对应的 ContextVariable 相关信息的 markdown 文档。 你所需要包含的 ContextVariable 信息如下: ``` $contextVariable ``` 你所需要包含的 PsiContextVariable 信息如下: ``` $psiContextVariable ``` 要求 1. Variable 信息使用表格展示,表格的第一列是变量名,第二列是描述,表格的第一行是表头,表头的第一列是 `变量名`,第二列是 `描述`。 要求 2. 变量名应该取自 `variableName` field,即采用 camelCase 命名法。 要求 3. 你应该输出的格式是 markdown 格式,包含 front matter 和正文。 返回示例格式如下: ```markdown \-\-\- layout: default title: Shire Context Variable parent: Shire Language nav_order: 5 \-\-\- ## Context Variable ## PsiContextVariable ``` 请将内容放到上述的 markdown 模板中 。 ================================================ FILE: .shire/docs/hobbit-hole.shire ================================================ --- name: "Hobbit Hole" description: "Here is a description of the action." interaction: RunPanel variables: "currentCode": /HobbitHole\.kt/ { cat } "testCode": /ShireCompileTest\.kt/ { cat } onStreamingEnd: { saveFile("docs/shire/shire-hobbit-hole.md") } --- 我有一份用户手册写得不好,需要你从用户容易阅读的角度,重新写一份。 根据如下的代码用例、文档,编写对应的 HobbitHole 相关信息的 markdown 文档。 现有代码: $currentCode 代码用例如下: $testCode 要求: 1. 尽详细介绍 HobbitHole 的相关信息和示例。 2. 请按现有的文档 Heading 方式编写,并去除非必要的代码。 ================================================ FILE: .shire/docs/on-streamin-done.shire ================================================ --- name: "On Streaming Done" description: "Here is a description of the action." interaction: RunPanel variables: "currentDoc": /on-streaming-done.md/ { cat } "currentCode": /BuiltinPostHandler\.kt/ { cat } onStreamingEnd: { saveFile("docs/lifecycle/on-streaming-done.md") } --- 根据如下当前的最新代码,更新现有的文档,请返回更新后的文档内容。 最新的代码如下: $currentCode 你需要更新的文档如下: $currentDoc 要求: 1. 使用表格展示内置的后处理器 2. 粘贴必要的代码(如果有的话) 请重新整理文档内容,使其符合最新的代码。 ================================================ FILE: .shire/docs/shire-command.shire ================================================ --- name: "Shire Command" description: "Here is a description of the action." interaction: RunPanel variables: "currentDoc": /shire-command-template\.md/ { cat } "currentCode": /BuiltinCommand\.kt/ { cat } onStreamingEnd: { saveFile("docs/shire/shire-command-template.md") } --- 根据如下当前的最新代码,更新现有的文档,请返回更新后的文档内容。 最新的代码如下: $currentCode 你需要更新的文档如下: $currentDoc 请重新整理文档内容,使其符合最新的代码。 ================================================ FILE: CHANGELOG.md ================================================ # [](https://github.com/phodal/shire/compare/v1.3.4...v) (2025-02-05) ## [Unreleased] ## [1.3.4](https://github.com/phodal/shire/compare/v1.3.3...v[1.3.4]) (2025-02-05) ### Features * **command:** add isApplicable method to ShireCommand ([4857af8](https://github.com/phodal/shire/commit/4857af8de1afb8301881ecaf71c5f0fed3145bd5)) * **commands:** add commandName property to all ShireCommand implementations ([08fc7f3](https://github.com/phodal/shire/commit/08fc7f38a14151d2b9d8444829d21065812758b7)) * **commands:** enhance command descriptions and add enableInSketch flag ([0f88b46](https://github.com/phodal/shire/commit/0f88b4661598efb0a8577d47a45cc827e251e958)) * **database:** add schema function and simplify toolchain provider ([05acdae](https://github.com/phodal/shire/commit/05acdaedd0312df00d08a99c7a8180208af0f3b7)) * **search:** add ripgrep search command ([bafff1b](https://github.com/phodal/shire/commit/bafff1b3747ef7d9cc4cc9073d3987a36adeff6c)) * **terminal:** add terminal sketch provider for bash support ([1148dc8](https://github.com/phodal/shire/commit/1148dc868a3c8cc71ac98be4caca00eac6500a28)) ## [1.3.3](https://github.com/phodal/shire/compare/v1.3.2...v[1.3.3]) (2025-01-14) ### Bug Fixes * **compiler:** update file validation in LocalSearchInsCommand ([fb26400](https://github.com/phodal/shire/commit/fb264003c10e968d8177a1c11d2543e651c9d1f8)) * **run-code:** wrap scratch file deletion in runWriteAction ([5271cd7](https://github.com/phodal/shire/commit/5271cd7072d1ac33535c07ecaeda5fb229e1edfd)) ### Features * **commands:** add local search, related symbol, and open file commands ([7e7de3b](https://github.com/phodal/shire/commit/7e7de3b5174e8b6f9959ad5ceff28684a37d426e)) * **editor:** Add file selection support in preview editor ([07140f3](https://github.com/phodal/shire/commit/07140f3597d46a62a08dcf8feb1e7fe7361fe507)) * **kotlin:** add KotlinPsiContextVariableProvider ([512cb3a](https://github.com/phodal/shire/commit/512cb3a11c088ac9dee1791ef28afc8e2dc75792)) * **markdown:** add support for Shire code fence parsing ([b72753c](https://github.com/phodal/shire/commit/b72753c50581c4eb0cb24acefecda3907d0dad39)) * **parser:** add support for `DIR` command to list directory contents ([8d0ddc9](https://github.com/phodal/shire/commit/8d0ddc940e36245f6e049e60a5e34afe9a87a3dc)) * **streaming:** integrate OnStreamingService into ShireInlineChat ([23858b1](https://github.com/phodal/shire/commit/23858b103a00d3b11abb62741ebb3c045a5a45b6)) ## [1.3.2](https://github.com/phodal/shire/compare/v1.3.1...v[1.3.2]) (2025-01-09) ### Bug Fixes * **debugger:** restrict debug/run execution based on executor ID [#183](https://github.com/phodal/shire/issues/183) ([47d2b71](https://github.com/phodal/shire/commit/47d2b7106a3b94d2cffcc94428b23ce8cb62d885)) ### Features * **debugger:** add debug tab layout and session listener [#183](https://github.com/phodal/shire/issues/183) ([2053470](https://github.com/phodal/shire/commit/20534702c693ba2a72e3e8d22ab68b5672658fbd)) * **debugger:** add Shire debugger support [#183](https://github.com/phodal/shire/issues/183) ([3b1fd51](https://github.com/phodal/shire/commit/3b1fd51d6de03ba8e41b60201e6e7b2d00596b78)) * **debugger:** enhance debug process with variable snapshot support [#183](https://github.com/phodal/shire/issues/183) ([184e691](https://github.com/phodal/shire/commit/184e69186e765f727bcd1bbfe71c8fa29c71e034)) * **debugger:** enhance debugger with breakpoint handling and UI improvements [#183](https://github.com/phodal/shire/issues/183) ([743f4ca](https://github.com/phodal/shire/commit/743f4ca1c2f8181e7edb6271265c051b0faca580)) * **debugger:** enhance stack frame presentation with custom variables [#183](https://github.com/phodal/shire/issues/183) ([bfc1f10](https://github.com/phodal/shire/commit/bfc1f10ce65ced92b28e821c97316e24cb0e378d)) ### Reverts * Revert "refactor(debugger): remove unused ShireDebugSettings file #183" ([c7f6f14](https://github.com/phodal/shire/commit/c7f6f1424298d5d0c2ae0f54a5ceb670a2110698)), closes [#183](https://github.com/phodal/shire/issues/183) * Revert "refactor(debugger): remove unused ShireDebugSettings configuration" ([358a343](https://github.com/phodal/shire/commit/358a34383d75ed7706d2d49cfbe9a6e4a650ad83)) ## [1.3.1](https://github.com/phodal/shire/compare/v1.3.0...v[1.3.1]) (2025-01-06) ### Bug Fixes * **build:** disable prepareTestSandbox task ([0140675](https://github.com/phodal/shire/commit/014067564ce8801c1b5a020c9200e160e7c3d4b2)) * **code-highlight:** normalize line separators in text replacement [#157](https://github.com/phodal/shire/issues/157) ([1a4ddd4](https://github.com/phodal/shire/commit/1a4ddd4f458e2c6d36d1e6b23f82ac993dfd5379)) * **compatibility:** refactor database session handling ([0d4885e](https://github.com/phodal/shire/commit/0d4885e701d7c1c0da99d4bacb174f8da50c6eb6)) * **compatibility:** remove deprecated client property setting ([f0b2774](https://github.com/phodal/shire/commit/f0b27741779d7e93195304928a88dda4f38ab466)) * **editor:** add missing row for highlight sketch [#178](https://github.com/phodal/shire/issues/178) ([3d9f6cb](https://github.com/phodal/shire/commit/3d9f6cb17496f8fd0322f7c957da186b0ff4a7a8)) * **editor:** set PlainTextLanguage for CodeHighlightSketch [#157](https://github.com/phodal/shire/issues/157) ([6fd404f](https://github.com/phodal/shire/commit/6fd404f04b53c5a844352a6c9f8bf60141f3cf72)) * **editor:** update sample file label format [#178](https://github.com/phodal/shire/issues/178) ([8c68528](https://github.com/phodal/shire/commit/8c68528ce1f3b93e7345247552ce3d12ccfafb5d)) * **injection:** change error log to warn for language injection ([6cf0cda](https://github.com/phodal/shire/commit/6cf0cda4048f31d6e1f4a768148509fc7e250704)) * **input:** ensure safe removal of elements from listModel ([a433c42](https://github.com/phodal/shire/commit/a433c428b260cf4cdab3bd173e6d95030758f48b)) * **input:** Resolved close button functionality in chat box ([5d66d68](https://github.com/phodal/shire/commit/5d66d688d1574cbfbdbe919095b3f022909dbdc4)) * **input:** skip invalid psiFile in ShireInput ([c3c49a8](https://github.com/phodal/shire/commit/c3c49a8137503a21998ae687d79700c7b5c54603)) * **input:** skip invalid psiFile in ShireInput ([0b0f011](https://github.com/phodal/shire/commit/0b0f0110d2cdd84dca09a8c6445833902d51699f)) * **python:** restore PythonCore plugin and simplify PsiUtil check ([63f73e5](https://github.com/phodal/shire/commit/63f73e5102eb05def5b85c88a670cad7481b0bde)) * **tests:** update file extension and add error handling ([eae3a8c](https://github.com/phodal/shire/commit/eae3a8c0d7eeba6f540e3f19576a88ef9038e9c8)) * **ui:** execute table update on UI thread ([9bec8f3](https://github.com/phodal/shire/commit/9bec8f363a11ef824d7516168a8739b92bfe072d)) ### Features * **chat:** add inline chat provider support [#157](https://github.com/phodal/shire/issues/157) ([594d985](https://github.com/phodal/shire/commit/594d985c19388b716f6133163cd4e07e24e396a6)) * **chat:** add prompt method to inline chat service [#157](https://github.com/phodal/shire/issues/157) ([ab09b82](https://github.com/phodal/shire/commit/ab09b822be0af908d5441731d2a19a9a4c56d32d)) * **chat:** enhance inline chat UI and interaction [#157](https://github.com/phodal/shire/issues/157) ([c85ff46](https://github.com/phodal/shire/commit/c85ff467ada569b28ef0c6acb434130cd1665736)) * **compiler:** add database command support [#271](https://github.com/phodal/shire/issues/271) ([6f079b4](https://github.com/phodal/shire/commit/6f079b4b6cebc53d1064f0d4f6dbf1cb02ed427d)) * **compiler:** add ResolvableVariableSnapshot for variable tracking [#178](https://github.com/phodal/shire/issues/178) ([22131dd](https://github.com/phodal/shire/commit/22131dd3a890e54d62763b02653eb6c30215ec62)) * **compiler:** add ResolvableVariableSnapshot for variable tracking [#183](https://github.com/phodal/shire/issues/183) ([c6f319a](https://github.com/phodal/shire/commit/c6f319a06817d9cf512d4511cc3e299e82362984)) * **completion:** enhance custom agent completion with auto-insert ([146ecc4](https://github.com/phodal/shire/commit/146ecc4222bf0dfcc59eb933a93cb13877d4d9a0)) * **database:** add SQL query execution support [#171](https://github.com/phodal/shire/issues/171) ([660eb67](https://github.com/phodal/shire/commit/660eb674116ec5b73a1347871977f2a8271d270a)) * **editor:** add document listener for real-time updates [#157](https://github.com/phodal/shire/issues/157) ([68c88ba](https://github.com/phodal/shire/commit/68c88ba638628c14876503f43c35da746d4a22f5)) * **editor:** add gutter icon handler for editor selections [#157](https://github.com/phodal/shire/issues/157) ([bf85b08](https://github.com/phodal/shire/commit/bf85b0802a7604ddce409dc1f67affc88e213f58)) * **editor:** add ShireFileEditorProvider for custom file editing [#178](https://github.com/phodal/shire/issues/178) ([a309063](https://github.com/phodal/shire/commit/a3090638501c360afeebaf505222328d8d9c1959)) * **editor:** add ShirePreviewEditorProvider and enhance editor creation [#157](https://github.com/phodal/shire/issues/157) ([99058b6](https://github.com/phodal/shire/commit/99058b6b03e6749addc3823c1224381c75de4419)) * **editor:** add ShireVariablePanel for variable display [#157](https://github.com/phodal/shire/issues/157) ([ff245f5](https://github.com/phodal/shire/commit/ff245f5f927e14aee99d5574aafec2ed8c159947)) * **editor:** enhance variable debug panel UI and functionality [#157](https://github.com/phodal/shire/issues/157) ([c51b388](https://github.com/phodal/shire/commit/c51b388178b370be375ea9d6e048c4dbe2ad5bdb)) * **editor:** improve UI and async handling ([42e350f](https://github.com/phodal/shire/commit/42e350fdfe3147c73ffab7216490b8a16e0bc7a8)), closes [#178](https://github.com/phodal/shire/issues/178) * **editor:** replace ShireFileEditor with ShireSplitEditor [#157](https://github.com/phodal/shire/issues/157) ([4b51084](https://github.com/phodal/shire/commit/4b51084becc30eb0c2235cda3f1ab2d9bb1336b0)) * **editor:** replace variable panel with table and add refresh action [#157](https://github.com/phodal/shire/issues/157) ([1dca503](https://github.com/phodal/shire/commit/1dca503ed88d91bc6fd0b816ef350f17cf163eb0)) * **inline-chat:** add inline chat service and integrate with gutter icon [#157](https://github.com/phodal/shire/issues/157) ([933e8d7](https://github.com/phodal/shire/commit/933e8d79e00eca2eced45f419b757381ad5cac17)) * **inline-chat:** add LLM integration and input handling [#157](https://github.com/phodal/shire/issues/157) ([13b49e2](https://github.com/phodal/shire/commit/13b49e2f59da1d4538d16d065daba19702b983cd)) * **inline-chat:** enhance inline chat panel with improved UI and state management [#157](https://github.com/phodal/shire/issues/157) ([a9cfd60](https://github.com/phodal/shire/commit/a9cfd60982a105c0995bb6f1cff73f6005e2b837)) * **inline-chat:** enhance prompt handling with dynamic actions [#157](https://github.com/phodal/shire/issues/157) ([97ce772](https://github.com/phodal/shire/commit/97ce772b52f0f81cf42532c2b179a7537340dcbf)) * **java:** add test file lookup for related classes ([680c804](https://github.com/phodal/shire/commit/680c8040b1947b40dfcac7e367da20cd1a19397b)) * **kotlin:** add Kotlin language support for testing, refactoring, and related classes ([9f55993](https://github.com/phodal/shire/commit/9f5599354f8f34fb13ae3245dc81b49f51524e4c)) * **parser:** add DATABASE command to code block checks [#171](https://github.com/phodal/shire/issues/171) ([8339428](https://github.com/phodal/shire/commit/83394289f9ae052d815bda627cf6b89911369f4b)) * **prompt:** add ShirePromptBuilder for dynamic prompt generation [#157](https://github.com/phodal/shire/issues/157) ([df07fcc](https://github.com/phodal/shire/commit/df07fcc4109dbd4506c5c913f111b3e1db837e54)) * **runner:** add streaming service support in ShireRunner [#157](https://github.com/phodal/shire/issues/157) ([58124a3](https://github.com/phodal/shire/commit/58124a3061f64f292c7e40182d651bd80383639c)) * **runner:** add support for custom editor in ShireRunner [#178](https://github.com/phodal/shire/issues/178) ([dd139d2](https://github.com/phodal/shire/commit/dd139d25ee5ba75193010cc7c5da82fd6ab2b874)) * **sonarlint:** move SonarLint listener to thirdparty package [#157](https://github.com/phodal/shire/issues/157) ([b3c1530](https://github.com/phodal/shire/commit/b3c15304acd7ba22fe335dac8c096b9945cc985e)) * **sse:** enhance custom request body handling ([5a424fb](https://github.com/phodal/shire/commit/5a424fbf5dfc65cc78a718b38bc4089c7669591c)) * **ui:** adjust editor and panel border styles [#157](https://github.com/phodal/shire/issues/157) ([ae76259](https://github.com/phodal/shire/commit/ae762595c719dca22456ac5349a54b1c86e604cf)) * **ui:** enhance border styling and add submit button [#157](https://github.com/phodal/shire/issues/157) ([982db35](https://github.com/phodal/shire/commit/982db35ba49ff2032cefb3b24ea506c363128f06)) ### Reverts * Revert "chore(gradle): update Gradle wrapper to 8.12 and try to resolve GitHub Action issue and add SPDX license" ([fcad6ba](https://github.com/phodal/shire/commit/fcad6ba533cbb0bdce9e07fb334ea826df5f3e74)) ## [1.2.4](https://github.com/phodal/shire/compare/v1.2.3...v[1.2.4]) (2024-12-26) ### Bug Fixes * **input:** skip unsupported languages in selectionChanged [#170](https://github.com/phodal/shire/issues/170) ([cd182d3](https://github.com/phodal/shire/commit/cd182d3fd91a3ae945024e11c420561bf337d548)) ### Features * **compiler:** add file path comment in code block output [#170](https://github.com/phodal/shire/issues/170) ([81a93dd](https://github.com/phodal/shire/commit/81a93dd295f9f9d53663d90549e55ffe06b7e253)) * **completion:** add priority to file reference completion elements ([c97be93](https://github.com/phodal/shire/commit/c97be93ef4fe596214ab3ab8d7fced8e927407e2)) * **completion:** add ShireCompletionLookupActionProvider and ShireLookupElement ([8ff9ee3](https://github.com/phodal/shire/commit/8ff9ee3669eed108d497338719a23fad3c79f9e5)) * **completion:** enhance lookup functionality and refactor dependencies ([38b2f3d](https://github.com/phodal/shire/commit/38b2f3d13a8036a5d0e51efb782b48c29c96896f)) * **core:** add related classes provider and lookup functionality ([e97e828](https://github.com/phodal/shire/commit/e97e82831925c5daece6688a8a3c16813731c0af)) * **core:** add relative file path and caching for related classes [#170](https://github.com/phodal/shire/issues/170) ([58858b9](https://github.com/phodal/shire/commit/58858b913c056397a175437c862fb9ca38b47c15)) * **diff:** add openDiffView method and refactor diff panel creation ([acf2245](https://github.com/phodal/shire/commit/acf22455d8e99be3ba43873cc57d87aa54b88c1d)) * **diff:** enhance diff view and prevent duplicate entries ([61795f2](https://github.com/phodal/shire/commit/61795f2dd02b5ebb49fdcb592050deba11c354f6)) * **diff:** try to add for local compare view ([8d6e4c1](https://github.com/phodal/shire/commit/8d6e4c1e39427c0230c9c780e0222d14d59a0d4c)) * **folding:** add file command support to folding builder ([b502605](https://github.com/phodal/shire/commit/b5026055ca824d493a542f552739e6557e595485)) * **input:** add editor and related listeners to ShireInput [#170](https://github.com/phodal/shire/issues/170) ([9e71d1e](https://github.com/phodal/shire/commit/9e71d1e6a7cc774e8714d20adac8920376449704)) * **shirelang:** add support for STRUCTURE command [#170](https://github.com/phodal/shire/issues/170) ([be8084e](https://github.com/phodal/shire/commit/be8084ef487fc403d547d540b0fbc6e2dd24e087)) * **ui:** add element list and append text functionality ([fb5c61d](https://github.com/phodal/shire/commit/fb5c61dca54d2c374836035df2f153d988ece929)) * **ui:** extract LookupManagerListener to separate file [#170](https://github.com/phodal/shire/issues/170) ([6514ce9](https://github.com/phodal/shire/commit/6514ce9fe1b72ce17f892f939917432f7233188d)) ## [1.2.3](https://github.com/phodal/shire/compare/v1.2.2...v[1.2.3]) (2024-12-23) ### Bug Fixes * **core:** handle missing Shire language and FileCreateService ([dcece27](https://github.com/phodal/shire/commit/dcece27bdbd97cbdb05bee4637d4fbe3cc146538)) * fix Compatibility issue for 243 ([81b5d5f](https://github.com/phodal/shire/commit/81b5d5f002c206d349525e0ff241b64ec538c58a)) ### Features * **shire:** add FileCreateService and integrate with ShireInput ([50e698e](https://github.com/phodal/shire/commit/50e698ecd49c057dc5c3d86d870e14e84292e433)) * **ui:** add stop button and refactor document listener ([c544688](https://github.com/phodal/shire/commit/c544688ad3b8a148d26a89959d49954aec70ce2c)) ## [1.2.2](https://github.com/phodal/shire/compare/v1.2.1...v[1.2.2]) (2024-12-23) ### Bug Fixes * **core:** Unregister the shortcut to ensure it works the second time ([5381851](https://github.com/phodal/shire/commit/5381851ed60e406ad9a8ed657a9a067986beb6cd)) * **llm:** NPE in streamSSE ([07a900c](https://github.com/phodal/shire/commit/07a900c4009d5b5eae122f31d9b5577222e87f7f)) * **run:** 修复ShireProgramRunner的ID获取问题并优化代码结构 ([b4aaabe](https://github.com/phodal/shire/commit/b4aaabe49c3bc235ddd471e4e6641bc4acb25d1a)) ### Features * **CodeHighlight:** enhance editor with line number handling [#159](https://github.com/phodal/shire/issues/159) ([4a416f4](https://github.com/phodal/shire/commit/4a416f43203b3ce78a22a4ed60ce6c9fe5b7c891)) * **compiler:** add line number functionality [#159](https://github.com/phodal/shire/issues/159) ([471d710](https://github.com/phodal/shire/commit/471d7107d6dfd6b3f7b9f2652488bf35e26d3770)) * **lifycycle:** Add TimingStreamingService and refactor provider interface [#160](https://github.com/phodal/shire/issues/160) ([656915e](https://github.com/phodal/shire/commit/656915e5031368436035cad803fe496cd4999adf)) * **marketplace:** replace LightVirtualFile with ScratchRootType for temp files [#165](https://github.com/phodal/shire/issues/165) ([11c1082](https://github.com/phodal/shire/commit/11c1082117b71f620fa190e29efb1d1615abfd48)) * **run:** add ShireConsoleView and ShireProcessAdapter ([617ae40](https://github.com/phodal/shire/commit/617ae40dcc0be7d584bf835ab5527b831a9cfd1f)) * **runner:** add ShireFileRunService and enhance run configuration ([e08c831](https://github.com/phodal/shire/commit/e08c83140c2f40185cd36b5f16ef3d99fdc462ca)), closes [#165](https://github.com/phodal/shire/issues/165) * **shire:** add chatbox functionality and update file references ([d045cd3](https://github.com/phodal/shire/commit/d045cd319108d2361a3ab20631a13091e6a5b6ca)) * **streaming:** add logging streaming service and refactor streaming API [#160](https://github.com/phodal/shire/issues/160) ([ecbd342](https://github.com/phodal/shire/commit/ecbd342ab3da934c9f2b12d01f423a171c4a2505)) * **streaming:** Add profiling capability and enhance documentation [#160](https://github.com/phodal/shire/issues/160) ([8f299e2](https://github.com/phodal/shire/commit/8f299e2a5bd1f185632648883b43aaffd4d5da86)) * **streaming:** add StreamingServiceProvider interface and OnStreamingService [#160](https://github.com/phodal/shire/issues/160) ([8997e96](https://github.com/phodal/shire/commit/8997e966299f8b181bbaeab008894e30a66f76d1)) * **streaming:** enhance OnStreamingService with registration and notification [#160](https://github.com/phodal/shire/issues/160) ([b7cc22e](https://github.com/phodal/shire/commit/b7cc22e539c55a21ef808e59b70b75e7b449233b)) * **ui:** add custom progress bar and input section components [#165](https://github.com/phodal/shire/issues/165) ([6531f4c](https://github.com/phodal/shire/commit/6531f4c882e54ca9235a9308c2651b934894885a)) * **ui:** add scratch file management to ShireInput ([510965e](https://github.com/phodal/shire/commit/510965eda304107ab0d87a588da76b88d139784c)) * **ui:** add ShireInputTextField component and integrate with ShireInputSection [#165](https://github.com/phodal/shire/issues/165) ([7f71c0d](https://github.com/phodal/shire/commit/7f71c0d55d698a646b195f9e9466af99aa1751f4)) * **utils:** add PostCodeProcessor for markdown code formatting ([11cd109](https://github.com/phodal/shire/commit/11cd109ba816f0210a3582e68cd7b9608ae06f6e)) * **variable-resolver:** add support for selection with line numbers [#159](https://github.com/phodal/shire/issues/159) ([cdf84c4](https://github.com/phodal/shire/commit/cdf84c4262dc2928de8e57de50b0b91f65fc62cf)) * **viewer:** Add cancel functionality to progress bar ([d947425](https://github.com/phodal/shire/commit/d9474257b9a5cf8b1846ca4ebdce66eb591b2cf5)) ## [1.2.1](https://github.com/phodal/shire/compare/v1.2.0...v[1.2.1]) (2024-12-12) ### Bug Fixes * **diff:** correct line selection logic in DiffStreamHandler [#153](https://github.com/phodal/shire/issues/153) ([511f7f1](https://github.com/phodal/shire/commit/511f7f1d63591cd1b74684fcb78ca6ffb665ef00)) * **diff:** optimize StreamDiff handling and add DiffLineType conversion [#153](https://github.com/phodal/shire/issues/153) ([b103f58](https://github.com/phodal/shire/commit/b103f58bf282c6da8b13639ba2d413312dcbafe4)) * **openrewrite:** resolve missing OpenRewriteType class ([46f4084](https://github.com/phodal/shire/commit/46f408436fc31f53e9307446b61d5a46a8ca1dee)) * **ui:** remove redundant revalidate and repaint calls ([78374cd](https://github.com/phodal/shire/commit/78374cdbf6ebdb6a9e2f3177be06e9cb50ab0605)) * **utils:** prevent adding empty new lines to CodeFence content ([449efdf](https://github.com/phodal/shire/commit/449efdf5b35972c8bdd04b4078a2e8f4dec297d0)), closes [#153](https://github.com/phodal/shire/issues/153) ### Features * **action:** Add RunCode action and refactor execution flow ([11d48c0](https://github.com/phodal/shire/commit/11d48c089cef5c4aeba7b27144de9fd5e9fa6c1c)) * **code-highlight:** enhance setupActionBar and add Mermaid support [#153](https://github.com/phodal/shire/issues/153) ([f8f69a2](https://github.com/phodal/shire/commit/f8f69a26fb10ceef4810db9e431ab1bbf3f09432)) * **core:** add DiffLangSketchProvider for diff language support [#153](https://github.com/phodal/shire/issues/153) ([61b592b](https://github.com/phodal/shire/commit/61b592bfeb0cab4b4dd59132c6bd0f12d14c1d72)) * **diff-ui:** implement apply patch from clipboard dialog and enhance SingleFileDiffLangSketch [#153](https://github.com/phodal/shire/issues/153) ([a1e89d2](https://github.com/phodal/shire/commit/a1e89d29fde66eacf29b538992ed3a353dbdfb97)) * **diff:** add diff streaming support for editors [#153](https://github.com/phodal/shire/issues/153) ([f2014bc](https://github.com/phodal/shire/commit/f2014bc60fc29f77f5472bb6fe87d1bc70582fc0)) * **diff:** enhance diff view and context handling ([ea610a4](https://github.com/phodal/shire/commit/ea610a4e3febb39a9dd76376e8f6ce5e77027e4a)) * **diff:** enhance SingleFileDiffLangSketch with new actions and dialog ([72a667f](https://github.com/phodal/shire/commit/72a667f856bf8d62d3e67ac9b7fe03fbcbd33545)), closes [#153](https://github.com/phodal/shire/issues/153) * **DiffPatchViewer:** enhance patch application and UI [#153](https://github.com/phodal/shire/issues/153) ([a2099cf](https://github.com/phodal/shire/commit/a2099cfc3da4e472332bdddd3c7abe272b223da8)) * **diffs:** add StreamDiff feature and improve UI [#153](https://github.com/phodal/shire/issues/153) ([b260b49](https://github.com/phodal/shire/commit/b260b497adfd9f36b73dcd337448a0342dadbf79)) * **extensions:** add DiffStreamService project service [#153](https://github.com/phodal/shire/issues/153) ([d8cb1b0](https://github.com/phodal/shire/commit/d8cb1b055507d9062137dbe386e192504ae78b07)) * **patch:** add clipboard patch application dialog ([7ea22c5](https://github.com/phodal/shire/commit/7ea22c542d895e75c94bf84c329455a67860b4b1)) * **shirelang:** implement `/goto` command for file navigation [#153](https://github.com/phodal/shire/issues/153) ([bd37fda](https://github.com/phodal/shire/commit/bd37fda786e3e6f1b6e131a8dc51b9c1eba95094)) * **stream-diff:** implement stream diff functionality [#153](https://github.com/phodal/shire/issues/153) ([57ee622](https://github.com/phodal/shire/commit/57ee622fd0c470e561e733d17a55a6ced481e0a0)) * **toolbar:** add ShireDiffCodeAction [#153](https://github.com/phodal/shire/issues/153) ([74d995a](https://github.com/phodal/shire/commit/74d995a13caca4580a3ed95ff47785f578d5dabe)) * **toolsets:** add mermaid support and integration plugin [#153](https://github.com/phodal/shire/issues/153) ([55e33d0](https://github.com/phodal/shire/commit/55e33d0b34a8644c7f6a92990dba06ea0a044b7f)) * **ui:** add diff viewer and toolbar actions [#153](https://github.com/phodal/shire/issues/153) ([ed903cd](https://github.com/phodal/shire/commit/ed903cd1bb8d05ca86361291b0977d470db796d4)) * **ui:** add doneUpdateText method and optimize component handling [#153](https://github.com/phodal/shire/issues/153) ([5622123](https://github.com/phodal/shire/commit/56221238217a4fddfa655f0c9358eea3b5c1f55a)) * **ui:** add patch display and action buttons to ShireMarketplaceTableView [#153](https://github.com/phodal/shire/issues/153) ([17eb9ff](https://github.com/phodal/shire/commit/17eb9ffbdd7960fb5d29b97a80f29d352127dbf8)) * **ui:** enhance diff viewer with preview dialog [#153](https://github.com/phodal/shire/issues/153) ([b9a62db](https://github.com/phodal/shire/commit/b9a62db07c2dde01b228015d478ecbc3538b6c91)) * **ui:** enhance DiffPatchViewer with project context and diff viewing [#153](https://github.com/phodal/shire/issues/153) ([7b88f2a](https://github.com/phodal/shire/commit/7b88f2a1efb2b491d4a39d8c8954e846b9b09bcc)) * **ui:** enhance DiffPatchViewer with project context and diff viewing [#153](https://github.com/phodal/shire/issues/153) ([7cd80ea](https://github.com/phodal/shire/commit/7cd80ea8d5971aa0d514e93bef0e487241ec4e71)) * **ui:** implement LangSketch and add PlantUML support [#153](https://github.com/phodal/shire/issues/153) ([6aa5b75](https://github.com/phodal/shire/commit/6aa5b755d9eed391597a3a02cc2e83225ee115c7)) * **view:** Implement ShirePanelView for enhanced UI representation ([676df38](https://github.com/phodal/shire/commit/676df3895f62522e33a51c99360a6f4123ac3e82)) ### Reverts * revert change of delete file for MKT place ([1adbd5a](https://github.com/phodal/shire/commit/1adbd5a0cc12adb3bae2b4cb8dabbd00a7886fb7)) ## [1.1.1](https://github.com/phodal/shire/compare/v1.1.0...v[1.1.1]) (2024-12-02) ### Bug Fixes * **core:** correctly assign stdout and stderr in RunServiceTask ([c320749](https://github.com/phodal/shire/commit/c32074984d9e757d95625a9103ef16ca46fe4795)) * **core:** resolve compatibility issues with editor highlighter service ([175e6c9](https://github.com/phodal/shire/commit/175e6c96b86bd7cd5ef54ce13583dcaa5b15ad63)) * set default python execute to python 3 and update && closed [#146](https://github.com/phodal/shire/issues/146) ([3f57680](https://github.com/phodal/shire/commit/3f57680f93fd8ee35b7352fa0470f94b457f4349)) * **shire-javascript:** remove deprecated JSXHarmonyFileType reference ([9760487](https://github.com/phodal/shire/commit/9760487a3ead0449dd6297acf1a45edfdeb4d36d)) ### Features * **intellij-plugin:** Add soft wrap and scroll to end actions to console toolbar ([8453f57](https://github.com/phodal/shire/commit/8453f57a38874750a504df56f025f4710a3ae683)) * **shell:** improve temp file cleanup in shell command execution ([8a5493d](https://github.com/phodal/shire/commit/8a5493d2320e502775b256e680859bcc39869516)) * **ui:** add CodeView component for code preview ([bc8e07a](https://github.com/phodal/shire/commit/bc8e07a063d42cd26d0f10b52b7726e4842ae437)) * **ui:** add toolwindow actions for copy, insert code, and language label ([b42962c](https://github.com/phodal/shire/commit/b42962c4e9de0bcfab63a76705935b1340a6444b)) * **ui:** scroll to end of text on insert and add interaction type check ([4b3ecf8](https://github.com/phodal/shire/commit/4b3ecf8f88ba89ea5b777b35b65644e0131c7500)) * **variables:** add Component data class and ReactPsiUtil utility ([0300680](https://github.com/phodal/shire/commit/03006808a73caf24d34feb9c9c4686027889d6ed)) ## [1.0.6](https://github.com/phodal/shire/compare/v1.0.4-SNAPSHOT...v[1.0.6]) (2024-11-24) ### Bug Fixes * **core:** return complexity count as String instead of Int ([4cea791](https://github.com/phodal/shire/commit/4cea7917ed2bfc9553ed77c918b26df9a7efa0a6)) * **runner:** Run fails due to duplicate variables ([430e57a](https://github.com/phodal/shire/commit/430e57a7b375c3709beeb6af6be8b7dbcf0dbe4d)) ### Features * **actions:** add ShireConsoleAction and update related configurations ([8ee1a18](https://github.com/phodal/shire/commit/8ee1a18bb43d0762549064b37377c1e9708052ad)) * **actions:** add ShireDatabaseAction to Database panel menu bar ([19d02df](https://github.com/phodal/shire/commit/19d02dfeefce3b4e6d8fa630a6207a8adfae7c77)) * **compiler:** Improve the logic of the if expression to handle variables in velocityBlock ([61e3aab](https://github.com/phodal/shire/commit/61e3aab3f0e408d883f4cd90e947e31d751ac0ec)) * **kotlin:** add complexity analysis for Kotlin language ([412a90d](https://github.com/phodal/shire/commit/412a90d55a2144162a9d49aa3cfb28b5d0d55253)), closes [#139](https://github.com/phodal/shire/issues/139) * **provider:** add change count, line count, and complexity count variables [#143](https://github.com/phodal/shire/issues/143) and closed [#139](https://github.com/phodal/shire/issues/139) ([e23687c](https://github.com/phodal/shire/commit/e23687c8a0c31f4b23ea09483e482dd9b3fb53b0)) * **provider:** add process method to ComplexityProvider interface [#139](https://github.com/phodal/shire/issues/139) ([61a6cad](https://github.com/phodal/shire/commit/61a6cad3a26d1c6f884fe1059d6da98ba93d2f15)) * **provider:** implement complexity count calculation for PsiVariableProvider [#139](https://github.com/phodal/shire/issues/139) ([8a8f1e8](https://github.com/phodal/shire/commit/8a8f1e8df6171714ab0f6660fd91474b3fb7b769)) * **shirelang:** add Shire Vcs Log Action and update menu text ([b90f450](https://github.com/phodal/shire/commit/b90f45013defc16685bbe5483af2a71c6c798c0e)) * **shirelang:** add SonarLint action and tool window integration ([20e7f13](https://github.com/phodal/shire/commit/20e7f1308220d4f5ad24f34614ff8783aac0d9ad)) * **shirelang:** add Vcs.Log action to ShireActionStartupActivity ([d34a8da](https://github.com/phodal/shire/commit/d34a8da8f1b2eefef80c3789f52e64af93f5b16c)) * **vcs:** add support for Diff variable in VcsToolchainVariable ([d448e33](https://github.com/phodal/shire/commit/d448e33ee43fd34fb12f7653d84e4421de8f32d7)) ## [1.0.4-SNAPSHOT](https://github.com/phodal/shire/compare/v1.0.2...v[1.0.4-SNAPSHOT]) (2024-11-13) ### Features * **core:** add ActionableWebView class for enhanced web interaction [#132](https://github.com/phodal/shire/issues/132) ([b65fdc0](https://github.com/phodal/shire/commit/b65fdc02d34cc1065bfcc614fd05993822ac2a39)) * **grammar:** Add support for if expressions ([d536289](https://github.com/phodal/shire/commit/d536289b7518fbef89f6c41dc08cb3ee4eab8be4)) ## [1.0.2](https://github.com/phodal/shire/compare/v1.0.1...v[1.0.2]) (2024-11-03) ### Bug Fixes * **hobbit:** refine key bindings for approval processor ([d402baa](https://github.com/phodal/shire/commit/d402baa9192e7f16a7bb3b45a3a0cc1c38be5bd4)) * **openrewrite:** improve run configuration setup [#119](https://github.com/phodal/shire/issues/119) ([eecd294](https://github.com/phodal/shire/commit/eecd294277565db875ec21c8f756b5f1d80219e2)) * **openrewrite:** update working directory for configuration [#119](https://github.com/phodal/shire/issues/119) ([35600e3](https://github.com/phodal/shire/commit/35600e368289e06bb52f68a3db4fbac0b8987913)) ### Features * **brace-matching:** enhance ShireBraceMatcher with improved pairs and conditions ([65f500e](https://github.com/phodal/shire/commit/65f500e1d1cdf72b71bf6235edd55691f1664d84)) * **navigation:** add APPROVAL_EXECUTE pattern and refactor goto declaration handling ([f856236](https://github.com/phodal/shire/commit/f856236edf01eda761d4de1ef396a976e3cbbc18)) * **openwrite:** try to add OpenRewrite support and integration [#119](https://github.com/phodal/shire/issues/119) ([b1cc797](https://github.com/phodal/shire/commit/b1cc7978be1a0fbec4e03ae340f18f1431b6acc0)) * **shirelang:** add formatter support for Shire language files ([a37bf9d](https://github.com/phodal/shire/commit/a37bf9dad6ec86f3a11a5c7c7b683f306e87feaa)) * **shirelang:** add highlight error filter for improved syntax highlighting ([c743528](https://github.com/phodal/shire/commit/c743528ddd01b1900fd63e52985f4e533ec533a6)) * **shirelang:** implement beforeCharTyped override for custom handling ([ee12c7f](https://github.com/phodal/shire/commit/ee12c7f9cd1eb6dda9df4ec5790f1182e3841b89)) * **shirelang:** implement highlighting, brace matching, and quote handling ([ced541f](https://github.com/phodal/shire/commit/ced541fa5bba0b55a8d532984928546afbddbd9a)) ## [0.9.1](https://github.com/phodal/shire/compare/v0.9.0...v[0.9.1]) (2024-10-10) ### Bug Fixes * **actions:** The shire file downloaded from the marketplace has not been dynamically updated ([4a0aa6f](https://github.com/phodal/shire/commit/4a0aa6f57306d693cf78a7098e826f87de1179cc)) * **build:** comment out failureLevel in pluginVerification [#100](https://github.com/phodal/shire/issues/100) ([e260a10](https://github.com/phodal/shire/commit/e260a1055c6b6a93acd0fa938e5e06c400aa0843)) * fix for json module outside project issue [#112](https://github.com/phodal/shire/issues/112) ([ed12724](https://github.com/phodal/shire/commit/ed12724e2fc460e2a511a9becfa0b8f402ed3fb6)) * fix plugin group error issue ([acbdeca](https://github.com/phodal/shire/commit/acbdeca7cd2bc22953cea47eaac37b7c44e06506)) * **github-actions:** correct gradle task paths in release workflow [#100](https://github.com/phodal/shire/issues/100) ([4d5a39d](https://github.com/phodal/shire/commit/4d5a39dbe22ea76845a34ccbadf61c994045643f)) * **javascript:** fix for siganture issue ([94ebee5](https://github.com/phodal/shire/commit/94ebee5af25aa07864175a351780595ffc08df34)) * **javascript:** update variable resolution in JSPsiContextVariableProvider ([8e2a7e9](https://github.com/phodal/shire/commit/8e2a7e91b2344bef5ab9f57aae432f72fdd833ac)) * **lexer:** make sure front-matter parse success [#112](https://github.com/phodal/shire/issues/112) ([a9c3770](https://github.com/phodal/shire/commit/a9c3770fdcf0d52ad412e3a1a157de83e36f13be)) * **logging:** improve error messages for PatternActionFunc arguments ([844dcd8](https://github.com/phodal/shire/commit/844dcd813d069be62d7f6cddb057f1e2d4ef0a58)) * **settings:** Test connection failed without applying settings ([e883e38](https://github.com/phodal/shire/commit/e883e383a540368f26295ce3504d5253f637b294)) * **shirelang:** correct typo in ShireFileModificationListener class name ([8baad36](https://github.com/phodal/shire/commit/8baad367cae909fa19ce542f64041c1be05e65b7)) ### Features * **build.gradle.kts:** import Changelog and update PatchPluginXmlTask ([cd7df2c](https://github.com/phodal/shire/commit/cd7df2c4ea3769aec3681e5418a683015558b37b)) * **build:** configure specific subprojects in build.gradle.kts [#100](https://github.com/phodal/shire/issues/100) ([e7db461](https://github.com/phodal/shire/commit/e7db4611e96e523bc774040857c40d3b04345f71)) * **build:** optimize GitHub Actions build space ([0f3a6f9](https://github.com/phodal/shire/commit/0f3a6f9f6c991619c3fa3be990d0ea0f5211b820)) * **build:** try to resize verify range for reduce size of disk in GitHub Action [#100](https://github.com/phodal/shire/issues/100) ([e127f88](https://github.com/phodal/shire/commit/e127f88b628448dea3d80cbf3befa951c9011e73)) * **build:** update Gradle setup and plugin verification tasks ([b779cd0](https://github.com/phodal/shire/commit/b779cd0be31599f8e05e45ae634fdec0689fbe04)) * **build:** update Gradle version and enable PatchPluginXmlTask [#100](https://github.com/phodal/shire/issues/100) ([8044084](https://github.com/phodal/shire/commit/804408437ab7b3e6fb9c6e6d15d7dc566b4a4e90)) * **compiler:** enable support for custom foreign functions [#116](https://github.com/phodal/shire/issues/116) ([c4e51fc](https://github.com/phodal/shire/commit/c4e51fcc4a64356910723673c2a3c52cdd51b57f)) * **compiler:** implement foreign function support [#116](https://github.com/phodal/shire/issues/116) ([61059cd](https://github.com/phodal/shire/commit/61059cd8982c6383b6a09584d5caf78fd4769724)) * **compiler:** refine input type rules in PatternActionProcessor [#112](https://github.com/phodal/shire/issues/112) ([db58a87](https://github.com/phodal/shire/commit/db58a874035ef8c5a202e22f85959e146093f091)) * **docs, shirelang:** update function parameters and syntax highlighting [#116](https://github.com/phodal/shire/issues/116) ([b6c6e9d](https://github.com/phodal/shire/commit/b6c6e9d98f58c11cedb0f1164f86dd43be777a52)) * **gradle:** update IntelliJ IDEA version and add plugin verification [#100](https://github.com/phodal/shire/issues/100) ([c5b58a1](https://github.com/phodal/shire/commit/c5b58a1d4d9fbb96788b72634ca216caa10b8448)) * **gradle:** update IntelliJ platform version and add shire-json language support [#100](https://github.com/phodal/shire/issues/100) ([da4f120](https://github.com/phodal/shire/commit/da4f1206e70922bfd10960981123e9d4d1c8771b)) * **IDEA-243:** add deps plugins [#100](https://github.com/phodal/shire/issues/100) ([276f391](https://github.com/phodal/shire/commit/276f391c617265d67e562616787322db3c3fa123)) * init tokenizer function for [#104](https://github.com/phodal/shire/issues/104) ([1ae89ed](https://github.com/phodal/shire/commit/1ae89ed7c39a4f4421f6946fb24e14d232596f59)) * **javascript:** enhance JavaScriptLanguageToolchainProvider for detailed context collection ([d4341e4](https://github.com/phodal/shire/commit/d4341e44abd263c0c932ff86e729c26c9aa1c747)) * **javascript:** fix for import issues ([c646e4c](https://github.com/phodal/shire/commit/c646e4c568803e3e7b34ed3dff4904a059e3c0b7)) * **json-plugin:** add dependency on IntelliJ JSON plugin [#100](https://github.com/phodal/shire/issues/100) ([ce0bbad](https://github.com/phodal/shire/commit/ce0bbadaebd310307f29409c324803ad2d8e9a7d)) * **markdown:** add support for markdown headers in ShireCompileTest [#112](https://github.com/phodal/shire/issues/112) ([b1d96f6](https://github.com/phodal/shire/commit/b1d96f6c18568766c1213911944a7a41929ccd2f)) * **parser:** add process and output for parser [#116](https://github.com/phodal/shire/issues/116) ([5b9bbd9](https://github.com/phodal/shire/commit/5b9bbd995ee4f10259eb3198458e6e74843c9a50)) * **parser:** Support Markdown headers in ShireParser [#112](https://github.com/phodal/shire/issues/112) ([f3e2e09](https://github.com/phodal/shire/commit/f3e2e0936e8a2dc9cdddf5e211f4065ec359d33f)) * **patternaction:** add tokenizer function and refactor PatternActionFuncType to PatternActionFuncDef ([37ece32](https://github.com/phodal/shire/commit/37ece322421625f46b90ed96615bced0792ca36e)) * **patternaction:** enhance function documentation and formatting [#112](https://github.com/phodal/shire/issues/112) ([1dfd2a9](https://github.com/phodal/shire/commit/1dfd2a9d553e8ff905249c1ab994bb3a8b57c080)) * **plugin:** update plugin name and improve markdown table formatting ([05aa38a](https://github.com/phodal/shire/commit/05aa38a5a5440514429c08da1fcb1c878d7be2a3)) * **post-processors:** Add descriptions and list view for processors [#112](https://github.com/phodal/shire/issues/112) ([4535863](https://github.com/phodal/shire/commit/4535863463b322b2c9619432d4180250c9e43f3b)) * **shire-json:** move JSON related functionalities from core to shire-json module [#100](https://github.com/phodal/shire/issues/100) ([3f3a77a](https://github.com/phodal/shire/commit/3f3a77a4399a6e66c7b400d452cb1509f2fb674d)) * **ShireFileListener:** ignore directories and LightVirtualFiles in onUpdated method ([086ed79](https://github.com/phodal/shire/commit/086ed79e9064c56d4eef15b2dee41110fa886c2f)) * **shirelang:** add example option to ShireToolchainFunctionProvider [#112](https://github.com/phodal/shire/issues/112) ([4c16e36](https://github.com/phodal/shire/commit/4c16e3668540b95a544e00c55583e83f66602645)) * **shirelang:** add ShireToolchainFunctionProvider and update GitFunctionProvider [#112](https://github.com/phodal/shire/issues/112) ([90a962f](https://github.com/phodal/shire/commit/90a962f762f81537416aba7abe310e72864193dc)) * **shirelang:** add support for custom functions and update lexer and parser [#116](https://github.com/phodal/shire/issues/116) ([14ef5a0](https://github.com/phodal/shire/commit/14ef5a0ee6e4c30355d8833233b098cc666c475c)) * **shirelang:** enhance foreign function execution with args support ([f9658ea](https://github.com/phodal/shire/commit/f9658ea65d4a7453985c7327d6f43422c4c227f4)), closes [#119](https://github.com/phodal/shire/issues/119) * timeout ([c02f7b2](https://github.com/phodal/shire/commit/c02f7b29ef0f0389b3a9e5adac72ff7a6415bfb5)) * **tokenizer:** add Jieba Chinese tokenizer support [#112](https://github.com/phodal/shire/issues/112) ([2f73edf](https://github.com/phodal/shire/commit/2f73edf38b6ce0124d37ab99f26de8a931d0098c)) * **tokenizer:** add multiple tokenizer types support in TokenizerProcessor [#100](https://github.com/phodal/shire/issues/100) ([9575b14](https://github.com/phodal/shire/commit/9575b141109b81ad87157bbe2546908960e53868)) * **tokenizer:** add variable substitution in tokenizer and related test [#104](https://github.com/phodal/shire/issues/104) ([30c40c5](https://github.com/phodal/shire/commit/30c40c52b1cef465d49339aeb3b9c1b59764cb45)) * **tokenizer:** ensure distinct tokenization results ([09746ca](https://github.com/phodal/shire/commit/09746cafce7766ffe2a8a6d8b9f866def8a98d21)) ## [0.8.2](https://github.com/phodal/shire/compare/v0.8.1...v[0.8.2]) (2024-09-23) ### Bug Fixes * **middleware:** use ([483ab87](https://github.com/phodal/shire/commit/483ab8781e146ba483906394d285c02a217db1bb)) ### Features * add OpenWebpage processor ([24c2bf2](https://github.com/phodal/shire/commit/24c2bf217e601f99bd4628e9438f3edbc0e5d14a)) * **core:** add ShowWebviewProcessor and WebViewWindow ([6a3ddb4](https://github.com/phodal/shire/commit/6a3ddb47110621558fdc46feaed2e27246bcd116)) * **core:** allow OpenFileProcessor to accept arguments ([f54e0c0](https://github.com/phodal/shire/commit/f54e0c0ed14292018ae9c99b894cb95299019ea8)) * **core:** enhance error message in RunCodeProcessor ([87d65cd](https://github.com/phodal/shire/commit/87d65cdc3868efffc3ee91277a3aff07590df85e)) * **database:** add Excel toolchain functions ([868510c](https://github.com/phodal/shire/commit/868510c6cf949efc813d2e0d7dfd64ed1ae1e3e2)) * **docs:** update documentation and add WebView functionality ([15b5a4c](https://github.com/phodal/shire/commit/15b5a4c280c91173cbc34d33e2ebffc02155ef64)) * **gradle:** update pluginUntilBuild version in gradle.properties ([a219ce4](https://github.com/phodal/shire/commit/a219ce41edd44aee7f44c261b27967a3eb0c227a)) * **httpclient:** add log display in console for HTTP requests ([d181831](https://github.com/phodal/shire/commit/d18183196a3e77845db1dd538d350078b7f4f54e)) * **httpclient:** improve request logging format in CUrlHttpHandler ([3f7740e](https://github.com/phodal/shire/commit/3f7740e7ecdf0e30935f605c16e86ea743ec7c1f)) * **httpclient:** refactor CUrlHttpHandler and CUrlConverter for better variable handling ([a039679](https://github.com/phodal/shire/commit/a03967985a5156c8abfc56bb96a3b699495b6e7e)) * **json-path:** enable for parse start for `data:` ([d5a2d71](https://github.com/phodal/shire/commit/d5a2d71b8eba81c7ecd42e787870552cd59d9d54)) * **PatternFuncProcessor:** add newline join for Array and List results ([57ea7e6](https://github.com/phodal/shire/commit/57ea7e6e609173f6c67bcfd32eafa71cb17710aa)) * **ShireFileModifier:** wrap file processing in runReadAction ([e8cc6f0](https://github.com/phodal/shire/commit/e8cc6f06fd8238ea7679dc0e31a1c3f976d2f1d0)) * **UI:** enable off-screen rendering and improve webview popup ([96f3791](https://github.com/phodal/shire/commit/96f37913f889de070c608313481361cb73badce4)) ## [0.8.1](https://github.com/phodal/shire/compare/v0.8.0...v[0.8.1]) (2024-09-18) ### Bug Fixes * **core:** update file path validation regex and related tests ([3ab4e9f](https://github.com/phodal/shire/commit/3ab4e9f4e8d42643170141ecc4d0538612907def)) * **database:** update rror messages ([8b4e932](https://github.com/phodal/shire/commit/8b4e932e12cef7a6a2df78b8c76303ceaff5c15a)) * **llm:** add configuration update from state in OpenAILikeProvider [#85](https://github.com/phodal/shire/issues/85) ([56a1961](https://github.com/phodal/shire/commit/56a19612a4e4fb94471d4a63b876d7b1b6b7d562)) * **middleware:** 修正文件保存处理器,避免非法文件名 ([35785bd](https://github.com/phodal/shire/commit/35785bdb980f28ba61d30c7827a29d5f33658275)) * **patternaction:** 修复PatternActionFunc中的空指针异常 ([e048ab9](https://github.com/phodal/shire/commit/e048ab9c058393ad6a9aa509ceeda52c896357ea)) * **PatternFuncProcessor:** enhance path resolution and refactor tests [#83](https://github.com/phodal/shire/issues/83) ([a81c603](https://github.com/phodal/shire/commit/a81c6032b36f295c2c828010546bae61d64d600f)) * **PatternFuncProcessor:** remove joinToString from array operations [#83](https://github.com/phodal/shire/issues/83) ([9e251f7](https://github.com/phodal/shire/commit/9e251f7734affa2cc24c1d8be59be97a5bd48f60)) * **runner:** adjust execution subscription order in ConfigurationRunner ([8ba7447](https://github.com/phodal/shire/commit/8ba7447e5e5570281d370a38917a089cdd67c5d3)) * **runner:** LlmProvider is still working after canceling the shire process ([d458e1f](https://github.com/phodal/shire/commit/d458e1f88e0ea621b77e593b6b1dc66aba7e7974)) * **runner:** 简化输出LLM模型名 ([06ddd76](https://github.com/phodal/shire/commit/06ddd76fca69ca6782ed3bc09ed4d2e196c27295)) * **shirelang:** add empty intentions check and change error level in ShireIntentionHelper ([badc42c](https://github.com/phodal/shire/commit/badc42cb9e6f5dc09326ee0ffdb773fa4a76fe15)) * **shirelang:** refactor array handling and add regression test [#83](https://github.com/phodal/shire/issues/83) ([1f4c8fe](https://github.com/phodal/shire/commit/1f4c8fe8d96e65959f3c0d61cb6899febb87edf6)) * **Wiremock:** 添加文件路径到 Wiremock 错误信息中,以便于调试 ([2a888cd](https://github.com/phodal/shire/commit/2a888cd75703c161bb009e0569276557862eae5e)) * 在ShireProcessHandler中添加异常日志记录 ([28d6dea](https://github.com/phodal/shire/commit/28d6dea0c4650b6254079ed2fee5af6b78ccfe70)) ### Features * add basic batch processor of content ([ec4fa1c](https://github.com/phodal/shire/commit/ec4fa1c58d2053fd22bfdea8c87a318473879d96)) * add more psiUtil for helper [#89](https://github.com/phodal/shire/issues/89) ([b4fa3eb](https://github.com/phodal/shire/commit/b4fa3eb59c6ab2b64430392bbf4aa5090f283796)) * **batch-processing:** implement batch processing functionality ([f38dbc2](https://github.com/phodal/shire/commit/f38dbc2915891945fae99f245e2d3425142bd5cc)) * **batch:** add custom variables support to ShireTemplateCompiler ([6ce8430](https://github.com/phodal/shire/commit/6ce8430ca9a35e272824f43e9ad2b159b7acb9e9)) * **batch:** add goto decl ([9412afb](https://github.com/phodal/shire/commit/9412afbdf5ff6c38999c603bcd12d4a0055442be)) * **beforeStreaming:** refactor function naming and add coroutine support ([1a1bd30](https://github.com/phodal/shire/commit/1a1bd30964bf477dcdd038211f2b4b3e13625fc1)) * **codemodel:** enhance class name extraction in DirectoryStructure [#89](https://github.com/phodal/shire/issues/89) ([dfb02b3](https://github.com/phodal/shire/commit/dfb02b320cfc3fe28528fb513053b47cd2b555f4)) * **compiler:** add Batch and Destroy functions to PatternActionFunc ([e32fc61](https://github.com/phodal/shire/commit/e32fc61cebd44597c09beba059c32e904c5592fe)) * **compiler:** refactor action classes and add beforeStreaming functionality ([461361d](https://github.com/phodal/shire/commit/461361df471d668cf4d6748cf53d150306501319)) * **execute:** enable for gradle run support ([9521375](https://github.com/phodal/shire/commit/95213753eaeaf10ab4da3f17e72a4e0d92deec7d)) * **git:** add git commit function support ([820ec89](https://github.com/phodal/shire/commit/820ec89991e313caf5d9aa77a7039114c3a4e726)) * **git:** enhance commitChanges function in GitFunctionProvider ([fb7c286](https://github.com/phodal/shire/commit/fb7c286f31bee87565ac83388ac932becfee7daa)) * **go:** add golang tool context provider [#89](https://github.com/phodal/shire/issues/89) ([6058a5a](https://github.com/phodal/shire/commit/6058a5a680a2bb5886a810884e90beb6f47ef921)) * **GoLanguageProvider:** add method to get Go version [#89](https://github.com/phodal/shire/issues/89) ([56478b5](https://github.com/phodal/shire/commit/56478b52b0d367acef7bb984cf3db3f743b3ae75)) * **GoPsiContextVariableProvider:** map related types to text [#89](https://github.com/phodal/shire/issues/89) ([cce91ed](https://github.com/phodal/shire/commit/cce91ed81c4be2b9a80412e8fa9d8bac10b5f28e)) * init downloader for marketplace [#86](https://github.com/phodal/shire/issues/86) ([9aaa430](https://github.com/phodal/shire/commit/9aaa43010dca2b55655e32d3ae46961851367658)) * **javascript:** add variable provider and utility functions ([1e5ff4e](https://github.com/phodal/shire/commit/1e5ff4e2a7084720c8a9ba3b40a841b418140a81)) * **lifecycle:** add 'beforeStreaming' and 'mock' functions ([293d361](https://github.com/phodal/shire/commit/293d3616296451a8e892a9bb41fe082a8c10cdef)) * **marketplace:** add download notifications and refresh functionality [#86](https://github.com/phodal/shire/issues/86) ([97482aa](https://github.com/phodal/shire/commit/97482aa964ee633224ca58e55bea44b66178dc4b)) * **marketplace:** add MarketplacePanel UI and functionality [#86](https://github.com/phodal/shire/issues/86) ([ece56e0](https://github.com/phodal/shire/commit/ece56e07f267f4ea1f7fe85c9165549e21621ab7)) * **marketplace:** add MarketplaceToolWindowFactory and ShireIdeaIcons [#86](https://github.com/phodal/shire/issues/86) ([70ef324](https://github.com/phodal/shire/commit/70ef324d15432ab190d25b8ebe8304f65281c5c4)) * **marketplace:** add refresh button and refactor UI layout [#86](https://github.com/phodal/shire/issues/86) ([f996913](https://github.com/phodal/shire/commit/f99691388694d64c8ecac7e652b3f1f18c50af63)) * **marketplace:** change UI anchor and update error messages [#86](https://github.com/phodal/shire/issues/86) ([a34fd1c](https://github.com/phodal/shire/commit/a34fd1c1cf7c9ccc56a981a49d5c7e9ef7503f8d)) * **marketplace:** enhance ShirePackage table with download functionality [#86](https://github.com/phodal/shire/issues/86) ([e868010](https://github.com/phodal/shire/commit/e868010c94e958df6b465e28b12a027212cef31a)) * **marketplace:** refactor MarketplacePanel and add table view [#86](https://github.com/phodal/shire/issues/86) ([7088492](https://github.com/phodal/shire/commit/70884927f878b2ca193c45e5781a9ea86a7e894a)) * **marketplace:** refactor table component and add install action [#86](https://github.com/phodal/shire/issues/86) ([fc18040](https://github.com/phodal/shire/commit/fc18040664e54dce91189b57c81ecad45739e7e7)) * **marketplace:** update ShireMarketplaceTable to fetch data from API [#86](https://github.com/phodal/shire/issues/86) ([bcecadb](https://github.com/phodal/shire/commit/bcecadbe2c19e227b42ee6dc858b1610146fa1c7)) * **mock:** init for run mock serivce ([ec41dfb](https://github.com/phodal/shire/commit/ec41dfbe31dd8dafa2c026a8d26226d20f503369)) * **mock:** init mock server for testing apis ([6813876](https://github.com/phodal/shire/commit/6813876203fdb4c313d07895cae9d63939666846)) * **mock:** update Wiremock provider path and improve error handling ([2389f53](https://github.com/phodal/shire/commit/2389f53b53c7db0a3b47826fae412574637955dd)) * **patternaction:** refactor pattern action function parsing ([4b6e13a](https://github.com/phodal/shire/commit/4b6e13a9f0c5ed7bc5243ba0a6ffef0ff0e89bf7)) * **PatternFuncProcessor:** change argument addition order and improve error message ([9d2c1bc](https://github.com/phodal/shire/commit/9d2c1bc22fcde20caf3ad34ed52dd24ae6dbc797)) * **python:** init py psi util ([c441f62](https://github.com/phodal/shire/commit/c441f626132224d34a375bbcb5fb19e0bb68ca84)) * **run-service:** wrap operations in runReadAction in ShirePythonRunService.kt ([478a3d9](https://github.com/phodal/shire/commit/478a3d990c23cb5d30520cf876fe886c3cd2499f)) * **search:** disable semantic embedding functionality ([3560a23](https://github.com/phodal/shire/commit/3560a23c1481f9aeddc71bbc86c8739094387345)) * **shire-go:** add GoPsiContextVariableProvider for Go language support [#89](https://github.com/phodal/shire/issues/89) ([aefdadb](https://github.com/phodal/shire/commit/aefdadbb28e8fa65799445d796f082b19db91c99)) * **shire-go:** add iota detection in Go expressions and constants [#89](https://github.com/phodal/shire/issues/89) ([33216d7](https://github.com/phodal/shire/commit/33216d77c9e2d4fb510d5f8f8f74ebfeae92e32c)) * **shire-go:** add support for Go language [#89](https://github.com/phodal/shire/issues/89) ([ab09a6f](https://github.com/phodal/shire/commit/ab09a6fe57586d1ac65893346e18373e6bac8af1)) * **shire-go:** enhance related classes and code smell handling in GoPsiContextVariableProvider [#89](https://github.com/phodal/shire/issues/89) ([8d91a88](https://github.com/phodal/shire/commit/8d91a88943e350e5365ceb6705f6ceeee42bf5e2)) * **variable-provider:** enhance context variable handling in JS and Go ([d344944](https://github.com/phodal/shire/commit/d34494437238f58c06c2e4c6c403be02735faf9a)) * 添加 LLM 提供者未找到的错误信息 ([fde5a88](https://github.com/phodal/shire/commit/fde5a88f24ce99392ccc67335bc7f81c25d20e6e)) ## [0.7.4](https://github.com/phodal/shire/compare/v0.7.3...v[0.7.4]) (2024-09-09) ### Bug Fixes * **db:** fix toolchain call issue ([99eb680](https://github.com/phodal/shire/commit/99eb680b20d806967df871b64a0c752e844f6e76)) * **runner:** An unexpected exception occurred, causing the shire process cannot be canceled ([7eba18c](https://github.com/phodal/shire/commit/7eba18c8b1adaccf226fad1362a239cb60d19da9)) * **runner:** The consoleView is not the original consoleView when processing the exit code of the script ([474b681](https://github.com/phodal/shire/commit/474b6813565e790a832c37f74e7ac4acd6db7696)) * **runner:** The messageFilter of the console view appends extra data ([a47db5d](https://github.com/phodal/shire/commit/a47db5dd1fe51408409339a4711c7d16a23922d1)) * **shirelang:** ensure null safety in ShireVcsSingleAction [#78](https://github.com/phodal/shire/issues/78) ([0c4665b](https://github.com/phodal/shire/commit/0c4665b9faf4573b2fb66abb1fabccc484fc3d51)) ### Features * **actions:** add support for enabling/disabling actions and improve action config handling [#78](https://github.com/phodal/shire/issues/78) ([045c962](https://github.com/phodal/shire/commit/045c962be11e55139c83cdb241d3a8a13f749b52)) * **codemodel:** Add JavaScript and TypeScript structure providers ([ce936bd](https://github.com/phodal/shire/commit/ce936bda74be3e1aa07bc47ffde133fefa867f8e)) * **javascript:** add JavaScript support and build system integration ([c09e3d3](https://github.com/phodal/shire/commit/c09e3d36483be5e85c3cead1dbec88f361f16d71)) * **javascript:** add JestCodeModifier and JSAutoTestingService ([fde85ad](https://github.com/phodal/shire/commit/fde85ad1e959a18d102e4ce44c6197e0914f3bc8)) * **javascript:** implement TypeScript refactoring tool and language support ([f46a3f8](https://github.com/phodal/shire/commit/f46a3f877a2aeed6fdce703a9b3008e617cf6625)) * **llm:** add LlmConfig class for LLM configuration management [#78](https://github.com/phodal/shire/issues/78) ([a824eda](https://github.com/phodal/shire/commit/a824eda7e6bc15b7e81c8fc088484582c1bd4276)) * **llm:** add maxTokens parameter to CustomFields and LlmConfig [#78](https://github.com/phodal/shire/issues/78) ([c47ae75](https://github.com/phodal/shire/commit/c47ae75e062a65b7b2ca4479efa16346c05dd644)) * **model:** enable for custom model in project && closed [#78](https://github.com/phodal/shire/issues/78) ([d3e4859](https://github.com/phodal/shire/commit/d3e4859b28f60d7b85e8188ae50eaf5a46ba1abb)) * **navigation:** implement GotoDeclarationHandler for Shire language ([2c7d744](https://github.com/phodal/shire/commit/2c7d7444759e81e9c3661f5ec5633ae7a086f810)) * **pattern-action:** refactor to use PatternActionFuncType enum ([445b2ac](https://github.com/phodal/shire/commit/445b2ac9e9539cf41468a7c9a0a819b98d63aa7f)) ## [0.7.2](https://github.com/phodal/shire/compare/v0.7.1...v[0.7.2]) (2024-09-05) ### Bug Fixes * **runtime:** switch to workerThread for terminal UI tasks execution [#72](https://github.com/phodal/shire/issues/72) ([53672d3](https://github.com/phodal/shire/commit/53672d3def0a373de2358a0754a3798c398e8934)) ### Features * **database:** add function to retrieve and display database info ([c30dd13](https://github.com/phodal/shire/commit/c30dd130809290e1a5495ab5960f098945b02f73)) * **httpclient:** enable pass variable table value to curl.sh file ([615b280](https://github.com/phodal/shire/commit/615b280ec54f4b76dfd1b1823f815373865a6747)) * **middleware:** add DiffProcessor support [#66](https://github.com/phodal/shire/issues/66) ([b3110f6](https://github.com/phodal/shire/commit/b3110f634fe71be5c11170ccfae155558fd714de)) * **middleware:** add Patch processor for applying code patches ([425fdb4](https://github.com/phodal/shire/commit/425fdb4ab28aa6f06ee645339d70289b9ae3acf8)) * **parser:** enable regex pattern function support ([8f88ddd](https://github.com/phodal/shire/commit/8f88ddd0e21d5326f9b6a20c53b43f0c438749c9)) * **parser:** implement custom ShireGrepFuncCall and refactor related components ([4a319ec](https://github.com/phodal/shire/commit/4a319ec9295b9b82e851c8bd1cdbacb822bd240e)) * **parser:** implement sed function call and improve injection handling ([00f3aca](https://github.com/phodal/shire/commit/00f3acaf0724eaeea8ce0535ee15bad3d5016f70)) * **shirelang:** implement regex pattern support for 'grep' function ([e3bb682](https://github.com/phodal/shire/commit/e3bb6828bfa72e9fba2bb39f954097487c8fa1bb)) * **testing:** add Shire language annotation to test cases and implement shell script runner ([9c53db9](https://github.com/phodal/shire/commit/9c53db9d4609cb78c026a93fb6b719c4b294a05f)) ## [0.7.1](https://github.com/phodal/shire/compare/v0.7.0...v[0.7.1]) (2024-09-02) ### Features * **browse:** add useragent generator [#60](https://github.com/phodal/shire/issues/60) ([df595f4](https://github.com/phodal/shire/commit/df595f4d983a794a41840be289bd6ca119fe35bb)) * **compiler:** add JsonPath support for pattern actions and closed [#11](https://github.com/phodal/shire/issues/11) ([14bed16](https://github.com/phodal/shire/commit/14bed168ec74647072184b344bb5eb3e70b2a97c)) * **compiler:** add support for 'capture' and 'thread' pattern actions [#11](https://github.com/phodal/shire/issues/11) ([dc50586](https://github.com/phodal/shire/commit/dc50586aec094172179e469c6faf380d2bae876d)) * **core:** add cURL execution support and HTTP handler extension point [#11](https://github.com/phodal/shire/issues/11) ([2717014](https://github.com/phodal/shire/commit/271701435325c6856791ce17e81f64c8ff0119bb)) * **httpclient:** add functionality to convert cURL to HTTP request scratch file [#11](https://github.com/phodal/shire/issues/11) ([079968e](https://github.com/phodal/shire/commit/079968e4ff354e03d139751aaa2d6f5df28669be)) * **httpclient:** enhance CUrlConverter with variable support and testing adjustments [#11](https://github.com/phodal/shire/issues/11) ([9e97423](https://github.com/phodal/shire/commit/9e97423f583b1e4ec7becb75864945d5e88a4d70)) * **httpclient:** implement buildFullUrl function for RestClientRequest [#11](https://github.com/phodal/shire/issues/11) ([b49049b](https://github.com/phodal/shire/commit/b49049be5cb56d6214389f03d20fbc120f477e80)) * **httpclient:** Implement URL builder and scratch file creation [#11](https://github.com/phodal/shire/issues/11) ([9a2162e](https://github.com/phodal/shire/commit/9a2162e29d32f7c167649bf93aec7eff66551df9)) * **index:** add ShireEnvironmentIndex for indexing environment variables [#11](https://github.com/phodal/shire/issues/11) ([d43eb96](https://github.com/phodal/shire/commit/d43eb96eb51507b6bc8ea2b2e2bec9d57fdac31a)) * **kotlin-refactor:** implement Kotlin refactoring tool support [#58](https://github.com/phodal/shire/issues/58) ([ad6d1b2](https://github.com/phodal/shire/commit/ad6d1b253670b2d8a77eb901c0fb50830dccebc6)) * **kotlin:** implement structure providers for Kotlin plugin [#58](https://github.com/phodal/shire/issues/58) ([393b747](https://github.com/phodal/shire/commit/393b74799db5d56d09b769bb5b1f5862a5e33e73)) * **languages:** add shire-markdown module and update dependencies [#59](https://github.com/phodal/shire/issues/59) ([26fd92a](https://github.com/phodal/shire/commit/26fd92adf599b97fc599aa3d4c5be7678ddff000)) * **markdown:** add MarkdownPsiCapture for URL extraction [#59](https://github.com/phodal/shire/issues/59) ([ab9739b](https://github.com/phodal/shire/commit/ab9739bf0607d00d914d1265a56c94c490ce92b6)) * **runner:** Add LLM output to runFinish method and process handling [#60](https://github.com/phodal/shire/issues/60) ([5870ef8](https://github.com/phodal/shire/commit/5870ef8e00231abb12be061cf4c1a89964da01d1)) * **shire-kotlin:** add Java support for KotlinLanguageToolchainProvider [#58](https://github.com/phodal/shire/issues/58) ([e026929](https://github.com/phodal/shire/commit/e0269299c57e99f3dc7b15608657544148901004)) * **shirelang:** add crawl functionality and processor support [#59](https://github.com/phodal/shire/issues/59) ([ee5a3ea](https://github.com/phodal/shire/commit/ee5a3ea42b3b2b7feef4974002d5bb8b92817037)) * **shirelang:** add support for threading function execution [#60](https://github.com/phodal/shire/issues/60) ([0d31f9e](https://github.com/phodal/shire/commit/0d31f9e8636d9fa169eb6359dd9fff1c4ebc61ad)) ### Bug Fixes * **executor:** handle exceptions in ShireDefaultLlmExecutor [#60](https://github.com/phodal/shire/issues/60) ([da44e85](https://github.com/phodal/shire/commit/da44e858f32ea9bdb1c68b7b2b5d9f1db671676a)) * **plugin:** add Kotlin module support ([c3c1ecb](https://github.com/phodal/shire/commit/c3c1ecb98b03418b88415e26eb9742db82da3806)) * **shirelang:** add exception handling for LlmProvider streaming output [#60](https://github.com/phodal/shire/issues/60) ([a8272b2](https://github.com/phodal/shire/commit/a8272b2341e7770d2109ac666036e02d3d4bf103)) ## [0.5.2](https://github.com/phodal/shire/compare/v0.5.0...v[0.5.2]) (2024-08-15) ### Bug Fixes * add lost files ([e6f524d](https://github.com/phodal/shire/commit/e6f524dbf661b128270591bf92fe6a24795a9ae6)) ### Features * **compiler:** enhance query processor to lookup elements [#41](https://github.com/phodal/shire/issues/41) ([dbcdc6b](https://github.com/phodal/shire/commit/dbcdc6b89a8ab913245b2eed8858197c447bd96b)) * **git-plugin:** add Git4Idea plugin dependency [#41](https://github.com/phodal/shire/issues/41) ([f59cc83](https://github.com/phodal/shire/commit/f59cc83a6cb449f7de4f70d8e5cbe01a2064299e)) * **search:** add LLM reranker and reranker interface [#46](https://github.com/phodal/shire/issues/46) ([6a7b599](https://github.com/phodal/shire/commit/6a7b599be6ed44ac3e540865d3af6d995bebcf1d)) * **search:** add new ranking algorithm and update reranking methods [#46](https://github.com/phodal/shire/issues/46) ([b0b92b5](https://github.com/phodal/shire/commit/b0b92b555118543f5a10bd7187788b0db6e4de5a)) * **search:** enhance search functionality in SemanticService [#46](https://github.com/phodal/shire/issues/46) ([995331c](https://github.com/phodal/shire/commit/995331c76efafc9bf1deb5224c61044951a576c6)) * **search:** replace IndexEntry with ScoredEntry and add reranking functionality [#46](https://github.com/phodal/shire/issues/46) ([6a84387](https://github.com/phodal/shire/commit/6a843872b0e185028ae7f92f5aa0226efd3bd92b)) ### Reverts * Revert "refactor(core): update SecretPattern and SecretPatterns classes #47" ([1e1d556](https://github.com/phodal/shire/commit/1e1d5560303bd59dae2b73567c03b853041c33a7)), closes [#47](https://github.com/phodal/shire/issues/47) ## [0.4.8](https://github.com/phodal/shire/compare/v0.4.7...v[0.4.8]) (2024-08-02) ### Bug Fixes * **guard:** improve regex pattern validation and update UK phone pattern [#47](https://github.com/phodal/shire/issues/47) ([bcc7e16](https://github.com/phodal/shire/commit/bcc7e162ad328445c707d4df8a0041ac23d391ad)) ### Features * **core:** add GuardScanner interface and ScanResult data class [#47](https://github.com/phodal/shire/issues/47) ([b32b7d8](https://github.com/phodal/shire/commit/b32b7d8e53dd52129ea2a2280fe4682bba3591b8)) * **guard:** add matching and masking functions, remove Joni dependency [#47](https://github.com/phodal/shire/issues/47) ([6a12233](https://github.com/phodal/shire/commit/6a12233170625ed147694167b8c71690649f0266)) * **scanner:** refactor scanner classes and update documentation [#47](https://github.com/phodal/shire/issues/47) ([a223428](https://github.com/phodal/shire/commit/a223428131c757fdf1bf2cde8a0877895020d13c)) * **schema-provider:** add CustomAgentSchemaFileProvider to factory list [#47](https://github.com/phodal/shire/issues/47) ([c8999e5](https://github.com/phodal/shire/commit/c8999e59d6ea9fac58a59e2864680d89ec163253)) * **search:** add dimensions parameter to embedInternal function ([494a8b5](https://github.com/phodal/shire/commit/494a8b56f27d8e0f90338457e3276321fac724dc)) * **secrets-detection:** rename secretType to description and add LocalModelBasedScanner [#47](https://github.com/phodal/shire/issues/47) ([091bacd](https://github.com/phodal/shire/commit/091bacdaea7fe70542b1016840670998e2649d38)) * **secrets-guard:** implement regex-based secret detection [#47](https://github.com/phodal/shire/issues/47) ([1eb2517](https://github.com/phodal/shire/commit/1eb2517e69f2b46066d41e13fc07539eb021ddb2)) * **secrets:** implement regex patterns for PII detection ([a718ac7](https://github.com/phodal/shire/commit/a718ac7c248c7de043f7c2b7c641ed162b7bd7a4)) * **security:** implement BanKeywordsScanner and Replacer interface [#47](https://github.com/phodal/shire/issues/47) ([47f2b69](https://github.com/phodal/shire/commit/47f2b697b857c39d76d85f1338b7128288fb4b99)) * **shirelang:** implement redact function for data masking [#47](https://github.com/phodal/shire/issues/47) ([ed28585](https://github.com/phodal/shire/commit/ed285856f36e443687fc8bc75f3a70084d75723f)) ## [0.4.7](https://github.com/phodal/shire/compare/v0.4.6...v[0.4.7]) (2024-07-30) ### Bug Fixes * **core:** enhance InsertUtil to validate offset range ([022d8e2](https://github.com/phodal/shire/commit/022d8e249da9b5aaa7e7166cc69de2269ff05e34)) ### Features * **core:** add UpdateEditorTextProcessor ([1d8b566](https://github.com/phodal/shire/commit/1d8b566e2b2b0da7ba98f88fecb9ec8be89214c2)) * **docs:** update lifecycle documentation and examples ([9ca64fe](https://github.com/phodal/shire/commit/9ca64fee7870c5b9365541e4d4a67391758fd83f)) * **git-provider:** implement lookupGitData and ShireVcsCommit updates [#41](https://github.com/phodal/shire/issues/41) ([2d56eaf](https://github.com/phodal/shire/commit/2d56eafbdbd15c6824da891366529da3e6282d49)) * **llm:** enhance OpenAILikeProvider to include temperature setting ([cbb6f1d](https://github.com/phodal/shire/commit/cbb6f1d15041520c19f8f2ffde54b8ae7aa1bbeb)) * **provider:** add GitQLDataProvider and update ShireQLDataProvider interface [#41](https://github.com/phodal/shire/issues/41) ([5af617e](https://github.com/phodal/shire/commit/5af617e2ebbabd0908c0ef9d9c82eead4886a8f7)) * **settings:** add temperature setting and UI component ([264211f](https://github.com/phodal/shire/commit/264211f5937a0d42f10d90a8dfee92aae29035ac)) * **shirelang:** add VcsStatementProcessor for commit info handling [#41](https://github.com/phodal/shire/issues/41) ([c71fe57](https://github.com/phodal/shire/commit/c71fe57d5dcf73a37298139a2ac1dee34b4f5c91)) ## [0.4.6](https://github.com/phodal/shire/compare/v0.4.5...v[0.4.6]) (2024-07-24) ### Bug Fixes * **compiler:** handle exceptions when finding files ([e85eb79](https://github.com/phodal/shire/commit/e85eb79923442db4a519c62fa78242862cd16d76)) ### Features * **compiler:** add support for preserving last output in function execution ([73739f5](https://github.com/phodal/shire/commit/73739f5bf2bd1ab4c1fa2b71979690cfd53fd64f)) * **compiler:** enhance afterStreaming execution flow ([c2b7227](https://github.com/phodal/shire/commit/c2b7227abe7f129cbad9f553c2f759928709ba77)) * **core:** add MarkdownPsiContextVariableProvider ([5418957](https://github.com/phodal/shire/commit/5418957719f848ac0e28aac345c8b0a2e95c3dd4)) * **core:** allow null values in compiledVariables and refactor file execution ([20eed68](https://github.com/phodal/shire/commit/20eed683f0d265bcbbef2e710afe0784f320134b)) * **core:** enhance MarkdownPsiContextVariableProvider for file context ([33b97da](https://github.com/phodal/shire/commit/33b97da7fbb7db91ae34c397f8caf1165266f819)) * **core:** enhance MarkdownPsiContextVariableProvider for HTML conversion ([451e61d](https://github.com/phodal/shire/commit/451e61d73e370601437af95c12ce6364e8efa094)) * **custom:** add custom SSE handling ([5735a58](https://github.com/phodal/shire/commit/5735a589b4f7e45b3a750dfc736135b578eb0e08)) * **docs:** update response routing for Java and dynamic input ([0bd650d](https://github.com/phodal/shire/commit/0bd650dc75d39a4a966d8873551cd33b29c8ef11)) * **runner:** enhance Shire runner to handle last output ([4e2804b](https://github.com/phodal/shire/commit/4e2804b6501a7086c3e0437e1bff6e9fcaa6f309)) * **searching:** add similarity threshold to search function ([dd8cd73](https://github.com/phodal/shire/commit/dd8cd73c9c9466f14551650a685fb2d5f0538f03)) * **search:** normalize embeddings and update search methods ([ad907ec](https://github.com/phodal/shire/commit/ad907ec2b27c822d3c8b07467f550618133a6ebc)) * **Shirelang:** enhance HobbitHole and ShireRunFileAction for dynamic interaction ([18f3679](https://github.com/phodal/shire/commit/18f3679711dea6dcc1588a1d921dc9339ad03279)) * **shirelang:** improve file not found error logging ([043488e](https://github.com/phodal/shire/commit/043488e6c1a81066d2dda95bcc661d932af7fff1)) * **ShireRunner:** enhance error handling for detachProcess ([d221082](https://github.com/phodal/shire/commit/d22108282ac9e2fedba2442986708b70b19b35cd)) * **testing:** add new test case for afterStreamingOnly functionality ([a91be0d](https://github.com/phodal/shire/commit/a91be0d2532bc7540ac3bdfefbcdfc707513622c)) ## [0.4.5](https://github.com/phodal/shire/compare/v0.4.3...v[0.4.5]) (2024-07-19) ### Features * **core:** implement equals and hashCode for IndexEntry ([2543805](https://github.com/phodal/shire/commit/25438059ca7ee50a2c47fba29a7e0cc8ef8ed677)) * **search:** add interface for similarity algorithms ([27c65c1](https://github.com/phodal/shire/commit/27c65c1c0dd85c7c91ddcfd72ba90470c03136f8)) * **search:** implement BM25 similarity algorithm and refactor SimilarChunkSearcher ([ef32475](https://github.com/phodal/shire/commit/ef3247552446f1218bd6a0ea30857cb952160933)) ## [0.4.3](https://github.com/phodal/shire/compare/v0.4.2...v[0.4.3]) (2024-07-14) ### Bug Fixes * **actions:** handle null hole in context menu actions ([4a04f35](https://github.com/phodal/shire/commit/4a04f356b16689514bdc4af10df30b8d136b8b6a)) ### Features * **compiler:** add Tee class for writing to files [#36](https://github.com/phodal/shire/issues/36) ([153cc93](https://github.com/phodal/shire/commit/153cc93097f162e96b2f37aa1011f3caf6178886)) * **interaction:** add PasteBoard interaction type ([33afb37](https://github.com/phodal/shire/commit/33afb37775288360865cb3e47b6a943bee1895bd)) * **middleware:** add append functionality and AppendProcessor [#36](https://github.com/phodal/shire/issues/36) ([7413368](https://github.com/phodal/shire/commit/74133686a19869e25ff84a5c0392c081c9df787d)) * **middleware:** expose and update compiledVariables across components [#36](https://github.com/phodal/shire/issues/36) ([c83ffbe](https://github.com/phodal/shire/commit/c83ffbe27659d96139ee3b7165aa6982495c1187)) * **provider:** add method support for JavaPsiQLInterpreter ([ad83803](https://github.com/phodal/shire/commit/ad838038fefac63ad64c4e24351e34db50319f89)) * **provider:** add PsiQLMethodCallInterpreter interface ([c9b6606](https://github.com/phodal/shire/commit/c9b6606d127d0a4e4f6df2d1c0229569272f5e25)) * **shirelang:** extend pipelineArg syntax and add contentTee test [#36](https://github.com/phodal/shire/issues/36) ([42031b7](https://github.com/phodal/shire/commit/42031b7065886b9e399b07497e5157c2d2cd5652)) ## [0.4.2](https://github.com/phodal/shire/compare/v0.4.1...v[0.4.2]) (2024-07-09) ### Bug Fixes * **git:** handle null data context in GitToolchainVariableProvider ([69c48cd](https://github.com/phodal/shire/commit/69c48cd74e444cebdbeadae31bcf84b71c11a0ed)) ### Features * **build:** add kotlinx-coroutines-core dependency ([836332b](https://github.com/phodal/shire/commit/836332bfdca2481f4762a331b7d82b207a4f374b)) * **run:** add console message before running configuration ([a27e899](https://github.com/phodal/shire/commit/a27e8993cd2090293d42764fb9215a7955853c84)) * **search:** add caching support to semantic search ([7ef7287](https://github.com/phodal/shire/commit/7ef7287ecd15b27726d456587942204ccb184005)) ## [0.4.1](https://github.com/phodal/shire/compare/v0.3.0...v[0.4.1]) (2024-07-07) ### Bug Fixes * **compiler:** handle null defaultTask in TaskRoutes.kt ([10cfc67](https://github.com/phodal/shire/commit/10cfc67464c249822a1abdc400bbadd32726537b)) * **completion:** update HobbitHoleValueCompletion to display action location with descriptions. ([71153b4](https://github.com/phodal/shire/commit/71153b4a8aed7d8c617b1ec37cd39762a04ca1cf)) * **config:** handle nullable Flow in EditorInteractionProvider ([6086166](https://github.com/phodal/shire/commit/6086166d6e95bf7c1d24e13fba5e6587ed74d349)) * **core:** ensure thread safety in BaseCodeGenTask.kt ([4479e8a](https://github.com/phodal/shire/commit/4479e8aef4356f49ac6bb2ee7c5674415e3c6359)) * **run:** handle line markers for leaf elements only ([62f09f6](https://github.com/phodal/shire/commit/62f09f6399e03b5e14500b268341e99ad5ab80f1)) * **runner:** detach process handler in ShireRunner ([041c356](https://github.com/phodal/shire/commit/041c35682330af2257c80b8bb63377b0f5b72317)) * **shirelang:** check for null before adding action to toolsMenu ([289f578](https://github.com/phodal/shire/commit/289f57876eecea204acdabde87dec7016b5d4857)) * **shirelang:** update line marker provider to support front matter entries ([2e843a9](https://github.com/phodal/shire/commit/2e843a913b19fb201190212f5d35244064495274)) ### Features * **actions:** add method to set keymap shortcut ([f99686a](https://github.com/phodal/shire/commit/f99686a6017f9a50a506aa31a77f6902ff96adae)) * **code-completion:** refactor code completion task and add InsertUtil [#29](https://github.com/phodal/shire/issues/29) ([b02987e](https://github.com/phodal/shire/commit/b02987edb180f8dc4db36a607651d512fc3b6d1e)) * **compiler:** add CaseMatch functionality in PatternActionFunc [#29](https://github.com/phodal/shire/issues/29) ([0521198](https://github.com/phodal/shire/commit/05211984004d3e0b96e77c76a6a252e168eafd87)) * **compiler:** add save file functionality ([c6bbde6](https://github.com/phodal/shire/commit/c6bbde6e87f9aa065e7daa9832f6bacec1197878)) * **compiler:** add support for WHEN condition and VARIABLES ([593abd5](https://github.com/phodal/shire/commit/593abd56c897d527891ae0e365f177aad0809b18)) * **completion:** add PostProcessor completion provider ([4968586](https://github.com/phodal/shire/commit/49685869b416679d0c050d06d2bfa523e3266c6d)) * **completion:** add PSI context variables to VariableCompletionProvider [#29](https://github.com/phodal/shire/issues/29) ([564b0fe](https://github.com/phodal/shire/commit/564b0fe36aa318bb852b08a5e000edc05d315be5)) * **completion:** add QueryStatementCompletion provider ([696a306](https://github.com/phodal/shire/commit/696a3069f8e4302f94fdfed4c5b0d87e56511580)) * **core:** add code completion task and related changes [#29](https://github.com/phodal/shire/issues/29) ([34eb6fe](https://github.com/phodal/shire/commit/34eb6fec9f5d70b99068870382f7fe8f1cc7154c)) * **core:** add GitActionLocationEditor for commit menu ([48db3c4](https://github.com/phodal/shire/commit/48db3c4e1325ab93201fcde518cd4924cc58f2f8)) * **core:** add postExecute callback to code execution tasks [#29](https://github.com/phodal/shire/issues/29) ([af6567b](https://github.com/phodal/shire/commit/af6567bfdf418643d44af590ce1a266b422b8a92)) * **core:** add postExecute invocation and update interactionType in ShireDefaultRunner [#29](https://github.com/phodal/shire/issues/29) ([8bc4b8b](https://github.com/phodal/shire/commit/8bc4b8bf2b9ee1397286b1769db504b953e72c29)) * **core:** add reflection support for ToolchainVariable ([72ec463](https://github.com/phodal/shire/commit/72ec463441f50c0a65adc174c65e8a7fcdc86700)) * **docs:** add agent examples and documentation provider ([8d7aafe](https://github.com/phodal/shire/commit/8d7aafeb9107503fa3797700a214db19b37939de)) * **docs:** add examples for code comments, refactoring, CLI copilot, and commit message generation ([1e07f68](https://github.com/phodal/shire/commit/1e07f689fc50d0eaa4eff7c72339765d6b646dff)) * **EditorInteractionProvider:** enhance task creation and error handling [#29](https://github.com/phodal/shire/issues/29) ([e0f34a3](https://github.com/phodal/shire/commit/e0f34a381cc19847330881966d02956f891786f8)) * **git:** add commit message UI retrieval improvement in ShireVcsSingleAction ([a8f11de](https://github.com/phodal/shire/commit/a8f11de61dcc51fa55a128f05140a8e83223b73d)) * **input:** add custom input box action ([8ec40a4](https://github.com/phodal/shire/commit/8ec40a41517b17f5644268a9eb2ea885233377c6)) * **interaction:** add support for running code in Run panel ([7c4e8d5](https://github.com/phodal/shire/commit/7c4e8d5f4f798e73b01fa91a473afb66c3e1ec5c)) * **interaction:** improve code completion and generation tasks [#29](https://github.com/phodal/shire/issues/29) ([957e75d](https://github.com/phodal/shire/commit/957e75dbef245538472313595882c872093c1309)) * **interaction:** refactor code generation tasks and add BaseCodeGenTask [#29](https://github.com/phodal/shire/issues/29) ([0f84b3b](https://github.com/phodal/shire/commit/0f84b3bd80af33b07c5831ad5441b9df1ee9a7a8)) * **java:** add class structure representation and data builder [#29](https://github.com/phodal/shire/issues/29) ([90f2364](https://github.com/phodal/shire/commit/90f2364ffbbb45f73a6e09e801461080d8a9010a)) * **java:** add method caller and called method lookup [#29](https://github.com/phodal/shire/issues/29) ([6b46c00](https://github.com/phodal/shire/commit/6b46c001b5f0a209b8cfd65418205c5206698852)) * **java:** add methods to retrieve containing class and method ([b4c26ea](https://github.com/phodal/shire/commit/b4c26eaf868a1dfb9845d377b330b933d97eda61)) * **keyboard:** add support for setting keymap shortcuts ([4eda080](https://github.com/phodal/shire/commit/4eda0800f6b168c315b1ce72231a66508ef625ef)) * **lints:** add duplicate agent inspection ([7947d3c](https://github.com/phodal/shire/commit/7947d3cd92329d9e81a241682646dfdb566e0002)) * **logging:** improve error handling and logging in ShireActionStartupActivity ([e661e16](https://github.com/phodal/shire/commit/e661e161f51a7837cbdb88387bbc3b7533eb306e)) * **middleware:** add InsertNewlineProcessor ([73cebef](https://github.com/phodal/shire/commit/73cebef93bff5e71558938e8d6153f906276a895)) * **middleware:** add ParseCommentProcessor ([aafb938](https://github.com/phodal/shire/commit/aafb9386c5e9e55a54577c531cd770c83a557f5f)) * **provider:** add terminal location executor ([99c93a0](https://github.com/phodal/shire/commit/99c93a0da396940ddd1f2fe1c186474d609fbcce)) * **runner:** add support for user input in Shire configuration ([6ffebd6](https://github.com/phodal/shire/commit/6ffebd6a2fd0ab5004dde65526b2eb11de395dd1)) * **runner:** refactor ShireRunner to improve terminal task execution and error handling ([b236ad2](https://github.com/phodal/shire/commit/b236ad2f5c7b238c26e7e021d17991aaf312bc0f)) * **shirelang:** add icon support and improve line marker provider [#29](https://github.com/phodal/shire/issues/29) ([f12c199](https://github.com/phodal/shire/commit/f12c19900f307ff84e057b304564e0b5d37e5454)) * **shirelang:** add ShirePsiExprLineMarkerProvider for line marker support [#29](https://github.com/phodal/shire/issues/29) ([1a5c3ca](https://github.com/phodal/shire/commit/1a5c3ca56752179586baf390c85a4f21f2c4bd07)) * **shirelang:** refactor and improve pattern action processing [#29](https://github.com/phodal/shire/issues/29) ([57689a2](https://github.com/phodal/shire/commit/57689a2ae0e8c803bbeb6e646e95f39ec095a8e9)) * **shirelang:** update line marker provider for ShirePsiExpr ([6448aa1](https://github.com/phodal/shire/commit/6448aa154ccc9a6e02213cf664dc7d6aa84aa05d)) * **shirelang:** update line marker provider to support front matter entries ([1d4bb4b](https://github.com/phodal/shire/commit/1d4bb4b4c9403b88ede31b2ca38b92e1ce4d8fef)) * **terminal:** add input box popup for terminal action ([03dce26](https://github.com/phodal/shire/commit/03dce2625011eea61cd3b2b5e0860527a904229f)) * **terminal:** add shell command suggestion action ([958340c](https://github.com/phodal/shire/commit/958340c89a7dcc8e6e1eef190d31f409356be83c)) * **terminal:** add ShireTerminalAction for custom assistants ([01023de](https://github.com/phodal/shire/commit/01023de953d69115760d0b6c29a085f344a2f067)) * **terminal:** add TerminalToolchainVariableProvider ([8b13dc3](https://github.com/phodal/shire/commit/8b13dc39d88636f34196e7807c700d94f957feb3)) * **variable:** add BuiltinVariable and resolver ([f88af49](https://github.com/phodal/shire/commit/f88af49fe0bc197eb9d1443c0addafb1d7157667)) * **variable:** add SystemInfoVariable and resolver ([c1a54eb](https://github.com/phodal/shire/commit/c1a54eb4cdc1f06cbb0b47d7ce58e639d56404bd)) * **VariableCompletionProvider:** add icon to variable lookup elements [#29](https://github.com/phodal/shire/issues/29) ([7ae12c5](https://github.com/phodal/shire/commit/7ae12c5f071c1ec494f7410f482a4375a1eba01b)) * **variables:** add code smell detection and test data generation [#29](https://github.com/phodal/shire/issues/29) ([3763184](https://github.com/phodal/shire/commit/3763184da20ade0d3df8a9916730855438502f41)) * **variables:** add support for similar code search ([5fe7f8f](https://github.com/phodal/shire/commit/5fe7f8f2db96727312d56101e77abff9726e9138)) * **vcs): add Shirefeat VCS single(vcs:** action ([e982cec](https://github.com/phodal/shire/commit/e982cecd524b786fd0c30d12770587286082e3f4)) ## [0.0.8](https://github.com/phodal/shire/compare/v0.0.7...v[0.0.8]) (2024-07-01) ### Bug Fixes * **compiler:** wrap parsing operations in read actions ([97c8d15](https://github.com/phodal/shire/commit/97c8d156b78971f787bcfd202eb6f6aa3f030a06)) * **compiler:** wrap parsing operations in read actions ([76e1700](https://github.com/phodal/shire/commit/76e1700b7b213d73de07f53053df28c96bba7905)) * **completion:** fix code fence insertion in completion ([7beb240](https://github.com/phodal/shire/commit/7beb240a2eaafd997e85d82e52330ea5f5fb3cf2)) * **shirelang:** refine action body parsing in FrontmatterParser kt file ([32776cd](https://github.com/phodal/shire/commit/32776cdf389710c941010a3d117bd5076202d1b1)) * **shirelang:** update when condition syntax and test evaluation ([2ee436c](https://github.com/phodal/shire/commit/2ee436c73362d06ecb7dd1858c42d094f0205a34)) ### Features * **actions:** refactor action groups and context menu action ([900d613](https://github.com/phodal/shire/commit/900d613900ed6dd9785b8ea3a1b082065db18f54)) * **chat:** add ChatRole enum and FileGenerateTask for file output ([9bf98d5](https://github.com/phodal/shire/commit/9bf98d590d0c9a5e6726969009809fdefe02d4a8)) * **compiler:** add 'afterStreaming' feature and enhance pattern action processing [#24](https://github.com/phodal/shire/issues/24) ([bb21198](https://github.com/phodal/shire/commit/bb2119819f27bdc966dc0949f4b79681989bccaf)) * **compiler:** add FunctionStatementProcessor and refactor related classes [#24](https://github.com/phodal/shire/issues/24) ([1179c5e](https://github.com/phodal/shire/commit/1179c5e2089a1bd821e17dcd8d37a54c3a3977ca)) * **compiler:** add jsonpath support and modify error condition [#24](https://github.com/phodal/shire/issues/24) ([1913e4f](https://github.com/phodal/shire/commit/1913e4f994c208d624dbed7f70caff4e888e297e)) * **compiler:** enhance function statement processing and add new pattern actions [#24](https://github.com/phodal/shire/issues/24) ([054fec5](https://github.com/phodal/shire/commit/054fec5ae136a0507a715bd5c222351616e81c73)) * **compiler:** enhance statement processing in FunctionStatementProcessor [#24](https://github.com/phodal/shire/issues/24) ([56a551c](https://github.com/phodal/shire/commit/56a551c2b40ec86c59ee4ca2226040f5e0ef9a57)) * **compiler:** refactor function execution and improve logging [#24](https://github.com/phodal/shire/issues/24) ([8944045](https://github.com/phodal/shire/commit/8944045043e5b6b4b00f18e4c49c27f97aade484)) * **compiler:** refactor method invocation in FunctionStatementProcessor ([5b4911b](https://github.com/phodal/shire/commit/5b4911b38c8a9f1808a6028e8ea596c86c21d041)) * **compiler:** update function execution and case matching logic [#24](https://github.com/phodal/shire/issues/24) ([ae44272](https://github.com/phodal/shire/commit/ae44272ddab12bedfa89cd1efd6cc7dc53253809)) * **core:** enhance code parsing and saving [#24](https://github.com/phodal/shire/issues/24) ([0e95539](https://github.com/phodal/shire/commit/0e95539119bddbf5c1ffd5f95179fa28f6da3213)) * **core:** modify execute method to return string [#27](https://github.com/phodal/shire/issues/27) ([aeaf5d4](https://github.com/phodal/shire/commit/aeaf5d488b98da2db5adccc1d8159f6f8912dff8)) * **core:** refactor LlmProvider and related classes to shirecore package [#27](https://github.com/phodal/shire/issues/27) ([92e4026](https://github.com/phodal/shire/commit/92e4026c5407c9fa59e1854e24f7b5712797dcb3)) * **core:** update InteractionType and improve coroutine handling ([fffda5c](https://github.com/phodal/shire/commit/fffda5cc122eb48f60dc1063648e9aa81f22e7ec)) * **custom:** add custom SSE processor and JSON response callback [#10](https://github.com/phodal/shire/issues/10) ([6f75068](https://github.com/phodal/shire/commit/6f750687997d7660c73be3aa38781871da0f9a27)) * **docs:** add custom AI agent quickstart guide [#10](https://github.com/phodal/shire/issues/10) ([03f777b](https://github.com/phodal/shire/commit/03f777bf3437316259e9b932c0c01aada1592881)) * **docs:** add IDE note and update GitToolchainVariableProvider [#27](https://github.com/phodal/shire/issues/27) ([d59fcb8](https://github.com/phodal/shire/commit/d59fcb804185a9eb94a71b297b741ecf7e18f7e2)) * **editor:** add smart code insertion feature [#24](https://github.com/phodal/shire/issues/24) ([788051f](https://github.com/phodal/shire/commit/788051f45f39712844536c4b8460c440a403b799)) * **folding:** add support for query statements and block comments ([32e1da8](https://github.com/phodal/shire/commit/32e1da8e9287509d7e561aeda12fa081eaa2ee21)) * **grammar:** add support for QUOTE_STRING in agentId ([3fdf55c](https://github.com/phodal/shire/commit/3fdf55c59a306e3884a8fd37aecee94c610b0a68)) * **InteractionType:** add new interaction type and change defaults ([b944f93](https://github.com/phodal/shire/commit/b944f935b4a891297d03933fbf7517e0eced93f3)) * **java:** add smart insert method in JavaCodeModifier ([42d8326](https://github.com/phodal/shire/commit/42d8326c95c5df03d34beb11b8b9a08374fc6884)) * **lexer:** add brace level tracking and state transitions [#16](https://github.com/phodal/shire/issues/16) ([882444a](https://github.com/phodal/shire/commit/882444a62f0053e44705a058632a4c73abd784ef)) * **lexer:** add support for multiple front matter variables [#16](https://github.com/phodal/shire/issues/16) ([9eb9f8d](https://github.com/phodal/shire/commit/9eb9f8d6ed8761f33f3f3736e51b285667004867)) * **middleware:** Add console parameter to PostProcessor execute methods [#24](https://github.com/phodal/shire/issues/24) ([ebcbd94](https://github.com/phodal/shire/commit/ebcbd944182804dfa44050f46ef38e42f658eb96)) * **middleware:** add FormatCode functionality ([6905f4f](https://github.com/phodal/shire/commit/6905f4f25dea2a7757811a8b14076f5afd0e045c)) * **middleware:** add OpenFileProcessor to handle file opening [#24](https://github.com/phodal/shire/issues/24) ([99a2ec2](https://github.com/phodal/shire/commit/99a2ec2efb2f04194c927ccab4ef767466f9867c)) * **middleware:** add RunCodeProcessor and ExtensionPoint for file execution [#24](https://github.com/phodal/shire/issues/24) ([0b9f065](https://github.com/phodal/shire/commit/0b9f065f7b4925158429f43152429e38b38dcd21)) * **middleware:** enhance code execution, file saving and verification [#24](https://github.com/phodal/shire/issues/24) ([ed7ca3b](https://github.com/phodal/shire/commit/ed7ca3baaa7af894af532499d33c86b704af4d9f)) * **parser:** add parentheses detection in method calls [#16](https://github.com/phodal/shire/issues/16) ([29abe86](https://github.com/phodal/shire/commit/29abe86737527e152a686bcf32c89e74849aa574)) * **plugin:** add Python support to Shirelang plugin [#24](https://github.com/phodal/shire/issues/24) ([7da1a1f](https://github.com/phodal/shire/commit/7da1a1ff6e798613740924fefb21cbb58cc6e92e)) * **QueryStatementProcessor:** enhance error logging for method or field not found [#16](https://github.com/phodal/shire/issues/16) ([9dd696f](https://github.com/phodal/shire/commit/9dd696fac416c077712e5a998242af664e700436)) * **run-code:** add CLI execution support for Python, JavaScript, and Ruby files [#24](https://github.com/phodal/shire/issues/24) ([0619ecb](https://github.com/phodal/shire/commit/0619ecb511d8976a5b4535df1f6debbf035388b1)) * **runFile:** wrap file search in runReadAction for thread safety ([d8324c3](https://github.com/phodal/shire/commit/d8324c350f2e7580d613cde6e90e2bada8e1bc7a)) * **runner:** refactor interaction handling in IDE locations [#27](https://github.com/phodal/shire/issues/27) ([e51dc98](https://github.com/phodal/shire/commit/e51dc98a938b291b67dc68fcd9e47b05ba25486b)) * **runners:** add HobbitHole to ShireRunner classes [#24](https://github.com/phodal/shire/issues/24) ([9e55bd1](https://github.com/phodal/shire/commit/9e55bd1d97de1c5ce2e9151a65c05cb7950ab29f)) * **schema:** add Shire Custom Agent schema provider factory [#16](https://github.com/phodal/shire/issues/16) ([dc03d0c](https://github.com/phodal/shire/commit/dc03d0c08d50c146ecd9539730db84c497f734a8)) * **shire-core:** implement EditorInteractionProvider and add Shire Toolchain Variable doc [#27](https://github.com/phodal/shire/issues/27) ([aff5380](https://github.com/phodal/shire/commit/aff5380c05edf560a202de52947812b1d58994fa)) * **shirelang:** add default condition and new test for afterStreaming [#24](https://github.com/phodal/shire/issues/24) ([e5a28cb](https://github.com/phodal/shire/commit/e5a28cbe8e1a5036dd24080883b8c3ce71590224)) * **shirelang:** add file execution support and improve condition handling ([d37e306](https://github.com/phodal/shire/commit/d37e3060f47d5b8e3ff290d18ac2cc59d7beebd8)), closes [#24](https://github.com/phodal/shire/issues/24) * **shirelang:** add new lifecycle keywords and update grammar [#24](https://github.com/phodal/shire/issues/24) ([c957282](https://github.com/phodal/shire/commit/c9572825a23a9c2c3ded709427784d3f736f7532)) * **shirelang:** add new lifecycle keywords to syntax highlighter ([3e43d38](https://github.com/phodal/shire/commit/3e43d38282bc35e46ff6785f03f0371f8ee9f75b)) * **shirelang:** add support for finish flags in output control flow [#24](https://github.com/phodal/shire/issues/24) ([99f1023](https://github.com/phodal/shire/commit/99f10230c6c856d6fe429c5c8c8b996d835525ee)) * **shirelang:** enhance pattern action processing and test handling [#24](https://github.com/phodal/shire/issues/24) ([cb59bfb](https://github.com/phodal/shire/commit/cb59bfb8233c027ace91680c750ffe38a63a778b)) * **TaskRoutes:** make defaultTask optional and refactor execution logic [#24](https://github.com/phodal/shire/issues/24) ([5787cca](https://github.com/phodal/shire/commit/5787cca2ad6fad9e00b59e5108e701a267d5f5e0)) * **testing:** add success condition and improve function execution [#24](https://github.com/phodal/shire/issues/24) ([6eadfda](https://github.com/phodal/shire/commit/6eadfdaae2deb177158c134f4b72ddee51397f69)) * **variable resolver:** add project context to resolveAll method [#27](https://github.com/phodal/shire/issues/27) ([b5f05f9](https://github.com/phodal/shire/commit/b5f05f940c3619db1c5ee38e9ca235ce4fdc6ffb)) * **variable-resolver:** add support for toolchain variables [#27](https://github.com/phodal/shire/issues/27) ([fdbb4c9](https://github.com/phodal/shire/commit/fdbb4c91b01f9ef78b0a0752d545c70f801fab02)) * **variables:** add ToolsetVariable and ToolsetVariableProvider [#27](https://github.com/phodal/shire/issues/27) ([c1da8a5](https://github.com/phodal/shire/commit/c1da8a5d3ac7be9c8452f2f9c34bd5aa49a5dd86)) * **vcs:** add dynamic actions to VCS action group [#24](https://github.com/phodal/shire/issues/24) ([9018da5](https://github.com/phodal/shire/commit/9018da5056323b148a9c5d797c9f994efb1efd93)) * **vcs:** add ShireVcsActionGroup for dynamic actions ([902bc2a](https://github.com/phodal/shire/commit/902bc2ab24df996bfc1fd7bd47eb5204b5dc916c)) * wrap code blocks with appropriate application run actions [#24](https://github.com/phodal/shire/issues/24) ([55f7495](https://github.com/phodal/shire/commit/55f7495db0c4a23adb3e736545a61dc2d6555ee4)) ## [0.0.7](https://github.com/phodal/shire/compare/v0.0.6...v[0.0.7]) (2024-06-24) ### Bug Fixes * **grammar:** update frontMatterArray syntax in ShireParser.bnf [#16](https://github.com/phodal/shire/issues/16) ([428033c](https://github.com/phodal/shire/commit/428033cb0b365e23e2e6fd77981ac38208c732ad)) * **pattern-searcher:** handle invalid regex and refactor code [#18](https://github.com/phodal/shire/issues/18) ([b00cd54](https://github.com/phodal/shire/commit/b00cd54bc1d9eb82a2dd259e2617f89b33ff6831)) ### Features * **codemodel:** add FileStructure and VariableStructure classes [#14](https://github.com/phodal/shire/issues/14) ([322e897](https://github.com/phodal/shire/commit/322e89770f2ef3b93804f514705c0d5f220a4111)) * **codemodel:** add MethodStructureProvider and related modifications [#14](https://github.com/phodal/shire/issues/14) ([29eedb9](https://github.com/phodal/shire/commit/29eedb9f5c00bcdcb78554f2c0c4e42d74dd78e2)) * **codemodel:** add VariableStructureProvider and FileStructureProvider, refactor MethodStructureProvider [#14](https://github.com/phodal/shire/issues/14) ([4be010b](https://github.com/phodal/shire/commit/4be010b8ca205aba73159d8436b1ab0d9049daa8)) * **codemodel:** enhance ClassStructure and MethodStructure formatting [#14](https://github.com/phodal/shire/issues/14) ([07488b0](https://github.com/phodal/shire/commit/07488b029ba653957738d226eb464f1270b64ebc)) * **codemodel:** update FileStructure and add DirectoryStructure [#14](https://github.com/phodal/shire/issues/14) ([076368e](https://github.com/phodal/shire/commit/076368ed87806d32bbfe4453ccdca613ad9a8f22)) * **compiler:** add error handling for function arguments ([376691f](https://github.com/phodal/shire/commit/376691f8ad251658282c69e62525cdcfc8ccee3d)) * **compiler:** add execution logic for variable pattern functions [#16](https://github.com/phodal/shire/issues/16) ([3542461](https://github.com/phodal/shire/commit/354246175027b6ff0cd8df14c1af0e3cd53edb0f)) * **compiler:** add operator handling in QueryStatementProcessor [#16](https://github.com/phodal/shire/issues/16) ([fc9f89d](https://github.com/phodal/shire/commit/fc9f89d6664e475f85227724eebf2496d8f26c16)) * **compiler:** add PatternSearcher for file matching by regex [#18](https://github.com/phodal/shire/issues/18) ([d129e40](https://github.com/phodal/shire/commit/d129e406478cfaba891b8ea57a608149da7796f9)) * **compiler:** add query statement support in pattern action [#16](https://github.com/phodal/shire/issues/16) ([12fd4fd](https://github.com/phodal/shire/commit/12fd4fda5671881128059ccdb844f12fa210f8dc)) * **compiler:** add regex support and array handling in PatternActionFunc [#18](https://github.com/phodal/shire/issues/18) ([aebf478](https://github.com/phodal/shire/commit/aebf478ba9b04a9ac4240809b7699a6edd6ddc73)) * **compiler:** add support for query statements in Shire language [#16](https://github.com/phodal/shire/issues/16) ([824c9f0](https://github.com/phodal/shire/commit/824c9f009ca0e428779703d30daf65ed7db65137)) * **compiler:** add Value class and evaluate function in ShireExpression [#16](https://github.com/phodal/shire/issues/16) ([8b04548](https://github.com/phodal/shire/commit/8b0454857ef01d31e669f69488d7ba277005413c)) * **compiler:** enhance QueryStatementProcessor functionality [#16](https://github.com/phodal/shire/issues/16) ([ddc2ff5](https://github.com/phodal/shire/commit/ddc2ff51e6b38d17d4de7db9a31de2366537e4b3)) * **compiler:** handle null arguments and improve error handling [#16](https://github.com/phodal/shire/issues/16) ([59502be](https://github.com/phodal/shire/commit/59502be32bcd45294fc884be19728eecdbe2327a)) * **compiler:** improve pattern action execution and testing [#18](https://github.com/phodal/shire/issues/18) ([4cada42](https://github.com/phodal/shire/commit/4cada4234799def70e7fc64534d8a7b50bd6acc2)) * **compiler:** improve pattern handling and file loading in ShireCompiler [#18](https://github.com/phodal/shire/issues/18) ([4f3062e](https://github.com/phodal/shire/commit/4f3062e4e3b4ca5e95db03a5c48c9f4f12ed424b)) * **compiler:** refactor built-in methods to enum class [#18](https://github.com/phodal/shire/issues/18) ([07e0475](https://github.com/phodal/shire/commit/07e0475851e59c0778254d6522666705ba78f58a)) * **compiler:** refactor pattern action classes and move to ast package [#18](https://github.com/phodal/shire/issues/18) ([35a5515](https://github.com/phodal/shire/commit/35a55153df29b82c715d010237ef0bf6ab916349)) * **compiler:** refactor query statement parsing and improve documentation [#16](https://github.com/phodal/shire/issues/16) ([6e385d5](https://github.com/phodal/shire/commit/6e385d58b2055f8d8faa0c37a308bd00d8f53f5d)) * **compiler:** refactor template compilation and variable resolution ([93d7536](https://github.com/phodal/shire/commit/93d75369b19e13938ab604e3be7d08f42c31db90)), closes [#18](https://github.com/phodal/shire/issues/18) * **compiler:** refactor VariableStatement to VariableElement and add new PatternActionFunc subclasses [#16](https://github.com/phodal/shire/issues/16) ([6c9c9f3](https://github.com/phodal/shire/commit/6c9c9f3eddc5f33130fb6401250ff54d00571495)) * **console:** add console output for Shirelang execution and error handling [#18](https://github.com/phodal/shire/issues/18) ([b9e4bb1](https://github.com/phodal/shire/commit/b9e4bb19e8b04b828513ae5066ee4169e73b8b2d)) * **core:** add comment and refactor code in CustomAgent, ClassStructure, ShireExpression ([d5c0573](https://github.com/phodal/shire/commit/d5c05735dc7680c9cfc1060986f29c683a7da425)) * **docs, core, languages:** add design samples and method context variables ([bf99f08](https://github.com/phodal/shire/commit/bf99f086f14796d42a82b92d801a8968eac958e7)) * **FrontMatterType:** add QUERY_STATEMENT subclass [#16](https://github.com/phodal/shire/issues/16) ([a7ccc1a](https://github.com/phodal/shire/commit/a7ccc1a2629762fb11e0507bdc5188790610c997)) * **java-toolchain:** add Maven support and refactor tech stack detection [#18](https://github.com/phodal/shire/issues/18) ([9c14272](https://github.com/phodal/shire/commit/9c1427233e41d329add38933c399eb1110b80385)) * **java-toolchain:** refactor Maven build tool functionality into separate class [#18](https://github.com/phodal/shire/issues/18) ([39f4783](https://github.com/phodal/shire/commit/39f47837a79b159c1db04c2c7badfd1dd2863264)) * **java-toolchain:** replace JavaTasksUtil with GradleTasksUtil [#18](https://github.com/phodal/shire/issues/18) ([83c5e00](https://github.com/phodal/shire/commit/83c5e00f642226c5a0fb2b16d9e9d303bc666afe)) * **java:** enhance RelatedClassesProvider to support PsiClass [#14](https://github.com/phodal/shire/issues/14) ([f39aa4e](https://github.com/phodal/shire/commit/f39aa4e59f8472e7989c240c94ec061f5fdb36be)) * **java:** refactor method name and add JavaCodeModifier [#14](https://github.com/phodal/shire/issues/14) ([319cd74](https://github.com/phodal/shire/commit/319cd740b96e5217d27f92495dc7d9023b1f9410)) * **parser:** add 'and' operator and improve comment handling [#16](https://github.com/phodal/shire/issues/16) ([153c5a4](https://github.com/phodal/shire/commit/153c5a447fbe6ec575f04a5951fb917ce9a3a083)) * **parser:** add support for comments in ShireParserDefinition [#16](https://github.com/phodal/shire/issues/16) ([dbdf410](https://github.com/phodal/shire/commit/dbdf4108077c40d6440b71dd1710acf8e5858ba7)) * **parser:** remove whitespace support in query and from clauses [#16](https://github.com/phodal/shire/issues/16) ([9ac9d82](https://github.com/phodal/shire/commit/9ac9d8210727d584a77a72ff2806ce3730aa2c84)) * **parser:** update grammar and lexer for query expressions [#16](https://github.com/phodal/shire/issues/16) ([7fd6746](https://github.com/phodal/shire/commit/7fd67469b351e12af06d32770ecf0a43b64e00ec)) * **parsing:** update grammar and lexer for query expressions [#16](https://github.com/phodal/shire/issues/16) ([383e1ac](https://github.com/phodal/shire/commit/383e1ac48807f9b7befbfaa0f47344eb953e84d0)) * **pattern-action:** add array handling in Grep function [#18](https://github.com/phodal/shire/issues/18) ([9a76f1c](https://github.com/phodal/shire/commit/9a76f1cd3f5237534bae63d69937e7d1444e1f40)) * **plugin:** add Shire plugin configuration and enhance symbol lookup [#16](https://github.com/phodal/shire/issues/16) ([d25e77b](https://github.com/phodal/shire/commit/d25e77bac3f17cd0a9bb0964929b906dbe6ceb4b)) * **QueryStatementProcessor:** implement Expression and String cases ([915dd81](https://github.com/phodal/shire/commit/915dd81ddb7c710263d8e0f38f82514239d1007e)) * **QueryStatementProcessor:** implement operator and type evaluation [#16](https://github.com/phodal/shire/issues/16) ([4369408](https://github.com/phodal/shire/commit/4369408a74328b8a46418122f9e5a4e944f71e8e)) * remove saql project from build ([53d4d1f](https://github.com/phodal/shire/commit/53d4d1f919cf2219a9fbba6c053cc55b6af39047)) * **runner:** add system info variable resolution [#16](https://github.com/phodal/shire/issues/16) ([9f5d86b](https://github.com/phodal/shire/commit/9f5d86b91c29da377bcfe9eda39f402f84fa581d)) * **runner:** enhance data fetching methods in SystemInfoVariable ([411ebf4](https://github.com/phodal/shire/commit/411ebf4840152624f15a6800478bcf5715fdbf74)) * **saql:** add lexer and parser for Shire SQL [#16](https://github.com/phodal/shire/issues/16) ([a4479c3](https://github.com/phodal/shire/commit/a4479c32b9c72a6f6c57e8be490d825e8bf7fcfb)) * **saql:** add saql project and related files [#16](https://github.com/phodal/shire/issues/16) ([572694e](https://github.com/phodal/shire/commit/572694e1a129f2786a8e1e0fd15ff5584f2cea43)) * **saql:** enable getSqlTable methods in SAQLParser ([16d1c3c](https://github.com/phodal/shire/commit/16d1c3c93e45304038ae5d4a726097f167dd425c)) * **saql:** refactor Saql language support and add new classes [#16](https://github.com/phodal/shire/issues/16) ([cad778d](https://github.com/phodal/shire/commit/cad778dd28a51b8d5d931af54378189ec7df031f)) * **saql:** refactor Saql language support and add new classes [#16](https://github.com/phodal/shire/issues/16) ([0b00f60](https://github.com/phodal/shire/commit/0b00f60c65bac04b4bf80b095da22817ae638825)) * **shirelang:** add ShireCommenter and update lexer and parser [#16](https://github.com/phodal/shire/issues/16) ([916a7d7](https://github.com/phodal/shire/commit/916a7d758e09a77615f9f34f894914f882df6be4)) * **shirelang:** enhance FrontMatterType subclasses and refactor pattern action processing [#18](https://github.com/phodal/shire/issues/18) ([4777ee3](https://github.com/phodal/shire/commit/4777ee34b2cb65c26c7d506a25d694bb67c90b8b)) * **shirelang:** improve query expression and statement processing [#16](https://github.com/phodal/shire/issues/16) ([62cd708](https://github.com/phodal/shire/commit/62cd708d59179e40407c0bda184eb82baf7968a2)) * **shirelang:** refactor methods and improve error handling [#18](https://github.com/phodal/shire/issues/18) ([9b170ee](https://github.com/phodal/shire/commit/9b170ee80e973f6fe5f6b3c93c684290826210b5)) * **ShireLang:** update grammar rules and lexer definitions [#16](https://github.com/phodal/shire/issues/16) ([29cc1b1](https://github.com/phodal/shire/commit/29cc1b1364981f6548c08b4a7933ffc63d96fa09)) * **syntax-highlighter:** add new keywords to ShireSyntaxHighlighter [#16](https://github.com/phodal/shire/issues/16) ([9d937ec](https://github.com/phodal/shire/commit/9d937ec7dd5d7b1424f5ef6814af09f711cc9c68)) * **testing:** add DefaultShireSymbolProvider and update ShireQueryExpressionTest ([e756f77](https://github.com/phodal/shire/commit/e756f77af2a8b23b6b3ac136982b5a234b82d148)), closes [#16](https://github.com/phodal/shire/issues/16) * **variable-resolver:** add date and time to SystemInfoVariableResolver ([bc3356b](https://github.com/phodal/shire/commit/bc3356b694f63036fb2b06860b3631097a87966f)) * **variable-resolver:** refactor variable resolution system [#18](https://github.com/phodal/shire/issues/18) ([a96c00e](https://github.com/phodal/shire/commit/a96c00ee704f545c270ef1a2333ffab33ab5904c)) * **VariablePatternActionExecutor:** add project, editor, and hole as class properties [#18](https://github.com/phodal/shire/issues/18) ([54da7a6](https://github.com/phodal/shire/commit/54da7a6d47c60b4c1cce75a9d833049b745c24c1)) * **variable:** refactor PsiVariable to PsiContextVariable [#18](https://github.com/phodal/shire/issues/18) ([6e5e176](https://github.com/phodal/shire/commit/6e5e176ef30e4ae83a93bfba07386dfb4773812d)) ### Performance Improvements * **build:** optimize Gradle build performance in GitHub Actions ([f09254d](https://github.com/phodal/shire/commit/f09254d1bd81a47a47794545d5abb9e0988b78bc)) ## [0.0.6](https://github.com/phodal/shire/compare/v0.0.4...v[0.0.6]) (2024-06-16) ### Bug Fixes * **java:** fix null check for JavaSdkType in JavaToolchainProvider ([fb8ad5f](https://github.com/phodal/shire/commit/fb8ad5f88d980f14505f8af1eb798de733438b81)) * **java:** handle null psiElement in resolveVariableValue ([ba34428](https://github.com/phodal/shire/commit/ba34428e95ca67e1373853be6e590d5ed4cf1eb9)) * **test:** update file patterns in test data ([bfbe463](https://github.com/phodal/shire/commit/bfbe46370b41a4c7f2c10f8d48f3446c97866b47)) ### Features * **actions:** add WhenConditionValidator for dynamic actions ([2e1c06a](https://github.com/phodal/shire/commit/2e1c06a1088c0c69cb471e4ae90af020f9147c36)) * **compiler:** add support for method call with arguments ([1cb74e3](https://github.com/phodal/shire/commit/1cb74e3771f265266480fa35daa18053630883ff)) * **compiler:** update HobbitHole variables to use list of conditions ([dd9dc0a](https://github.com/phodal/shire/commit/dd9dc0ae5638f0693dd3c651705d5da6e17f0dce)) * **completion:** add support for when condition functions ([35e13aa](https://github.com/phodal/shire/commit/35e13aa45c33886252ed31f9cde1af2accd154ff)) * **core:** add CodeStructVariableProvider for code struct generation [#14](https://github.com/phodal/shire/issues/14) ([9a6c573](https://github.com/phodal/shire/commit/9a6c5739066fd4cd2f0a4f61e0683919d89bb6b8)) * **frontmatter:** add support for logical OR expressions in frontmatter parsing ([3c0f5dc](https://github.com/phodal/shire/commit/3c0f5dc9e48aa7c4ec296cfab95cd5a5e59d3f29)) * **grammar:** add support for velocity expressions ([c71fb07](https://github.com/phodal/shire/commit/c71fb07f638afa52279cbc4921733c17c52d6cc7)) * **highlight:** add 'WHEN' keyword support ([abdcb6c](https://github.com/phodal/shire/commit/abdcb6cbfc46d5a12f81ff631304b47cac36a392)) * **java:** add JavaCodeStructVariableProvider for code struct generation [#15](https://github.com/phodal/shire/issues/15) ([378f44e](https://github.com/phodal/shire/commit/378f44ec4a4f4779cbdcc0329568257e1a519adb)) * **runner:** add SymbolResolver for variable resolution [#14](https://github.com/phodal/shire/issues/14) ([ed03e25](https://github.com/phodal/shire/commit/ed03e25211dc222a92dca99269150af5a4fbf351)) * **search:** add TfIdf class for text analysis [#14](https://github.com/phodal/shire/issues/14) ([55e94a6](https://github.com/phodal/shire/commit/55e94a6f109dd4677f985f605119d133045c75c6)) * **shire:** add PatternFun Cat subclass ([ca2b531](https://github.com/phodal/shire/commit/ca2b53117d2ca0c512bf182d8d83fb55922ee8bb)) * **template:** add Shire Action template and action [#14](https://github.com/phodal/shire/issues/14) ([0493d23](https://github.com/phodal/shire/commit/0493d2363006ae25073272d8035d0851aee15824)) * **tokenizer:** add TermSplitter and StopwordsBasedTokenizer [#14](https://github.com/phodal/shire/issues/14) ([a774f48](https://github.com/phodal/shire/commit/a774f48a17c64553ab417763511b230dfaf73b18)) ## [0.0.4](https://github.com/phodal/shire/compare/2b4a6f06733149d0cd9763e2d4719a048fa37ce3...v[0.0.4]) (2024-06-11) ### Bug Fixes * **build:** remove unnecessary project dependency ([4628e8b](https://github.com/phodal/shire/commit/4628e8b8b992b9904f68fd46d5c0117af9c51418)) * **build:** update paths for plugin directory in workflows ([a0b6570](https://github.com/phodal/shire/commit/a0b657083ec8d49d65dff87f4efd003848aac7ed)) * **parser:** fix filenameRules regex in ShireFmObject test ([85de5bd](https://github.com/phodal/shire/commit/85de5bdf5964886d6568aee24b61a1ff2e873da2)) * **parser:** fix filenameRules regex in ShireFmObject test ([74783a1](https://github.com/phodal/shire/commit/74783a1d7dfb15b956132f68e5316ba9775c0512)) * **release:** update gradle task path for patching changelog ([201c458](https://github.com/phodal/shire/commit/201c458e09399435039ade13fac81a0d43d26754)) ### Features * **action:** add ShireAction and ShireActionRegister interfaces ([e5ed7b8](https://github.com/phodal/shire/commit/e5ed7b8577e577717c26bb44520e4099dc57cb37)) * **actions:** add context and intent action retrieval ([1be1fda](https://github.com/phodal/shire/commit/1be1fda1b58f6d1e8f7a9e535ab1fedff0c17a15)) * **agent:** add custom agent response actions and configurations ([d6b7501](https://github.com/phodal/shire/commit/d6b7501a707c0c0172c6fc2a44db8dfc51a03569)) * **codeedit:** add CodeModifier interface for code editing ([3bcdf44](https://github.com/phodal/shire/commit/3bcdf44b35840e90af31045c0a387c93fbf2f38a)) * **codemodel:** add ClassStructureBuilder, ClassStructureProvider, and FormatableElement ([5504468](https://github.com/phodal/shire/commit/55044689e759e1ed7de6f4c1ca9c71ae19cee83e)) * **compiler:** add ElementStrategy for auto selecting parent block element ([4f5118b](https://github.com/phodal/shire/commit/4f5118b68a75af91262a2d5540e83c9dd3a1e920)) * **compiler:** add FrontmatterParser for dynamic action configuration ([12bb82a](https://github.com/phodal/shire/commit/12bb82a23275910b9195655ae9cdee16462a585c)) * **compiler:** add ShellRunService for running shell scripts ([63ab840](https://github.com/phodal/shire/commit/63ab840a6844d7a7a608aa11126da80863603a10)) * **compiler:** add support for browsing URLs ([3b63e52](https://github.com/phodal/shire/commit/3b63e52e2ff4debc066c6ae17de5ae4218bc9edd)) * **compiler:** add support for DATE type in front matter ([3062445](https://github.com/phodal/shire/commit/3062445a58cba099b1c3031a72bf8ec7247132f4)) * **compiler:** add support for filename rules ([c9e860c](https://github.com/phodal/shire/commit/c9e860c9bd14f7b8dc88b0f24792c1aacba19c56)) * **compiler:** add support for front matter configuration ([aada8af](https://github.com/phodal/shire/commit/aada8afdd3efaeb25504e3f7aa81350cb1c11080)) * **compiler:** add support for frontmatter parsing ([b80cee1](https://github.com/phodal/shire/commit/b80cee1da7c9e1a629777abcd4e9e202799e6898)) * **compiler:** add support for head and tail functions ([b3f0aa0](https://github.com/phodal/shire/commit/b3f0aa024408ff8add6091b8f2f2204d5b538aaf)) * **compiler:** add support for loading custom agents ([62357fe](https://github.com/phodal/shire/commit/62357feadc08f20ccfc3665e3b8498b65d4a3383)) * **compiler:** add support for print function ([511cdde](https://github.com/phodal/shire/commit/511cddef30a3d4169cb1679a0ecd2623da082d02)) * **compiler:** add support for replace pattern function ([9d4160f](https://github.com/phodal/shire/commit/9d4160f75700f130370a5112c4e1e3f1f43f6b1c)) * **compiler:** add support for variables in HobbitHole ([0178647](https://github.com/phodal/shire/commit/01786479ab1b3c524489afcbd12cc2bd6cc8ed47)) * **completion:** add basic completion for project run tasks ([e8c6578](https://github.com/phodal/shire/commit/e8c6578c4ce9a06a567bf4301b50041792286f61)) * **completion:** add completion providers for code fence languages, file references, variables, agents, commands, and more ([4ef7ab4](https://github.com/phodal/shire/commit/4ef7ab49a09f58fdf9dbd379e41f64f16dad7aaa)) * **completion:** add completion support for Git revisions ([c977564](https://github.com/phodal/shire/commit/c9775640d40ae7818a2b8c339195a22e50dbc7af)) * **completion:** add Hobbit Hole code completion ([4fd6e41](https://github.com/phodal/shire/commit/4fd6e419b9ece791a1194a6dd17a256d56e944b2)) * **core:** add commit functionality to RevisionProvider ([7be17f1](https://github.com/phodal/shire/commit/7be17f1cb0136bb177a478a5bc1a51e3cf91d543)) * **core:** add DAG support with topological sort and cycle detection ([c01ada5](https://github.com/phodal/shire/commit/c01ada5760574a31ada3b6b9c8598a4840ff5cf7)) * **core:** add file filtering functionality ([8864237](https://github.com/phodal/shire/commit/88642371187671d02f3fc95335a8b1fd88073d88)) * **core:** add Java build system provider ([5afd1ba](https://github.com/phodal/shire/commit/5afd1ba4c2c81722a488f454130d13fca996f2c4)) * **core:** add ProjectRunService interface and extension point ([71d02e1](https://github.com/phodal/shire/commit/71d02e1fa5e7f5ed01c243f02fd68859dd5485b2)) * **core:** add ToolchainProvider extension point ([6a6d1d8](https://github.com/phodal/shire/commit/6a6d1d8a679e15694618b5043311714027dda14d)) * **docs:** add roadmap section to README ([e2def4d](https://github.com/phodal/shire/commit/e2def4d965b5dace4c3978dce269426df39dd401)) * **docs:** add support for frontmatter in code highlighting ([dbf5765](https://github.com/phodal/shire/commit/dbf5765e5eb9b4b76f2b27e625065af063dfb78f)) * **frontmatter:** add filename and file content filters ([be33598](https://github.com/phodal/shire/commit/be3359814375937a125fbe25efd7f01582dfc8c0)) * **git:** add GitQuery and VcsPrompting classes ([f5d5fe7](https://github.com/phodal/shire/commit/f5d5fe743edcef610e14b5ffb902856035faecb7)) * **git:** add GitQuery and VcsPrompting classes ([eddfe37](https://github.com/phodal/shire/commit/eddfe372741acc6fac73c39f78ad9b22763806cf)) * **grammar:** add qualRefExpr to ShireParser.bnf ([e5bf261](https://github.com/phodal/shire/commit/e5bf26123704d8ade8f2302925aa60cb18854a82)) * **grammar:** add support for 'when' keyword in condition expressions ([ee3dc7e](https://github.com/phodal/shire/commit/ee3dc7ee33568d8265bfc07d368c9ee6a5148930)) * **grammar:** add support for pattern actions ([9471994](https://github.com/phodal/shire/commit/947199424eb5f6c5354d278500c65e7dfa132343)) * **grammar:** extend grammar for expressions and operators ([0aa661f](https://github.com/phodal/shire/commit/0aa661fc31934df8661f288c2efbbc300c7a5a6d)) * **hobbit:** add support for action location in HobbitHole ([3c530dd](https://github.com/phodal/shire/commit/3c530ddd20b938454e77edc22c069e2d1aec6dea)) * **hobbit:** add support for selection strategy ([0b4e090](https://github.com/phodal/shire/commit/0b4e090472f418023342460f4905bdcba9fb3326)) * **httpclient:** add HttpClientRunService for HTTP requests ([8a33137](https://github.com/phodal/shire/commit/8a33137a00783c4c110b00e810f8a08b1925be9e)) * **httpclient:** add support for REST client plugin ([994ff0e](https://github.com/phodal/shire/commit/994ff0e142419d33951036e9bce3300b57b94fc8)) * **index:** add ShireIdentifierIndex for file content ([62891a0](https://github.com/phodal/shire/commit/62891a0d0c5ffc8888515ff2471fdcab41c6b930)) * **intention:** add Shire Assistant with AI AutoAction ([0b23d35](https://github.com/phodal/shire/commit/0b23d3584dbb87b193aedd14a8d93bd4288051f5)) * **intention:** add Shire Hobbit AI action support ([c1122e1](https://github.com/phodal/shire/commit/c1122e182d5deb5da52f061177e5f1a34f60511d)) * **java:** add Java element strategy builder ([a4d571a](https://github.com/phodal/shire/commit/a4d571ac2a3b6bb41cb6af23c80955ff7d1e1b46)) * **java:** add Java symbol provider implementation ([76c2042](https://github.com/phodal/shire/commit/76c2042268e2e5f92b7ff261dbe8c793269891ed)) * **java:** add Java toolchain provider ([864c1cb](https://github.com/phodal/shire/commit/864c1cb6ca6af4088dcdbb586954af8d72174f70)) * **java:** add method to find nearest target element ([3cf769c](https://github.com/phodal/shire/commit/3cf769c0c41efd8fe001cce91e5bc4a97e3d1df3)) * **java:** add method to get run task name ([4e4ab8a](https://github.com/phodal/shire/commit/4e4ab8a1b107a0d0eeb5c02dbd67d39272affaab)) * **java:** add MvcContextService and ControllerContext ([b5edce5](https://github.com/phodal/shire/commit/b5edce576ef24eae8892a8e1b3b09ff1acfa8b91)) * **language:** add Shire language injector, folding builder, and completion contributor ([8f5ca99](https://github.com/phodal/shire/commit/8f5ca99d51554dd0d93da3a62fe50e9ee8324bef)) * **language:** add Shire language support ([8332a3c](https://github.com/phodal/shire/commit/8332a3c93dd0c922e118694089264b68d3d312a8)) * **language:** add Shire language support ([f351552](https://github.com/phodal/shire/commit/f3515520c86f99c8b0387614f86cc93a2e87bfa5)) * **llm:** add MockProvider for testing ([089d496](https://github.com/phodal/shire/commit/089d4968d9fa6c44314a0f21c393de96808ce863)) * **llm:** add OpenAI LLM provider and settings ([8518bd3](https://github.com/phodal/shire/commit/8518bd3360b2dd1d40bc688c3409419b4acc4aa6)) * **middleware:** add CodeVerifyProcessor for syntax error checking ([1bec4c3](https://github.com/phodal/shire/commit/1bec4c33f07be7cbc3f516e4ce57cb216d9b76d1)) * **middleware:** add post processor support ([0a2aeea](https://github.com/phodal/shire/commit/0a2aeeaf5ec9407cbf95620a3303373e3a886beb)) * **middleware:** add PostCodeHandler interface and PostCodeHandle enum ([9d734c5](https://github.com/phodal/shire/commit/9d734c54d475a887df34ebb2ade8a429857a1d28)) * **middleware:** add TimeMetricProcessor for measuring code execution time ([2c3b1a6](https://github.com/phodal/shire/commit/2c3b1a6c9589aea733666bc533dc130747fea5da)) * **modify:** add ShireModificationListener for file modification ([c61f973](https://github.com/phodal/shire/commit/c61f97347945ed6d2610608289c74537909bdf43)) * **modify:** add ShireModificationListener for file modification ([230b00e](https://github.com/phodal/shire/commit/230b00e9d0b36c82a36da3b3bf861845faeb439c)) * **parser:** add new test data files and refactor code ([b666fc6](https://github.com/phodal/shire/commit/b666fc6d3a9d51ab17972082227eeee6a5ba32d6)) * **parser:** add support for front matter parsing ([01ef64e](https://github.com/phodal/shire/commit/01ef64ebaed4951e32892b8d0bf0704b0974c994)) * **parser:** add support for pattern actions ([bb30004](https://github.com/phodal/shire/commit/bb30004e14684c9127eb138167336e5b4fd93d87)) * **parser:** add support for pattern element ([12fb06e](https://github.com/phodal/shire/commit/12fb06e83338c143b8614a0ad27485c1c0c1d2c7)) * **pattern:** add pattern actions for filtering, sorting, and executing commands ([be08b28](https://github.com/phodal/shire/commit/be08b28353dfbd940c3c585bd1a2f1fe1f7563f6)) * **project:** add core and language modules ([2b4a6f0](https://github.com/phodal/shire/commit/2b4a6f06733149d0cd9763e2d4719a048fa37ce3)) * **provider:** add AutoTesting provider for unit testing ([2fb10e2](https://github.com/phodal/shire/commit/2fb10e26c27dbd4d2d2f2090c84827dae5a37680)) * **provider:** add PsiElementStrategyBuilder interface ([c5ca72b](https://github.com/phodal/shire/commit/c5ca72b86b4fa84417bf7646500b1cb4ddf52da4)) * **provider:** add RevisionProvider interface and implementation ([fa05221](https://github.com/phodal/shire/commit/fa05221654c53c7821e5de8ff148d62c6c0a9b3c)) * **provider:** add RunService interface and implementation ([a509717](https://github.com/phodal/shire/commit/a5097170adb1efc848aa027761613b213f0c39b8)) * **psi:** add method to get relative PsiElement with PsiComment ([ba3b521](https://github.com/phodal/shire/commit/ba3b5210647242802c6c66b0fb968344345f674c)) * **run:** add Shire program runner and process processor ([554c39f](https://github.com/phodal/shire/commit/554c39f8784773a53063e5eab9246e4b4d87a8aa)) * **runner:** add support for running tasks in projects ([586a9d6](https://github.com/phodal/shire/commit/586a9d6bcfc5979fd12d5b2089eb339ae03238f5)) * **settings:** add LlmCoroutineScope, CustomAgent loadFromProject, and TestConnection ([efdc2f9](https://github.com/phodal/shire/commit/efdc2f99ff0739edb0a9d40a62404a68defece37)) * **settings:** add Shire settings configurable UI ([dba5d68](https://github.com/phodal/shire/commit/dba5d68ec0f969ebce518ab573d3471de3b2fe10)) * **shell:** add shell language support plugin file ([bdc1c90](https://github.com/phodal/shire/commit/bdc1c90f10d7c2c4fb8f7157db343dd756c11352)) * **shire:** add Shire context action group and location support ([e3df86d](https://github.com/phodal/shire/commit/e3df86d16d0c9985fec787a8e76ce553be9a9a45)) [Unreleased]: https://github.com/phodal/shire/compare/v1.3.4...HEAD [1.3.4]: https://github.com/phodal/shire/compare/v1.3.3...v1.3.4 [1.3.3]: https://github.com/phodal/shire/compare/v1.3.2...v1.3.3 [1.3.2]: https://github.com/phodal/shire/compare/v1.3.1...v1.3.2 [1.3.1]: https://github.com/phodal/shire/compare/v1.2.4...v1.3.1 [1.2.4]: https://github.com/phodal/shire/compare/v1.2.3...v1.2.4 [1.2.3]: https://github.com/phodal/shire/compare/v1.2.2...v1.2.3 [1.2.2]: https://github.com/phodal/shire/compare/v1.2.1...v1.2.2 [1.2.1]: https://github.com/phodal/shire/compare/v1.1.1...v1.2.1 [1.1.1]: https://github.com/phodal/shire/compare/v1.0.6...v1.1.1 [1.0.6]: https://github.com/phodal/shire/compare/v1.0.4-SNAPSHOT...v1.0.6 [1.0.4-SNAPSHOT]: https://github.com/phodal/shire/compare/v1.0.2...v1.0.4-SNAPSHOT [1.0.2]: https://github.com/phodal/shire/compare/v0.9.1...v1.0.2 [0.9.1]: https://github.com/phodal/shire/compare/v0.8.2...v0.9.1 [0.8.2]: https://github.com/phodal/shire/compare/v0.8.1...v0.8.2 [0.8.1]: https://github.com/phodal/shire/compare/v0.7.4...v0.8.1 [0.7.4]: https://github.com/phodal/shire/compare/v0.7.2...v0.7.4 [0.7.2]: https://github.com/phodal/shire/compare/v0.7.1...v0.7.2 [0.7.1]: https://github.com/phodal/shire/compare/v0.5.2...v0.7.1 [0.5.2]: https://github.com/phodal/shire/compare/v0.4.8...v0.5.2 [0.4.8]: https://github.com/phodal/shire/compare/v0.4.7...v0.4.8 [0.4.7]: https://github.com/phodal/shire/compare/v0.4.6...v0.4.7 [0.4.6]: https://github.com/phodal/shire/compare/v0.4.5...v0.4.6 [0.4.5]: https://github.com/phodal/shire/compare/v0.4.3...v0.4.5 [0.4.3]: https://github.com/phodal/shire/compare/v0.4.2...v0.4.3 [0.4.2]: https://github.com/phodal/shire/compare/v0.4.1...v0.4.2 [0.4.1]: https://github.com/phodal/shire/compare/v0.0.8...v0.4.1 [0.0.8]: https://github.com/phodal/shire/compare/v0.0.7...v0.0.8 [0.0.7]: https://github.com/phodal/shire/compare/v0.0.6...v0.0.7 [0.0.6]: https://github.com/phodal/shire/compare/v0.0.4...v0.0.6 [0.0.4]: https://github.com/phodal/shire/commits/v0.0.4 ================================================ FILE: LICENSE ================================================ Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. ================================================ FILE: README.md ================================================

logo

Shire - AI Coding Agent Language

Build Version Downloads

Shire offers a straightforward AI Coding Agent Language that enables communication between an LLM and control IDE for automated programming. [Quick Start →](https://shire.phodal.com/) (Documentation) > The concept of Shire originated from [AutoDev](https://github.com/unit-mesh/auto-dev), a subproject > of [UnitMesh](https://unitmesh.cc/). In AutoDev, we designed an AI-driven IDE for developers that includes DevIns, the > precursor to Shire. DevIns aims to enable users to create AI agents tailored to their own IDEs, allowing them to build > their customized AI-driven development environments. --- ## Unite Your Dev Ecosystem, Create Your AI Copilot ![Inline Chat](https://shire.run/images/shire-ecology-system.png) ### Agentic with Tool Ecosystem, Reshaping the SDLC 不论是组织内部的 DevOps 工具链:Jira、Confluence、SonarQube、Jenkins、GitLab、GitHub,还是各种内部 LLM 模型平台。 | 又或者在代码编辑器、终端、数据库、版本控制等等,Shire 都可以帮助你快速实现自动化编程。 | ![Shire Command](https://shire.run/images/shire-command.png) | |------------------------------------------------|--------------------------------------------------------------| ### Customize your AI Copilot with Your IDE 我们内置了多种交互方式,以快速将你的 IDE 变为你的专属 AI Copilot。。 | ![Shire Customize Menu](https://shire.run/images/shire-customize-menu.png) | 右键菜单、Alt+Enter、终端菜单、提交菜单、运行面板、输入框、数据库菜单、控制台菜单、VCS 日志菜单、聊天框、内联聊天等等。 | |----------------------------------------------------------------------------|--------------------------------------------------------------------| ### Follow Leading Community Practices 结合我们在行业的最佳洞见([https://aise.phodal.com/](https://aise.phodal.com/)),你可以在 Shire 上体验到最佳的编程实践。 | StreamDiff、多文件编辑、FastApply、InlineChat 等 | Shire Best Practice | |-----------------------------------------|-------------------------------------------------------------------------------------------------------------| ## Shire Shire example Project: [Java example](https://github.com/shire-lang/shire-spring-java-demo) ### SDLC - [Code change analysis](https://github.com/shire-lang/shire-demo/blob/master/.shire/requirement/crud/analysis-requirements.shire) use LLM to analysis requirements, then choose the best files to change. - [Requirement + AutoCRUD](https://github.com/shire-lang/shire-demo/blob/master/.shire/requirement/analysis-requirements.shire) analysis requirements, then auto generate CRUD code. - [Dify + OpenAPI/Swagger](https://github.com/shire-lang/shire-demo/blob/master/.shire/api/design/design-rest-api.shire) interactive with Dify agent to design REST API - [Add Spring doc to project](https://github.com/shire-lang/shire-demo/blob/master/.shire/api/setup-dep/setup-spring-doc-openapi.shire) add Spring doc to project. - [Generate RestAssured Test](https://github.com/shire-lang/shire-demo/blob/master/.shire/api/verify/rest-assure.shire) AI to generate RestAssured test code. - [Generate JavaDoc](https://github.com/shire-lang/shire-demo/blob/master/.shire/documentation/javadoc.shire) use LLM to generate JavaDoc. - [Complexity Analysis](https://github.com/shire-lang/shire-demo/blob/master/.shire/git/complexity.shire) calculate code complexity. - [PlantUML: fetch Github issue for analysis](https://github.com/shire-lang/shire-demo/blob/master/.shire/requirement/visual/mindmap.shire) fetch GitHub issue to generate mindmap. FrontEnd: - [Frontend + HTML mockup](https://github.com/shire-lang/shire-demo/blob/master/.shire/frontend/html-mock-up.shire) use LLM to generate HTML mockup and show in WebView. - [Mobile + Ionic](https://github.com/shire-lang/shire-demo/blob/master/.shire/frontend/mobile-mock-up.shire) use LLM to generate mobile mockup with Ionic, show in WebView. - [Mobile + React](https://github.com/shire-lang/shire-demo/blob/master/.shire/frontend/react-mock-up.shire) use LLM to generate mobile mockup with React, show in WebView. - [JavaScript Auto Unittest](https://github.com/shire-lang/shire-demo/blob/master/.shire/frontend/js-test.shire) use LLM to generate JavaScript test code. Test: - [E2E Test: Playwright](https://github.com/shire-lang/shire-demo/blob/master/.shire/api/e2e/playwright.shire) AI to use Playwright to test the API and auto execute test. - [API Test: Java](https://github.com/shire-lang/shire-demo/blob/master/.shire/test/java/api-test.shire) use LLM to generate Java API test code. - [Unit Test: Java](https://github.com/shire-lang/shire-demo/blob/master/.shire/test/java/autotest.shire) use LLM to generate Java unit test code. - [Unit Test: Python](https://github.com/shire-lang/shire-demo/blob/master/.shire/test/python/AutoTest.shire) use LLM to generate Python unit test code. - [Unit Test: Golang](https://github.com/shire-lang/shire-demo/blob/master/.shire/test/go/AutoTest.shire) use LLM to generate Golang unit test code. ### Workflow & IDE Integration - [Capture web pages and generate report](https://github.com/shire-lang/shire-demo/blob/master/.shire/research/research.shire) capture web pages and generate report. - [approvalExecute](https://github.com/shire-lang/shire-demo/blob/master/.shire/approve/approve.shire) waiting for approval to execute next shire code - [Custom InlineChat](https://github.com/shire-lang/shire-demo/blob/master/.shire/chatbox/inline-chat.shire) custom inline chat - [Custom ChatBox](https://github.com/shire-lang/shire-demo/blob/master/.shire/chatbox/wrapper-chat.shire) custom prompt to use right panel chat box - [Python as Foreign Function Interface](https://github.com/shire-lang/shire-demo/blob/master/.shire/ffi/python-shell-thread.shire) use Python to run shell command in thread. - [Quick Input](https://github.com/shire-lang/shire-demo/blob/master/.shire/miscs/quick-input.shire) show quick input dialog. - [Terminal Agent](https://github.com/shire-lang/shire-demo/blob/master/.shire/miscs/terminal.shire) use terminal agent to run shell command. ### EcoSystem - [Git: Auto push code](https://github.com/shire-lang/shire-demo/blob/master/.shire/git/auto-push.shire) auto commit and push code to server. - [Git: diff AI changed code](https://github.com/shire-lang/shire-demo/blob/master/.shire/git/diff-example.shire) diff AI changed code. - [Git: Commit message](https://github.com/shire-lang/shire-demo/blob/master/.shire/git/login-commit-message.shire) generate commit message. - [Git: Commit ID with Jira](https://github.com/shire-lang/shire-demo/blob/master/.shire/git/commit-message.shire) generate commit message with Jira ID. - [Database: GitHub issue + Design Database Schema](https://github.com/shire-lang/shire-demo/blob/master/.shire/database/design-db.shire) fetch GitHub issue as context to design database schema - [Database: Run SQL in Database](https://github.com/shire-lang/shire-demo/blob/master/.shire/database/command.shire) run SQL with `/database` command. - [OpenRewrite: generate refactoring code](https://github.com/shire-lang/shire-demo/blob/master/.shire/refactor/openRewrite.shire) use OpenRewrite to generate refactoring code. - [MockServer: WireMock](https://github.com/shire-lang/shire-demo/blob/master/.shire/api/mock/gen-mock.shire) AI to generate mock server with WireMock and auto start mock server. - [PlantUML: with remote Agent](https://github.com/shire-lang/shire-demo/blob/master/.shire/toolchain/puml/plantuml-remote.shire) use remote agent to generate PlantUML code. - [Mermaid: with remote Agent](https://github.com/shire-lang/shire-demo/blob/master/.shire/toolchain/mermaid.shire) use remote agent to generate Mermaid code. - [Sonarlint: fix issue](https://github.com/shire-lang/shire-demo/blob/master/.shire/toolchain/sonarfix.shire) use Sonarlint to fix issue. ## Shire Resources Shire Cheatsheet ![Shire Cheatsheet](docs/images/shire-sheet.svg) Shire Data Architecture: ![Shire Data Architecture](docs/images/shire-data-flow.svg) Shire Resources - Documentation: [Shire AI Coding Agent Language](https://shire.phodal.com/) - [Shire Book: AI for software-engineering](https://aise.phodal.com/) (Chinese only) - [Shire.Run - the shareable AI coding agent](https://shire.run/) ## Demo Video Youtube: [![Shire AI Coding Agent Language](https://img.youtube.com/vi/z1ijWOL1rFY/0.jpg)](https://www.youtube.com/watch?v=z1ijWOL1rFY) Bilibili [![Shire AI Coding Agent Language](https://img.youtube.com/vi/z1ijWOL1rFY/0.jpg)](https://www.bilibili.com/video/BV1Lf421q7S7/) ## Thanks 感谢智谱 AI 赞助的 GLM 4 Air 资源包。[【加入Z计划,和智谱AI一起创业】(点击跳转👇)](https://zhipu-ai.feishu.cn/share/base/form/shrcntPu1mUMhoapEseCJpmUUuf) logo ## LICENSE Notes: StreamDiff based on Continue Dev, Inc, which is licensed under the Apache License, Version 2.0. See `LICENSE-APACHE` in this directory. This code is distributed under the MPL 2.0 license. See `LICENSE` in this directory. ================================================ FILE: build.gradle.kts ================================================ import groovy.util.Node import groovy.xml.XmlParser import org.jetbrains.changelog.Changelog import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType.* import org.jetbrains.intellij.platform.gradle.TestFrameworkType import org.jetbrains.intellij.platform.gradle.extensions.IntelliJPlatformDependenciesExtension import org.jetbrains.intellij.platform.gradle.extensions.IntelliJPlatformTestingExtension import org.jetbrains.intellij.platform.gradle.tasks.RunIdeTask import org.jetbrains.intellij.platform.gradle.tasks.VerifyPluginTask import org.jetbrains.intellij.platform.gradle.utils.extensionProvider import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.util.* fun properties(key: String) = providers.gradleProperty(key) fun environment(key: String) = providers.environmentVariable(key) // Configure Gradle Changelog Plugin - read more: https://github.com/JetBrains/gradle-changelog-plugin changelog { version.set(properties("pluginVersion")) groups.empty() path.set(rootProject.file("CHANGELOG.md").toString()) repositoryUrl.set(properties("pluginRepositoryUrl")) itemPrefix.set("*") } /// maybe refs: https://github.com/HaxeFoundation/intellij-haxe/blob/develop/build.gradle.kts /// and https://github.com/JetBrains/educational-plugin @Suppress("DSL_SCOPE_VIOLATION") plugins { id("java") // Java support alias(libs.plugins.kotlin) // Kotlin support alias(libs.plugins.gradleIntelliJPlugin) // Gradle IntelliJ Plugin alias(libs.plugins.changelog) // Gradle Changelog Plugin alias(libs.plugins.qodana) // Gradle Qodana Plugin alias(libs.plugins.kover) // Gradle Kover Plugin alias(libs.plugins.serialization) id("org.jetbrains.grammarkit") version "2022.3.2.2" id("net.saliman.properties") version "1.5.2" } val ideaPlatformVersion = prop("ideaPlatformVersion").toInt() val pluginProjects: List get() = rootProject.allprojects.toList() val basePluginArchiveName = "intellij-shire" val ideaPlugins = listOf( "org.jetbrains.plugins.terminal", "com.intellij.java", "org.jetbrains.plugins.gradle", "org.jetbrains.idea.maven", "JavaScript", "org.jetbrains.kotlin", "com.jetbrains.restClient" ) + if (ideaPlatformVersion == 243) { listOf(prop("jsonPlugin")) } else { emptyList() } + prop("platformPlugins").split(",") repositories { intellijPlatform { defaultRepositories() jetbrainsRuntime() } } configure( subprojects - project(":languages") - project(":toolsets") ) { apply { plugin("org.jetbrains.intellij.platform.module") } intellijPlatform { instrumentCode = false buildSearchableOptions = false } tasks { prepareSandbox { enabled = false } // prepareTestSandbox { enabled = false } } val testOutput = configurations.create("testOutput") dependencies { testOutput(sourceSets.test.get().output.classesDirs) if (ideaPlatformVersion == 243) { testImplementation("junit:junit:4.13.2") testImplementation("org.opentest4j:opentest4j:1.3.0") } intellijPlatform { testFramework(TestFrameworkType.Bundled) } } } configure( allprojects - project(":languages") - project(":toolsets") ) { apply { plugin("idea") plugin("kotlin") plugin("org.jetbrains.kotlinx.kover") plugin("org.jetbrains.intellij.platform.module") } repositories { mavenCentral() intellijPlatform { defaultRepositories() } } dependencies { // compileOnly(kotlin("stdlib-jdk8")) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") { excludeKotlinDeps() } implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1") { excludeKotlinDeps() } intellijPlatform { } // slf4j-simple implementation("org.slf4j:slf4j-simple:1.7.36") } configurations.all { resolutionStrategy { eachDependency { if (requested.group == "org.slf4j" && requested.name == "slf4j-api") { useVersion("1.7.36") } // "org.apache.velocity:velocity-engine-core:2.4.1" if (requested.group == "org.apache.velocity" && requested.name == "velocity-engine-core") { useVersion("2.4.1") } } } } idea { module { generatedSourceDirs.add(file("src/gen")) isDownloadJavadoc = true isDownloadSources = true } } } project(":core") { apply { plugin("org.jetbrains.kotlin.plugin.serialization") } intellijPlatform { instrumentCode = false } dependencies { intellijPlatform { intellijIde(prop("ideaVersion")) intellijPlugins(ideaPlugins) pluginModule(implementation(project(":languages:shire-json"))) testFramework(TestFrameworkType.Platform) } implementation(project(":languages:shire-json")) implementation("com.charleskorn.kaml:kaml:0.98.0") implementation("org.reflections:reflections:0.10.2") { exclude(group = "org.slf4j", module = "slf4j-api") } // chocolate factory // follow: https://onnxruntime.ai/docs/get-started/with-java.html // implementation("com.microsoft.onnxruntime:onnxruntime:1.19.2") // implementation("ai.djl.huggingface:tokenizers:0.29.0") implementation("cc.unitmesh:document:1.0.0") implementation("cc.unitmesh:cocoa-core:1.0.0") { exclude(group = "org.jetbrains.kotlinx", module = "kotlinx-coroutines-core") excludeKotlinDeps() } // custom agent deps implementation("com.nfeld.jsonpathkt:jsonpathkt:2.0.1") implementation("com.squareup.okhttp3:okhttp:4.4.1") implementation("com.squareup.okhttp3:okhttp-sse:4.12.0") // open ai deps implementation("io.reactivex.rxjava3:rxjava:3.1.10") implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.19.0") implementation("com.fasterxml.jackson.core:jackson-databind:2.18.3") } } project(":languages:shire-java") { dependencies { intellijPlatform { intellijIde(prop("ideaVersion")) intellijPlugins(ideaPlugins) } implementation(project(":core")) } } project(":languages:shire-javascript") { dependencies { intellijPlatform { intellijIde(prop("ideaVersion")) intellijPlugins(ideaPlugins) intellijPlugins(prop("nodejsPlugin")) testFramework(TestFrameworkType.Plugin.JavaScript) } implementation(project(":core")) } } project(":languages:shire-kotlin") { dependencies { intellijPlatform { intellijIde(prop("ideaVersion")) intellijPlugins(ideaPlugins) } implementation(project(":core")) implementation(project(":languages:shire-java")) } } project(":languages:shire-markdown") { dependencies { intellijPlatform { intellijIde(prop("ideaVersion")) intellijPlugins(ideaPlugins) } implementation(project(":core")) } } project(":languages:shire-python") { dependencies { intellijPlatform { intellijIde(prop("ideaVersion")) intellijPlugins(ideaPlugins + prop("pythonPlugins")) } implementation(project(":core")) } } project(":languages:shire-go") { dependencies { intellijPlatform { intellijIde(prop("ideaVersion")) intellijPlugins(ideaPlugins) intellijPlugins(prop("goPlugin")) } implementation(project(":core")) } } project(":languages:shire-proto") { dependencies { intellijPlatform { intellijIde(prop("ideaVersion")) intellijPlugins(ideaPlugins) intellijPlugins(prop("protoPlugin")) } implementation(project(":core")) } } project(":languages:shire-json") { dependencies { intellijPlatform { intellijIde(prop("ideaVersion")) if (ideaPlatformVersion == 243) { plugins(prop("jsonPlugin")) } intellijPlugins(ideaPlugins + if (ideaPlatformVersion == 243) "com.intellij.modules.json" else "") } } sourceSets { main { resources.srcDirs("src/$ideaPlatformVersion/main/resources") } test { resources.srcDirs("src/$ideaPlatformVersion/test/resources") } } kotlin { sourceSets { main { kotlin.srcDirs("src/main/kotlin", "src/$ideaPlatformVersion/main/kotlin") } test { kotlin.srcDirs("src/$ideaPlatformVersion/test/kotlin") } } } } project(":toolsets:git") { dependencies { intellijPlatform { intellijIde(prop("ideaVersion")) intellijPlugins(ideaPlugins + "Git4Idea") } implementation(project(":core")) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") } } project(":toolsets:httpclient") { dependencies { intellijPlatform { intellijIde(prop("ideaVersion")) intellijPlugins(ideaPlugins) } implementation(project(":core")) implementation(project(":languages:shire-json")) // custom agent deps implementation("com.nfeld.jsonpathkt:jsonpathkt:2.0.1") implementation("com.squareup.okhttp3:okhttp:4.4.1") implementation("com.squareup.okhttp3:okhttp-sse:4.12.0") // open ai deps implementation("io.reactivex.rxjava3:rxjava:3.1.10") implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.19.0") implementation("com.fasterxml.jackson.core:jackson-databind:2.19.0") } } project(":toolsets:terminal") { dependencies { intellijPlatform { intellijIde(prop("ideaVersion")) intellijPlugins(ideaPlugins) } implementation(project(":core")) } } project(":toolsets:sonarqube") { dependencies { intellijPlatform { intellijIde(prop("ideaVersion")) intellijPlugins(ideaPlugins + prop("sonarPlugin")) } implementation(project(":core")) } } project(":toolsets:plantuml") { dependencies { intellijPlatform { intellijIde(prop("ideaVersion")) intellijPlugins(ideaPlugins + prop("plantUmlPlugin")) } implementation(project(":core")) } } project(":toolsets:mock") { dependencies { intellijPlatform { intellijIde(prop("ideaVersion")) intellijPlugins(ideaPlugins + prop("wireMockPlugin")) } implementation(project(":core")) } } project(":toolsets:database") { dependencies { intellijPlatform { intellijIde(prop("ideaVersion")) intellijPlugins(ideaPlugins + "com.intellij.database") } implementation(project(":core")) } } project(":toolsets:openrewrite") { dependencies { intellijPlatform { intellijIde(prop("ideaVersion")) intellijPlugins(ideaPlugins + "com.intellij.openRewrite") } implementation(project(":core")) } } project(":toolsets:mermaid") { dependencies { intellijPlatform { intellijIde(prop("ideaVersion")) intellijPlugins(ideaPlugins + prop("mermaidPlugin")) } implementation(project(":core")) } } project(":toolsets:docker") { dependencies { intellijPlatform { intellijIde(prop("ideaVersion")) intellijPlugins(ideaPlugins + "Docker") } implementation(project(":core")) } } project(":shirelang") { apply { plugin("org.jetbrains.grammarkit") } dependencies { intellijPlatform { intellijIde(prop("ideaVersion")) intellijPlugins(ideaPlugins + "org.intellij.plugins.markdown" + "com.jetbrains.sh" + "Git4Idea") } implementation("com.nfeld.jsonpathkt:jsonpathkt:2.0.1") implementation("org.apache.velocity:velocity-engine-core:2.4.1") { exclude(group = "org.slf4j", module = "slf4j-api") } // https://mvnrepository.com/artifact/com.huaban/jieba-analysis implementation("com.huaban:jieba-analysis:1.0.2") implementation("cc.unitmesh:cocoa-core:1.0.0") { excludeKotlinDeps() } // for ShireQL Schema implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.2") implementation(kotlin("reflect")) implementation(project(":core")) implementation(project(":languages:shire-json")) } tasks { generateLexer { sourceFile.set(file("src/main/grammar/ShireLexer.flex")) targetOutputDir.set(file("src/gen/com/phodal/shirelang/lexer")) purgeOldFiles.set(true) } generateParser { sourceFile.set(file("src/main/grammar/ShireParser.bnf")) targetRootOutputDir.set(file("src/gen")) pathToParser.set("com/phodal/shirelang/parser/ShireParser.java") pathToPsiRoot.set("com/phodal/shirelang/psi") purgeOldFiles.set(true) } withType { dependsOn(generateLexer, generateParser) } } sourceSets { main { java.srcDirs("src/gen") } } } /** * Creates `run$[baseTaskName]` Gradle task to run IDE of given [type] * via `runIde` task with plugins according to [ideToPlugins] map */ fun IntelliJPlatformTestingExtension.customRunIdeTask( type: IntelliJPlatformType, versionWithCode: String? = null, baseTaskName: String = type.name, ) { runIde.register("run$baseTaskName") { useInstaller = false if (versionWithCode != null) { val version = versionWithCode.toTypeWithVersion().version this.type = type this.version = version } else { val pathProperty = baseTaskName.replaceFirstChar { it.lowercaseChar() } + "Path" // Avoid throwing exception during property calculation. // Some IDE tooling (for example, Package Search plugin) may try to calculate properties during `Sync` phase for all tasks. // In our case, some `run*` task may not have `pathProperty` in your `gradle.properties`, // and as a result, the `Sync` tool window will show you the error thrown by `prop` function. // // The following solution just moves throwing the corresponding error to task execution, // i.e., only when a task is actually invoked if (hasProp(pathProperty)) { localPath.convention(layout.dir(provider { file(prop(pathProperty)) })) } else { task { doFirst { throw GradleException("Property `$pathProperty` is not defined in gradle.properties") } } } } // Specify custom sandbox directory to have a stable path to log file sandboxDirectory = intellijPlatform.sandboxContainer.dir("${baseTaskName.lowercase()}-sandbox-${prop("ideaPlatformVersion")}") plugins { plugins(ideaPlugins) } } } project(":") { apply { plugin("org.jetbrains.changelog") plugin("org.jetbrains.intellij.platform") } repositories { intellijPlatform { defaultRepositories() jetbrainsRuntime() } } intellijPlatform { projectName = basePluginArchiveName pluginConfiguration { id = "com.phodal.shire" name = "Shire - AI Coding Agent Language" version = prop("pluginVersion") ideaVersion { sinceBuild = prop("pluginSinceBuild") untilBuild = prop("pluginUntilBuild") } vendor { name = "Phodal Huang" } val changelog = project.changelog // local variable for configuration cache compatibility // Get the latest available change notes from the changelog file changeNotes = providers.gradleProperty("pluginVersion").map { pluginVersion -> with(changelog) { renderItem( (getOrNull(pluginVersion) ?: getUnreleased()) .withHeader(false) .withEmptySections(false), Changelog.OutputType.HTML, ) } } } pluginVerification { freeArgs = listOf("-mute", "TemplateWordInPluginId,ForbiddenPluginIdPrefix") failureLevel = listOf(VerifyPluginTask.FailureLevel.MISSING_DEPENDENCIES) ides { select { sinceBuild = "242" untilBuild = "243" //// sinceBuild = prop("pluginSinceBuild") //// untilBuild = prop("pluginUntilBuild") } } } instrumentCode = false buildSearchableOptions = false signing { certificateChain = providers.environmentVariable("CERTIFICATE_CHAIN") privateKey = providers.environmentVariable("PRIVATE_KEY") password = providers.environmentVariable("PRIVATE_KEY_PASSWORD") } publishing { token = providers.environmentVariable("PUBLISH_TOKEN") channels.set(properties("pluginVersion").map { listOf(it.split('-').getOrElse(1) { "default" }.split('.').first()) }) } } dependencies { intellijPlatform { pluginVerifier() intellijIde(prop("ideaVersion")) intellijPlugins(ideaPlugins) if (hasProp("jbrVersion")) { jetbrainsRuntime(prop("jbrVersion")) } else { jetbrainsRuntime() } plugin(prop("jsonPlugin")) pluginModule(implementation(project(":core"))) pluginModule(implementation(project(":shirelang"))) pluginModule(implementation(project(":languages:shire-java"))) pluginModule(implementation(project(":languages:shire-javascript"))) pluginModule(implementation(project(":languages:shire-python"))) pluginModule(implementation(project(":languages:shire-kotlin"))) pluginModule(implementation(project(":languages:shire-go"))) pluginModule(implementation(project(":languages:shire-markdown"))) pluginModule(implementation(project(":languages:shire-proto"))) // pluginModule(implementation(project(":languages:shire-json"))) pluginModule(implementation(project(":toolsets:git"))) pluginModule(implementation(project(":toolsets:httpclient"))) pluginModule(implementation(project(":toolsets:terminal"))) pluginModule(implementation(project(":toolsets:sonarqube"))) pluginModule(implementation(project(":toolsets:database"))) pluginModule(implementation(project(":toolsets:mock"))) pluginModule(implementation(project(":toolsets:openrewrite"))) pluginModule(implementation(project(":toolsets:plantuml"))) pluginModule(implementation(project(":toolsets:mermaid"))) pluginModule(implementation(project(":toolsets:docker"))) testFramework(TestFrameworkType.Bundled) } // custom agent deps implementation("com.nfeld.jsonpathkt:jsonpathkt:2.0.1") implementation("com.squareup.okhttp3:okhttp:4.4.1") implementation("com.squareup.okhttp3:okhttp-sse:4.12.0") // open ai deps implementation("io.reactivex.rxjava3:rxjava:3.1.10") implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.19.0") implementation("com.fasterxml.jackson.core:jackson-databind:2.19.0") } // kotlin { // sourceSets { // main { // resources.srcDirs("src/$ideaPlatformVersion/main/resources") // } // } // } tasks { val projectName = project.extensionProvider.flatMap { it.projectName } composedJar { archiveBaseName.convention(projectName) } withType { // Disable auto plugin reloading. See `com.intellij.ide.plugins.DynamicPluginVfsListener` // To enable dynamic reloading, change value to `true` and disable `EduDynamicPluginListener` autoReload = false jvmArgs("-Xmx2g") jvmArgs("-Dide.experimental.ui=true") // Uncomment to show localized messages // jvmArgs("-Didea.l10n=true") // Uncomment to enable memory dump creation if plugin cannot be unloaded by the platform // jvmArgs("-Dide.plugins.snapshot.on.unload.fail=true") // Uncomment to enable FUS testing mode // jvmArgs("-Dfus.internal.test.mode=true") } patchPluginXml { pluginDescription.set(provider { file("src/description.html").readText() }) changelog { version.set(properties("pluginVersion")) groups.empty() path.set(rootProject.file("CHANGELOG.md").toString()) repositoryUrl.set(properties("pluginRepositoryUrl")) } val changelog = project.changelog // Get the latest available change notes from the changelog file changeNotes.set(properties("pluginVersion").map { pluginVersion -> with(changelog) { renderItem( (getOrNull(pluginVersion) ?: getUnreleased()) .withHeader(false) .withEmptySections(false), Changelog.OutputType.HTML, ) } }) } val newName = "intellij-shire-" + prop("ideaVersion") + "-" + prop("pluginVersion") publishPlugin { dependsOn("patchChangelog") archiveFile = file("build/distributions/$newName.zip") } buildPlugin { archiveBaseName.convention(newName) } intellijPlatformTesting { // Generates event scheme for JetBrains Academy plugin FUS events to `build/eventScheme.json` runIde.register("buildEventsScheme") { task { args( "buildEventsScheme", "--outputFile=${buildDir()}/eventScheme.json", "--pluginId=com.jetbrains.edu" ) // Force headless mode to be able to run command on CI systemProperty("java.awt.headless", "true") // BACKCOMPAT: 2024.1. Update value to 242 and this comment // `IDEA_BUILD_NUMBER` variable is used by `buildEventsScheme` task to write `buildNumber` to output json. // It will be used by TeamCity automation to set minimal IDE version for new events environment("IDEA_BUILD_NUMBER", "241") } } runIde.register("runInSplitMode") { splitMode = true // Specify custom sandbox directory to have a stable path to log file sandboxDirectory = intellijPlatform.sandboxContainer.dir("split-mode-sandbox-$ideaPlatformVersion") plugins { plugins(ideaPlugins) } } customRunIdeTask(IntellijIdeaUltimate, prop("ideaVersion"), baseTaskName = "Idea") } } } fun File.isPluginJar(): Boolean { if (!isFile) return false if (extension != "jar") return false return zipTree(this).files.any { it.isManifestFile() } } fun File.isManifestFile(): Boolean { if (extension != "xml") return false val rootNode = try { val parser = XmlParser() parser.parse(this) } catch (e: Exception) { logger.error("Failed to parse $path", e) return false } return rootNode.name() == "idea-plugin" } data class TypeWithVersion(val type: IntelliJPlatformType, val version: String) fun String.toTypeWithVersion(): TypeWithVersion { val (code, version) = split("-", limit = 2) return TypeWithVersion(IntelliJPlatformType.fromCode(code), version) } fun IntelliJPlatformDependenciesExtension.intellijIde(versionWithCode: String) { val (type, version) = versionWithCode.toTypeWithVersion() create(type, version, useInstaller = false) } fun IntelliJPlatformDependenciesExtension.intellijPlugins(vararg notations: String) { for (notation in notations) { if (notation.contains(":")) { plugin(notation) } else { bundledPlugin(notation) } } } fun IntelliJPlatformDependenciesExtension.intellijPlugins(notations: List) { intellijPlugins(*notations.toTypedArray()) } fun hasProp(name: String): Boolean = extra.has(name) fun prop(name: String): String = extra.properties[name] as? String ?: error("Property `$name` is not defined in gradle.properties") fun withProp(name: String, action: (String) -> Unit) { if (hasProp(name)) { action(prop(name)) } } fun withProp(filePath: String, name: String, action: (String) -> Unit) { if (!file(filePath).exists()) { println("$filePath doesn't exist") return } val properties = loadProperties(filePath) val value = properties.getProperty(name) ?: return action(value) } fun buildDir(): String { return project.layout.buildDirectory.get().asFile.absolutePath } fun T.excludeKotlinDeps() { exclude(module = "kotlin-runtime") exclude(module = "kotlin-reflect") exclude(module = "kotlin-stdlib") exclude(module = "kotlin-stdlib-common") exclude(module = "kotlin-stdlib-jdk8") } fun loadProperties(path: String): Properties { val properties = Properties() file(path).bufferedReader().use { properties.load(it) } return properties } fun parseManifest(file: File): Node { val node = XmlParser().parse(file) check(node.name() == "idea-plugin") { "Manifest file `$file` doesn't contain top-level `idea-plugin` attribute" } return node } fun manifestFile(project: Project): File? { var filePath: String? = null val mainOutput = project.sourceSets.main.get().output val resourcesDir = mainOutput.resourcesDir ?: error("Failed to find resources dir for ${project.name}") if (filePath != null) { return resourcesDir.resolve(filePath).takeIf { it.exists() } ?: error("Failed to find manifest file for ${project.name} module") } val rootManifestFile = manifestFile(project(":intellij-plugin")) ?: error("Failed to find manifest file for :intellij-plugin module") val rootManifest = parseManifest(rootManifestFile) val children = ((rootManifest["content"] as? List<*>)?.single() as? Node)?.children() ?: error("Failed to find module declarations in root manifest") return children.filterIsInstance() .flatMap { node -> if (node.name() != "module") return@flatMap emptyList() val name = node.attribute("name") as? String ?: return@flatMap emptyList() listOfNotNull(resourcesDir.resolve("$name.xml").takeIf { it.exists() }) }.firstOrNull() ?: error("Failed to find manifest file for ${project.name} module") } fun findModulePackage(project: Project): String? { val moduleManifest = manifestFile(project) ?: return null val node = parseManifest(moduleManifest) return node.attribute("package") as? String ?: error("Failed to find package for ${project.name}") } fun verifyClasses(project: Project) { val pkg = findModulePackage(project) ?: return val expectedDir = pkg.replace('.', '/') var hasErrors = false for (classesDir in project.sourceSets.main.get().output.classesDirs) { val basePath = classesDir.toPath() for (file in classesDir.walk()) { if (file.isFile && file.extension == "class") { val relativePath = basePath.relativize(file.toPath()) if (!relativePath.startsWith(expectedDir)) { logger.error( "Wrong package of `${ relativePath.joinToString(".").removeSuffix(".class") }` class. Expected `$pkg`" ) hasErrors = true } } } } if (hasErrors) { throw GradleException("Classes with wrong package were found. See https://docs.google.com/document/d/1pOy-qNlGOJe6wftHVYHkH8sZOoAfav1fdGDPJgkQWJo") } } fun DependencyHandler.implementationWithoutKotlin(dependencyNotation: Provider<*>) { implementation(dependencyNotation) { excludeKotlinDeps() } } fun DependencyHandler.testImplementationWithoutKotlin(dependencyNotation: Provider<*>) { testImplementation(dependencyNotation) { excludeKotlinDeps() } } ================================================ FILE: core/README.md ================================================ # MultiplePlatform with Cross IDE APIs ================================================ FILE: core/src/embeddings/LocalEmbedding.kt ================================================ package com.phodal.shirecore.search.function import ai.djl.huggingface.tokenizers.HuggingFaceTokenizer import ai.onnxruntime.OnnxTensor import ai.onnxruntime.OrtEnvironment import ai.onnxruntime.OrtSession import ai.onnxruntime.OrtUtil import com.intellij.util.concurrency.annotations.RequiresBackgroundThread import kotlin.collections.indices import kotlin.collections.slice import kotlin.collections.toLongArray import kotlin.ranges.until import kotlin.to import kotlinx.coroutines.* class LocalEmbedding( private val tokenizer: HuggingFaceTokenizer, private val session: OrtSession, private val env: OrtEnvironment, ) { @OptIn(ExperimentalCoroutinesApi::class) private val embeddingContext = Dispatchers.Default.limitedParallelism(1) @RequiresBackgroundThread suspend fun embed(input: String): FloatArray { return withContext(embeddingContext) { embedInternal(input, 512) } } fun embedInternal(input: String, dimensions: Int = 512): FloatArray { val tokenized = tokenizer.encode(input, true, true) var inputIds = tokenized.ids var attentionMask = tokenized.attentionMask var typeIds = tokenized.typeIds if (tokenized.ids.size >= dimensions) { inputIds = inputIds.slice(0 until dimensions).toLongArray() attentionMask = attentionMask.slice(0 until dimensions).toLongArray() typeIds = typeIds.slice(0 until dimensions).toLongArray() } val tensorInput = OrtUtil.reshape(inputIds, longArrayOf(1, inputIds.size.toLong())) val tensorAttentionMask = OrtUtil.reshape(attentionMask, longArrayOf(1, attentionMask.size.toLong())) val tensorTypeIds = OrtUtil.reshape(typeIds, longArrayOf(1, typeIds.size.toLong())) val result = session.run( mapOf( "input_ids" to OnnxTensor.createTensor(env, tensorInput), "attention_mask" to OnnxTensor.createTensor(env, tensorAttentionMask), "token_type_ids" to OnnxTensor.createTensor(env, tensorTypeIds), ), ) val outputTensor: OnnxTensor = result.get(0) as OnnxTensor val floatArray = outputTensor.floatBuffer.array() // floatArray is an inputIds.size * 384 array, we need to mean it to 384 * 1 // 1, shape, shape.length val shapeSize = outputTensor.info.shape[2].toInt() val meanArray = FloatArray(shapeSize) for (i in 0 until shapeSize) { var sum = 0f for (j in inputIds.indices) { sum += floatArray[j * shapeSize + i] } meanArray[i] = sum / inputIds.size } return meanArray } companion object { /** * Create a new instance of [LocalEmbedding] with default model. * We use official model: [all-MiniLM-L6-v2](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2) * We can use [optimum](https://github.com/huggingface/optimum) to transform the model to onnx. */ fun create(): LocalEmbedding? { val currentThread = Thread.currentThread() val originalClassLoader = currentThread.contextClassLoader val pluginClassLoader = Companion::class.java.classLoader return try { currentThread.setContextClassLoader(pluginClassLoader); val tokenizerStream = pluginClassLoader.getResourceAsStream("model/tokenizer.json") val tokenizer = HuggingFaceTokenizer.newInstance(tokenizerStream, null) val ortEnv = OrtEnvironment.getEnvironment() val sessionOptions = OrtSession.SessionOptions() val onnxStream = pluginClassLoader.getResourceAsStream("model/model.onnx")!! // load onnxPath as byte[] val onnxPathAsByteArray = onnxStream.readAllBytes() val session = ortEnv.createSession(onnxPathAsByteArray, sessionOptions) return LocalEmbedding(tokenizer, session, ortEnv) } catch (e: Exception) { e.printStackTrace() currentThread.setContextClassLoader(originalClassLoader) null } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/ProjectUtil.kt ================================================ package com.phodal.shirecore import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.runReadAction import com.intellij.openapi.fileTypes.FileTypeManager import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.io.FileUtilRt import com.intellij.openapi.vcs.changes.VcsIgnoreManager import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.psi.search.FilenameIndex import com.intellij.psi.search.ProjectScope fun Project.lookupFile(path: String): VirtualFile? { val projectPath = this.guessProjectDir()?.toNioPath() val realpath = projectPath?.resolve(path) return VirtualFileManager.getInstance().findFileByUrl("file://${realpath?.toAbsolutePath()}") } fun Project.findFile(filename: String, caseSensitively: Boolean = true): VirtualFile? { ApplicationManager.getApplication().assertReadAccessAllowed() val currentTask = ApplicationManager.getApplication().executeOnPooledThread { val searchedFiles = runReadAction { FilenameIndex.getVirtualFilesByName(filename, caseSensitively, ProjectScope.getProjectScope(this)) } return@executeOnPooledThread searchedFiles.firstOrNull() } return currentTask.get() } fun VirtualFile.canBeAdded(project: Project): Boolean { if (!this.isValid || this.isDirectory) return false if (this.fileType.isBinary || FileUtilRt.isTooLarge(this.length)) return false if (FileTypeManager.getInstance().isFileIgnored(this)) return false if (isIgnoredByVcs(project, this)) return false return true } fun VirtualFile.relativePath(project: Project): String { val projectDir = project.guessProjectDir()!!.toNioPath().toFile() val relativePath = FileUtil.getRelativePath(projectDir, this.toNioPath().toFile()) return relativePath ?: this.path } fun isIgnoredByVcs(project: Project?, file: VirtualFile?): Boolean { val ignoreManager = VcsIgnoreManager.getInstance(project!!) return ignoreManager.isPotentiallyIgnoredFile(file!!) } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/ShireConstants.kt ================================================ package com.phodal.shirecore import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.vfs.VirtualFile public const val SHIRE_CHAT_BOX_FILE = "shire-chatbox.default.shire" public const val SHIRE_TEMP_OUTPUT = ".shire-output" public const val LLM_LOGGING_JSONL = "logging.jsonl" public const val LLM_LOGGING = "logging.log" public const val SHIRE_MKT_HOST = "https://shire.run/packages.json" object ShireConstants { fun outputDir(project: Project): VirtualFile? { val baseDir = project.guessProjectDir() ?: throw IllegalStateException("Project directory not found") val virtualFile = baseDir.findFileByRelativePath(SHIRE_TEMP_OUTPUT) if (virtualFile == null) { baseDir.createChildDirectory(this, SHIRE_TEMP_OUTPUT) } return virtualFile } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/ShireCoreBundle.kt ================================================ package com.phodal.shirecore import com.intellij.DynamicBundle import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.PropertyKey @NonNls private const val BUNDLE = "messages.ShireCoreBundle" object ShireCoreBundle : DynamicBundle(BUNDLE) { @Suppress("SpreadOperator") @JvmStatic fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = getMessage(key, *params) @Suppress("SpreadOperator", "unused") @JvmStatic fun messagePointer(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = getLazyMessage(key, *params) } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/ShireCoroutineScope.kt ================================================ package com.phodal.shirecore import com.intellij.openapi.components.Service import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.project.Project import com.intellij.util.concurrency.AppExecutorUtil import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.asCoroutineDispatcher public val workerThread = AppExecutorUtil.getAppExecutorService().asCoroutineDispatcher() @Service(Service.Level.PROJECT) class ShireCoroutineScope { val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable -> Logger.getInstance(ShireCoroutineScope::class.java).error(throwable) } val coroutineScope: CoroutineScope = CoroutineScope( SupervisorJob() + workerThread + coroutineExceptionHandler ) companion object { fun scope(project: Project): CoroutineScope = project.getService(ShireCoroutineScope::class.java).coroutineScope fun workerThread(): CoroutineScope = CoroutineScope(SupervisorJob() + workerThread) } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/ShirelangNotifications.kt ================================================ package com.phodal.shirecore import com.intellij.notification.NotificationGroup import com.intellij.notification.NotificationGroupManager import com.intellij.notification.NotificationType import com.intellij.openapi.project.Project object ShirelangNotifications { private fun group(): NotificationGroup? { return NotificationGroupManager.getInstance().getNotificationGroup("Shirelang.notification.group") } fun info(project: Project, msg: String) { group()?.createNotification(msg, NotificationType.INFORMATION)?.notify(project) } fun error(project: Project, msg: String) { group()?.createNotification(msg, NotificationType.ERROR)?.notify(project) } fun warn(project: Project, msg: String) { group()?.createNotification(msg, NotificationType.WARNING)?.notify(project) } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/agent/CustomAgent.kt ================================================ package com.phodal.shirecore.agent import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.psi.search.FilenameIndex import com.phodal.shirecore.schema.CUSTOM_AGENT_JSON_EXTENSION import com.phodal.shirecore.config.InteractionType import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json @Serializable data class CustomFlowTransition( /** * will be JsonPath */ val source: String, /** * will be JsonPath too */ val target: String, ) /** * Basic configuration for a custom agent. * For Example: * ```json * { * "name": "CustomAgent", * "description": "This is a custom agent configuration example.", * "url": "https://custom-agent.example.com", * "icon": "https://custom-agent.example.com/icon.png", * "responseAction": "Direct", * "transition": [ * { * "source": "$.from", * "target": "$.to" * } * ], * "interactive": "ChatPanel", * "auth": { * "type": "Bearer", * "token": "" * } * } * ``` */ @Serializable data class CustomAgent( val name: String, val description: String = "", val url: String = "", val icon: String = "", val connector: ConnectorConfig? = null, val responseAction: CustomAgentResponseAction = CustomAgentResponseAction.Direct, val transition: List = emptyList(), val interactive: InteractionType = InteractionType.AppendCursor, val auth: CustomAgentAuth? = null, /** * Default to 10 minutes */ val defaultTimeout: Long = 10, val enabled: Boolean = true, val isLocalAgent: Boolean = false, ) { var state: CustomAgentState = CustomAgentState.START companion object { private val cacheForVersion: MutableMap> = mutableMapOf() fun loadFromProject(project: Project): List { val configFiles = FilenameIndex.getAllFilesByExt(project, CUSTOM_AGENT_JSON_EXTENSION) if (configFiles.isEmpty()) return emptyList() val firstFile = configFiles.first() val content = firstFile.inputStream.reader().readText() if (cacheForVersion.containsKey(content)) { return cacheForVersion[content]!! } val configs: List = try { Json.decodeFromString(content) } catch (e: Exception) { logger().error("Failed to load custom agent configuration", e) emptyList() } /** * Only return enabled agents */ return configs.filter { it.enabled } } } } @Serializable data class ConnectorConfig( /** * will be Json Config */ val requestFormat: String = "", /** * will be JsonPath */ val responseFormat: String = "", ) /** * CustomAgentState is an enumeration that defines the possible states of a custom agent. * * - [CustomAgentState.START] indicates that the agent has just been initialized and is ready to receive commands. * - [CustomAgentState.HANDLING] means that the agent is currently processing a command. * - [CustomAgentState.FINISHED] signifies that the agent has completed its execution and is no longer operational. * * This enum is used to track the state of the agent and make decisions accordingly in the agent's lifecycle. */ @Serializable enum class CustomAgentState { START, HANDLING, FINISHED } @Serializable data class CustomAgentAuth( val type: AuthType = AuthType.Bearer, val token: String = "", ) @Serializable enum class AuthType { Bearer, } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/agent/CustomAgentResponseAction.kt ================================================ package com.phodal.shirecore.agent /** * CustomAgentResponseAction is an enumeration of possible response actions that can be taken by the * CustomAgent when processing user input. * * @property Direct Direct display result - The CustomAgent will directly display the result to the user. * @property TextChunk Text splitting result - The CustomAgent will split the result into text chunks for easier consumption. * @property Flow Will be handled by the client - The response will be handled by the client application. * @property Stream Stream response - The CustomAgent will stream the response to the user. * @property WebView Display result in WebView - The CustomAgent will display the result in a WebView. * @property Shire Handle by Shire language compile and run in code block. */ enum class CustomAgentResponseAction { /** * Direct display result */ Direct, /** * Stream response */ Stream, /** * Text splitting result */ TextChunk, /** * will be handled by the client */ Flow, /** * Display result in WebView */ WebView, /** * Handle by Shire language compile and run in code block. * @since: AutoDev@1.8.2 */ Shire } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/agent/agenttool/AgentToolContext.kt ================================================ package com.phodal.shirecore.agent.agenttool import com.intellij.openapi.project.Project class AgentToolContext( val project: Project, val argument: String ) { } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/agent/agenttool/AgentToolResult.kt ================================================ package com.phodal.shirecore.agent.agenttool data class AgentToolResult( val isSuccess: Boolean, val output: String? = null ) ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/agent/agenttool/browse/BrowseTool.kt ================================================ package com.phodal.shirecore.agent.agenttool.browse import com.phodal.shirecore.agent.agenttool.AgentToolContext import com.phodal.shirecore.provider.agent.AgentTool import com.phodal.shirecore.agent.agenttool.AgentToolResult import com.phodal.shirecore.agent.agenttool.ua.RandomUserAgent import org.jsoup.Jsoup import org.jsoup.nodes.Document class BrowseTool : AgentTool { override val name: String get() = "Browse" override val description: String = "Get the content of a given URL." override fun execute(context: AgentToolContext): AgentToolResult { return AgentToolResult( isSuccess = true, output = parse(context.argument).body ) } companion object { /** * Doc for parseHtml * * Intellij API: [com.intellij.inspectopedia.extractor.utils.HtmlUtils.cleanupHtml] */ fun parse(url: String): DocumentContent { val doc: Document = Jsoup.connect(url) .ignoreContentType(true) .userAgent(RandomUserAgent.random()) .followRedirects(true) .get() return DocumentCleaner().cleanHtml(doc) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/agent/agenttool/browse/DocumentCleaner.kt ================================================ package com.phodal.shirecore.agent.agenttool.browse import org.jsoup.Jsoup import org.jsoup.nodes.Document import org.jsoup.nodes.Element class DocumentCleaner { fun cleanHtml(html: String): DocumentContent { val doc = Jsoup.parse(html) return cleanHtml(doc) } fun cleanHtml(doc: Document): DocumentContent { return DocumentContent( title = doc.title(), language = metaContent(doc, "http-equiv", "Content-Language"), description = metaDescription(doc), body = articleNode(doc) ) } private fun metaDescription(doc: Document): String? { val attributes = arrayOf(arrayOf("property", "description"), arrayOf("name", "description")) return attributes .asSequence() .mapNotNull { (key, value) -> metaContent(doc, key, value) } .firstOrNull() } private fun metaContent(doc: Document, key: String, value: String): String? { val metaElements = doc.select("head meta[$key=$value]") return metaElements .map { it.attr("content").trim() } .firstOrNull { it.isNotEmpty() } } private val ARTICLE_BODY_ATTR: Array> = arrayOf( Pair("itemprop", "articleBody"), Pair("data-testid", "article-body"), Pair("name", "articleBody") ) private fun articleNode(doc: Document): String? { var bodyElement: Element? = doc.select("html").select("body").first() val firstBodyElement = bodyElement ?: return null // the Microdata for ((attr, value) in ARTICLE_BODY_ATTR) { bodyElement = doc.selectFirst("[$attr=$value]") if (bodyElement != null) { return bodyElement.text() } } return trySelectBestCode(firstBodyElement) } private fun trySelectBestCode(doc: Element): String { val commonBestNodes = doc.select("article, main, #main, #content, #doc-content, #contents, .book-body") if (commonBestNodes.isNotEmpty()) { return commonBestNodes.first()?.text() ?: "" } return doc.text() } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/agent/agenttool/browse/DocumentContent.kt ================================================ package com.phodal.shirecore.agent.agenttool.browse data class DocumentContent( val title: String?, val language: String?, val description: String?, val body: String? ) { } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/agent/agenttool/ua/BrowserType.kt ================================================ /** * This is free and unencumbered software released into the public domain. * GitHub: https://github.com/jonaskahn/user-agents */ package com.phodal.shirecore.agent.agenttool.ua enum class BrowserType { CHROME, FIREFOX, SAFARI; companion object { fun all(): List { return arrayListOf(CHROME, FIREFOX, SAFARI) } fun cross(): List { return arrayListOf(CHROME, FIREFOX) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/agent/agenttool/ua/DeviceType.kt ================================================ /** * This is free and unencumbered software released into the public domain. * GitHub: https://github.com/jonaskahn/user-agents */ package com.phodal.shirecore.agent.agenttool.ua enum class DeviceType { MACOS, LINUX, WINDOWS, IOS, ANDROID; companion object { fun mobile(): List { return arrayListOf(IOS, ANDROID) } fun desktop(): List { return arrayListOf(MACOS, LINUX, WINDOWS) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/agent/agenttool/ua/Pattern.kt ================================================ /** * This is free and unencumbered software released into the public domain. * GitHub: https://github.com/jonaskahn/user-agents */ package com.phodal.shirecore.agent.agenttool.ua object Pattern { const val WINDOWS_CHROME_AGENT = "Mozilla/5.0 (%s) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36" const val WINDOWS_FIREFOX_AGENT = "Mozilla/5.0 (%s; rv:%s) Gecko/20100101 Firefox/%s" const val LINUX_CHROME_AGENT = WINDOWS_CHROME_AGENT const val LINUX_FIREFOX_AGENT = WINDOWS_FIREFOX_AGENT const val MACOS_FIREFOX_AGENT = WINDOWS_FIREFOX_AGENT const val MACOS_CHROME_AGENT = WINDOWS_CHROME_AGENT const val MACOS_SAFARI_AGENT = "Mozilla/5.0 (%s) AppleWebKit/%s (KHTML, like Gecko) Version/%s Safari/%s" const val IOS_FIREFOX_AGENT = "Mozilla/5.0 (%s) AppleWebKit/%s (KHTML, like Gecko) FxiOS/%s Mobile/%s Safari/%s" const val IOS_CHROME_AGENT = "Mozilla/5.0 (%s) AppleWebKit/%s (KHTML, like Gecko) CriOS/%s Mobile/%s Safari/%s" const val IOS_SAFARI_AGENT = "Mozilla/5.0 (%s) AppleWebKit/%s (KHTML, like Gecko) Version/%s Mobile/%s Safari/%s" const val ANDROID_FIREFOX_AGENT = "Mozilla/5.0 (%s; rv:%s) Gecko/%s Firefox/%s" const val ANDROID_CHROME_AGENT = "Mozilla/5.0 (%s) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Mobile Safari/537.36" } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/agent/agenttool/ua/RandomUserAgent.kt ================================================ /** * This is free and unencumbered software released into the public domain. * GitHub: https://github.com/jonaskahn/user-agents */ package com.phodal.shirecore.agent.agenttool.ua import java.util.* import java.util.concurrent.ThreadLocalRandom object RandomUserAgent { private val random = ThreadLocalRandom.current() /** * Random a user-agent, result can be a {@link #desktop(DeviceType, BrowserType) desktop} * or {@link #mobile(DeviceType, BrowserType) mobile} user-agent type * */ fun random(): String { return if (random.nextBoolean()) desktop() else mobile() } /** * Generate a desktop(MacOS, Linux, Windows) user-agent * @param deviceType DeviceType must be a valid type or null. If device type is mobile, an exception will be thrown. * @param browserType BrowserType must be a valid type or null. * @return {@link String} * @see one.ifelse.tools.useragent.types.DeviceType * @see one.ifelse.tools.useragent.types.BrowserType */ fun desktop( deviceType: DeviceType? = null, browserType: BrowserType? = null ): String { val device = deviceType ?: DeviceType.desktop().random() return when (device) { DeviceType.MACOS -> generateMacOS(browserType) DeviceType.LINUX -> generateLinux(browserType) DeviceType.WINDOWS -> generateWindowsAgent(browserType) DeviceType.IOS -> throw UserAgentException("Require desktop device type like: ${DeviceType.desktop()}") DeviceType.ANDROID -> throw UserAgentException("Require desktop device type like: ${DeviceType.desktop()}") } } private fun generateWindowsAgent(browserType: BrowserType?): String { val windowsVersion = Seeds.WINDOWS_DEVICES.random() val type = browserType ?: BrowserType.cross().random() return when (type) { BrowserType.CHROME -> { String.format( Pattern.WINDOWS_CHROME_AGENT, windowsVersion, Seeds.CHROME_VERSIONS.random() ) } BrowserType.FIREFOX -> { val firefoxVersion = Seeds.FIREFOX_VERSIONS.random() String.format( Pattern.WINDOWS_FIREFOX_AGENT, windowsVersion, firefoxVersion, firefoxVersion ) } BrowserType.SAFARI -> { throw UserAgentException("Cannot generate user-gent for Windows and Safari") } } } private fun generateLinux(browserType: BrowserType?): String { val linuxVersion = Seeds.LINUX_DEVICES.random() val type = browserType ?: BrowserType.cross().random() return when (type) { BrowserType.CHROME -> { String.format( Pattern.LINUX_CHROME_AGENT, linuxVersion, Seeds.CHROME_VERSIONS.random() ) } BrowserType.FIREFOX -> { val firefoxVersion = Seeds.FIREFOX_VERSIONS.random() return String.format( Pattern.LINUX_FIREFOX_AGENT, linuxVersion, firefoxVersion, firefoxVersion ) } BrowserType.SAFARI -> { throw UserAgentException("Cannot generate user-gent for Linux and Safari") } } } private fun generateMacOS(browserType: BrowserType?): String { val type = browserType ?: BrowserType.all().random() val arch = Seeds.MAC_ARCHS.random() val version = Seeds.MAC_VERSIONS.random() val device = "$arch $version" return when (type) { BrowserType.CHROME -> { String.format( Pattern.MACOS_CHROME_AGENT, device, Seeds.CHROME_VERSIONS.random(), ) } BrowserType.FIREFOX -> { val firefoxVersion = Seeds.FIREFOX_VERSIONS.random() return String.format( Pattern.MACOS_FIREFOX_AGENT, device, firefoxVersion, firefoxVersion ) } BrowserType.SAFARI -> { val safariVersion = Seeds.SAFARI_VERSIONS.random() return String.format( Pattern.MACOS_SAFARI_AGENT, device, safariVersion, version.replace("_", "."), safariVersion ) } } } /** * Generate a mobile(IOS, Android) user-agent * @param deviceType DeviceType must be a valid type or null. If device type is desktop, an exception will be thrown. * @param browserType BrowserType must be a valid type or null. * @return {@link String} * @see one.ifelse.tools.useragent.types.DeviceType * @see one.ifelse.tools.useragent.types.BrowserType */ fun mobile( deviceType: DeviceType? = null, browserType: BrowserType? = null ): String { val device = deviceType ?: DeviceType.mobile().random() return when (device) { DeviceType.MACOS -> throw UserAgentException("Require mobile device type like: ${DeviceType.mobile()}") DeviceType.LINUX -> throw UserAgentException("Require mobile device type like: ${DeviceType.mobile()}") DeviceType.WINDOWS -> throw UserAgentException("Require mobile device type like: ${DeviceType.mobile()}") DeviceType.IOS -> generateIOSAgent(browserType) DeviceType.ANDROID -> generateAndroidAgent(browserType) } } private fun generateIOSAgent(browserType: BrowserType?): String { val type = browserType ?: BrowserType.all().random() val version = Seeds.IOS_VERSIONS.entries.random() val arch = Seeds.IOS_ARCHS.random() val device = String.format(arch, version.key) val safariVersion = Seeds.SAFARI_VERSIONS.random() return when (type) { BrowserType.CHROME -> { String.format( Pattern.IOS_CHROME_AGENT, device, safariVersion, Seeds.CHROME_VERSIONS.random(), version.value, safariVersion ) } BrowserType.FIREFOX -> { String.format( Pattern.IOS_FIREFOX_AGENT, device, safariVersion, Seeds.FIREFOX_VERSIONS.random(), version.value, safariVersion ) } BrowserType.SAFARI -> { String.format( Pattern.IOS_SAFARI_AGENT, device, safariVersion, safariVersion, version.value, safariVersion ) } } } private fun generateAndroidAgent(browserType: BrowserType?): String { val type = browserType ?: BrowserType.cross().random() val device = Seeds.ANDROID_DEVICES.random() return when (type) { BrowserType.CHROME -> { val chromeVersion = Seeds.CHROME_VERSIONS.random() String.format( Pattern.ANDROID_CHROME_AGENT, device, chromeVersion, ) } BrowserType.FIREFOX -> { val firefoxVersion = Seeds.FIREFOX_VERSIONS.random() String.format( Pattern.ANDROID_FIREFOX_AGENT, device, firefoxVersion, firefoxVersion, firefoxVersion ) } BrowserType.SAFARI -> throw UserAgentException("Cannot generate user-gent for Android and Safari") } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/agent/agenttool/ua/Seeds.kt ================================================ /** * This is free and unencumbered software released into the public domain. * GitHub: https://github.com/jonaskahn/user-agents */ package com.phodal.shirecore.agent.agenttool.ua object Seeds { val CHROME_VERSIONS = arrayListOf( "117.0.5938.104", "116.0.5845.177", "116.0.5845.146", "116.0.5845.118", "116.0.5845.103", "115.0.5790.160", "115.0.5790.130", "115.0.5790.84", "114.0.5735.124", "114.0.5735.99", "114.0.5735.50", "113.0.5672.121", "113.0.5672.109", "113.0.5672.69", "112.0.5615.167", "117.0.5938.104", "112.0.5615.70", "112.0.5615.46", "111.0.5563.101", "111.0.5563.72", "110.0.5481.114", "110.0.5481.83", "109.0.5414.112", "109.0.5414.83", "108.0.5359.112", "108.0.5359.52", "107.0.5304.101", "107.0.5304.66", "106.0.5249.92", "106.0.5249.70", "117.0.5938.104", "105.0.5195.147", "105.0.5195.129", "105.0.5195.100", "105.0.5195.98", "104.0.5112.99", "104.0.5112.88", "104.0.5112.71", "103.0.5060.63", "103.0.5060.54", "102.0.5005.87", "102.0.5005.67", "101.0.4951.58", "101.0.4951.44", "100.0.4896.85" ) val FIREFOX_VERSIONS = arrayListOf( "117.3", "117.2", "117.0", "116.4", "116.2", "116.1", "116.0", "115.1", "115.0", "114.4", "114.3", "114.2", "114.1", "114.0", "113.2", "113.1", "113.0", "112.2", "112.1", "112.0", "111.2", "111.1", "111.0", "110.2", "110.1", "110.0", "109", "108.1", "107.3", "107.2", "107.1", "107.0", "106.2", "106.1", "106.0", "105.1", "105", "104.2", "104.1", "104", ) val SAFARI_VERSIONS = arrayListOf( "600.7.11", "610.15.6", "604.1.5", "602.1.39", "604.3.2", "604.8.6", "604.3.4", "602.1.40", "600.2.2", "602.4.8", "603.6.6", "603.1.10", "601.4.4", "602.7.7", "603.7.8", "601.1.1", "600.3.17", "605.1.33", "600.4.10", "602.1.25", "600.7.10", "601.3.8", "602.1.32", "602.4.2", "603.1.30", "603.2.5", "603.2.1", "605.1.13", "604.1.6", "607.3.10", "612.4.9", "602.1.35", "612.2.9", "600.6.16", "611.1.21", "604.4.5", "605.4.4", "603.2.7", "611.2.7", "602.1.33", "603.1.11", "600.1.17", "603.1.13", "600.3.5", "600.5.17", "602.3.3", "601.4.3", "600.1.8", "600.8.2", "604.3.1", "600.7.7", "604.7.8", "604.5.3", "602.3.7", "604.6.3", "604.1.17", "602.1.21", "601.4.2", "601.5.2", "603.3.1", "602.1.28", "612.1.1", "601.1.32", "604.7.3", "604.7.6", "611.2.11", "605.1.15", "601.7.2", "600.4.8", "600.3.10", "602.1.27", "603.1.20", "610.3.7", "600.4.22", "600.5.6", "600.5.16", "604.6.8", "612.1.18", "601.1.27", "605.1.12", "604.1.19", "601.7.6", "601.2.4", "6033.1.15", "602.1.37", "612.1.26", "600.2.5", "603.3.6", "600.5.15", "605.1.11", "600.6.17", "604.1.31", "603.2.4", "604.2.4", "603.1.23", "601.5.17", "601.2.5", "602.3.10", "612.1.13", "611.3.10", "601.7.1", "602.3.6", "601.7.5", "608.5.12", "600.1.3", "609.4.1", "601.6.17", "603.3.8", "601.2.7", "601.1.3", "604.3.8", "604.4.6", "601.2.8", "604.1.8", "604.0.13", "603.7.5", "602.4.3", "601.5.18", "601.1.46", "604.1.25", "604.1.16", "601.6.7", "601.7.7", "604.1.22", "603.3.7", "603.3.3", "602.2.7", "601.6.16", "602.5.1", "602.1.41", "600.6.24", "604.2.7", "600.7.12", "604.1.28", "604.4.3", "603.3.4", "614.2.8", "603.1.6", "609.1.20", "604.1.34", "600.5.9", "600.1.4", "600.2.14", "601.3.4", "610.4.3", "606.1.36", "604.1.32", "602.1.38", "612.3.6", "603.1.29", "603.1.12", "605.1.4", "610.2.11", "600.1.25", "600.3.14", "600.8.9", "605.4.2", "603.2.8", "601.5.3", "600.5.22", "602.1.1", "605.7.4", "600.8.13", "605.1.2", "632.5.12", "603.1.1", "601.1.56", "606.1.15", "601.5.8", "604.1.21", "601.1.43", "602.1.31", "601.3.5", "600.2.27", "604.1.15", "603.29.12", "601.6.14", "600.4.5", "605.5.4", "603.3.5", "600.5.8", "601.1.35", "602.1.50", "602.4.6", "604.1.23", "612.1.15", "607.3.9", "601.5.4", "612.1.8", "602.2.3", "602.3.12", "601.3.9", "605.1.3", "602.1.7", "605.6.5", "601.1.33", "600.1.22", "603.1.3", "602.7.8", "604.5.6", "604.1.27", "604.1.35", "605.2.1", "612.1.4", "603.5.1", "605.1.10", "608.2.11", "601.5.10", "609.3.5", "605.6.4", "6050.1.15", "604.3.5", "601.1.50", "603.2.2", "602.4.4", "603.1.5", "601.3.2", "640.3.18", "627.9.17", "605.2.6", "612.1.12", "605.4.7", "602.2.11", "602.7.2", "602.7.1", "600.6.3", "605.4.8", "601.1.39", "602.1.43", "601.3.6", "601.4.8", "600.1.15", "600.8.7", "600.8.22", "604.5.100", "604.4.7", "604.5.2", "602.2.14", "604.5.5", "600.3.8", "604.1.38", "602.1.18", "601.1.37", "600.3.18", "603.2.3", "607.1.15", "601.1.52", "601.1.41", "603.1.8", "600.5.3", "605.7.3", "601.5.13", "601.7.8", "605.3.8", "612.1.7", "612.1.27", "607.1.40", ) val WINDOWS_DEVICES = arrayListOf( "Windows NT 11.0; Win64; x64", "Windows NT 11.0; Win32; x32", "Windows NT 10.0; Win64; x64", "Windows NT 10.0; Win32; x32", "Windows NT 10; WOW64", "Windows NT 10; WOW32", "Windows NT 6.1; WOW64", "Windows NT 6.1; WOW32", "Windows NT 6.3; WOW64", "Windows NT 6.3; WOW32", "Windows NT 6.2; WOW64", "Windows NT 6.2; WOW32", ) val LINUX_DEVICES = arrayListOf( "X11; Linux x86_64", "X11; Ubuntu; Linux x86_64", "X11; CrOS x86_64 7077.111.0", "X11; CrOS x86_64 6946.86.0", "X11; CrOS armv7l 7262.52.0", "X11; FC Linux i686" ) val ANDROID_DEVICES = arrayListOf( "Linux; Android 13; SM-S911U", "Linux; Android 13 QPR5; SM-S918U1", "Linux; Android 13; SM-S918B Build/TP1A.220624.014; wv", "Linux; Android 13; SAMSUNG SM-S918W", "Linux; Android 13; SM-S911B", "Linux; Android 13; SAMSUNG SM-S9110", "Linux; Android 13; SAMSUNG SM-S916", "Linux; Android 13; SAMSUNG SM-S9160", "Linux; Android 13; SM-S908E Build/TP1A.220624.014; wv", "Linux; Android 13; SM-F936U Build/TP1A.220624.014; wv", "Linux; Android 13; SM-F936B Build/TP1A.220624.014; wv)", "Linux; Android 13; SM-F936U1 Build/TP1A.220624.014; wv", "Linux; Android 13; Samsung Galaxy Tab S9 Ultra", "Linux; Android 13; SM-G980F Build/TP1A.220624.014; wv", "Linux; Android 13; SM-G980F Build/TP1A.220624.014; wv", "Linux; Android 13; SM-G980F", "Linux; Android 13; SAMSUNG SM-G980F", "Linux; Android 13; SM-G996B Build/TP1A.220624.014", "Linux; Android 12; SM-G975F Build/SP1A.210812.016; wv", "Linux; Android 10; SM-G986B/DS", "Linux; Android 13; SAMSUNG SM-A526W", "Linux; Android 13; SM-A526B Build/TP1A.220624.014; wv", "Linux; Android 10; CDY-AN95 Build/HUAWEICDY-AN95; wv)", "Linux; Android 10; MAR-LX3Bm Build/HUAWEIMAR-L03B; wv", "(Linux; Android 11; BE2026 Build/RKQ1.201217.002; wv", "Linux; Android 11; BE2029", "Linux; Android 11; BE2025 Build/RKQ1.201217.002; wv", "Linux; Android 12; LE2123", "Linux; Android 12; LE2120", "Linux; Android 12; LE2125", "Linux; Android 13; 22071212AG", "Linux; Android 13; 2201122G", "Linux; Android 13; 2201122G Build/TKQ1.220807.001; wv", "Linux; Android 7.1.2; Xiaomi 10 Pro Build/MBFMIEK", "Linux; Android 12; 2210129SG" ) val MAC_ARCHS = arrayListOf( "Macintosh; Intel Mac OS X", "Macintosh; M1 Mac OS X", "Macintosh; M2 Mac OS X" ) val MAC_VERSIONS = arrayListOf( "12.0", "12.0.1", "12.1", "12.2", "12.2.1", "12.3", "12.3.1", "12.4", "12.5", "12.5.1", "12.6", "12.6.1", "12.6.2", "12.6.3", "12.6.4", "12.6.5", "12.6.6", "12.6.7", "12.6.8", "12.6.9", "13", "13.0.1", "13.1", "13.2", "13.2.1", "13.3", "13.3.1", "13.4", "13.4.1", "13.5", "13.5.1", "13.5.2" ) val IOS_ARCHS = arrayListOf( "iPhone; CPU iPhone OS %s like Mac OS X", "iPad; CPU OS %s like Mac OS X" ) val IOS_VERSIONS = mapOf( "13.2.2" to "17B102", "13.2.3" to "17B111", "13.3" to "17C54", "13.3.1" to "17D50", "13.6" to "17G68", "13.6.1" to "17G80", "13.7" to "17H35", "14" to "18A373", "14.0.1" to "18A393", "14.1" to "18A8395", "14.2" to "18B92", "14.2.1" to "18B121", "14.3" to "18C66", "14.4" to "18D52", "14.4.1" to "18D61", "14.4.2" to "18D70", "15.0.1" to "19A348", "15.0.2" to "19A404", "15.1" to "19B74", "15.1.1" to "19B81", "15.2" to "19C56", "15.2.1" to "19C63", "15.3" to "19D50", "15.3.1" to "19D52", ) } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/agent/agenttool/ua/UserAgentException.kt ================================================ package com.phodal.shirecore.agent.agenttool.ua class UserAgentException(message: String) : RuntimeException(message) ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/ast/ComplexityPoint.kt ================================================ /** * The MIT License (MIT) *

* https://github.com/nikolaikopernik/code-complexity-plugin *

*/ package com.phodal.shirecore.ast data class ComplexityPoint( val complexity: Int, val nesting: Int, val type: PointType) { override fun toString(): String = ". ".repeat(nesting) + "$type + $complexity" } enum class PointType { LOOP_WHILE, LOOP_FOR, IF, ELSE, SWITCH, CATCH, BREAK, CONTINUE, LOGICAL_AND, LOGICAL_OR, RECURSION, METHOD, UNKNOWN } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/ast/ComplexitySink.kt ================================================ /** * The MIT License (MIT) *

* https://github.com/nikolaikopernik/code-complexity-plugin *

*/ package com.phodal.shirecore.ast class ComplexitySink { private var nesting: Int = 0 private val points = mutableListOf() fun decreaseNesting() { if (nesting > 0) nesting-- } fun increaseNesting() { nesting++ } fun increaseComplexity(type: PointType) { increaseComplexity(1, type) } fun increaseComplexity(amount: Int, type: PointType) { points.add( ComplexityPoint( complexity = amount, nesting = nesting, type = type ) ) } fun increaseComplexityAndNesting(type: PointType) { points.add( ComplexityPoint( complexity = 1 + nesting, nesting = nesting++, type = type ) ) } fun getComplexity(): Int { return points.sumOf { it.complexity } } fun getNesting(): Int { return nesting } fun getPoints() = points.toList() } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/ast/ComplexityVisitor.kt ================================================ /** * The MIT License (MIT) *

* https://github.com/nikolaikopernik/code-complexity-plugin *

*/ package com.phodal.shirecore.ast import com.intellij.psi.PsiElement import com.intellij.psi.PsiRecursiveElementVisitor abstract class ComplexityVisitor: PsiRecursiveElementVisitor(true) { override fun visitElement(element: PsiElement) { processElement(element) if (shouldVisitElement(element)) { super.visitElement(element) } postProcess(element) } /** * Increases complexity and nesting */ protected abstract fun processElement(element: PsiElement) /** * Decreases nesting */ protected abstract fun postProcess(element: PsiElement) /** * @return true if PsiElement is Binary Expression with operations || or && */ protected abstract fun shouldVisitElement(element: PsiElement): Boolean } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/ast/PsiSyntaxCheckingVisitor.kt ================================================ package com.phodal.shirecore.ast import com.intellij.openapi.application.runReadAction import com.intellij.psi.PsiElement import com.intellij.psi.PsiElementVisitor abstract class PsiSyntaxCheckingVisitor : PsiElementVisitor() { override fun visitElement(element: PsiElement) { runReadAction { element.children.forEach { it.accept(this) } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/completion/ShireLookupElement.kt ================================================ package com.phodal.shirecore.completion import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementDecorator import com.intellij.openapi.util.ClassConditionKey import com.intellij.openapi.vfs.VirtualFile class ShireLookupElement private constructor( delegate: T, val priority: Double, val virtualFile: VirtualFile, ) : LookupElementDecorator(delegate) { fun getFile(): VirtualFile { return virtualFile } companion object { private val CLASS_CONDITION_KEY: ClassConditionKey> = ClassConditionKey.create( ShireLookupElement::class.java ) /** * @param element element * @param priority priority (higher priority puts the item closer to the beginning of the list) * @return decorated lookup element */ fun withPriority(element: LookupElement, priority: Double, virtualFile: VirtualFile): LookupElement { val prioritized = element.`as`(CLASS_CONDITION_KEY) val lookupElement = if (prioritized !== element) element else prioritized.delegate return ShireLookupElement(lookupElement, priority, virtualFile) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/config/InteractionType.kt ================================================ package com.phodal.shirecore.config enum class InteractionType(val description: String) { AppendCursor("Append content at the current cursor position"), AppendCursorStream("Append content at the current cursor position, stream output"), OutputFile("Output to a new file"), ReplaceSelection("Replace the currently selected content"), ReplaceCurrentFile("Replace the content of the current file"), InsertBeforeSelection("Insert content before the currently selected content"), RunPanel("Show Result in Run panel which is the bottom of the IDE"), OnPaste("Copy the content to the clipboard"), RightPanel("Show Result in Right panel which is the right of the IDE"), StreamDiff("Use streaming diff to show the result") ; companion object { fun from(interaction: String): InteractionType { return when (interaction.lowercase()) { AppendCursor.name.lowercase() -> AppendCursor AppendCursorStream.name.lowercase() -> AppendCursorStream OutputFile.name.lowercase() -> OutputFile ReplaceSelection.name.lowercase() -> ReplaceSelection ReplaceCurrentFile.name.lowercase() -> ReplaceCurrentFile InsertBeforeSelection.name.lowercase() -> InsertBeforeSelection RunPanel.name.lowercase() -> RunPanel OnPaste.name.lowercase() -> OnPaste RightPanel.name.lowercase() -> RightPanel StreamDiff.name.lowercase() -> StreamDiff else -> RunPanel } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/config/ShireActionLocation.kt ================================================ package com.phodal.shirecore.config enum class ShireActionLocation(val location: String, val description: String) { CONTEXT_MENU("ContextMenu", "Show in Context Menu by Right Click"), INTENTION_MENU("IntentionMenu", "Show in Intention Menu by Alt+Enter"), TERMINAL_MENU("TerminalMenu", "Show in Terminal panel menu bar"), COMMIT_MENU("CommitMenu", "Show in Commit panel menu bar"), RUN_PANEL("RunPanel", "Show in Run panel which is the bottom of the IDE"), INPUT_BOX("InputBox", "Show in Input Box"), DATABASE_MENU("DatabaseMenu", "Show in Database panel menu bar"), CONSOLE_MENU("ConsoleMenu", "Show in Console panel menu bar"), VCS_LOG_MENU("VcsLogMenu", "Show in VCS Log panel menu bar"), CHAT_BOX("ChatBox", "Show in Chat Box, default in Right Panel"), INLINE_CHAT("InlineChat", "Show in Inline Chat"), EXT_SONARQUBE_MENU("ExtSonarQubeMenu", "Show in SonarQube panel menu bar"), ; companion object { fun from(locationName: String): ShireActionLocation { return fromLocationName(locationName) ?: RUN_PANEL } private fun fromLocationName(locationName: String): ShireActionLocation? { return entries.firstOrNull { it.location == locationName } } fun all(): Array = entries.toTypedArray() fun default(): String = RUN_PANEL.location } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/config/interaction/EditorInteractionProvider.kt ================================================ package com.phodal.shirecore.config.interaction import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.openapi.application.invokeLater import com.intellij.openapi.application.runReadAction import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator import com.intellij.openapi.wm.ToolWindowManager import com.intellij.ui.content.impl.ContentManagerImpl import com.phodal.shirecore.ShireCoroutineScope import com.phodal.shirecore.ShirelangNotifications import com.phodal.shirecore.config.InteractionType import com.phodal.shirecore.config.interaction.dto.CodeCompletionRequest import com.phodal.shirecore.config.interaction.task.ChatCompletionTask import com.phodal.shirecore.config.interaction.task.FileGenerateTask import com.phodal.shirecore.config.interaction.task.cancelWithConsole import com.phodal.shirecore.diff.DiffStreamHandler import com.phodal.shirecore.runner.console.cancelWithConsole import com.phodal.shirecore.llm.LlmProvider import com.phodal.shirecore.provider.ide.LocationInteractionContext import com.phodal.shirecore.provider.ide.LocationInteractionProvider import com.phodal.shirecore.runner.console.cancelHandler import com.phodal.shirecore.ui.ShirePanelView import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.cancellable import kotlinx.coroutines.launch class EditorInteractionProvider : LocationInteractionProvider { override fun isApplicable(context: LocationInteractionContext): Boolean { return true } override fun execute(context: LocationInteractionContext, postExecute: PostFunction) { val targetFile = context.editor?.virtualFile when (context.interactionType) { InteractionType.AppendCursor, InteractionType.AppendCursorStream, -> { val task = createTask( context, context.prompt, isReplacement = false, postExecute = postExecute, false )?.cancelWithConsole(context.console) if (task == null) { ShirelangNotifications.error(context.project, "Failed to create code completion task.") postExecute.invoke("", null) return } ProgressManager.getInstance() .runProcessWithProgressAsynchronously(task, BackgroundableProcessIndicator(task)) } InteractionType.OutputFile -> { val fileName = targetFile?.name val task = FileGenerateTask( context.project, context.prompt, fileName, postExecute = postExecute ).cancelWithConsole(context.console) ProgressManager.getInstance() .runProcessWithProgressAsynchronously(task, BackgroundableProcessIndicator(task)) } InteractionType.ReplaceSelection -> { val task = createTask(context, context.prompt, true, postExecute, false)?.cancelWithConsole(context.console) if (task == null) { ShirelangNotifications.error(context.project, "Failed to create code completion task.") postExecute.invoke("", null) return } ProgressManager.getInstance() .runProcessWithProgressAsynchronously(task, BackgroundableProcessIndicator(task)) } InteractionType.ReplaceCurrentFile -> { val fileName = targetFile?.name val task = FileGenerateTask( context.project, context.prompt, fileName, postExecute = postExecute ).cancelWithConsole(context.console) ProgressManager.getInstance() .runProcessWithProgressAsynchronously(task, BackgroundableProcessIndicator(task)) } InteractionType.InsertBeforeSelection -> { val task = createTask(context, context.prompt, false, postExecute, isInsertBefore = true)?.cancelWithConsole( context.console ) if (task == null) { ShirelangNotifications.error(context.project, "Failed to create code completion task.") postExecute.invoke("", null) return } ProgressManager.getInstance() .runProcessWithProgressAsynchronously(task, BackgroundableProcessIndicator(task)) } InteractionType.RunPanel -> { val flow: Flow? = LlmProvider.provider(context.project)?.stream(context.prompt, "", false) ShireCoroutineScope.scope(context.project).launch { val suggestion = StringBuilder() flow?.cancelWithConsole(context.console)?.cancellable()?.collect { char -> suggestion.append(char) invokeLater { context.console?.print(char, ConsoleViewContentType.NORMAL_OUTPUT) } } postExecute.invoke(suggestion.toString(), null) } } InteractionType.RightPanel -> { val toolWindowManager = ToolWindowManager.getInstance(context.project).getToolWindow("ShireToolWindow") ?: run { logger().warn("Tool window not found") return } val contentManager = toolWindowManager.contentManager val panelView = ShirePanelView(context.project) contentManager.factory.createContent(panelView, "Shire RightPanel Run", false).let { // Default is 3, configurable in the future. if (contentManager.contentCount >= 3) { contentManager.getContent(0)?.let { content -> contentManager.removeContent(content, false) (content.component as? ShirePanelView)?.cancel("This content is removed") } } contentManager.addContent(it) (contentManager as ContentManagerImpl).setSelectedContent(it) } toolWindowManager.activate(null) val flow: Flow? = LlmProvider.provider(context.project)?.stream(context.prompt, "", false) ShireCoroutineScope.scope(context.project).launch { val suggestion = StringBuilder() panelView.onStart() panelView.addRequestPrompt(context.prompt) flow?.cancelHandler { panelView.handleCancel = it }?.cancellable()?.collect { char -> suggestion.append(char) invokeLater { panelView.onUpdate(suggestion.toString()) } } panelView.onFinish(suggestion.toString()) postExecute.invoke(suggestion.toString(), null) } } InteractionType.OnPaste -> { /** * already handle in [com.phodal.shirelang.actions.copyPaste.ShireCopyPastePreProcessor] */ } InteractionType.StreamDiff -> { if (context.editor == null) { ShirelangNotifications.error(context.project, "Editor is null, please open a file to continue.") return } val code = context.editor.document.text val diffStreamHandler = DiffStreamHandler( context.project, editor = context.editor, 0, code.lines().size, onClose = { }, onFinish = { postExecute.invoke(it, null) ShirelangNotifications.info(context.project, "Patch Applied") }) diffStreamHandler.streamDiffLinesToEditor(code, context.prompt) } } } private fun createTask( context: LocationInteractionContext, userPrompt: String, isReplacement: Boolean, postExecute: PostFunction, isInsertBefore: Boolean, ): ChatCompletionTask? { if (context.editor == null) { ShirelangNotifications.error(context.project, "Editor is null, please open a file to continue.") return null } val editor = context.editor val offset = if (isInsertBefore) { editor.selectionModel.selectionStart } else { editor.caretModel.offset } val request = runReadAction { CodeCompletionRequest.create( editor, offset, userPrompt = userPrompt, isReplacement = isReplacement, postExecute = postExecute, isInsertBefore = isInsertBefore, ) } ?: return null val task = ChatCompletionTask(request) return task } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/config/interaction/PostFunction.kt ================================================ package com.phodal.shirecore.config.interaction import com.intellij.openapi.util.TextRange /** * Don't remove public modifier, it's required Kotlin compile, in IDEA will failed. */ public typealias PostFunction = (response: String?, textRange: TextRange?) -> Unit ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/config/interaction/dto/CodeCompletionRequest.kt ================================================ package com.phodal.shirecore.config.interaction.dto import com.intellij.openapi.Disposable import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.ex.DocumentEx import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement import com.phodal.shirecore.config.interaction.PostFunction class CodeCompletionRequest( val project: Project, val fileUri: VirtualFile, val prefixText: String, val startOffset: Int, val element: PsiElement?, val editor: Editor, val suffixText: String, val isReplacement: Boolean = false, val postExecute: PostFunction, val isInsertBefore: Boolean, val userPrompt: String, ) : Disposable { companion object { fun create( editor: Editor, offset: Int, element: PsiElement? = null, prefix: String? = null, suffix: String? = null, isReplacement: Boolean = false, postExecute: PostFunction, isInsertBefore: Boolean = false, userPrompt: String, ): CodeCompletionRequest? { val project = editor.project ?: return null val document = editor.document val file = PsiDocumentManager.getInstance(project).getPsiFile(document) ?: return null val useTabs = editor.settings.isUseTabCharacter(project) val tabWidth = editor.settings.getTabSize(project) val documentVersion = if (document is DocumentEx) { document.modificationSequence.toLong() } else { document.modificationStamp } val suffixText = suffix ?: document.text.substring(offset) return CodeCompletionRequest( project, file.virtualFile, prefix ?: document.text, offset, element, editor, suffixText, isReplacement, postExecute = postExecute, isInsertBefore = isInsertBefore, userPrompt ) } } @Volatile var isCancelled = false fun cancel() { if (isCancelled) { return } isCancelled = true Disposer.dispose(this) } override fun dispose() { isCancelled = true } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/config/interaction/task/ChatCompletionTask.kt ================================================ package com.phodal.shirecore.config.interaction.task import com.intellij.openapi.util.TextRange import com.intellij.openapi.actionSystem.CustomShortcutSet import com.intellij.openapi.actionSystem.KeyboardShortcut import com.intellij.openapi.application.invokeLater import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.project.DumbAwareAction import com.phodal.shirecore.ShireCoreBundle import com.phodal.shirecore.ShireCoroutineScope import com.phodal.shirecore.config.interaction.dto.CodeCompletionRequest import com.phodal.shirecore.runner.console.cancelHandler import com.phodal.shirecore.llm.LlmProvider import com.phodal.shirecore.utils.markdown.CodeFence import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import java.awt.event.KeyEvent import javax.swing.KeyStroke open class ChatCompletionTask(private val request: CodeCompletionRequest) : ShireInteractionTask(request.project, ShireCoreBundle.message("intentions.chat.code.complete.name"), request.postExecute) { private val logger = logger() private var isCanceled: Boolean = false private var cancelCallback: ((String) -> Unit)? = null override fun run(indicator: ProgressIndicator) { indicator.isIndeterminate = false indicator.fraction = 0.1 indicator.text = ShireCoreBundle.message("intentions.step.prepare-context") val flow: Flow = LlmProvider.provider(request.project)!!.stream(request.userPrompt, "", false) logger.info("Prompt: ${request.userPrompt}") val shortcutAction = DumbAwareAction.create { isCanceled = true }.apply { registerCustomShortcutSet( CustomShortcutSet( KeyboardShortcut(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), null), ), request.editor.component ) } val editor = request.editor val project = request.project var currentOffset = request.startOffset val modifyStart = request.startOffset indicator.isIndeterminate = false indicator.fraction = 0.5 indicator.text = ShireCoreBundle.message("intentions.request.background.process.title") ShireCoroutineScope.scope(request.project).launch { val suggestion = StringBuilder() flow.cancelHandler { cancelCallback = it }.cancellable().collect { char -> if (isCanceled) { cancel() return@collect } suggestion.append(char) invokeLater { if (!isCanceled && !request.isReplacement) { if (request.isInsertBefore) { InsertUtil.insertStreamingToDoc(project, char, editor, currentOffset) currentOffset += char.length } else { InsertUtil.insertStreamingToDoc(project, char, editor, currentOffset) currentOffset += char.length } } } } val modifyEnd = currentOffset if (request.isReplacement) { val parsedContent = CodeFence.parse(suggestion.toString()).text InsertUtil.replaceText(project, editor, parsedContent) } indicator.fraction = 0.8 logger.info("Suggestion: $suggestion") val textRange = TextRange(modifyStart, modifyEnd) request.postExecute.invoke(suggestion.toString(), textRange) indicator.fraction = 1.0 }.invokeOnCompletion { shortcutAction.unregisterCustomShortcutSet(editor.component) } } override fun onThrowable(error: Throwable) { super.onThrowable(error) } override fun onCancel() { this.isCanceled = true this.cancelCallback?.invoke("This job is canceled") super.onCancel() } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/config/interaction/task/CodeCompletionTask.kt ================================================ package com.phodal.shirecore.config.interaction.task import com.intellij.openapi.application.invokeLater import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.progress.ProgressIndicator import com.phodal.shirecore.ShireCoreBundle import com.phodal.shirecore.ShireCoroutineScope import com.phodal.shirecore.config.interaction.dto.CodeCompletionRequest import com.phodal.shirecore.llm.LlmProvider import com.phodal.shirecore.utils.markdown.CodeFence import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.cancellable import kotlinx.coroutines.launch class CodeCompletionTask(private val request: CodeCompletionRequest) : ShireInteractionTask(request.project, ShireCoreBundle.message("intentions.chat.code.complete.name"), request.postExecute) { private val logger = logger() private var isCanceled: Boolean = false override fun run(indicator: ProgressIndicator) { val prompt = completionPrompt() val flow: Flow = LlmProvider.provider(request.project)!!.stream(prompt, "", false) logger.info("Prompt: $prompt") val editor = request.editor ShireCoroutineScope.scope(request.project).launch { var currentOffset = request.startOffset val project = request.project val suggestion = StringBuilder() flow.cancellable().collect { char -> if (isCanceled) { cancel() return@collect } val parsedContent = CodeFence.parse(char).text; suggestion.append(parsedContent) invokeLater { if (!isCanceled && !request.isReplacement) { InsertUtil.insertStreamingToDoc(project, parsedContent, editor, currentOffset) currentOffset += char.length } } } if (request.isReplacement) { // remove all selection code val selectionModel = editor.selectionModel val start = selectionModel.selectionStart val end = selectionModel.selectionEnd editor.document.deleteString(start, end) InsertUtil.insertStringAndSaveChange( project, suggestion.toString(), editor.document, request.startOffset, false ) } logger.info("Suggestion: $suggestion") request.postExecute.invoke(suggestion.toString(), null) indicator.fraction = 1.0 } } private fun completionPrompt(): String { val documentLength = request.editor.document.textLength val prefix = if (request.startOffset > documentLength) { request.prefixText } else { val text = request.editor.document.text text.substring(0, request.startOffset) } val prompt = "complete code for given code: \n$prefix" return prompt } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/config/interaction/task/FileGenerateTask.kt ================================================ package com.phodal.shirecore.config.interaction.task import com.intellij.openapi.application.runInEdt import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.fileTypes.PlainTextLanguage import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.Task import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VirtualFile import com.phodal.shirecore.ShireCoreBundle import com.phodal.shirecore.config.interaction.PostFunction import com.phodal.shirecore.llm.LlmProvider import com.phodal.shirecore.utils.markdown.CodeFence import kotlinx.coroutines.flow.cancellable import kotlinx.coroutines.runBlocking import java.nio.file.Path import kotlin.io.path.Path open class FileGenerateTask( @JvmField val project: Project, val prompt: String, val fileName: String?, private val codeOnly: Boolean = false, private val taskName: String = ShireCoreBundle.message("intentions.request.background.process.title"), postExecute: PostFunction, ) : ShireInteractionTask(project, taskName, postExecute) { private val projectRoot = project.guessProjectDir()!! override fun run(indicator: ProgressIndicator) { val stream = LlmProvider.provider(project)?.stream(prompt, "", false) if (stream == null) { logger().error("Failed to create stream") postExecute?.invoke("", null) return } var result = "" runBlocking { stream.cancellable().collect { result += it } } val inferFileName = if (fileName == null) { val language = CodeFence.parse(result).ideaLanguage val timestamp = System.currentTimeMillis() "output-" + timestamp + if (language == PlainTextLanguage.INSTANCE) ".txt" else ".$language" } else { fileName } val file = project.guessProjectDir()?.toNioPath()?.resolve(inferFileName)?.toFile() if (file == null) { logger().error("Failed to create file") postExecute?.invoke(result, null) return } if (!file.exists()) { file.createNewFile() } if (codeOnly) { val code = CodeFence.parse(result).text file.writeText(code) refreshAndOpenInEditor(file.toPath(), projectRoot) } else { file.writeText(result) refreshAndOpenInEditor(Path(projectRoot.path), projectRoot) } postExecute?.invoke(result, null) } private fun refreshAndOpenInEditor(file: Path, parentDir: VirtualFile) = runBlocking { ProgressManager.getInstance().run(RefreshProjectModal(file, parentDir)) } inner class RefreshProjectModal(private val file: Path, private val parentDir: VirtualFile) : Modal(project, "Refreshing Project Model", true) { override fun run(indicator: ProgressIndicator) { repeat(5) { val virtualFile = LocalFileSystem.getInstance().findFileByNioFile(file) if (virtualFile == null) { VfsUtil.markDirtyAndRefresh(true, true, true, parentDir) } else { try { runInEdt { FileEditorManager.getInstance(project).openFile(virtualFile, true) } return } catch (e: Exception) { // } } } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/config/interaction/task/InsertUtil.kt ================================================ package com.phodal.shirecore.config.interaction.task import com.intellij.openapi.application.runReadAction import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.ScrollType import com.intellij.openapi.project.Project import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiDocumentManager import com.intellij.psi.codeStyle.CodeStyleManager import com.phodal.shirecore.ShireCoreBundle object InsertUtil { fun insertStringAndSaveChange( project: Project, content: String, document: Document, startOffset: Int, withReformat: Boolean, ) { if (startOffset < 0 || startOffset > document.textLength) return document.insertString(startOffset, content) PsiDocumentManager.getInstance(project).commitDocument(document) if (!withReformat) return val psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document) psiFile?.let { file -> val reformatRange = TextRange(startOffset, startOffset + content.length) CodeStyleManager.getInstance(project).reformatText(file, listOf(reformatRange)) } } fun insertStreamingToDoc(project: Project, char: String, editor: Editor, currentOffset: Int) { WriteCommandAction.runWriteCommandAction( project, ShireCoreBundle.message("intentions.chat.code.complete.name"), "intentions.write.action", { insertStringAndSaveChange(project, char, editor.document, currentOffset, false) }) editor.caretModel.moveToOffset(currentOffset + char.length) editor.scrollingModel.scrollToCaret(ScrollType.MAKE_VISIBLE) } fun replaceText(project: Project, editor: Editor, output: String) { val primaryCaret = editor.caretModel.primaryCaret; val start = runReadAction { primaryCaret.selectionStart } val end = runReadAction { primaryCaret.selectionEnd } val document = runReadAction { editor.document } WriteCommandAction.runWriteCommandAction(project) { document.replaceString(start, end, output) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/config/interaction/task/ShireInteractionTask.kt ================================================ package com.phodal.shirecore.config.interaction.task import com.intellij.execution.ui.ConsoleView import com.intellij.openapi.progress.Task.Backgroundable import com.intellij.openapi.project.Project import com.phodal.shirecore.config.interaction.PostFunction import com.phodal.shirecore.runner.console.addCancelCallback import java.util.concurrent.CompletableFuture /** * @author lk */ abstract class ShireInteractionTask(project: Project, taskName: String, val postExecute: PostFunction?): Backgroundable(project, taskName) { /** * An unexpected exception occurred, causing the shire process cannot be canceled, * postExecute was not executed,it may have used the [CompletableFuture]. */ override fun onThrowable(error: Throwable) { super.onThrowable(error) postExecute?.invoke(null, null) } } fun ShireInteractionTask.cancelWithConsole(consoleView: ConsoleView?): ShireInteractionTask = apply { consoleView?.addCancelCallback { onCancel() } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/diff/DiffStreamHandler.kt ================================================ /** * Copyright 2023 Continue Dev, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.phodal.shirecore.diff import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.command.undo.UndoManager import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.markup.HighlighterLayer import com.intellij.openapi.editor.markup.RangeHighlighter import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.fileEditor.TextEditor import com.intellij.openapi.fileTypes.PlainTextLanguage import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.phodal.shirecore.ShireCoroutineScope import com.phodal.shirecore.diff.model.DiffLine import com.phodal.shirecore.diff.model.streamDiff import com.phodal.shirecore.llm.LlmProvider import com.phodal.shirecore.utils.markdown.CodeFence import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.cancellable import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch import kotlin.math.min /** * * JButton("Apply Patch").apply { * * addActionListener { * val lookupFile = * project.lookupFile("src/main/java/com/phodal/shire/demo/service/BlogService.java")!! * val editor = FileEditorManager.getInstance(project).selectedTextEditor * val code = lookupFile.inputStream.bufferedReader().use { it.readText() } * * val diffStreamHandler = DiffStreamHandler( * project, * editor = editor!!, 0, code.lines().size, * onClose = { * }, * onFinish = { * ShirelangNotifications.info(project, "Patch Applied") * } * ) * * runInEdt { * diffStreamHandler * .streamDiffLinesToEditor( * code, * "使用为如下的代码添加删除功能,请使用 Markdown code 返回完整代码块: $code" * ) * } * } * } */ class DiffStreamHandler( private val project: Project, private val editor: Editor, private val startLine: Int, private val endLine: Int, private val onClose: () -> Unit, private val onFinish: (response: String) -> Unit, ) { private data class CurLineState( var index: Int, var highlighter: RangeHighlighter? = null, var diffBlock: VerticalDiffBlock? = null, ) private var curLine = CurLineState(startLine) private var isRunning: Boolean = false private var hasAcceptedOrRejectedBlock: Boolean = false private val unfinishedHighlighters: MutableList = mutableListOf() private val diffBlocks: MutableList = mutableListOf() private val curLineKey = createTextAttributesKey("CONTINUE_DIFF_CURRENT_LINE", 0x40888888, editor) private val unfinishedKey = createTextAttributesKey("CONTINUE_DIFF_UNFINISHED_LINE", 0x20888888, editor) init { initUnfinishedRangeHighlights() } fun acceptAll() { editor.markupModel.removeAllHighlighters() resetState() } fun rejectAll() { // The ideal action here is to undo all changes we made to return the user's edit buffer to the state prior // to our changes. However, if the user has accepted or rejected one or more diff blocks, there isn't a simple // way to undo our changes without also undoing the diff that the user accepted or rejected. if (hasAcceptedOrRejectedBlock) { diffBlocks.forEach { it.handleReject() } } else { undoChanges() } resetState() } fun streamDiffLinesToEditor(originContent: String, prompt: String) { val lines = originContent.lines() isRunning = true val flow: Flow = LlmProvider.provider(project)!!.stream(prompt, "", false) var lastLineNo = 0 ShireCoroutineScope.scope(project).launch { val suggestion = StringBuilder() flow.cancellable().collect { char -> suggestion.append(char) val code = CodeFence.parse(suggestion.toString()) if (PlainTextLanguage.INSTANCE != code.ideaLanguage && code.ideaLanguage.displayName != "Markdown" && code.text.isNotEmpty()) { var value: List = code.text.lines() value = value.dropLast(1) if (value.isEmpty()) return@collect val newLines = if (lastLineNo < value.size) { value.subList(lastLineNo, value.size) } else { listOf() } if (newLines.isEmpty()) return@collect val flowValue: Flow = flowOf(*newLines.toTypedArray()) val oldLinesContent = if (lastLineNo + newLines.size <= lines.size) { lines.subList(lastLineNo, lastLineNo + newLines.size) } else { listOf() } lastLineNo = value.size streamDiff(oldLinesContent, flowValue).collect { ApplicationManager.getApplication().invokeLater { WriteCommandAction.runWriteCommandAction(project) { updateByDiffType(it) } } } } } handleFinishedResponse(suggestion.toString()) } } private fun updateByDiffType(diffLine: DiffLine) { when (diffLine) { is DiffLine.New -> handleNewLine(diffLine.line) is DiffLine.Old -> handleOldLine() is DiffLine.Same -> handleSameLine() } updateProgressHighlighters(diffLine) } private fun initUnfinishedRangeHighlights() { for (i in startLine..endLine) { val highlighter = editor.markupModel.addLineHighlighter( unfinishedKey, min( i, editor.document.lineCount - 1 ), HighlighterLayer.LAST ) unfinishedHighlighters.add(highlighter) } } private fun handleDiffBlockAcceptOrReject(diffBlock: VerticalDiffBlock, didAccept: Boolean) { hasAcceptedOrRejectedBlock = true diffBlocks.remove(diffBlock) if (!didAccept) { updatePositionsOnReject(diffBlock.startLine, diffBlock.addedLines.size, diffBlock.deletedLines.size) } if (diffBlocks.isEmpty()) { onClose() } } private fun createDiffBlock(): VerticalDiffBlock { val diffBlock = VerticalDiffBlock( editor, project, curLine.index, ::handleDiffBlockAcceptOrReject ) diffBlocks.add(diffBlock) return diffBlock } private fun handleSameLine() { if (curLine.diffBlock != null) { curLine.diffBlock!!.onLastDiffLine() } curLine.diffBlock = null curLine.index++ } private fun handleNewLine(text: String) { if (curLine.diffBlock == null) { curLine.diffBlock = createDiffBlock() } curLine.diffBlock!!.addNewLine(text, curLine.index) curLine.index++ } private fun handleOldLine() { if (curLine.diffBlock == null) { curLine.diffBlock = createDiffBlock() } curLine.diffBlock!!.deleteLineAt(curLine.index) } private fun updateProgressHighlighters(type: DiffLine) { // Update the highlighter to show the current line curLine.highlighter?.let { editor.markupModel.removeHighlighter(it) } curLine.highlighter = editor.markupModel.addLineHighlighter( curLineKey, min(curLine.index, editor.document.lineCount - 1), HighlighterLayer.LAST ) // Remove the unfinished lines highlighter if (type is DiffLine.Old && unfinishedHighlighters.isNotEmpty()) { editor.markupModel.removeHighlighter(unfinishedHighlighters.removeAt(0)) } } private fun updatePositionsOnReject(startLine: Int, numAdditions: Int, numDeletions: Int) { val offset = -numAdditions + numDeletions diffBlocks.forEach { block -> if (block.startLine > startLine) { block.updatePosition(block.startLine + offset) } } } private fun resetState() { // Clear the editor of highlighting/inlays editor.markupModel.removeAllHighlighters() diffBlocks.forEach { it.clearEditorUI() } // Clear state vars diffBlocks.clear() curLine = CurLineState(startLine) isRunning = false // Close the Edit input onClose() } private fun undoChanges() { WriteCommandAction.runWriteCommandAction(project) { val undoManager = UndoManager.getInstance(project) val virtualFile = getVirtualFile() ?: return@runWriteCommandAction val fileEditor = FileEditorManager.getInstance(project).getSelectedEditor(virtualFile) as TextEditor if (undoManager.isUndoAvailable(fileEditor)) { val numChanges = diffBlocks.sumOf { it.deletedLines.size + it.addedLines.size } repeat(numChanges) { undoManager.undo(fileEditor) } } } } private fun getVirtualFile(): VirtualFile? { return FileDocumentManager.getInstance().getFile(editor.document) ?: return null } private fun handleFinishedResponse(response: String) { ApplicationManager.getApplication().invokeLater { // Since we only call onLastDiffLine() when we reach a "same" line, we need to handle the case where // the last line in the diff stream is in the middle of a diff block. curLine.diffBlock?.onLastDiffLine() onFinish(response) cleanupProgressHighlighters() } } private fun cleanupProgressHighlighters() { curLine.highlighter?.let { editor.markupModel.removeHighlighter(it) } unfinishedHighlighters.forEach { editor.markupModel.removeHighlighter(it) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/diff/DiffStreamService.kt ================================================ /** * Copyright 2023 Continue Dev, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.phodal.shirecore.diff import com.intellij.openapi.components.Service import com.intellij.openapi.editor.Editor @Service(Service.Level.PROJECT) class DiffStreamService { private val handlers = mutableMapOf() fun register(handler: DiffStreamHandler, editor: Editor) { if (handlers.containsKey(editor)) { handlers[editor]?.rejectAll() } handlers[editor] = handler } fun reject(editor: Editor) { handlers[editor]?.rejectAll() handlers.remove(editor) } fun accept(editor: Editor) { handlers[editor]?.acceptAll() handlers.remove(editor) } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/diff/EditorComponentInlaysManager.kt ================================================ /** * Copyright 2023 Continue Dev, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.phodal.shirecore.diff import com.intellij.openapi.Disposable import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.editor.ex.util.EditorUtil import com.intellij.openapi.editor.impl.EditorEmbeddedComponentManager import com.intellij.openapi.editor.impl.EditorImpl import com.intellij.openapi.editor.impl.view.FontLayoutService import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Key import com.intellij.ui.components.JBScrollPane import com.intellij.util.concurrency.annotations.RequiresEdt import com.intellij.util.ui.JBUI import java.awt.Dimension import java.awt.Font import java.awt.event.ComponentAdapter import java.awt.event.ComponentEvent import javax.swing.JComponent import javax.swing.ScrollPaneConstants import kotlin.math.ceil import kotlin.math.max import kotlin.math.min /** * Copied from com.intellij.util.ui.codereview.diff.EditorComponentInlaysManager * via https://github.com/cursive-ide/component-inlay-example/blob/master/src/main/kotlin/inlays/EditorComponentInlaysManager.kt */ class EditorComponentInlaysManager(val editor: EditorImpl, private val onlyOneInlay: Boolean) : Disposable { private val managedInlays = mutableMapOf() private val editorWidthWatcher = EditorTextWidthWatcher() init { editor.scrollPane.viewport.addComponentListener(editorWidthWatcher) Disposer.register(this, Disposable { editor.scrollPane.viewport.removeComponentListener(editorWidthWatcher) }) EditorUtil.disposeWithEditor(editor, this) } @RequiresEdt fun insert(lineIndex: Int, component: JComponent, showAbove: Boolean = false): Disposable? { if (Disposer.isDisposed(this)) return null if (onlyOneInlay) { // Dispose all other inlays managedInlays.values.forEach(Disposer::dispose) } val wrappedComponent = ComponentWrapper(component) val offset = editor.document.getLineStartOffset(lineIndex) return EditorEmbeddedComponentManager.getInstance() .addComponent( editor, wrappedComponent, EditorEmbeddedComponentManager.Properties( EditorEmbeddedComponentManager.ResizePolicy.none(), null, !editor.inlayModel.getBlockElementsInRange(offset,offset).isEmpty(), showAbove, 0, offset ) )?.also { managedInlays[wrappedComponent] = it Disposer.register(it, Disposable { managedInlays.remove(wrappedComponent) }) } } private inner class ComponentWrapper(private val component: JComponent) : JBScrollPane(component) { init { isOpaque = false viewport.isOpaque = false border = JBUI.Borders.empty() viewportBorder = JBUI.Borders.empty() horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER verticalScrollBar.preferredSize = Dimension(0, 0) setViewportView(component) component.addComponentListener(object : ComponentAdapter() { override fun componentResized(e: ComponentEvent) = dispatchEvent(ComponentEvent(component, ComponentEvent.COMPONENT_RESIZED)) }) } override fun getPreferredSize(): Dimension { return Dimension(editor.contentComponent.width, component.preferredSize.height) } } override fun dispose() { managedInlays.values.forEach(Disposer::dispose) } private inner class EditorTextWidthWatcher : ComponentAdapter() { var editorTextWidth: Int = 0 private val maximumEditorTextWidth: Int private val verticalScrollbarFlipped: Boolean init { val metrics = editor.getFontMetrics(Font.PLAIN) val spaceWidth = FontLayoutService.getInstance().charWidth2D(metrics, ' '.toInt()) // -4 to create some space maximumEditorTextWidth = ceil(spaceWidth * (editor.settings.getRightMargin(editor.project)) - 4).toInt() val scrollbarFlip = editor.scrollPane.getClientProperty(JBScrollPane.Flip::class.java) verticalScrollbarFlipped = scrollbarFlip == JBScrollPane.Flip.HORIZONTAL || scrollbarFlip == JBScrollPane.Flip.BOTH } override fun componentResized(e: ComponentEvent) = updateWidthForAllInlays() override fun componentHidden(e: ComponentEvent) = updateWidthForAllInlays() override fun componentShown(e: ComponentEvent) = updateWidthForAllInlays() private fun updateWidthForAllInlays() { val newWidth = calcWidth() if (editorTextWidth == newWidth) return editorTextWidth = newWidth managedInlays.keys.forEach { it.dispatchEvent(ComponentEvent(it, ComponentEvent.COMPONENT_RESIZED)) it.invalidate() } } private fun calcWidth(): Int { val visibleEditorTextWidth = editor.scrollPane.viewport.width - getVerticalScrollbarWidth() - getGutterTextGap() return min(max(visibleEditorTextWidth, 0), maximumEditorTextWidth) } private fun getVerticalScrollbarWidth(): Int { val width = editor.scrollPane.verticalScrollBar.width return if (!verticalScrollbarFlipped) width * 2 else width } private fun getGutterTextGap(): Int { return if (verticalScrollbarFlipped) { val gutter = (editor as EditorEx).gutterComponentEx gutter.width - gutter.whitespaceSeparatorOffset } else 0 } } companion object { val INLAYS_KEY: Key = Key.create("EditorComponentInlaysManager") fun from(editor: Editor, onlyOneInlay: Boolean): EditorComponentInlaysManager { return synchronized(editor) { val manager = editor.getUserData(INLAYS_KEY) if (manager == null) { val newManager = EditorComponentInlaysManager(editor as EditorImpl, false) editor.putUserData(INLAYS_KEY, newManager) newManager } else manager } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/diff/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2023 Continue Dev, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/diff/VerticalDiffBlock.kt ================================================ /** * Copyright 2023 Continue Dev, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.phodal.shirecore.diff import com.intellij.openapi.Disposable import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.LogicalPosition import com.intellij.openapi.editor.colors.EditorFontType import com.intellij.openapi.editor.colors.TextAttributesKey import com.intellij.openapi.editor.markup.HighlighterLayer import com.intellij.openapi.editor.markup.TextAttributes import com.intellij.openapi.project.Project import com.intellij.openapi.util.TextRange import com.intellij.ui.Gray import com.intellij.ui.JBColor import com.phodal.shirecore.ShireCoreBundle import java.awt.* import javax.swing.BorderFactory import javax.swing.JButton import javax.swing.JTextArea import kotlin.math.min class VerticalDiffBlock( private val editor: Editor, private val project: Project, var startLine: Int, private val onAcceptReject: (VerticalDiffBlock, Boolean) -> Unit, ) { val deletedLines: MutableList = mutableListOf(); val addedLines: MutableList = mutableListOf(); private val acceptButton: JButton private val rejectButton: JButton private var deletionInlay: Disposable? = null private var textArea: JTextArea? = null // Used for calculation of the text area height when rendering buttons private var hasRenderedDiffBlock: Boolean = false private val editorComponentInlaysManager = EditorComponentInlaysManager.from(editor, false) private val greenKey = createTextAttributesKey("CONTINUE_DIFF_NEW_LINE", 0x3000FF00, editor) init { val (acceptBtn, rejectBtn) = createButtons() acceptButton = acceptBtn rejectButton = rejectBtn } fun clearEditorUI() { deletionInlay?.dispose() removeGreenHighlighters() removeButtons() } fun updatePosition(newLineNumber: Int) { startLine = newLineNumber val (x, y) = getButtonsXYPositions() rejectButton.location = Point(x, y) acceptButton.location = Point(x + rejectButton.preferredSize.width + 5, y) refreshEditor() } fun deleteLineAt(line: Int) { val startOffset = editor.document.getLineStartOffset(line) val endOffset = min(editor.document.getLineEndOffset(line) + 1, editor.document.textLength) val deletedText = editor.document.getText(TextRange(startOffset, endOffset)) deletedLines.add(deletedText.trimEnd()) // Unable to ensure that text length has not changed, so we need to get it again editor.document.deleteString(startOffset, min(endOffset, editor.document.textLength)) } fun addNewLine(text: String, line: Int) { if (line == editor.document.lineCount) { editor.document.insertString(editor.document.textLength, "\n") } val offset = editor.document.getLineStartOffset(line) editor.document.insertString(offset, text + "\n") editor.markupModel.addLineHighlighter(greenKey, line, HighlighterLayer.LAST) addedLines.add(text) } fun onLastDiffLine() { // Handles the case where we are invoking one last time on last line of diff stream, but the block has // already been rendered if (hasRenderedDiffBlock) { return } if (deletedLines.size > 0) { renderDeletedLinesInlay() } renderButtons() hasRenderedDiffBlock = true } fun handleReject() { revertDiff() clearEditorUI() } private fun refreshEditor() { editor.contentComponent.revalidate() editor.contentComponent.repaint() } private fun renderDeletedLinesInlay() { val textArea = createDeletionTextArea(deletedLines.joinToString("\n")) this.textArea = textArea val disposable = editorComponentInlaysManager.insert(startLine, textArea, true) deletionInlay = disposable } private fun renderButtons() { val (x, y) = getButtonsXYPositions() rejectButton.setBounds( x, y, rejectButton.preferredSize.width, rejectButton.preferredSize.height ) acceptButton.setBounds( x + rejectButton.width + 2, y, acceptButton.preferredSize.width, acceptButton.preferredSize.height ) editor.contentComponent.add(acceptButton) editor.contentComponent.add(rejectButton) editor.contentComponent.setComponentZOrder(acceptButton, 0) editor.contentComponent.setComponentZOrder(rejectButton, 0) refreshEditor() } private fun createButtons(): Pair { val rejectBtn = createButton(ShireCoreBundle.message("sketch.patch.action.reject"), JBColor(0x99FF0000.toInt(), 0x99FF0000.toInt()) ).apply { addActionListener { handleReject(); onAcceptReject(this@VerticalDiffBlock, false) } } val acceptBtn = createButton(ShireCoreBundle.message("sketch.patch.action.accept"), JBColor(0x8AA653, 0x8AA653)).apply { addActionListener { handleAccept(); onAcceptReject(this@VerticalDiffBlock, true) } } return Pair(acceptBtn, rejectBtn) } private fun removeButtons() { editor.contentComponent.remove(acceptButton) editor.contentComponent.remove(rejectButton) refreshEditor() } private fun handleAccept() { clearEditorUI() } private fun revertDiff() { WriteCommandAction.runWriteCommandAction(project) { // Delete the added lines val lineCount = editor.document.lineCount val textLength = editor.document.textLength val startOffset = if (startLine >= lineCount) { textLength } else { editor.document.getLineStartOffset(startLine) } val endOffset = editor.document.getLineEndOffset(Math.min(lineCount - 1, startLine + addedLines.size - 1)) + 1 editor.document.deleteString(startOffset, Math.min(endOffset, textLength)) // Add the deleted lines back if (deletedLines.isNotEmpty()) { editor.document.insertString(startOffset, deletedLines.joinToString("\n") + "\n") } } } private fun removeGreenHighlighters() { val highlightersToRemove = editor.markupModel.allHighlighters.filter { highlighter -> val highlighterLine = editor.document.getLineNumber(highlighter.startOffset) highlighterLine in startLine until (startLine + addedLines.size) } highlightersToRemove.forEach { editor.markupModel.removeHighlighter(it) } } private fun createDeletionTextArea(text: String) = JTextArea(text).apply { isEditable = false background = JBColor(0x30FF0000, 0x30FF0000) foreground = JBColor.GRAY border = BorderFactory.createEmptyBorder() lineWrap = false wrapStyleWord = false font = editor.colorsScheme.getFont(EditorFontType.PLAIN) } private fun getButtonsXYPositions(): Pair { val visibleArea = editor.scrollingModel.visibleArea val textAreaHeight = this.textArea?.height ?: 0 val lineStartPosition = editor.logicalPositionToXY(LogicalPosition(startLine, 0)) val xPosition = visibleArea.x + visibleArea.width - acceptButton.preferredSize.width - rejectButton.preferredSize.width - 20 val yPosition = lineStartPosition.y - textAreaHeight return Pair(xPosition, yPosition) } private fun createButton(text: String, backgroundColor: JBColor): JButton { return object : JButton(text) { override fun paintComponent(g: Graphics) { val g2 = g.create() as Graphics2D g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) g2.color = backgroundColor g2.fillRoundRect(0, 0, width - 1, height - 1, 4, 4) super.paintComponent(g2) g2.dispose() } }.apply { foreground = Gray._240 font = Font("Arial", Font.BOLD, 9) isContentAreaFilled = false isOpaque = false border = BorderFactory.createEmptyBorder(4, 2, 4, 2) preferredSize = Dimension(preferredSize.width - 30, 14) cursor = Cursor(Cursor.HAND_CURSOR) } } } fun createTextAttributesKey(name: String, color: Int, editor: Editor): TextAttributesKey { val attributes = TextAttributes().apply { backgroundColor = JBColor(color, color) } return TextAttributesKey.createTextAttributesKey(name).also { editor.colorsScheme.setAttributes(it, attributes) } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/diff/model/StreamDiff.kt ================================================ package com.phodal.shirecore.diff.model import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.toList sealed class DiffLine { data class Same(val line: String) : DiffLine() data class New(val line: String) : DiffLine() data class Old(val line: String) : DiffLine() } fun streamDiff(oldLines: List, newLines: Flow): Flow = flow { val newLinesList = newLines.toList() val diffs = diff(oldLines, newLinesList) for (diff in diffs) { emit(diff) } } private fun diff(oldLines: List, newLines: List): List { // 计算 LCS 长度表 val lcs = computeLcsTable(oldLines, newLines) // 回溯 LCS 表,构建差异列表 val diffs = mutableListOf() backtrackDiff(lcs, oldLines, newLines, oldLines.size, newLines.size, diffs) return diffs } private fun computeLcsTable(oldLines: List, newLines: List): Array { val m = oldLines.size val n = newLines.size val table = Array(m + 1) { IntArray(n + 1) } for (i in 0 until m) { for (j in 0 until n) { if (oldLines[i] == newLines[j]) { table[i + 1][j + 1] = table[i][j] + 1 } else { table[i + 1][j + 1] = maxOf(table[i + 1][j], table[i][j + 1]) } } } return table } private fun backtrackDiff( lcs: Array, oldLines: List, newLines: List, i: Int, j: Int, diffs: MutableList, ) { if (i > 0 && j > 0 && oldLines[i - 1] == newLines[j - 1]) { backtrackDiff(lcs, oldLines, newLines, i - 1, j - 1, diffs) diffs.add(DiffLine.Same(oldLines[i - 1])) } else if (j > 0 && (i == 0 || lcs[i][j - 1] >= lcs[i - 1][j])) { backtrackDiff(lcs, oldLines, newLines, i, j - 1, diffs) diffs.add(DiffLine.New(newLines[j - 1])) } else if (i > 0 && (j == 0 || lcs[i][j - 1] < lcs[i - 1][j])) { backtrackDiff(lcs, oldLines, newLines, i - 1, j, diffs) diffs.add(DiffLine.Old(oldLines[i - 1])) } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/function/guard/base/LocalScanner.kt ================================================ package com.phodal.shirecore.function.guard.base /** * GuardScanner is an interface for scanning user input for security vulnerabilities. */ interface GuardScanner { fun scan(prompt: String): ScanResult } interface Masker { fun mask(prompt: String): String } interface LocalScanner : GuardScanner abstract class EntityRecognizer { abstract fun load() abstract fun analyze(text: String, entities: List): List } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/function/guard/base/ScanResult.kt ================================================ package com.phodal.shirecore.function.guard.base /** * ScanResult is a data class that encapsulates the result of a scan. */ data class ScanResult( var isPassed: Boolean = false, val modifiedInput: String? = null, val message: String? = null ) ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/function/guard/model/SecretPattern.kt ================================================ package com.phodal.shirecore.function.guard.model import com.intellij.openapi.diagnostic.logger import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable @Serializable class SecretPattern( val name: String, val regex: String, val confidence: String, ) { @Contextual private val regexPattern: Regex? = try { Regex(regex) } catch (e: Exception) { logger().error("Invalid regex pattern: $regex, name: $name") null } fun matches(text: String): Boolean { return regexPattern?.containsMatchIn(text) ?: false } fun mask(text: String): String { return regexPattern?.replace(text, "****") ?: text } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/function/guard/model/SecretPatterns.kt ================================================ package com.phodal.shirecore.function.guard.model import kotlinx.serialization.Serializable @Serializable data class SecretPatterns( val patterns: List, val keywords: List = emptyList(), val models: List = emptyList() ) @Serializable data class SecretPatternItem( val pattern: SecretPattern, ) @Serializable data class Model( val name: String, val location: String ) ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/function/guard/scanner/SecretPatternsScanner.kt ================================================ package com.phodal.shirecore.function.guard.scanner import com.charleskorn.kaml.Yaml import com.intellij.openapi.components.Service import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.psi.search.FilenameIndex import com.phodal.shirecore.schema.SECRET_PATTERN_EXTENSION import com.phodal.shirecore.function.guard.base.LocalScanner import com.phodal.shirecore.function.guard.base.ScanResult import com.phodal.shirecore.function.guard.model.SecretPattern import com.phodal.shirecore.function.guard.model.SecretPatternItem import com.phodal.shirecore.function.guard.model.SecretPatterns import java.net.URL @Service(Service.Level.PROJECT) class SecretPatternsScanner(val project: Project) : LocalScanner { private val defaultPiiSecrets = "/secrets/default.shireSecretPattern.yml" private var patterns: List init { patterns = initPatterns() + loadFromProject(project) } fun getPatterns(): List { return patterns } private fun initPatterns(): List { val file: URL = javaClass.getResource(defaultPiiSecrets)!! val content = file.readText() val patterns = Yaml.default.decodeFromString(SecretPatterns.serializer(), content) return patterns.patterns.map(SecretPatternItem::pattern) } private fun loadFromProject(project: Project): List { return FilenameIndex.getAllFilesByExt(project, SECRET_PATTERN_EXTENSION) .mapNotNull { val content = it.inputStream.reader().readText() try { Yaml.default.decodeFromString(SecretPatterns.serializer(), content) } catch (e: Exception) { logger().error("Failed to load custom agent configuration", e) null } }.flatMap { it.patterns.map(SecretPatternItem::pattern) } } fun addPattern(pattern: SecretPattern) { patterns = patterns + pattern } fun removePattern(pattern: SecretPattern) { patterns = patterns - pattern } fun maskInput(text: String): String { var newText = text patterns.forEach { newText = it.mask(newText) } return newText } override fun scan(prompt: String): ScanResult { val result = ScanResult() patterns.forEach { if (it.matches(prompt)) { result.isPassed = false } } return result } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/function/shireql/JvmShireQLFuncType.kt ================================================ package com.phodal.shirecore.function.shireql enum class JvmShireQLFuncType(val methodName: String, val description: String) { GET_NAME("getName", "Get class name"), NAME("name", "Get class name"), EXTENDS("extends", "Get class extends"), IMPLEMENTS("implements", "Get class implements"), METHOD_CODE_BY_NAME("methodCodeByName", "Get method code by name"), FIELD_CODE_BY_NAME("fieldCodeByName", "Get field code by name"), SUBCLASSES_OF("subclassesOf", "Get subclasses of class"), ANNOTATED_OF("annotatedOf", "Get annotated classes"), SUPERCLASS_OF("superclassOf", "Get superclass of class"), IMPLEMENTS_OF("implementsOf", "Get implemented interfaces of class"), } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/llm/ChatMessage.kt ================================================ package com.phodal.shirecore.llm import kotlinx.serialization.Serializable enum class ChatRole { system, assistant, user; } @Serializable data class ChatMessage(val role: ChatRole, val content: String) @Serializable data class CustomRequest(val messages: List) ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/llm/LlmConfig.kt ================================================ package com.phodal.shirecore.llm import com.intellij.json.psi.JsonObject import com.phodal.shire.json.findNumber import com.phodal.shire.json.findString class LlmConfig( val title: String, val provider: String = "openai", val apiBase: String = "https://api.openai.com/v1/chat/completions", val apiKey: String, val model: String, val temperature: Double = 0.0, val maxTokens: Int? = 1024, val requestFormat: Map = mapOf(), val responseFormat: String = "\$.choices[0].delta.content", val messageKeys: Map = mapOf(), ) { fun checkAvailable(): Boolean = apiKey.isNotBlank() && model.isNotBlank() companion object { fun fromJson(modelConfig: JsonObject): LlmConfig? { val title = modelConfig.findString("title") ?: return null val provider = modelConfig.findString("provider") ?: "openai" val apiBase = modelConfig.findString("apiBase") ?: "https://api.openai.com/v1/chat/completions" val apiKey = modelConfig.findString("apiKey") ?: return null val model = modelConfig.findString("model") ?: return null val temperature = try { modelConfig.findNumber("temperature")?.toDouble() } catch (e: Exception) { null } val maxTokens = try { modelConfig.findNumber("maxTokens")?.toInt() } catch (e: Exception) { null } return LlmConfig( title = title, provider = provider, apiBase = apiBase, apiKey = apiKey, model = model, temperature = temperature ?: 0.0, maxTokens = maxTokens, requestFormat = mapOf(), responseFormat = "\$.choices[0].delta.content", messageKeys = mapOf(), ) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/llm/LlmProvider.kt ================================================ package com.phodal.shirecore.llm import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.project.Project import com.intellij.psi.search.ProjectScope import com.phodal.shirecore.ShireCoreBundle import com.phodal.shirecore.ShirelangNotifications import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shire.json.llm.LlmEnv import kotlinx.coroutines.flow.Flow /** * Interface for providing LLM (Language Model) services. * This interface defines methods for interacting with LLM services, such as checking applicability, sending prompts, * streaming chat completion responses, and clearing messages. * * Implementations of this interface should provide functionality for interacting with specific LLM services. * */ interface LlmProvider { var project: Project? /** * Default timeout for the provider. * This is used to set the default timeout for the provider. * For example, If you want to wait in 10min, you can use: * ```Kotlin * Duration.ofSeconds(defaultTimeout) * ``` */ val defaultTimeout: Long get() = 600 /** * Checks if the given project is applicable for some operation. * * @param project the project to check for applicability * @param llmConfig This llmConfig is used to verify if llmProvider is available. * For example, it may be an unsaved configuration that can be used to test LLM connection. * * @return true if the project is applicable, false otherwise */ fun isApplicable(project: Project, llmConfig: LlmConfig? = null): Boolean /** * Streams chat completion responses from the service. * * @param promptText The text prompt to send to the service. * @param systemPrompt The system prompt to send to the service. * @param keepHistory Flag indicating whether to keep the chat history. * @param llmConfig A default llmConfig, if not provided it will be read from the project settings. * @return A Flow of String values representing the chat completion responses. */ fun stream(promptText: String, systemPrompt: String, keepHistory: Boolean = true, llmConfig: LlmConfig? = null): Flow /** * config LLM Provider from [PostProcessorContext] */ fun configRunLlm(): LlmConfig? { if (project == null) return null val modelName = PostProcessorContext.getData()?.llmModelName ?: return null val scope = ProjectScope.getContentScope(project!!) val modelConfig = LlmEnv.configFromFile(modelName, scope, project!!) if (modelConfig != null) { return LlmConfig.fromJson(modelConfig) } return null } /** * Clears the message displayed in the UI. */ fun clearMessage() companion object { private val EP_NAME: ExtensionPointName = ExtensionPointName.create("com.phodal.shireLlmProvider") /** * Returns an instance of LlmProvider based on the given Project. * * @param project the Project for which to find a suitable LlmProvider * @param llmConfig provide llmConfig as a condition for finding a suitable LlmProvider. * * @return an instance of LlmProvider if a suitable provider is found, null otherwise */ fun provider(project: Project, llmConfig: LlmConfig? = null): LlmProvider? { val providers = EP_NAME.extensions.filter { it.isApplicable(project, llmConfig) } return if (providers.isEmpty()) { ShirelangNotifications.error(project, ShireCoreBundle.message("shire.llm.notfound")) null } else { providers.first() } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/builtin/AppendProcessor.kt ================================================ package com.phodal.shirecore.middleware.builtin import com.intellij.execution.ui.ConsoleView import com.intellij.openapi.project.Project import com.phodal.shirecore.middleware.post.PostProcessorType import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirecore.middleware.post.PostProcessor class AppendProcessor : PostProcessor { override val processorName: String = PostProcessorType.Append.handleName override val description: String = "`append` will append the text to the generated text" override fun isApplicable(context: PostProcessorContext): Boolean = true override fun execute( project: Project, context: PostProcessorContext, console: ConsoleView?, args: List, ): Any { context.genText += args.map { if (it.toString().startsWith("$")) { context.compiledVariables[it.toString().substring(1)] ?: "" } else { it } }.joinToString(" ") return context.genText ?: "" } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/builtin/DiffProcessor.kt ================================================ package com.phodal.shirecore.middleware.builtin import com.intellij.diff.DiffContentFactoryEx import com.intellij.diff.DiffDialogHints import com.intellij.diff.DiffManager import com.intellij.diff.chains.SimpleDiffRequestChain import com.intellij.diff.chains.SimpleDiffRequestProducer import com.intellij.diff.requests.SimpleDiffRequest import com.intellij.execution.ui.ConsoleView import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.openapi.application.runInEdt import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.phodal.shirecore.findFile import com.phodal.shirecore.middleware.post.PostProcessor import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirecore.middleware.post.PostProcessorType class DiffProcessor : PostProcessor { override val processorName: String = PostProcessorType.Diff.handleName override val description: String = "`diff` will show the diff of two texts, default is current code and llm response" private val diffFactory = DiffContentFactoryEx.getInstanceEx() override fun isApplicable(context: PostProcessorContext): Boolean { return true } override fun execute(project: Project, context: PostProcessorContext, console: ConsoleView?, args: List): Any { if (args.size < 2) { console?.print("DiffProcessor: not enough arguments", ConsoleViewContentType.ERROR_OUTPUT) return "" } val firstArg = args[0].toString() val virtualFile = runReadAction { project.findFile(firstArg) } ?: let { console?.print("DiffProcessor: file not found", ConsoleViewContentType.ERROR_OUTPUT) return "" } val currentDocContent = diffFactory.create(project, virtualFile) val newDocContent = diffFactory.create(args[1].toString()) val diffRequest = SimpleDiffRequest("Shire Diff Viewer", currentDocContent, newDocContent, "Current code", "AI generated") val producer = SimpleDiffRequestProducer.create(virtualFile.path) { diffRequest } val chain = SimpleDiffRequestChain.fromProducer(producer) runInEdt { DiffManager.getInstance().showDiff(project, chain, DiffDialogHints.FRAME) } return "" } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/builtin/FormatCodeProcessor.kt ================================================ package com.phodal.shirecore.middleware.builtin import com.intellij.execution.ui.ConsoleView import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.project.Project import com.intellij.psi.PsiDocumentManager import com.intellij.psi.codeStyle.CodeStyleManager import com.phodal.shirecore.middleware.post.PostProcessorType import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirecore.middleware.post.PostProcessor import com.phodal.shirecore.workerThread import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch class FormatCodeProcessor : PostProcessor { override val processorName: String = PostProcessorType.FormatCode.handleName override val description: String = "`formatCode` will format the code of the current file" override fun isApplicable(context: PostProcessorContext): Boolean = true override fun execute(project: Project, context: PostProcessorContext, console: ConsoleView?, args: List): Any { val file = context.currentFile ?: return "" val document = PsiDocumentManager.getInstance(project).getDocument(file) ?: return "" CoroutineScope(workerThread).launch { WriteCommandAction.runWriteCommandAction(project) { val codeStyleManager = CodeStyleManager.getInstance(project) if (context.modifiedTextRange != null) { codeStyleManager.reformatText(file, listOf(context.modifiedTextRange)) } else if (context.genPsiElement != null) { codeStyleManager.reformat(context.genPsiElement!!) } else { codeStyleManager.reformatText(file, 0, document.textLength) } } } return context.genText ?: "" } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/builtin/InsertCodeProcessor.kt ================================================ package com.phodal.shirecore.middleware.builtin import com.intellij.execution.ui.ConsoleView import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.project.Project import com.phodal.shirecore.provider.codeedit.CodeModifier import com.phodal.shirecore.middleware.post.PostProcessorType import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirecore.middleware.post.PostProcessor class InsertCodeProcessor : PostProcessor { override val processorName: String = PostProcessorType.InsertCode.handleName override val description: String = "`insertCode` will insert the code to the current file" override fun isApplicable(context: PostProcessorContext): Boolean = true override fun execute(project: Project, context: PostProcessorContext, console: ConsoleView?, args: List): String { if (context.currentLanguage == null || context.currentFile == null) { console?.print("No found current language\n", ConsoleViewContentType.ERROR_OUTPUT) return "" } val codeModifier = CodeModifier.forLanguage(context.currentLanguage!!) if (codeModifier == null) { console?.print("No code modifier found\n", ConsoleViewContentType.NORMAL_OUTPUT) // insert to the end of the file val editor = context.editor ?: return "" WriteCommandAction.runWriteCommandAction(project) { editor.document.insertString(editor.caretModel.offset, context.genText ?: "") } return "" } context.genPsiElement = codeModifier.smartInsert(context.currentFile!!.virtualFile!!, project, context.genText ?: "") return "" } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/builtin/InsertNewlineProcessor.kt ================================================ package com.phodal.shirecore.middleware.builtin import com.intellij.execution.ui.ConsoleView import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.project.Project import com.phodal.shirecore.middleware.post.PostProcessorType import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirecore.middleware.post.PostProcessor import com.phodal.shirecore.workerThread import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch class InsertNewlineProcessor : PostProcessor { override val processorName: String = PostProcessorType.InsertNewline.handleName override val description: String = "`insertNewline` will insert a newline at the cursor position" override fun isApplicable(context: PostProcessorContext): Boolean = true override fun execute(project: Project, context: PostProcessorContext, console: ConsoleView?, args: List): Any { val editor = context.editor ?: return "" CoroutineScope(workerThread).launch { WriteCommandAction.runWriteCommandAction(project) { editor.document.insertString(editor.caretModel.offset, "\n") } } return editor.document.text } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/builtin/OpenFileProcessor.kt ================================================ package com.phodal.shirecore.middleware.builtin import com.intellij.execution.filters.OpenFileHyperlinkInfo import com.intellij.execution.ui.ConsoleView import com.intellij.openapi.application.runInEdt import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.phodal.shirecore.findFile import com.phodal.shirecore.middleware.post.PostProcessorType import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirecore.middleware.post.PostProcessor class OpenFileProcessor : PostProcessor { override val processorName: String = PostProcessorType.OpenFile.handleName override val description: String = "`openFile` will open the file in the editor" override fun isApplicable(context: PostProcessorContext): Boolean = true override fun execute(project: Project, context: PostProcessorContext, console: ConsoleView?, args: List): String { val firstArg = args.firstOrNull() val file = firstArg ?: context.pipeData["output"] ?: context.genText if (file !is VirtualFile) { if (file is String) { // check has multiple files val files = file.split("\n") runInEdt { val findFiles = files.mapNotNull { project.findFile(it) } findFiles.map { console?.printHyperlink("$it", OpenFileHyperlinkInfo(project, it, -1, -1)) // new line console?.print("\n", com.intellij.execution.ui.ConsoleViewContentType.NORMAL_OUTPUT) } findFiles.mapIndexed { index, it -> val isFocus = index == findFiles.size - 1 FileEditorManager.getInstance(project).openFile(it, isFocus) } } return "" } else { console?.print("No file to open\n", com.intellij.execution.ui.ConsoleViewContentType.ERROR_OUTPUT) } return "" } runInEdt { FileEditorManager.getInstance(project).openFile(file, true) } return "" } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/builtin/OpenWebpageProcessor.kt ================================================ package com.phodal.shirecore.middleware.builtin import com.intellij.execution.ui.ConsoleView import com.intellij.ide.DataManager import com.intellij.ide.IdeBundle import com.intellij.ide.browsers.BrowserLauncher import com.intellij.ide.browsers.OpenInBrowserRequest import com.intellij.ide.browsers.WebBrowserService import com.intellij.ide.browsers.WebBrowserUrlProvider import com.intellij.ide.browsers.actions.findUsingBrowser import com.intellij.openapi.actionSystem.CommonDataKeys import com.intellij.openapi.application.runInEdt import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.project.Project import com.intellij.openapi.ui.Messages import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement import com.phodal.shirecore.middleware.post.PostProcessor import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirecore.middleware.post.PostProcessorType class OpenWebpageProcessor : PostProcessor { override val processorName: String get() = PostProcessorType.OpenWebpage.handleName override val description: String get() = "`openWebpage` will open the generated HTML in the browser" override fun isApplicable(context: PostProcessorContext): Boolean { return context.genText?.contains("): Any { val dataContext = DataManager.getInstance().dataContextFromFocusAsync.blockingGet(10000) ?: throw IllegalStateException("No data context") val editor = CommonDataKeys.EDITOR.getData(dataContext) ?: return "" val psiFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) ?: throw IllegalStateException("No PSI file") val request = object : OpenInBrowserRequest(psiFile, true) { private val lazyElement by lazy { file.findElementAt(editor.caretModel.offset) } override val element: PsiElement get() = lazyElement ?: file } try { val browser = findUsingBrowser() val urls = WebBrowserService.getInstance().getUrlsToOpen(request, true) if (!urls.isEmpty()) { val url = urls.first() runInEdt { FileDocumentManager.getInstance().saveAllDocuments() } BrowserLauncher.instance.browse(url.toExternalForm(), browser, request.project) } } catch (e: WebBrowserUrlProvider.BrowserException) { Messages.showErrorDialog(e.message, IdeBundle.message("browser.error")) } catch (e: Exception) { logger().warn(e) } return "" } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/builtin/ParseCodeProcessor.kt ================================================ package com.phodal.shirecore.middleware.builtin import com.intellij.execution.ui.ConsoleView import com.intellij.openapi.project.Project import com.phodal.shirecore.utils.markdown.CodeFence import com.phodal.shirecore.middleware.post.PostProcessorType import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirecore.middleware.post.PostProcessor class ParseCodeProcessor : PostProcessor { override val processorName: String = PostProcessorType.ParseCode.handleName override val description: String = "`parseCode` will parse the markdown from llm response." override fun isApplicable(context: PostProcessorContext): Boolean = true override fun execute(project: Project, context: PostProcessorContext, console: ConsoleView?, args: List): String { val code = CodeFence.parse(context.genText ?: "") val codeText = code.text context.genTargetLanguage = code.ideaLanguage context.genTargetExtension = code.extension context.pipeData["output"] = codeText context.pipeData["code"] = codeText return codeText } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/builtin/ParseCommentProcessor.kt ================================================ package com.phodal.shirecore.middleware.builtin import com.intellij.execution.ui.ConsoleView import com.intellij.openapi.project.Project import com.phodal.shirecore.middleware.post.PostProcessorType import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirecore.middleware.post.PostProcessor import com.phodal.shirecore.provider.psi.PsiElementDataBuilder import org.jetbrains.annotations.NonNls class ParseCommentProcessor : PostProcessor { override val processorName: String = PostProcessorType.ParseComment.handleName override val description: String = "`parseComment` will parse the comment from the llm response" override fun isApplicable(context: PostProcessorContext): Boolean = true fun preHandleDoc(newDoc: String): @NonNls String { val newDocWithoutCodeBlock = newDoc.removePrefix("```java") .removePrefix("```") .removeSuffix("```") val fromSuggestion = buildDocFromSuggestion(newDocWithoutCodeBlock, "/**", "*/") return fromSuggestion } fun buildDocFromSuggestion(suggestDoc: String, commentStart: String, commentEnd: String): String { val startIndex = suggestDoc.indexOf(commentStart) if (startIndex < 0) { return "" } val docComment = suggestDoc.substring(startIndex) val endIndex = docComment.indexOf(commentEnd, commentStart.length) if (endIndex < 0) { return docComment + commentEnd } val substring = docComment.substring(0, endIndex + commentEnd.length) return substring } private fun getDocFromOutput(context: PostProcessorContext) = preHandleDoc(context.pipeData["output"] as String? ?: context.genText ?: "") override fun execute(project: Project, context: PostProcessorContext, console: ConsoleView?, args: List): String { val defaultComment: String = getDocFromOutput(context) val currentFile = context.currentFile ?: return defaultComment val comment = PsiElementDataBuilder.provide(currentFile.language) ?.parseComment(project, defaultComment) ?: return defaultComment return comment } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/builtin/PatchProcessor.kt ================================================ package com.phodal.shirecore.middleware.builtin import com.intellij.execution.ui.ConsoleView import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.diff.impl.patch.FilePatch import com.intellij.openapi.diff.impl.patch.PatchReader import com.intellij.openapi.project.Project import com.intellij.openapi.vcs.changes.patch.AbstractFilePatchInProgress import com.intellij.openapi.vcs.changes.patch.ApplyPatchDefaultExecutor import com.intellij.openapi.vcs.changes.patch.MatchPatchPaths import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.containers.MultiMap import com.phodal.shirecore.middleware.post.PostProcessor import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirecore.middleware.post.PostProcessorType class PatchProcessor : PostProcessor { override val processorName: String = PostProcessorType.Patch.handleName override val description: String = "`patch` will apply the patch to the current file, default will use llm response." override fun isApplicable(context: PostProcessorContext): Boolean = true override fun execute( project: Project, context: PostProcessorContext, console: ConsoleView?, args: List, ): Any { val args = args.map { val argName = it.toString() if (argName.startsWith("$")) { if (argName == "output" && context.lastTaskOutput != null) { context.lastTaskOutput } else { context.compiledVariables[argName.substring(1)] ?: "" } } else { it } } if (args.size < 2) { console?.print("PatchProcessor: not enough arguments", ConsoleViewContentType.ERROR_OUTPUT) return "" } val fileName = args[0].toString() val content = args[1].toString() val shelfExecutor = ApplyPatchDefaultExecutor(project) val myReader = PatchReader(content) myReader.parseAllPatches() val filePatches: MutableList = myReader.allPatches ApplicationManager.getApplication().invokeAndWait { val matchedPatches = MatchPatchPaths(project).execute(filePatches, true) val patchGroups = MultiMap>() for (patchInProgress in matchedPatches) { patchGroups.putValue(patchInProgress.base, patchInProgress) } if(filePatches.isEmpty() ) { console?.print("PatchProcessor: no patches found", ConsoleViewContentType.ERROR_OUTPUT) return@invokeAndWait } val additionalInfo = myReader.getAdditionalInfo(ApplyPatchDefaultExecutor.pathsFromGroups(patchGroups)) shelfExecutor.apply(filePatches, patchGroups, null, fileName, additionalInfo) } return context.genText ?: "" } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/builtin/RunCodeProcessor.kt ================================================ package com.phodal.shirecore.middleware.builtin import com.intellij.execution.ui.ConsoleView import com.intellij.execution.ui.ConsoleViewContentType.* import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ReadAction import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiFile import com.intellij.psi.PsiFileFactory import com.intellij.psi.PsiManager import com.phodal.shirecore.middleware.post.PostProcessorType import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirecore.middleware.post.PostProcessor import com.phodal.shirecore.provider.shire.FileRunService class RunCodeProcessor : PostProcessor { override val processorName: String = PostProcessorType.RunCode.handleName override val description: String = "`runCode` will run the code, default will be test file." override fun isApplicable(context: PostProcessorContext): Boolean = true override fun execute( project: Project, context: PostProcessorContext, console: ConsoleView?, args: List, ): String { when (val code = context.pipeData["output"]) { is VirtualFile -> { LocalFileSystem.getInstance().refreshAndFindFileByPath(code.path) val psiFile = ReadAction.compute { PsiManager.getInstance(project).findFile(code) } psiFile?.let { doExecute(console, project, code, it) return "" } } is String -> { val ext = context.genTargetLanguage?.associatedFileType?.defaultExtension ?: "txt" ApplicationManager.getApplication().invokeAndWait { if (code.contains("\n")) { PsiFileFactory.getInstance(project).createFileFromText("temp.$ext", code).let { psiFile -> if (psiFile.virtualFile == null) { console?.print("Failed to create file for run\n", ERROR_OUTPUT) } else { doExecute(console, project, psiFile.virtualFile, psiFile) } } } else { val file = LocalFileSystem.getInstance().refreshAndFindFileByPath(code) if (file != null) { val psiFile = ReadAction.compute { PsiManager.getInstance(project).findFile(file) } psiFile?.let { doExecute(console, project, file, psiFile) } } } } } } console?.print("No code to run\n", ERROR_OUTPUT) return "" } private fun doExecute( console: ConsoleView?, project: Project, file: VirtualFile, psiFile: PsiFile, ) { val fileRunService = FileRunService.provider(project, file) if (fileRunService == null) { val cliResult = FileRunService.runInCli(project, psiFile) if (cliResult != null) { console?.print(cliResult, NORMAL_OUTPUT) return } FileRunService.retryRun(project, file)?.let { console?.print(it, NORMAL_OUTPUT) return } console?.print("RunCode: No run service found for file: $file\n", ERROR_OUTPUT) return } console?.print("Running code...\n", SYSTEM_OUTPUT) val output = fileRunService.runFileAsync(project, file, psiFile) console?.print(output ?: "", NORMAL_OUTPUT) } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/builtin/SaveFileProcessor.kt ================================================ package com.phodal.shirecore.middleware.builtin import com.intellij.execution.ui.ConsoleView import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.WriteAction import com.intellij.openapi.fileTypes.PlainTextLanguage import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.util.Disposer import com.intellij.openapi.vfs.VirtualFile import com.phodal.shirecore.utils.markdown.CodeFence import com.phodal.shirecore.ShireConstants import com.phodal.shirecore.middleware.post.PostProcessorType import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirecore.middleware.post.PostProcessor class SaveFileProcessor : PostProcessor, Disposable { override val processorName: String = PostProcessorType.SaveFile.handleName override val description: String = "`saveFile` will save the content / llm response to the file" override fun isApplicable(context: PostProcessorContext): Boolean = true override fun execute( project: Project, context: PostProcessorContext, console: ConsoleView?, args: List, ): String { val fileName: String val ext = getFileExt(context) if (args.isNotEmpty()) { fileName = getValidFilePath(args[0].toString(), ext) handleForProjectFile(project, fileName, context, console) } else { fileName = "${System.currentTimeMillis()}.$ext" handleForTempFile(project, fileName, context, console) } return fileName } private fun getFileExt(context: PostProcessorContext): String { val language = context.genTargetLanguage ?: PlainTextLanguage.INSTANCE return context.genTargetExtension ?: language?.associatedFileType?.defaultExtension ?: "txt" } private fun handleForTempFile( project: Project, fileName: String, context: PostProcessorContext, console: ConsoleView?, ) { ApplicationManager.getApplication().invokeAndWait { WriteAction.compute { val outputDir = ShireConstants.outputDir(project) val outputFile = outputDir?.createChildData(this, fileName) ?: throw IllegalStateException("Failed to save file") val content = getContent(context) outputFile.setBinaryContent(content?.toByteArray() ?: ByteArray(0)) context.pipeData["output"] = outputFile project.guessProjectDir()?.refresh(true, true) console?.print("Saved to ${outputFile.canonicalPath}\n", ConsoleViewContentType.SYSTEM_OUTPUT) outputFile } } } private fun handleForProjectFile( project: Project, filepath: String, context: PostProcessorContext, console: ConsoleView? ) { var fileName = filepath ApplicationManager.getApplication().invokeAndWait { WriteAction.compute { val projectDir = project.guessProjectDir() // if filename starts with / means it's an absolute path, we need to get relative path if (fileName.startsWith("/")) { val projectPath = projectDir?.canonicalPath if (projectPath != null) { fileName = fileName.replace(projectPath, "") } } // filename may include path, like: `src/main/java/HelloWorld.java`, we need to split it // first check if the file is already in the project var outputFile = projectDir?.findFileByRelativePath(fileName) if (outputFile == null) { outputFile = createFile(fileName, projectDir) } val content = getContent(context) outputFile!!.setBinaryContent(content?.toByteArray() ?: ByteArray(0)) context.pipeData["output"] = outputFile projectDir?.refresh(true, true) console?.print("Saved to ${outputFile.canonicalPath}", ConsoleViewContentType.SYSTEM_OUTPUT) outputFile } } } private fun getContent(context: PostProcessorContext): String? { val outputData = context.pipeData["output"] if (outputData is String && outputData.isNotEmpty()) { return outputData } if (context.lastTaskOutput?.isNotEmpty() == true) { return context.lastTaskOutput } return context.genText } private fun createFile( fileName: String, projectDir: VirtualFile?, ): VirtualFile { val path = fileName.split("/").dropLast(1) val name = fileName.split("/").last() var parentDir = projectDir // create directories if not exist for (dir in path) { parentDir = parentDir?.findChild(dir) ?: parentDir?.createChildDirectory(this, dir) } val outputFile = parentDir?.createChildData(this, name) ?: throw IllegalStateException("Failed to save file") return outputFile } override fun dispose() { Disposer.dispose(this) } } /** * 根据给定的文件路径和扩展名,返回一个有效的文件路径。 * *
 *    {@code
 *        String validPath = getValidFilePath("example.txt", "txt");
 *        // validPath = "example.txt"
 *
 *        String validPath2 = getValidFilePath("", "txt");
 *        // validPath2 = "1633024800000.txt" (当前时间戳)
 *    }
 *    
* * @param filePath 文件路径,可以为空或包含特殊字符 * @param ext 文件扩展名,用于在文件路径无效时生成默认文件名 * @return 有效的文件路径,如果输入路径无效,则返回基于时间戳的默认文件名 */ fun getValidFilePath(filePath: String, ext: String): String { val pathRegex = """^([a-zA-Z]:\\|\\\\|/|)([a-zA-Z0-9_\-\\/.]+)$""".toRegex() if (filePath.isBlank()) { return "${System.currentTimeMillis()}.$ext" } return if (pathRegex.matches(filePath)) { filePath } else if (filePath.contains("`") && filePath.contains("```")) { CodeFence.parse(filePath).text } else { "${System.currentTimeMillis()}.$ext" } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/builtin/ShowWebviewProcessor.kt ================================================ package com.phodal.shirecore.middleware.builtin import com.intellij.execution.ui.ConsoleView import com.intellij.ide.DataManager import com.intellij.openapi.application.runInEdt import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.editor.colors.EditorFontType import com.intellij.openapi.project.Project import com.intellij.openapi.ui.popup.JBPopup import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.openapi.ui.popup.JBPopupListener import com.intellij.openapi.ui.popup.LightweightWindowEvent import com.intellij.ui.components.JBTextArea import com.intellij.ui.dsl.builder.* import com.phodal.shirecore.ShireCoroutineScope import com.phodal.shirecore.llm.LlmProvider import com.phodal.shirecore.utils.markdown.CodeFence import com.phodal.shirecore.middleware.post.PostProcessor import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirecore.middleware.post.PostProcessorType import com.phodal.shirecore.middleware.builtin.ui.WebViewWindow import com.phodal.shirecore.runner.console.cancelHandler import kotlinx.coroutines.flow.cancellable import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import java.awt.event.KeyAdapter import java.awt.event.KeyEvent class ShowWebviewProcessor : PostProcessor { override val processorName: String get() = PostProcessorType.ShowWebview.handleName override val description: String get() = "`showWebview` will show the webview for the content if it's html" private var continueMessage: String = "" private var webview: WebViewWindow? = null override fun isApplicable(context: PostProcessorContext): Boolean { return true } override fun execute(project: Project, context: PostProcessorContext, console: ConsoleView?, args: List): Any { var html: String? = (context.pipeData["output"])?.toString() ?: context.genText val dataContext = DataManager.getInstance().dataContextFromFocusAsync.blockingGet(10000) ?: throw IllegalStateException("No data context") var keyAdapter: WebviewKeyAdapter? = null runInEdt { webview = WebViewWindow() val component = webview!!.apply { loadHtml(html ?: "") }.component val panel = panel { row { cell(component) } row { textArea() .align(Align.FILL) .bindText(::continueMessage) .applyToComponent { font = EditorFontType.getGlobalPlainFont() keyAdapter = WebviewKeyAdapter(project, this, webview, html) { html = it continueMessage = "" } addKeyListener(keyAdapter) } } } val popup = JBPopupFactory.getInstance() .createComponentPopupBuilder(panel, null) .setResizable(true) .setMovable(true) .setTitle("Preview") .setFocusable(true) .setRequestFocus(true) .createPopup() .also { keyAdapter?.popup = it } popup.showInBestPositionFor(dataContext) } return "" } } class WebviewKeyAdapter( val project: Project, val textarea: JBTextArea, val currentWebview: WebViewWindow?, val html: String?, val onEnter: (String) -> Unit, ) : KeyAdapter() { var popup: JBPopup? = null override fun keyPressed(e: KeyEvent) { if (e.keyCode == KeyEvent.VK_ENTER) { textarea.isEditable = false currentWebview?.loadHtml("Processing...") var result = "" ShireCoroutineScope.scope(project).launch { val flow = LlmProvider.provider(project) ?.stream( "According user input to modify code, return new code." + " Use input: ${textarea.text}\nCode: \n```html\n$html\n```" + "\n" + "Return new code: ", "", false )!! var popupListener: JBPopupListener? = null runBlocking { flow.cancelHandler { if (popup?.isDisposed == true) it.invoke("This popup has been disposed") else popup?.addListener(object : JBPopupListener { override fun onClosed(event: LightweightWindowEvent) { it.invoke("This popup has been closed") } }.also { popupListener = it }) }.cancellable().collect { result += it } popupListener?.run { popup?.removeListener(this) } textarea.isEditable = true } val newHtml = CodeFence.parse(result).text logger().info("Result: $result") runInEdt { currentWebview?.loadHtml(newHtml) } onEnter(newHtml) textarea.text = "" } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/builtin/TimeMetricProcessor.kt ================================================ package com.phodal.shirecore.middleware.builtin import com.intellij.execution.ui.ConsoleView import com.intellij.openapi.project.Project import com.phodal.shirecore.middleware.post.PostProcessorType import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirecore.middleware.post.PostProcessor class TimeMetricProcessor : PostProcessor { private var startTime: Long? = null override val processorName: String = PostProcessorType.TimeMetric.handleName override val description: String = "`timeMetric` will calculate the time metric" override fun isApplicable(context: PostProcessorContext): Boolean = true override fun setup(context: PostProcessorContext): String { startTime = System.currentTimeMillis() return startTime.toString() } override fun execute(project: Project, context: PostProcessorContext, console: ConsoleView?, args: List): String { val endTime = System.currentTimeMillis() return (endTime - startTime!!).toString() } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/builtin/UpdateEditorTextProcessor.kt ================================================ package com.phodal.shirecore.middleware.builtin import com.intellij.execution.ui.ConsoleView import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.phodal.shirecore.middleware.post.PostProcessorType import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirecore.middleware.post.PostProcessor import com.phodal.shirecore.workerThread import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch class UpdateEditorTextProcessor : PostProcessor { override val processorName: String = PostProcessorType.UpdateEditorText.handleName override val description: String = "`updateEditorText` will update the editor text from llm response" override fun isApplicable(context: PostProcessorContext): Boolean = true override fun execute( project: Project, context: PostProcessorContext, console: ConsoleView?, args: List, ): Any { val editor = context.editor ?: return "" val newText = if(args.isNotEmpty()) { args[0] } else { context.pipeData["output"] } if (newText == null) { logger().error("no new code to update, pipeData: ${context.pipeData}") return "" } CoroutineScope(workerThread).launch { WriteCommandAction.runWriteCommandAction(project) { editor.document.setText(newText.toString()) } } return newText } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/builtin/VerifyCodeProcessor.kt ================================================ package com.phodal.shirecore.middleware.builtin import com.intellij.execution.ui.ConsoleView import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiManager import com.phodal.shirecore.middleware.post.PostProcessor import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirecore.middleware.post.PostProcessorType import com.phodal.shirecore.psi.PsiErrorCollector class VerifyCodeProcessor : PostProcessor { override val processorName: String = PostProcessorType.VerifyCode.handleName override val description: String = "`verifyCode` will verify the code syntax and return the errors" override fun isApplicable(context: PostProcessorContext): Boolean = true override fun execute( project: Project, context: PostProcessorContext, console: ConsoleView?, args: List, ): String { val code = context.pipeData["output"] if (code !is VirtualFile) { console?.print("No code to verify\n", ConsoleViewContentType.ERROR_OUTPUT) return "" } val psiFile = PsiManager.getInstance(project).findFile(code) if (psiFile == null) { console?.print("No code to verify\n", ConsoleViewContentType.ERROR_OUTPUT) return "" } if (!psiFile.isValid) { console?.print("No code to verify\n", ConsoleViewContentType.ERROR_OUTPUT) return "" } val errors: List = PsiErrorCollector.collectSyntaxError(psiFile, project) if (errors.isNotEmpty()) { console?.print("Syntax errors found:\n${errors.joinToString("\n")}\n", ConsoleViewContentType.ERROR_OUTPUT) } else { console?.print("No syntax errors found\n", ConsoleViewContentType.SYSTEM_OUTPUT) } return errors.joinToString("\n") } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/builtin/ui/ActionableWebView.kt ================================================ package com.phodal.shirecore.middleware.builtin.ui import com.intellij.ui.jcef.JBCefBrowser import com.intellij.ui.jcef.JBCefJSQuery import java.util.concurrent.CompletableFuture class ActionableWebView(private val browser: JBCefBrowser) { private var future: CompletableFuture = CompletableFuture.completedFuture(null) private var onErrorAction: (() -> Unit)? = null fun open(url: String): ActionableWebView { future = future.thenRun { browser.loadURL(url) } return this } fun waitFor(selector: String, timeout: Long): ActionableWebView { future = future.thenCompose { val result = CompletableFuture() val jsQuery = JBCefJSQuery.create(browser) // 1 jsQuery.addHandler { response -> if (response == "success") { result.complete(null) } else { result.completeExceptionally(Exception("Timeout waiting for $selector")) } null } val script = """ (function() { var interval = setInterval(function() { if (document.querySelector('$selector') !== null) { clearInterval(interval); window['_java_callback_${jsQuery}']('success'); } }, 100); setTimeout(function() { clearInterval(interval); window['_java_callback_${jsQuery}']('error'); }, $timeout); })(); """ val wrappedScript = """ window['_java_callback_${jsQuery}'] = function(response) { ${jsQuery.inject("response")} }; $script """ browser.cefBrowser.executeJavaScript(wrappedScript, browser.cefBrowser.url, 0) result }.exceptionally { throwable -> onErrorAction?.invoke() null } return this } fun input(selector: String, text: String): ActionableWebView { future = future.thenRun { val script = "document.querySelector('$selector').value = '$text';" browser.cefBrowser.executeJavaScript(script, browser.cefBrowser.url, 0) } return this } fun click(selector: String): ActionableWebView { future = future.thenRun { val script = "document.querySelector('$selector').click();" browser.cefBrowser.executeJavaScript(script, browser.cefBrowser.url, 0) } return this } fun onError(action: () -> Unit): ActionableWebView { onErrorAction = action return this } companion object { fun create(browser: JBCefBrowser): ActionableWebView { // // Ensure JCEF is initialized // JBCefApp.getInstance() // // // Create the browser component // val browser = JBCefBrowser() // // // Add the browser to the tool window content // val contentFactory = ContentFactory.getInstance() // val content = contentFactory.createContent(browser.component, "", false) // // // Start the action chain as per your DSL return ActionableWebView(browser) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/builtin/ui/WebViewWindow.kt ================================================ package com.phodal.shirecore.middleware.builtin.ui import com.intellij.ui.JBColor import com.intellij.ui.jcef.JBCefBrowser import javax.swing.JComponent /** * WebViewWindow is a class that provides a custom webview functionality. It allows developers to * create a custom webview within their IntelliJ-based applications. This class is designed to be * used in conjunction with the JCEF (JetBrains CEF) plugin, which is a wrapper around the Chromium Embedded Framework. * */ class WebViewWindow { // official doc: https://plugins.jetbrains.com/docs/intellij/jcef.html#executing-javascript private val browser: JBCefBrowser init { browser = try { JBCefBrowser.createBuilder() .build() } catch (e: Exception) { JBCefBrowser() } browser.component.background = JBColor.WHITE } val component: JComponent = browser.component fun loadHtml(html: String) { browser.loadHTML(html) } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/post/PostProcessor.kt ================================================ package com.phodal.shirecore.middleware.post import com.intellij.execution.ui.ConsoleView import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.project.Project data class LifecycleProcessorSignature( val funcName: String, val args: List, ) /** * The PostProcessor interface defines a contract for post-processing tasks. Implementations of this * interface are expected to handle specific post-processing operations, which are identified by a * unique name. * * The interface provides methods to check the applicability of a given context for handling post codes, * to set up any necessary initial tasks, to execute the post-processing function, and to perform clean * up tasks after execution. * * * @property processorName the unique name of the post-processing function, the built-in functions are defined in [PostProcessorType] */ interface PostProcessor { val processorName: String val description: String /** * This function checks if a given context is applicable for handling post codes. * * @param context the PostCodeHandleContext to be checked for applicability * @return true if the context is applicable for handling post codes, false otherwise */ fun isApplicable(context: PostProcessorContext): Boolean /** * Some init tasks, like metric for time, etc. */ fun setup(context: PostProcessorContext): Any { return "" } /** * Executes a function with the given project, context, and generated text. * * @param project the project to execute the function on * @param context the context in which the function is executed * @param genText the generated text to be used in the execution * @return a string result of the execution */ fun execute(project: Project, context: PostProcessorContext, console: ConsoleView?, args: List): Any /** * Clean up tasks, like metric for time, etc. */ fun finish(context: PostProcessorContext): Any? { return "" } companion object { private val EP_NAME: ExtensionPointName = ExtensionPointName.create("com.phodal.shirePostProcessor") fun handler(handleName: String): PostProcessor? { return EP_NAME.extensionList.find { it.processorName == handleName } } fun allNames(): List { return EP_NAME.extensionList.map { it.processorName } } fun all(): List { return EP_NAME.extensionList } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/post/PostProcessorContext.kt ================================================ package com.phodal.shirecore.middleware.post import com.intellij.lang.Language import com.intellij.openapi.editor.Editor import com.intellij.openapi.util.Key import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.UserDataHolderBase import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile class PostProcessorContext( /** * Convert code to file */ var currentFile: PsiFile? = null, /** * The language of the code to be handled, which will parse from the GenText when parse code */ var currentLanguage: Language? = null, /** * Target Language */ var genTargetLanguage: Language? = null, var genTargetExtension: String? = null, /** * The element to be handled, which will be load from current editor when parse code */ var genPsiElement: PsiElement? = null, /** * The generated text to be handled */ var genText: String? = null, /** * The data to be passed to the post-processor */ val pipeData: MutableMap = mutableMapOf(), /** * post text range */ val modifiedTextRange: TextRange? = null, /** * current editor for modify */ val editor: Editor? = null, var lastTaskOutput: String? = null, var compiledVariables: Map = mapOf(), val llmModelName: String? = null, ) { companion object { private val DATA_KEY: Key = Key.create(PostProcessorContext::class.java.name) private val userDataHolderBase = UserDataHolderBase() // todo: refactor to GlobalVariableContext fun updateContextAndVariables(context: PostProcessorContext) { context.compiledVariables = dynamicUpdateVariables(context.compiledVariables) userDataHolderBase.putUserData(DATA_KEY, context) } private fun dynamicUpdateVariables(variables: Map): MutableMap { val userData = userDataHolderBase.getUserData(DATA_KEY) val oldVariables: MutableMap = userData?.compiledVariables?.toMutableMap() ?: mutableMapOf() variables.forEach { if (it.value.toString().startsWith("$")) { oldVariables.remove(it.key) } else if (it.value != null && it.value.toString().isNotEmpty()) { oldVariables[it.key] = it.value } } return oldVariables } fun getData(): PostProcessorContext? { return userDataHolderBase.getUserData(DATA_KEY) } fun updateOutput(output: Any?) { val context = getData() if (context != null) { context.lastTaskOutput = output.toString() updateContextAndVariables(context) } val compiledVariables = context?.compiledVariables?.toMutableMap() compiledVariables?.set("output", output) if (context != null) { context.compiledVariables = compiledVariables ?: mapOf() updateContextAndVariables(context) } } fun updateRunConfigVariables(variables: Map) { val context = getData() val compiledVariables = context?.compiledVariables?.toMutableMap() compiledVariables?.putAll(variables) if (context != null) { context.compiledVariables = compiledVariables ?: mapOf() updateContextAndVariables(context) } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/post/PostProcessorType.kt ================================================ package com.phodal.shirecore.middleware.post /** * Post middleware actions, like * Logging, Metrics, CodeVerify, RunCode, ParseCode etc. * */ enum class PostProcessorType(var handleName: String) { /** * Metric time spent on the action. */ TimeMetric("timeMetric"), /** * Check has code error or PSI issue. */ VerifyCode("verifyCode"), /** * Run generate text code */ RunCode("runCode"), /** * Parse text to code blocks */ ParseCode("parseCode"), /** * Save file to the disk */ SaveFile("saveFile"), /** * Open file in the editor */ OpenFile("openFile"), /** * Insert code to the editor by current cursor position. */ InsertCode("insertCode"), /** * Format code */ FormatCode("formatCode"), /** * Parse comment to the comment block */ ParseComment("parseComment"), /** * Insert new line */ InsertNewline("insertNewline"), /** * Append text to the file */ Append("append"), /** * Patch content to the file */ Patch("patch"), /** * Diff */ Diff("diff"), UpdateEditorText("updateEditorText"), /** * openWebpage */ OpenWebpage("openWebpage"), /** * showWebView */ ShowWebview("showWebView"), ; } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/select/DefaultPsiElementStrategy.kt ================================================ package com.phodal.shirecore.middleware.select import com.intellij.lang.injection.InjectedLanguageManager import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiUtilBase open class DefaultPsiElementStrategy : PsiElementStrategy { /** * Returns the PsiElement to explain in the given project and editor. * * @param project the project in which the element resides (nullable) * @param editor the editor in which the element is located (nullable) * @return the PsiElement to explain, or null if either the project or editor is null, or if no element is found */ override fun getElementToAction(project: Project?, editor: Editor?): PsiElement? { if (project == null || editor == null) return null val element = PsiUtilBase.getElementAtCaret(editor) ?: return null val psiFile = element.containingFile val selectionModel = editor.selectionModel if (selectionModel.hasSelection()) { val startOffset = selectionModel.selectionStart val endOffset = selectionModel.selectionEnd val startElement = PsiUtilBase.getElementAtOffset(psiFile, startOffset) val endElement = PsiUtilBase.getElementAtOffset(psiFile, endOffset) if (startElement == endElement) return startElement } if (InjectedLanguageManager.getInstance(project).isInjectedFragment(psiFile)) return psiFile val identifierOwner = PsiTreeUtil.getParentOfType(element, PsiNameIdentifierOwner::class.java) return identifierOwner ?: element } /** * This method calculates the frontend element to explain based on the given project, PsiFile, and TextRange. * * @param project the project to which the PsiFile belongs * @param psiFile the PsiFile in which the frontend element is located * @param range the TextRange specifying the range of the frontend element * @return the PsiElement representing the frontend element to explain, or null if the project is null, or the PsiFile is invalid */ override fun getElementToAction(project: Project?, psiFile: PsiFile, range: TextRange): PsiElement? { if (project == null || !psiFile.isValid) return null val element = PsiUtilBase.getElementAtOffset(psiFile, range.startOffset) if (InjectedLanguageManager.getInstance(project).isInjectedFragment(psiFile)) { return psiFile } val injected = InjectedLanguageManager.getInstance(project).findInjectedElementAt(psiFile, range.startOffset) if (injected != null) { return injected.containingFile } val psiElement: PsiElement? = PsiTreeUtil.getParentOfType(element, PsiNameIdentifierOwner::class.java) return psiElement ?: element } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/select/PsiElementStrategy.kt ================================================ package com.phodal.shirecore.middleware.select import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile interface PsiElementStrategy { fun getElementToAction(project: Project?, editor: Editor?): PsiElement? fun getElementToAction(project: Project?, psiFile: PsiFile, range: TextRange): PsiElement? } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/middleware/select/SelectElementStrategy.kt ================================================ package com.phodal.shirecore.middleware.select import com.intellij.openapi.application.runInEdt import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.openapi.util.NlsSafe import com.intellij.psi.PsiElement import com.intellij.psi.PsiWhiteSpace sealed class SelectedEntry(open val element: Any) { class Text(override val element: String) : SelectedEntry(element) class Entry(override val element: PsiElement) : SelectedEntry(element) } sealed class SelectElementStrategy { /** * Selection element */ abstract fun select(project: Project, editor: Editor?): Any? abstract fun getSelectedElement(project: Project, editor: Editor?): SelectedEntry? /** * Auto select parent block element, like function, class, etc. */ object Blocked : SelectElementStrategy() { override fun select(project: Project, editor: Editor?): PsiElement? { if (editor == null) { return null } val elementToAction = DefaultPsiElementStrategy().getElementToAction(project, editor) ?: return null runInEdt { selectElement(elementToAction, editor) } return elementToAction } /** * This function selects the specified PsiElement in the editor by setting the selection range from the start offset to the end offset of the element. * * @param elementToExplain the PsiElement to be selected in the editor * @param editor the Editor in which the selection is to be made */ private fun selectElement(elementToExplain: PsiElement, editor: Editor) { val startOffset = elementToExplain.textRange.startOffset val endOffset = elementToExplain.textRange.endOffset editor.selectionModel.setSelection(startOffset, endOffset) } override fun getSelectedElement(project: Project, editor: Editor?): SelectedEntry? { select(project, editor)?.let { return SelectedEntry.Entry(it) } return null } } object SelectedText : SelectElementStrategy() { override fun select(project: Project, editor: Editor?): @NlsSafe String? { return editor?.selectionModel?.selectedText ?: "" } override fun getSelectedElement(project: Project, editor: Editor?): SelectedEntry? { val selectedText = select(project, editor) ?: return null return SelectedEntry.Text(selectedText) } } object Default : SelectElementStrategy() { override fun select(project: Project, editor: Editor?): PsiElement? { val selectionModel = editor?.selectionModel ?: return null if (!selectionModel.hasSelection()) { return Blocked.select(project, editor) } return null } override fun getSelectedElement(project: Project, editor: Editor?): SelectedEntry? { return Blocked.getSelectedElement(project, editor) } } object SelectAll : SelectElementStrategy() { override fun select(project: Project, editor: Editor?): String? { val selectionModel = editor?.selectionModel ?: return null runInEdt { selectionModel.setSelection(0, editor.document.textLength) } return editor.document.text } override fun getSelectedElement(project: Project, editor: Editor?): SelectedEntry { return SelectedEntry.Text(editor?.document?.text ?: "") } } companion object { fun fromString(strategy: String): SelectElementStrategy? { return when (strategy.lowercase()) { "block" -> Blocked "select" -> SelectedText "selectAll" -> SelectAll else -> null } } fun all(): List { return SelectElementStrategy::class.sealedSubclasses.map { it.simpleName!! } } fun getElementAtOffset(psiFile: PsiElement, offset: Int): PsiElement? { var element = psiFile.findElementAt(offset) ?: return null if (element is PsiWhiteSpace) { element = element.getParent() } return element } fun resolvePsiElement(myProject: Project, editor: Editor): PsiElement? { val elementToAction = DefaultPsiElementStrategy().getElementToAction(myProject, editor) if (elementToAction != null) { return elementToAction } return null } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/project/ProjectFileUtil.kt ================================================ // Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.phodal.shirecore.project import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiManager // https://github.com/JetBrains/intellij-community/blob/master/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/ProjectFileIndexImpl.java#L32 fun isInProject(virtualFile: VirtualFile, project: Project): Boolean { // new version has better method if (virtualFile.path.startsWith(project.basePath ?: return false)) { return true } PsiManager.getInstance(project).findFile(virtualFile)?.let { return true } return false } fun Project.isInProject(virtualFile: VirtualFile): Boolean { return isInProject(virtualFile, this) || ProjectFileIndex.getInstance(this).isInLibrary(virtualFile) } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/TestingService.kt ================================================ package com.phodal.shirecore.provider import com.intellij.lang.LanguageExtension import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.phodal.shirecore.provider.codemodel.model.ClassStructure import com.phodal.shirecore.provider.shire.FileRunService import com.phodal.shirecore.psi.PsiErrorCollector import com.phodal.shirecore.variable.toolchain.unittest.AutoTestingPromptContext /** * The `TestService` class is an abstract class that provides a base implementation for writing tests in different programming languages. * It extends the `LazyExtensionInstance` class, which allows lazy initialization of the `TestService` instances. * * @property language The programming language for which the test service is applicable. * @property implementationClass The fully qualified name of the implementation class. * * @constructor Creates a new instance of the `TestService` class. */ abstract class TestingService : FileRunService { abstract fun isApplicable(element: PsiElement): Boolean /** * Finds or creates a test file for the given source file, project, and element. * * @param sourceFile The source file for which to find or create a test file. * @param project The project in which the test file should be created. * @param psiElement The element for which the test file should be created. * @return The TestFileContext object representing the found or created test file, or null if it could not be found or created. * * This method is responsible for locating an existing test file associated with the given source file and element, * or creating a new test file if one does not already exist. The test file is typically used for unit testing purposes. * The source file, project, and element parameters are used to determine the context in which the test file should be created. * If a test file is found or created successfully, a TestFileContext object representing the test file is returned. * If a test file cannot be found or created, null is returned. */ abstract fun findOrCreateTestFile( sourceFile: PsiFile, project: Project, psiElement: PsiElement, ): AutoTestingPromptContext? /** * Looks up the relevant classes in the project for the given element. * * @param project the project in which to perform the lookup * @param element the element for which to find the relevant classes * @return a list of ClassStructure objects representing the relevant classes found in the project */ abstract fun lookupRelevantClass(project: Project, element: PsiElement): List /** * This method is used to collect syntax errors from a given project and write them to an output file. * It takes the output file, the project to check, and an optional action to be executed with the list of errors. * * @param outputFile The virtual file where the syntax errors will be written to. * @param project The project to be analyzed for syntax errors. * @param runAction An optional lambda function that takes a list of strings as its parameter, representing the syntax errors. * If provided, this action is invoked with an empty list of errors, indicating no syntax errors were found. */ open fun collectSyntaxError(psiFile: PsiFile, project: Project, runAction: ((errors: List) -> Unit)?) { PsiErrorCollector.collectSyntaxError(psiFile, psiFile.virtualFile, project, runAction) } /** * Attempts to fix syntax errors in the given Kotlin file within the project. * This method is designed to be overridden by subclasses to provide custom syntax error fixing logic. * * @param outputFile The virtual file that needs to have its syntax errors fixed. * @param project The current project in which the file resides. */ open fun tryFixSyntaxError(outputFile: VirtualFile, project: Project, issues: List) { // send to chat panel } companion object { val log = logger() private val EP_NAME: LanguageExtension = LanguageExtension("com.phodal.shireAutoTesting") fun context(psiElement: PsiElement): TestingService? { return EP_NAME.forLanguage(psiElement.language) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/action/CustomActionLocationExecutor.kt ================================================ package com.phodal.shirecore.provider.action import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirecore.llm.LlmProvider interface CustomActionLocationExecutor { fun isApplicable(location: ShireActionLocation): Boolean fun execute(llmProvider: LlmProvider, location: ShireActionLocation) } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/action/TerminalLocationExecutor.kt ================================================ package com.phodal.shirecore.provider.action import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.project.Project import com.phodal.shirecore.provider.action.terminal.TerminalHandler import java.awt.Component interface TerminalLocationExecutor { fun getComponent(e: AnActionEvent): Component? fun bundler(project: Project, userInput: String): TerminalHandler? companion object { private val EP_NAME: ExtensionPointName = ExtensionPointName.create("com.phodal.shireTerminalExecutor") fun provide(project: Project): TerminalLocationExecutor? { return EP_NAME.extensionList.firstOrNull() } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/action/terminal/TerminalHandler.kt ================================================ package com.phodal.shirecore.provider.action.terminal import com.intellij.openapi.project.Project class TerminalHandler( val userInput: String, val project: Project, val onChunk: (str: String) -> Any?, val onFinish: ((str: String?) -> Any?)?, ) ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/agent/AgentTool.kt ================================================ package com.phodal.shirecore.provider.agent import com.intellij.openapi.extensions.ExtensionPointName import com.phodal.shirecore.agent.agenttool.AgentToolContext import com.phodal.shirecore.agent.agenttool.AgentToolResult interface AgentTool { val name: String val description: String fun execute(context: AgentToolContext): AgentToolResult // extension point companion object { private val EP_NAME = ExtensionPointName("com.phodal.shireAgentTool") fun allTools(): List { return EP_NAME.extensionList } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/codeedit/CodeModifier.kt ================================================ package com.phodal.shirecore.provider.codeedit import com.intellij.lang.Language import com.intellij.lang.LanguageExtension import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement /** * The `CodeModifier` interface provides methods for modifying code in a given project. * It allows for inserting test code, methods, and classes into source files. */ interface CodeModifier { /** * Checks if the given code language is applicable. * * @param language The language to check. * @return True if the language is applicable, false otherwise. */ fun isApplicable(language: Language): Boolean /** * According to the source file, project, and code, it will insert the code in a smart way. */ fun smartInsert(sourceFile: VirtualFile, project: Project, code: String): PsiElement? /** * Inserts the provided test code into the specified source file in the given project. * * @param sourceFile The virtual file representing the source file where the test code will be inserted. * @param project The project in which the source file belongs. * @param code The test code to be inserted into the source file. * @return True if the test code was successfully inserted, false otherwise. */ fun insertTestCode(sourceFile: VirtualFile, project: Project, code: String): PsiElement? /** * Inserts a method into the specified source file in the given project. * * @param sourceFile The virtual file representing the source file to insert the method into. * @param project The project in which the source file belongs. * @param code The code of the method to be inserted. * @return `true` if the method was successfully inserted, `false` otherwise. */ fun insertMethod(sourceFile: VirtualFile, project: Project, code: String): PsiElement? /** * Inserts a class into the specified source file in the given project. * * @param sourceFile The virtual file representing the source file to insert the class into. * @param project The project in which the source file belongs. * @param code The code representing the class to be inserted. * @return True if the class was successfully inserted, false otherwise. */ fun insertClass(sourceFile: VirtualFile, project: Project, code: String): PsiElement? companion object { private val languageExtension: LanguageExtension = LanguageExtension("com.phodal.shireCodeModifier") fun forLanguage(language: Language): CodeModifier? { return languageExtension.forLanguage(language) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/codemodel/ClassStructureProvider.kt ================================================ package com.phodal.shirecore.provider.codemodel import com.intellij.lang.LanguageExtension import com.intellij.openapi.diagnostic.logger import com.intellij.psi.PsiElement import com.phodal.shirecore.provider.codemodel.model.ClassStructure /** * The ClassContextBuilder interface provides a method to retrieve the class context for a given PsiElement. * The class context represents the surrounding context of a class, including its imports, package declaration, * and any other relevant information. */ interface ClassStructureProvider { /** * Retrieves the class context for the given [psiElement]. * * @param psiElement the PSI element for which to retrieve the class context * @param gatherUsages specifies whether to gather usages of the class * @return the class context for the given [psiElement], or null if the class context cannot be determined */ fun build(psiElement: PsiElement, gatherUsages: Boolean): ClassStructure? companion object { private val languageExtension = LanguageExtension("com.phodal.classStructureProvider") private val providers: Map = StructureProvider.loadProviders(languageExtension) private val logger = logger() fun from(psiElement: PsiElement, gatherUsages: Boolean = false): ClassStructure? { try { return providers[psiElement.language.id]?.build(psiElement, gatherUsages) } catch (e: Exception) { logger.error("Error while getting class context from", e) } return null } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/codemodel/FileStructureProvider.kt ================================================ package com.phodal.shirecore.provider.codemodel import com.intellij.lang.LanguageExtension import com.intellij.psi.PsiFile import com.phodal.shirecore.provider.codemodel.model.FileStructure interface FileStructureProvider { fun build(psiFile: PsiFile): FileStructure? companion object { private val languageExtension = LanguageExtension("com.phodal.fileStructureProvider") private val providers: Map = StructureProvider.loadProviders(languageExtension) fun from(psiFile: PsiFile): FileStructure? { return providers[psiFile.language.id]?.build(psiFile) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/codemodel/MethodStructureProvider.kt ================================================ package com.phodal.shirecore.provider.codemodel import com.intellij.lang.LanguageExtension import com.intellij.psi.PsiElement import com.phodal.shirecore.provider.codemodel.model.MethodStructure /** * The MethodContextBuilder interface provides a method for retrieving the method context of a given PsiElement. * A method context represents the context in which a method is defined or used within a codebase. * @see MethodStructure */ interface MethodStructureProvider { fun build(psiElement: PsiElement, includeClassContext: Boolean, gatherUsages: Boolean): MethodStructure? companion object { private val languageExtension = LanguageExtension("com.phodal.methodStructureProvider") private val providers: Map = StructureProvider.loadProviders(languageExtension) fun from(psiElement: PsiElement, includeClassContext: Boolean = false, gatherUsages: Boolean = false): MethodStructure? { return providers[psiElement.language.id]?.build(psiElement, includeClassContext, gatherUsages) ?: MethodStructure(psiElement, psiElement.text, null) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/codemodel/StructureProvider.kt ================================================ package com.phodal.shirecore.provider.codemodel import com.intellij.lang.Language import com.intellij.lang.LanguageExtension /** * @author lk */ object StructureProvider { private val registeredLanguages = Language.getRegisteredLanguages() /** * Load providers for different StructureProviders * and return specific providers based on the language. */ fun loadProviders(languageExtension: LanguageExtension): Map { return registeredLanguages.mapNotNull { languageExtension.forLanguage(it)?.run { it.id to this } }.toMap() } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/codemodel/VariableStructureProvider.kt ================================================ package com.phodal.shirecore.provider.codemodel import com.intellij.lang.LanguageExtension import com.intellij.psi.PsiElement import com.phodal.shirecore.provider.codemodel.model.VariableStructure interface VariableStructureProvider { fun build( psiElement: PsiElement, withMethodContext: Boolean, withClassContext: Boolean, gatherUsages: Boolean, ): VariableStructure? companion object { private val languageExtension = LanguageExtension("com.phodal.variableStructureProvider") private val providers: Map = StructureProvider.loadProviders(languageExtension) fun from( psiElement: PsiElement, includeMethodContext: Boolean = false, includeClassContext: Boolean = false, gatherUsages: Boolean = false, ): VariableStructure { return providers[psiElement.language.id]?.build(psiElement, includeMethodContext, includeClassContext, gatherUsages) ?: VariableStructure(psiElement, psiElement.text, null) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/codemodel/base/FormatableElement.kt ================================================ package com.phodal.shirecore.provider.codemodel.base import com.intellij.psi.PsiElement /** * The `FormatableElement` is an abstract class that represents a formatable element in a given context. * It provides information about the root element, text, and name of the element. * * @property root The root element of the context. * @property text The text representation of the context. * @property name The name of the element in the context. * * This class has a method `format()` which formats the named element context into a string representation. * The format of the string representation varies depending on the context. * For instance: * In the context of `com.phodal.shirecore.codemodel.DirectoryStructure`, the formatted string representation will be like a directory structure. * In the context of `com.phodal.shirecore.codemodel.FileStructure`, the formatted string representation will be like a file structure. * In the context of `com.phodal.shirecore.codemodel.ClassStructure`, the formatted string representation will be like UML. * In the context of `com.phodal.shirecore.codemodel.MethodStructure`, the formatted string representation will be just the method signature. * In the context of `com.phodal.shirecore.codemodel.VariableStructure`, the formatted string representation will be like a variable declaration. * * @return The formatted string representation of the named element context. */ abstract class FormatableElement(open val root: PsiElement, open val text: String?, open val name: String?) { /** * Formats the named element context into a string representation. * In [com.phodal.shirecore.codemodel.ClassStructure], the formatted string representation will be like UML.For example: * ```uml * 'package: cc.unitmesh.untitled.demo.controller.UserController * '@RestController, @RequestMapping("/user") * class UserController { * + @GetMapping public UserDTO getUsers() * } * ``` * In [com.phodal.shirecore.codemodel.MethodStructure], * the formatted string representation will be just the method signature.For Example * ```bash * path: /src/test.go * language: Go * fun name: f3 * fun signature: (float64, float64, float64) * ``` * * In [com.phodal.shirecore.codemodel.VariableStructure], the formatted string representation will be like: * ```bash * var name: content * var method name: format * var class name: NamedElementContext * ``` * @return The formatted string representation of the named element context. */ open fun format(): String = "" } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/codemodel/model/ClassStructure.kt ================================================ package com.phodal.shirecore.provider.codemodel.model import com.intellij.openapi.application.runReadAction import com.intellij.psi.PsiElement import com.intellij.psi.PsiReference import com.phodal.shirecore.provider.codemodel.MethodStructureProvider import com.phodal.shirecore.provider.codemodel.VariableStructureProvider import com.phodal.shirecore.provider.codemodel.base.FormatableElement class ClassStructure( override val root: PsiElement, override val text: String?, override val name: String?, val displayName: String?, val methods: List = emptyList(), val fields: List = emptyList(), val superClasses: List? = null, val annotations: List = mutableListOf(), val usages: List = emptyList(), ) : FormatableElement(root, text, name) { private fun getFieldNames(): List = fields.map { VariableStructureProvider.from(it, includeMethodContext = false, includeClassContext = false, gatherUsages = false ).shortFormat() } private fun getMethodSignatures(): List = methods.mapNotNull { MethodStructureProvider.from(it, false, gatherUsages = false)?.signature } override fun format(): String { val className = name ?: "_" val classFields = getFieldNames().joinToString(separator = "\n ") val superClasses = when { superClasses.isNullOrEmpty() -> "" else -> " : ${superClasses.joinToString(separator = ", ")}" } val methodSignatures = getMethodSignatures() .filter { it.isNotBlank() } .joinToString(separator = "\n ") { signature -> "+ $signature" } val filePath = displayName ?: runReadAction { root.containingFile?.virtualFile?.path } val annotations = if (annotations.isEmpty()) { "" } else { "\n'" + annotations.joinToString(separator = ", ") } return """ |'package: $filePath$annotations |class $className$superClasses { | $classFields | $methodSignatures |} """.trimMargin() } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/codemodel/model/DirectoryStructure.kt ================================================ package com.phodal.shirecore.provider.codemodel.model import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiNameIdentifierOwner import com.phodal.shirecore.provider.codemodel.base.FormatableElement class DirectoryStructure( override val root: PsiFile, override val name: String, private val path: String, /** * For some languages, path may not be enough to uniquely identify a package, like in Java, Kotlin, etc. */ private val packageString: String? = null, private val files: List = emptyList(), ) : FormatableElement(root, path, name) { /** * This method is used to format the details of the directory, package, and files into a string. * The formatted string includes the directory name, package name (if available), and the details of each file. * Each file's details include the file name and the names of the classes within the file. * * The method first creates a string representation of the file details. For each file, it appends the file name and the names of the classes within the file. * Then, it constructs the final string by appending the directory name, package name (if available), and the file details. * * @return A string representation of the directory, package, and file details. The string is formatted as follows: * ``` * directory name: /path/to/file * package: com.example * file `file1` classes: [class1, class2] * file `file2` classes: [class3, class4] * ``` * If the package name is not available, it is omitted from the string. If there are no file details, they are also omitted from the string. */ override fun format(): String { val fileDetails = files.joinToString("\n") { structure -> val file = structure.root val classes = structure.classes.mapNotNull { when (it) { is PsiNameIdentifierOwner -> it.name else -> null } }.joinToString(", ") "file `${file.name}` classes: [$classes]" } val filePath = path val filePackage = if (packageString != null) "package: $packageString" else "" return buildString { append("directory name: $filePath\n") if (filePackage.isNotEmpty()) append("$filePackage\n") if (fileDetails.isNotEmpty()) append("$fileDetails\n") } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/codemodel/model/FileStructure.kt ================================================ package com.phodal.shirecore.provider.codemodel.model import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiNameIdentifierOwner import com.phodal.shirecore.provider.codemodel.ClassStructureProvider import com.phodal.shirecore.provider.codemodel.base.FormatableElement class FileStructure( override val root: PsiFile, override val name: String, private val path: String, private val packageString: String? = null, private val imports: List = emptyList(), /// maybe PsiNameIdentifierOwner ? val classes: List = emptyList(), private val methods: List = emptyList(), ) : FormatableElement(root, path, name) { private fun getClassDetail(): List = classes.mapNotNull { ClassStructureProvider.from(it, false)?.format() } override fun format(): String { fun getFieldString(fieldName: String, fieldValue: String): String { return if (fieldValue.isNotBlank()) "$fieldName: $fieldValue" else "" } val filePackage = getFieldString("file package", packageString ?: "") val fileImports = getFieldString( "file imports", if (imports.isNotEmpty()) imports.joinToString(" ", transform = { it.text }) else "" ) val classDetails = getFieldString( "file classes", if (getClassDetail().isNotEmpty()) getClassDetail().joinToString(", ") else "" ) val filePath = getFieldString("file path", path) return buildString { append("file name: $name\n") if (filePackage.isNotEmpty()) append("$filePackage\n") if (fileImports.isNotEmpty()) append("$fileImports\n") if (classDetails.isNotEmpty()) append("$classDetails\n") append("$filePath\n") } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/codemodel/model/MethodStructure.kt ================================================ package com.phodal.shirecore.provider.codemodel.model import com.intellij.psi.PsiElement import com.intellij.psi.PsiReference import com.phodal.shirecore.provider.codemodel.ClassStructureProvider import com.phodal.shirecore.provider.codemodel.base.FormatableElement import com.phodal.shirecore.project.isInProject class MethodStructure( override val root: PsiElement, override val text: String, override val name: String?, val signature: String? = null, private val enclosingClass: PsiElement? = null, val language: String? = null, val returnType: String? = null, val paramNames: List = emptyList(), private val includeClassContext: Boolean = false, private val usages: List = emptyList(), private val fanInOut: List = emptyList(), ) : FormatableElement(root, text, name) { private val classContext: ClassStructure? = if (includeClassContext && enclosingClass != null) { ClassStructureProvider.from(enclosingClass, false) } else { null } override fun format(): String { val usageString = usages.joinToString("\n") { val classFile = it.element.containingFile val useText = it.element.text "${classFile.name} -> $useText" } var query = """ path: ${root.containingFile?.virtualFile?.path ?: "_"} language: ${language ?: "_"} fun name: ${name ?: "_"} fun signature: ${signature ?: "_"} """.trimIndent() if (usageString.isNotEmpty()) { query += "\nusages: \n$usageString" } if (classContext != null) { query += classContext.format() } return query } fun inputOutputString(): String { if (fanInOut.isEmpty()) return "" var result = "" this.fanInOut.forEach { val context: ClassStructure = ClassStructureProvider.from(it, false) ?: return@forEach val element = context.root if (!isInProject(element.containingFile?.virtualFile!!, root.project)) { return@forEach } context.let { classContext -> result += "${classContext.format()}\n" } } if (result.isEmpty()) { return "" } return """ ```uml $result ``` """.trimIndent() } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/codemodel/model/VariableStructure.kt ================================================ package com.phodal.shirecore.provider.codemodel.model import com.intellij.openapi.application.runReadAction import com.intellij.psi.PsiElement import com.intellij.psi.PsiReference import com.phodal.shirecore.provider.codemodel.ClassStructureProvider import com.phodal.shirecore.provider.codemodel.MethodStructureProvider import com.phodal.shirecore.provider.codemodel.base.FormatableElement class VariableStructure( override val root: PsiElement, override val text: String, override val name: String?, private val enclosingMethod: PsiElement? = null, private val enclosingClass: PsiElement?= null, private val usages: List = emptyList(), private val includeMethodContext: Boolean = false, private val includeClassContext: Boolean = false ) : FormatableElement(root, text, name) { private val methodContext: MethodStructure? = if (includeMethodContext && enclosingMethod != null) { MethodStructureProvider.from(enclosingMethod, includeClassContext = false, gatherUsages = false) } else { null } private val classContext: ClassStructure? = if (includeClassContext && enclosingClass != null) { ClassStructureProvider.from(enclosingClass, false) } else { null } fun shortFormat(): String = runReadAction { root.text ?: ""} /** * Returns a formatted string representation of the method. * * The returned string includes the following information: * - The name of the method, or "_" if the name is null. * - The name of the method's context, or "_" if the context is null. * - The name of the class's context, or "_" if the context is null. * * @return A formatted string representation of the method. */ override fun format(): String { return """ var name: ${name ?: "_"} var method name: ${methodContext?.name ?: "_"} var class name: ${classContext?.name ?: "_"} """.trimIndent() } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/complexity/ComplexityProvider.kt ================================================ package com.phodal.shirecore.provider.complexity import com.intellij.lang.Language import com.intellij.lang.LanguageExtension import com.intellij.psi.PsiElement import com.phodal.shirecore.ast.ComplexitySink import com.phodal.shirecore.ast.ComplexityVisitor interface ComplexityProvider { fun process(element: PsiElement): Int fun visitor(sink: ComplexitySink): ComplexityVisitor companion object { private val languageExtension = LanguageExtension("com.phodal.shireComplexityProvider") fun provide(language: Language): ComplexityProvider? { return languageExtension.forLanguage(language) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/context/ActionLocationEditor.kt ================================================ package com.phodal.shirecore.provider.context import com.intellij.ide.DataManager import com.intellij.openapi.actionSystem.CommonDataKeys import com.intellij.openapi.editor.Editor import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.Project import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirecore.variable.template.VariableActionEventDataHolder interface ActionLocationEditor { fun isApplicable(hole: ShireActionLocation): Boolean fun resolve(project: Project, hole: ShireActionLocation): Editor? companion object { private val EP_NAME: ExtensionPointName = ExtensionPointName.create("com.phodal.shireActionLocationEditor") fun provide(project: Project, location: ShireActionLocation? = null): Editor? { if (location == null) { return defaultEditor(project) } val locationEditors = EP_NAME.extensionList.filter { it.isApplicable(location) } if (locationEditors.isNotEmpty()) { return locationEditors.first().resolve(project, location) } val dataContext = DataManager.getInstance().dataContextFromFocus.result val contextEditor = dataContext?.getData(CommonDataKeys.EDITOR) if (contextEditor != null) { return contextEditor } val savedDataContext = VariableActionEventDataHolder.getData()?.dataContext val editor = savedDataContext?.getData(CommonDataKeys.EDITOR) if (editor != null) { return editor } return defaultEditor(project) } fun defaultEditor(project: Project) = FileEditorManager.getInstance(project).selectedTextEditor } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/context/BuildSystemProvider.kt ================================================ package com.phodal.shirecore.provider.context import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.project.Project import com.intellij.serviceContainer.LazyExtensionInstance import com.intellij.util.xmlb.annotations.Attribute import com.phodal.shirecore.variable.toolchain.buildsystem.BuildSystemContext /** * The `BuildSystemProvider` interface represents a provider for build system information. * It provides methods to retrieve the name and version of the build tool being used, as well as the name * and version of the programming language being used. */ abstract class BuildSystemProvider : LazyExtensionInstance() { abstract fun collect(project: Project): BuildSystemContext? @Attribute("implementationClass") var implementationClass: String? = null override fun getImplementationClassName(): String? { return implementationClass } companion object { private val EP_NAME: ExtensionPointName = ExtensionPointName.create("com.phodal.shireBuildSystemProvider") fun provide(project: Project): List { return EP_NAME.extensionList.mapNotNull { it.collect(project) } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/context/BuildTool.kt ================================================ package com.phodal.shirecore.provider.context import com.intellij.execution.configurations.LocatableConfigurationBase import com.intellij.openapi.externalSystem.service.ui.completion.TextCompletionInfo import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile /** * The `BuildTool` interface provides a set of methods for managing build tasks in a project. * It is designed to be implemented by classes that provide specific build tool functionality. * * This interface includes methods for preparing library data, collecting tasks, and configuring run tasks. * * Implementations of this interface are expected to provide specific functionality for different build tools. */ interface BuildTool { fun toolName(): String fun prepareLibraryData(project: Project): List? fun collectTasks(project: Project): List fun configureRun(project: Project, taskName: String, virtualFile: VirtualFile?): LocatableConfigurationBase<*>? } data class CommonLibraryData(val groupId: String?, val artifactId: String?, val version: String?) { fun prettyString(): String { return "$groupId:$artifactId:$version" } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/context/LanguageToolchainProvider.kt ================================================ package com.phodal.shirecore.provider.context import com.intellij.lang.LanguageExtension import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.util.concurrency.annotations.RequiresBackgroundThread import kotlin.reflect.KClass class ToolchainContextItem( val clazz: KClass<*>, var text: String, ) data class ToolchainPrepareContext( val sourceFile: PsiFile?, val element: PsiElement?, val extraItems: List = emptyList(), ) interface LanguageToolchainProvider { fun isApplicable(project: Project, context: ToolchainPrepareContext): Boolean @RequiresBackgroundThread suspend fun collect(project: Project, context: ToolchainPrepareContext): List companion object { private val EP_NAME: LanguageExtension = LanguageExtension("com.phodal.shireLanguageToolchainProvider") suspend fun collectToolchainContext( project: Project, toolchainPrepareContext: ToolchainPrepareContext, ): List { val elements = mutableListOf() toolchainPrepareContext.sourceFile?.language?.let { val provider = EP_NAME.forLanguage(it) if (provider.isApplicable(project, toolchainPrepareContext)) { elements.addAll(provider.collect(project, toolchainPrepareContext)) } } elements.addAll(toolchainPrepareContext.extraItems) return elements.distinctBy { it.text } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/function/ToolchainFunctionProvider.kt ================================================ package com.phodal.shirecore.provider.function import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.project.Project interface ToolchainFunctionProvider { fun isApplicable(project: Project, funcName: String): Boolean fun execute(project: Project, funcName: String, args: List, allVariables: Map): Any companion object { private val EP_NAME: ExtensionPointName = ExtensionPointName("com.phodal.shireToolchainFunctionProvider") fun all(): List { return EP_NAME.extensionList } fun lookup(providerName: String): ToolchainFunctionProvider? { return EP_NAME.extensionList.firstOrNull { it.javaClass.simpleName == providerName } } fun provide(project: Project, funcName: String): ToolchainFunctionProvider? { return EP_NAME.extensionList.firstOrNull { it.isApplicable(project, funcName) } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/http/HttpHandler.kt ================================================ package com.phodal.shirecore.provider.http import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.project.Project interface HttpHandler { fun isApplicable(type: HttpHandlerType): Boolean fun execute(project: Project, content: String, variablesName: Array, variableTable: MutableMap) : String? companion object { private val EP_NAME: ExtensionPointName = ExtensionPointName("com.phodal.shireHttpHandler") fun provide(type: HttpHandlerType): HttpHandler? { return EP_NAME.extensionList.find { it.isApplicable(type) } } } } enum class HttpHandlerType(val id: String) { CURL("cURL"), } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/ide/InlineChatProvider.kt ================================================ package com.phodal.shirecore.provider.ide import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.project.Project interface InlineChatProvider { fun addListener(project: Project) fun removeListener(project: Project) companion object { private val EP_NAME: ExtensionPointName = ExtensionPointName("com.phodal.shireInlineChatProvider") fun provide(): InlineChatProvider? { return EP_NAME.extensionList.firstOrNull() } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/ide/LocationInteractionContext.kt ================================================ package com.phodal.shirecore.provider.ide import com.intellij.execution.ui.ConsoleView import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirecore.config.InteractionType import kotlinx.coroutines.flow.Flow data class LocationInteractionContext( val location: ShireActionLocation, /** * the interaction type */ val interactionType: InteractionType, /** * the LLM generate text stream, which can be used for [InteractionType.AppendCursorStream] */ val streamText: Flow? = null, val editor: Editor?, val project: Project, /** * the [com.phodal.shirecore.llm.ChatMessage] */ val prompt: String, val selectElement: PsiElement? = null, /** * the console view */ val console: ConsoleView?, ) ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/ide/LocationInteractionProvider.kt ================================================ package com.phodal.shirecore.provider.ide import com.intellij.openapi.extensions.ExtensionPointName import com.phodal.shirecore.config.interaction.PostFunction /** * Interface for managing interactions in different IDE locations. * The interactions are categorized into three types: * - Terminal: Appends stream in the IDE terminal * - Editor: Appends stream in the IDE editor * - CommitPanel: Appends stream in the IDE commit panel */ interface LocationInteractionProvider { fun isApplicable(context: LocationInteractionContext): Boolean fun execute(context: LocationInteractionContext, postExecute: PostFunction) companion object { private val EP_NAME: ExtensionPointName = ExtensionPointName("com.phodal.shireLocationInteraction") fun provide(context: LocationInteractionContext): LocationInteractionProvider? { return EP_NAME.extensionList.firstOrNull { it.isApplicable(context) } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/ide/ShirePromptBuilder.kt ================================================ package com.phodal.shirecore.provider.ide import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.project.Project interface ShirePromptBuilder { fun build(project: Project, actionLocation: String, originPrompt: String) : String companion object { private val EP_NAME: ExtensionPointName = ExtensionPointName("com.phodal.shirePromptBuilder") fun provide(): ShirePromptBuilder? { return EP_NAME.extensionList.firstOrNull() } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/impl/MarkdownPsiContextVariableProvider.kt ================================================ package com.phodal.shirecore.provider.impl import com.intellij.openapi.application.ReadAction import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.openapi.util.NlsSafe import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.phodal.shirecore.provider.variable.PsiContextVariableProvider import com.phodal.shirecore.provider.variable.model.PsiContextVariable import org.intellij.markdown.IElementType import org.intellij.markdown.MarkdownElementTypes import org.intellij.markdown.MarkdownTokenTypes import org.intellij.markdown.ast.ASTNode import org.intellij.markdown.flavours.gfm.GFMElementTypes import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor import org.intellij.markdown.flavours.gfm.GFMTokenTypes import org.intellij.markdown.parser.MarkdownParser private val embeddedHtmlType = IElementType("ROOT") class MarkdownPsiContextVariableProvider : PsiContextVariableProvider { override fun resolve(variable: PsiContextVariable, project: Project, editor: Editor, psiElement: PsiElement?): Any { val psiFile = ReadAction.compute { PsiManager.getInstance(project).findFile(editor.virtualFile) } val markdownText = psiFile?.text ?: return "" return when (variable) { PsiContextVariable.STRUCTURE -> { toHtml(markdownText) } else -> "" } } fun toHtml(markdownText: @NlsSafe String): String { val flavour = GFMFlavourDescriptor() val parsedTree = MarkdownParser(flavour).parse(embeddedHtmlType, markdownText) val markdownNode = MarkdownNode(parsedTree, null, markdownText) return markdownNode.toHtml() } private fun MarkdownNode.toHtml(): String { if (node.type == MarkdownTokenTypes.WHITE_SPACE) { return text // do not trim trailing whitespace } val sb = StringBuilder() visit { node, processChildren -> fun wrapChildren(tag: String, level: Int = 0) { sb.append("#".repeat(level)) processChildren() sb.append("\n") } val nodeType = node.type val nodeText = node.text when (nodeType) { MarkdownElementTypes.ATX_1 -> wrapChildren("h1", 1) MarkdownElementTypes.ATX_2 -> wrapChildren("h2" ,2) MarkdownElementTypes.ATX_3 -> wrapChildren("h3", 3) MarkdownElementTypes.ATX_4 -> wrapChildren("h4", 4) MarkdownElementTypes.ATX_5 -> wrapChildren("h5", 5) MarkdownElementTypes.ATX_6 -> wrapChildren("h6", 6) MarkdownTokenTypes.TEXT, MarkdownTokenTypes.WHITE_SPACE, MarkdownTokenTypes.COLON, MarkdownTokenTypes.SINGLE_QUOTE, MarkdownTokenTypes.DOUBLE_QUOTE, MarkdownTokenTypes.LPAREN, MarkdownTokenTypes.RPAREN, MarkdownTokenTypes.LBRACKET, MarkdownTokenTypes.RBRACKET, MarkdownTokenTypes.EXCLAMATION_MARK, GFMTokenTypes.CHECK_BOX, GFMTokenTypes.GFM_AUTOLINK -> { sb.append(nodeText) } MarkdownTokenTypes.ATX_HEADER, MarkdownTokenTypes.ATX_CONTENT -> { processChildren() } else -> { if (nodeType.name == "ROOT") { processChildren() } // processChildren() } } } return sb.toString().trimEnd() } class MarkdownNode(val node: ASTNode, val parent: MarkdownNode?, val content: String) { val children: List = node.children.map { MarkdownNode(it, this, content) } val startOffset: Int get() = node.startOffset val type: IElementType get() = node.type var text: String = content.substring(node.startOffset, node.endOffset) fun child(type: IElementType): MarkdownNode? = children.firstOrNull { it.type == type } } private fun MarkdownNode.visit(action: (MarkdownNode, () -> Unit) -> Unit) { action(this) { for (child in children) { child.visit(action) } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/psi/PsiCapture.kt ================================================ package com.phodal.shirecore.provider.psi import com.intellij.lang.Language import com.intellij.lang.LanguageExtension interface PsiCapture { fun capture(fileContent: String, type: String): List companion object { private val languageExtension: LanguageExtension = LanguageExtension("com.phodal.shirePsiCapture") fun provide(language: Language): PsiCapture? { return languageExtension.forLanguage(language) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/psi/PsiElementDataBuilder.kt ================================================ package com.phodal.shirecore.provider.psi import com.intellij.lang.Language import com.intellij.lang.LanguageExtension import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.phodal.shirecore.provider.codemodel.model.ClassStructure /** * The `PsiElementDataBuilder` interface provides methods for generating test data for a given Kotlin language class. * * It defines the following methods: * - `baseRoute(element: PsiElement): String`: This method is used to lookup the base route of the method for the parent element. * It takes a `PsiElement` as a parameter and returns a `String` representing the base route. If no base route is found, an empty string is returned. * * - `inboundData(element: PsiElement): Map`: This method is used to generate inbound test data for the given element. * It takes a `PsiElement` as a parameter and returns a `Map` representing the inbound test data. The keys in the map represent the data field names, and the values represent the corresponding data values. * If no inbound data is found, an empty map is returned. * * - `outboundData(element: PsiElement): Map`: This method is used to generate outbound test data for the given element. * It takes a `PsiElement` as a parameter and returns a `Map` representing the outbound test data. The keys in the map represent the data field names, and the values represent the corresponding data values. * If no outbound data is found, an empty map is returned. * * The `TestDataBuilder` interface also provides a companion object with the following method: * - `forLanguage(language: Language): TestDataBuilder?`: This method is used to retrieve a `TestDataBuilder` instance for the specified language. * It takes a `Language` as a parameter and returns a `TestDataBuilder` instance associated with the given language. If no instance is found, null is returned. * * Note: The `TestDataBuilder` interface is intended to be implemented by concrete classes that provide the actual implementation for generating test data. * * @see PsiElement * @see Language * @see LanguageExtension */ interface PsiElementDataBuilder { /** * Lookup the base route of the Method for parent */ fun baseRoute(element: PsiElement): String = "" fun inboundData(element: PsiElement): Map = mapOf() fun outboundData(element: PsiElement): Map = mapOf() fun lookupElement(project: Project, canonicalName: String): ClassStructure? = null fun parseComment(project: Project, code: String): String? companion object { private val languageExtension: LanguageExtension = LanguageExtension("com.phodal.shirePsiElementDataBuilder") fun provide(language: Language): PsiElementDataBuilder? { return languageExtension.forLanguage(language) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/psi/PsiElementStrategyBuilder.kt ================================================ package com.phodal.shirecore.provider.psi import com.intellij.lang.Language import com.intellij.lang.LanguageExtension import com.intellij.openapi.project.Project import com.intellij.psi.PsiComment import com.intellij.psi.PsiElement import com.intellij.psi.PsiNameIdentifierOwner import com.phodal.shirecore.provider.codemodel.model.ClassStructure interface PsiElementStrategyBuilder { /** * Looks up a symbol in the given project by its canonical name. * The canonical name is the fully qualified name of the symbol, including the package name. * For example, the canonical name of the class `java.lang.String` is `java.lang.String`. * The canonical name of the method `java.lang.String#length` is `java.lang.String.length()`. * * @param project the project in which to search for the symbol * @param canonicalName the canonical name of the symbol to look up * @return the ClassStructure representing the symbol with the given canonical name, or null if not found */ fun lookupElement(project: Project, canonicalName: String): ClassStructure? /** * Return the relative [PsiElement] with [PsiComment] for givenElement in the given project. */ fun relativeElement(project: Project, givenElement: PsiElement, type: PsiComment): PsiElement? /** * Find the nearest target [PsiNameIdentifierOwner] for the given [PsiElement]. */ fun findNearestTarget(psiElement: PsiElement): PsiNameIdentifierOwner? companion object { private val languageExtension: LanguageExtension = LanguageExtension("com.phodal.shireElementStrategyBuilder") fun forLanguage(language: Language): PsiElementStrategyBuilder? { return languageExtension.forLanguage(language) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/psi/RelatedClassesProvider.kt ================================================ package com.phodal.shirecore.provider.psi import com.intellij.lang.Language import com.intellij.lang.LanguageExtension import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile /** * The `RelatedClassesProvider` interface is used to provide related classes for a given element. * * This interface is particularly useful in languages like Java, where it can analyze elements such as * `PsiMethod` or `PsiField` to find related classes. The analysis includes parameters, return type, and * generic types of the `PsiMethod` to find related classes that are part of the project content. * * The `RelatedClassesProvider` interface also includes methods to clean up unnecessary elements in a `PsiClass`, * find superclasses of a `PsiClass`, and determine if an element is part of the project content. * * The main function of this interface is `lookup(element: PsiElement)`, which is used to look up related classes * for the given method. The function takes a `PsiElement` as an argument and returns a list of `PsiElement` objects * that are related to the input element. * * Note: There is a need to investigate whether this function is also needed for fields or classes. */ interface RelatedClassesProvider { /** * Lookup related classes for the given method. * * todo: spike is need for field or class */ fun lookup(element: PsiElement): List fun lookup(element: PsiFile): List companion object { private val languageExtension: LanguageExtension = LanguageExtension("com.phodal.shireRelatedClass") fun provide(language: Language): RelatedClassesProvider? { return languageExtension.forLanguage(language) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/shire/FileCreateService.kt ================================================ package com.phodal.shirecore.provider.shire import com.intellij.lang.Language import com.intellij.lang.LanguageExtension import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile interface FileCreateService { fun createFile(prompt: String, project: Project): VirtualFile? companion object { private val languageExtension: LanguageExtension = LanguageExtension("com.phodal.shireFileCreateService") fun provide(language: Language): FileCreateService? { return languageExtension.forLanguage(language) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/shire/FileRunService.kt ================================================ package com.phodal.shirecore.provider.shire import com.intellij.execution.RunManager import com.intellij.execution.RunnerAndConfigurationSettings import com.intellij.execution.actions.ConfigurationContext import com.intellij.execution.configurations.GeneralCommandLine import com.intellij.execution.configurations.RunConfiguration import com.intellij.execution.configurations.RunProfile import com.intellij.execution.util.ExecUtil import com.intellij.openapi.application.runReadAction import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.phodal.shirecore.runner.RunServiceTask import java.util.concurrent.CompletableFuture interface FileRunService { private val logger: Logger get() = logger() fun isApplicable(project: Project, file: VirtualFile): Boolean /** * Retrieves the run configuration class for the given project. * * @param project The project for which to retrieve the run configuration class. * @return The run configuration class for the project. */ fun runConfigurationClass(project: Project): Class? /** * Creates a new run configuration for the given project and virtual file. * * 1. Looks up the PSI file from the virtual file using `PsiManager.getInstance(project).findFile(virtualFile)`. * 2. Creates a RunConfigurationSettings instance with the name "name" and the specified RunConfigurationType using `RunManager.getInstance(project).createConfiguration("name", RunConfigurationType)`. * * @param project The project for which to create the run configuration. * @param virtualFile The virtual file to associate with the run configuration. * @return The newly created RunConfiguration, or `null` if creation failed. */ fun createConfiguration(project: Project, virtualFile: VirtualFile): RunConfiguration? = null /** * Creates a new run configuration settings for the given project and virtual file. * * If a configuration with the same name already exists, it will be returned. * Otherwise, a new configuration is created and added to the run manager. * * @param project The project for which the configuration should be created. * @param virtualFile The virtual file for which the configuration should be created. * @return The created or found run configuration settings, or `null` if no suitable configuration could be */ fun createRunSettings( project: Project, virtualFile: VirtualFile, testElement: PsiElement?, ): RunnerAndConfigurationSettings? { if (testElement != null) { val settings = createDefaultConfigurations(project, testElement) if (settings != null) { return settings } } val runManager = RunManager.getInstance(project) var testConfig = runManager.allConfigurationsList.firstOrNull { val runConfigureClass = runConfigurationClass(project) it.name == virtualFile.nameWithoutExtension && (it.javaClass == runConfigureClass) } if (testConfig == null) { testConfig = createConfiguration(project, virtualFile) } if (testConfig == null) { logger.warn("Failed to find test configuration for: ${virtualFile.nameWithoutExtension}") return null } val settings = runManager.findConfigurationByTypeAndName(testConfig.type, testConfig.name) if (settings == null) { logger.warn("Failed to find test configuration for: ${virtualFile.nameWithoutExtension}") return null } settings.isTemporary = true runManager.selectedConfiguration = settings return settings } fun createDefaultConfigurations( project: Project, element: PsiElement, ): RunnerAndConfigurationSettings? { return runReadAction { ConfigurationContext(element).configurationsFromContext?.firstOrNull()?.configurationSettings } } /** * This function is responsible for running a file within a specified project and virtual file. It is a synchronous operation. * [runFileAsync] should be used for asynchronous operations. * * It creates a run configuration using the provided parameters and then attempts to execute it using * the `ExecutionManager`. The function returns `null` if an error occurs during the configuration creation or execution process. * * @param project The project within which the file is to be run. * @param virtualFile The virtual file that represents the file to be run. * @return The result of the run operation, or `null` if an error occurred. */ fun runFile(project: Project, virtualFile: VirtualFile, psiElement: PsiElement?): String? { try { val runTask = RunServiceTask(project, virtualFile, psiElement, this) ProgressManager.getInstance().run(runTask) } catch (e: Exception) { logger.error("Failed to run file: ${virtualFile.name}", e) return e.message } return null } /** * This function is responsible for running a file within a specified project and virtual file asynchronously. * * @param project The project within which the file is to be run. * @param virtualFile The virtual file that represents the file to be run. * @return The result of the run operation, or `null` if an error occurred. */ fun runFileAsync(project: Project, virtualFile: VirtualFile, psiElement: PsiElement?): String? { val future: CompletableFuture = CompletableFuture() try { val runTask = RunServiceTask(project, virtualFile, psiElement, this, future = future) ProgressManager.getInstance().run(runTask) } catch (e: Exception) { logger.error("Failed to run file: ${virtualFile.name}", e) future.completeExceptionally(e) return e.message } return future.get() } companion object { val EP_NAME: ExtensionPointName = ExtensionPointName("com.phodal.shireFileRunService") fun provider(project: Project, file: VirtualFile): FileRunService? { val fileRunServices = EP_NAME.extensionList return fileRunServices.firstOrNull { runReadAction { it.isApplicable(project, file) } } } fun runInCli(project: Project, psiFile: PsiFile, args: List? = null): String? { val commandLine = when (psiFile.language.displayName.lowercase()) { "python" -> GeneralCommandLine("python3", psiFile.virtualFile.path) "javascript" -> GeneralCommandLine("node", psiFile.virtualFile.path) "ecmascript 6" -> GeneralCommandLine("node", psiFile.virtualFile.path) "ruby" -> GeneralCommandLine("ruby", psiFile.virtualFile.path) "shell script" -> GeneralCommandLine("sh", psiFile.virtualFile.path) // kotlin script, `kotlinc -script hello.kts` "kotlin" -> GeneralCommandLine("kotlinc", "-script", psiFile.virtualFile.path) else -> { logger().warn("Unsupported language: ${psiFile.language.displayName}") return null } } if (args != null) { commandLine.addParameters(args) } commandLine.setWorkDirectory(project.basePath) return try { val output = ExecUtil.execAndGetOutput(commandLine) output.stdout } catch (e: Exception) { e.printStackTrace() e.message } } fun runInCli(project: Project, virtualFile: VirtualFile, args: List? = null): String? { val psiFile = runReadAction { PsiManager.getInstance(project).findFile(virtualFile) } ?: return null return runInCli(project, psiFile, args) } /** * We will handle Shire UnSupported FileType here */ fun retryRun(project: Project, virtualFile: VirtualFile, args: List? = null): String? { val defaultRunService = object : FileRunService { override fun isApplicable(project: Project, file: VirtualFile): Boolean = true override fun runConfigurationClass(project: Project): Class? = null } val file = runReadAction { PsiManager.getInstance(project).findFile(virtualFile) } defaultRunService.createRunSettings(project, virtualFile, file) ?: return null return defaultRunService.runFile(project, virtualFile, null) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/shire/ProjectRunService.kt ================================================ package com.phodal.shirecore.provider.shire import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.lookup.LookupElement import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.project.Project interface ProjectRunService { fun isAvailable(project: Project): Boolean fun run(project: Project, taskName: String) /** * Return List of available tasks * * This function takes in a Project, CompletionParameters, and CompletionResultSet as parameters * and returns a Map of LookupElement to Priority. It is used to lookup available tasks. * * @param project the project in which the tasks are available * @param parameters the completion parameters for the task lookup * @param result the completion result set to store the lookup results * @return a Map of LookupElement to Priority representing the available tasks */ fun lookupAvailableTask( project: Project, parameters: CompletionParameters, result: CompletionResultSet, ): List { return listOf() } fun tasks(project: Project): List { return listOf() } companion object { val EP_NAME: ExtensionPointName = ExtensionPointName("com.phodal.shireRunProjectService") fun all(): List { return EP_NAME.extensionList } fun provider(project: Project): ProjectRunService? { val projectRunServices = EP_NAME.extensionList return projectRunServices.firstOrNull { it.isAvailable(project) } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/shire/RefactoringTool.kt ================================================ package com.phodal.shirecore.provider.shire import com.intellij.lang.Language import com.intellij.lang.LanguageExtension import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.command.CommandProcessor import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiNamedElement import com.intellij.psi.util.PsiUtilCore import com.intellij.refactoring.RefactoringBundle import com.intellij.refactoring.listeners.RefactoringElementListener import com.intellij.refactoring.rename.RenameProcessor import com.intellij.refactoring.rename.RenameUtil import com.intellij.refactoring.rename.naming.AutomaticRenamerFactory import com.intellij.refactoring.util.CommonRefactoringUtil import com.intellij.usageView.UsageInfo import com.intellij.util.ThrowableRunnable /** * RefactoringTool is an interface that defines operations for performing various code refactoring tasks. * It provides functionality to work with PsiFiles and PsiElements, such as looking up files, renaming, * safely deleting elements, and moving elements or packages to a new location. */ interface RefactoringTool { /** * Looks up and retrieves a PsiFile from the given file path. * * @param path The path of the file to be looked up. It should be a valid file path within the project. * @return A PsiFile object if the file is found and successfully loaded, or null if the file doesn't exist or cannot be loaded. */ fun lookupFile(path: String): PsiFile? /** * Renames a given source name to a target name within the provided PSI file. * * @param sourceName The original name to be renamed. * @param targetName The new name to replace the original name with. * @param psiFile The PSI file where the renaming will take place; can be null if not applicable. * @return A boolean value indicating whether the renaming was successful. Returns true if the * renaming was successful, false otherwise. */ fun rename(sourceName: String, targetName: String, psiFile: PsiFile?): Boolean /** * Deletes the given PsiElement in a safe manner, ensuring that no syntax errors or unexpected behavior occur as a result. * The method performs checks before deletion to confirm that it is safe to remove the element from the code structure. * * @param element The PsiElement to be deleted. This should be a valid element within the PSI tree structure. * @return true if the element was successfully deleted without any issues, false otherwise. This indicates whether * the deletion was performed and considered safe. */ fun safeDelete(element: PsiElement): Boolean /** * Performs a refactoring rename operation on a given PSI element within the specified project. * The method iterates through available renamer factories to find the appropriate one for the * element to be renamed. It then collects usages of the element, checks read-only status, * and performs the rename operation on all found usages, including those in comments if applicable. * * @param myProject The project in which the refactoring operation is taking place. * @param elementToRename The PSI element (which must implement PsiNamedElement) that is to be renamed. * @param newName The new name to assign to the element and its usages. * * Note: The method uses ProgressManager to search for usages and may prompt the user for read-only * file access. It also uses ApplicationManager to schedule the rename operation on the EDT. */ fun performRefactoringRename(myProject: Project, elementToRename: PsiNamedElement, newName: String) { for (renamerFactory in AutomaticRenamerFactory.EP_NAME.extensionList) { if (!renamerFactory.isApplicable(elementToRename)) continue val usages: List = ArrayList() val renamer = renamerFactory.createRenamer(elementToRename, newName, ArrayList()) if (!renamer.hasAnythingToRename()) continue val runnable = Runnable { ApplicationManager.getApplication().runReadAction { renamer.findUsages(usages, false, false) } } if (!ProgressManager.getInstance().runProcessWithProgressSynchronously( runnable, RefactoringBundle.message("searching.for.variables"), true, myProject ) ) { return } if (!CommonRefactoringUtil.checkReadOnlyStatus( myProject, *PsiUtilCore.toPsiElementArray(renamer.elements) ) ) return val performAutomaticRename = ThrowableRunnable { CommandProcessor.getInstance().markCurrentCommandAsGlobal(myProject) val classified = RenameProcessor.classifyUsages(renamer.elements, usages) for (element in renamer.elements) { val newElementName = renamer.getNewName(element) if (newElementName != null) { val infos = classified[element] RenameUtil.doRename( element, newElementName, infos.toTypedArray(), myProject, RefactoringElementListener.DEAF ) } } } ApplicationManager.getApplication().invokeLater { WriteCommandAction.writeCommandAction(myProject) .withName("Rename").run(performAutomaticRename) } } } /** * In Java the canonicalName is the fully qualified name of the target package. * In Kotlin the canonicalName is the fully qualified name of the target package or class. */ fun move(element: PsiElement, canonicalName: String): Boolean companion object { private val languageExtension: LanguageExtension = LanguageExtension("com.phodal.shireRefactoringTool") fun forLanguage(language: Language): RefactoringTool? { val refactoringTool = languageExtension.forLanguage(language) if (refactoringTool != null) { return refactoringTool } // If no refactoring tool is found for the specified language, return java val javaLanguage = Language.findLanguageByID("JAVA") ?: return null return languageExtension.forLanguage(javaLanguage) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/shire/RevisionProvider.kt ================================================ package com.phodal.shirecore.provider.shire import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement interface RevisionProvider { /** * Fetches the changes made in the specified revision in the repository of the given project. * * @param myProject the project in which the Git repository is located * @param revision the revision for which changes need to be fetched * @return a String containing the changes in the specified revision in a unified diff format, * or null if the repository is not found */ fun fetchChanges(myProject: Project, revision: String): String? fun fetchCompletions(project: Project, result: CompletionResultSet) fun commitCode(project: Project, commitMessage: String): String fun countHistoryChange(project: Project, element: PsiElement): Int companion object { private val EP_NAME: ExtensionPointName = ExtensionPointName("com.phodal.shireRevisionProvider") fun provide(): RevisionProvider? { return EP_NAME.extensionList.firstOrNull() } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/shire/ShireQLDataProvider.kt ================================================ package com.phodal.shirecore.provider.shire import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.project.Project import com.phodal.shirecore.variable.vcs.GitEntity import com.phodal.shirecore.variable.vcs.ShireGitCommit enum class ShireQLDataType(val dataKey: String) { GIT_COMMIT("GitCommit"), GIT_BRANCH("GitBranch"), GIT_FILE_COMMIT("GitFileCommit"), GIT_FILE_BRANCH("GitFileBranch") } interface ShireQLDataProvider { fun lookupGitData(myProject: Project, dataTypes: List): Map?> fun lookup(myProject: Project, variableType: String): List? { return when (variableType) { ShireQLDataType.GIT_COMMIT.dataKey -> { return lookupGitData(myProject, listOf(ShireQLDataType.GIT_COMMIT))[ShireQLDataType.GIT_COMMIT] as List? } else -> { null } } } companion object { private val EP_NAME: ExtensionPointName = ExtensionPointName("com.phodal.shireQLDataProvider") fun all(): List { return EP_NAME.extensionList } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/shire/ShireSymbolProvider.kt ================================================ package com.phodal.shirecore.provider.shire import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.lookup.LookupElement import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.PsiNamedElement /** * The symbol provider for Shire completion and execution * - Completion will be triggered by like `/symbol:`, and the symbol provider will provide the completion for the symbol. * - Execution will be triggered by like `/symbol:java.lang.String`, all load children level elements, like `java.lang.String#length()` * * For execution, see in [ShireSymbolProvider.resolveSymbol] */ interface ShireSymbolProvider { val language: String /** * Lookup canonical name for different language */ fun lookupSymbol( project: Project, parameters: CompletionParameters, result: CompletionResultSet, ): List fun lookupElementByName(project: Project, name: String): List? /** * Resolves the symbol for different programming languages. * For example, in Java: * - If the parent is Root, the children will be packages * - If the parent is Package, the children will be classes * - If the parent is Class, the children will be methods and fields * * Format: `java.lang.String#length`, means: * - `.#` * - `.#` * - `.#` * */ fun resolveSymbol(project: Project, symbol: String): List companion object { private val EP_NAME: ExtensionPointName = ExtensionPointName("com.phodal.shireSymbolProvider") fun all(): List { return EP_NAME.extensionList } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/sketch/LanguageSketchProvider.kt ================================================ package com.phodal.shirecore.provider.sketch import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.project.Project import com.phodal.shirecore.sketch.LangSketch interface ExtensionLangSketch: LangSketch { fun getExtensionName(): String } interface LanguageSketchProvider { fun isSupported(lang: String): Boolean fun create(project: Project, content: String): ExtensionLangSketch companion object { private val EP_NAME: ExtensionPointName = ExtensionPointName("com.phodal.shireLangSketchProvider") fun provide(language: String): LanguageSketchProvider? { val lang = language.lowercase() return EP_NAME.extensionList.firstOrNull { it.isSupported(lang) } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/streaming/LoggingStreamingService.kt ================================================ package com.phodal.shirecore.provider.streaming import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.WriteAction import com.intellij.openapi.application.runInEdt import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.testFramework.LightVirtualFile import com.phodal.shirecore.LLM_LOGGING import com.phodal.shirecore.LLM_LOGGING_JSONL import com.phodal.shirecore.ShireConstants import com.phodal.shirecore.llm.ChatMessage import com.phodal.shirecore.llm.ChatRole import com.phodal.shirecore.runner.console.ShireConsoleViewBase import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json /** * The `LoggingStreamingService` class is an implementation of the `StreamingServiceProvider` interface. * It provides functionality to log streaming data to a file within a project's directory. * * ### Properties: * - `name`: A string that represents the name of the streaming service, initialized to "logging". * - `result`: A private string that accumulates the streaming data received. */ class LoggingStreamingService : StreamingServiceProvider { private var outputDir: VirtualFile = LightVirtualFile() override var name: String = "logging" private var result: String = "" private var userPrompt: String = "" override fun onBeforeStreaming(project: Project, userPrompt: String, console: ShireConsoleViewBase?) { this.userPrompt = userPrompt this.outputDir = ShireConstants.outputDir(project) ?: throw IllegalStateException("Project directory not found") if (outputDir.findChild(LLM_LOGGING) == null) { ApplicationManager.getApplication().invokeAndWait { WriteAction.compute { outputDir.createChildData(this, LLM_LOGGING) } } } else { runInEdt { val file = outputDir.findChild(LLM_LOGGING) runWriteAction { file?.setBinaryContent(ByteArray(0)) } } } if (outputDir.findChild(LLM_LOGGING_JSONL) == null) { ApplicationManager.getApplication().invokeAndWait { WriteAction.compute { outputDir.createChildData(this, LLM_LOGGING_JSONL) } } } } override fun onStreaming(project: Project, flow: String, args: List) { result += flow val virtualFile = outputDir.findChild(LLM_LOGGING) val file = virtualFile?.path?.let { java.io.File(it) } file?.appendText(flow) } override fun afterStreamingDone(project: Project) { ApplicationManager.getApplication().invokeAndWait { WriteAction.compute { val virtualFile = outputDir.createChildData(this, LLM_LOGGING_JSONL) val file = java.io.File(virtualFile.path) val value: List = listOf( ChatMessage(ChatRole.user, userPrompt), ChatMessage(ChatRole.system, result) ) val result = Json.encodeToString>(value) file.appendText(result) file.appendText("\n") virtualFile } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/streaming/OnStreamingService.kt ================================================ package com.phodal.shirecore.provider.streaming import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.Service import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.phodal.shirecore.ShirelangNotifications import com.phodal.shirecore.middleware.post.LifecycleProcessorSignature import com.phodal.shirecore.runner.console.ShireConsoleViewBase /** * The OnStreamingService class is responsible for managing all the [StreamingServiceProvider] instances related to streaming services. * It offers methods for registering, clearing, and initiating streaming services within the application. * * This class is annotated with the @Service annotation at the project level, indicating its role in the service management infrastructure. * * The class maintains a mutable map to associate [LifecycleProcessorSignature] objects with corresponding [StreamingServiceProvider] instances. * It also holds an optional reference to a console view object that can be used for outputting information to the user. */ @Service(Service.Level.PROJECT) class OnStreamingService { val map = mutableMapOf() var console: ShireConsoleViewBase? = null fun registerStreamingService(sign: LifecycleProcessorSignature, console: ShireConsoleViewBase?) { this.console = console val streamingService = StreamingServiceProvider.getStreamingService(sign.funcName) if (streamingService != null) { map[sign] = streamingService streamingService.onCreated(console) } } fun clearStreamingService() { map.clear() } fun all(): List { return StreamingServiceProvider.all() } fun onStart(project: Project, userPrompt: String) { map.forEach { (_, service) -> try { service.onBeforeStreaming(project, userPrompt, console) } catch (e: Exception) { ShirelangNotifications.error(project, "Error on start streaming service: ${e.message}") } } } fun onStreaming(project: Project, chunk: String) { map.forEach { (sign, service) -> try { service. onStreaming(project, chunk, sign.args) } catch (e: Exception) { ShirelangNotifications.error(project, "Error on streaming service: ${e.message}") } } } fun onDone(project: Project) { map.forEach { (_, service) -> try { service.afterStreamingDone(project) } catch (e: Exception) { ShirelangNotifications.error(project, "Error on done streaming service: ${e.message}") } } } fun onStreamingError() { // todo } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/streaming/ProfilingStreamingService.kt ================================================ package com.phodal.shirecore.provider.streaming import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.openapi.project.Project import com.intellij.util.io.IOUtil import com.phodal.shirecore.ShirelangNotifications import com.phodal.shirecore.runner.console.ShireConsoleViewBase /** * The ProfilingStreamingService class is a concrete implementation of the StreamingServiceProvider interface. * It provides profiling capabilities during the streaming process, outputting memory usage information to the console. */ class ProfilingStreamingService : StreamingServiceProvider { override var name: String = "profiling" private var console: ShireConsoleViewBase? = null override fun onBeforeStreaming(project: Project, userPrompt: String, console: ShireConsoleViewBase?) { this.console = console console?.print("Start profiling: ${getMemory()}", ConsoleViewContentType.SYSTEM_OUTPUT) } override fun afterStreamingDone(project: Project) { console?.print("End profiling: ${getMemory()}", ConsoleViewContentType.SYSTEM_OUTPUT) ShirelangNotifications.info(project, "Memory: ${getMemory()}MB") } private fun getMemory(): Long { val runtime = Runtime.getRuntime() val allocatedMem = runtime.totalMemory() val usedMem = allocatedMem - runtime.freeMemory() return toMb(usedMem) } private fun toMb(value: Long): Long { return value / IOUtil.MiB } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/streaming/StreamingServiceProvider.kt ================================================ package com.phodal.shirecore.provider.streaming import com.intellij.openapi.Disposable import com.intellij.openapi.project.Project import com.phodal.shirecore.runner.console.ShireConsoleViewBase /** * all - Returns a list of all registered StreamingServiceProvider implementations. * * @return A list of StreamingServiceProvider instances. */ interface StreamingServiceProvider : Disposable { var name: String /** * When create the service, you can do some initialization here, like start timer, etc. */ fun onCreated(console: ShireConsoleViewBase?) { /// do nothing } /** * For the start of the LLM streaming, you can do some initialization here, for example, you can create a file to log the data */ fun onBeforeStreaming(project: Project, userPrompt: String, console: ShireConsoleViewBase?) { /// do nothing } /** * For the streaming data, you can do some processing here, for example, you can log the data to a file */ fun onStreaming(project: Project, flow: String, args: List) { /// do nothing } /** * For the end of the streaming, for example, you can do some cleanup here, or show some notification */ fun afterStreamingDone(project: Project) { /// do nothing } override fun dispose() { /// do nothing } companion object { val EP_NAME = com.intellij.openapi.extensions.ExtensionPointName.create("com.phodal.shireStreamingService") fun getStreamingService(name: String): StreamingServiceProvider? { return EP_NAME.extensions.firstOrNull { it.name == name } } fun all(): List { return EP_NAME.extensions.toList() } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/streaming/TimingStreamingService.kt ================================================ package com.phodal.shirecore.provider.streaming import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.openapi.project.Project import com.phodal.shirecore.ShirelangNotifications import com.phodal.shirecore.runner.console.ShireConsoleViewBase /** * Logging start time and end time for each lifecycle */ class TimingStreamingService : StreamingServiceProvider { override var name: String = "timing" private var time: Long = 0 private var console: ShireConsoleViewBase? = null override fun onCreated(console: ShireConsoleViewBase?) { this.console = console val currentTime = System.currentTimeMillis() time = currentTime // new line console?.print("\n", ConsoleViewContentType.SYSTEM_OUTPUT) console?.print("Start timing: $currentTime \n", ConsoleViewContentType.SYSTEM_OUTPUT) } override fun afterStreamingDone(project: Project) { val currentTime = System.currentTimeMillis() console?.print("\n", ConsoleViewContentType.SYSTEM_OUTPUT) console?.print("End timing: $currentTime \n", ConsoleViewContentType.SYSTEM_OUTPUT) ShirelangNotifications.info(project, "Timing: ${currentTime - time}ms") } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/variable/PsiContextVariableProvider.kt ================================================ package com.phodal.shirecore.provider.variable import com.intellij.lang.Language import com.intellij.lang.LanguageExtension import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.phodal.shirecore.provider.complexity.ComplexityProvider import com.phodal.shirecore.provider.context.LanguageToolchainProvider import com.phodal.shirecore.provider.context.ToolchainPrepareContext import com.phodal.shirecore.provider.shire.RevisionProvider import com.phodal.shirecore.provider.variable.impl.DefaultPsiContextVariableProvider import com.phodal.shirecore.provider.variable.model.PsiContextVariable import kotlinx.coroutines.runBlocking import java.util.concurrent.CompletableFuture /** * Resolve variables for code struct generation. * This is used to provide the variables that are used in the code struct generation. */ interface PsiContextVariableProvider : VariableProvider { /** * Calculate the value for the given variable based on the provided PsiElement. * * @param psiElement the PsiElement to use for resolving the variable value * @param variable the PsiVariable for which to calculate the value * @return the calculated value for the variable as a String */ override fun resolve(variable: PsiContextVariable, project: Project, editor: Editor, psiElement: PsiElement?): Any fun collectFrameworkContext(psiElement: PsiElement?, project: Project): String { val future = CompletableFuture() runBlocking { val prepareContext = ToolchainPrepareContext(psiElement?.containingFile, psiElement) val contextItems = LanguageToolchainProvider.collectToolchainContext(project, prepareContext) future.complete(contextItems.joinToString("\n") { it.text }) } return future.get() } fun calculateChangeCount(psiElement: PsiElement?): String { return RevisionProvider.provide()?.countHistoryChange(psiElement?.project!!, psiElement).toString() } fun calculateLineCount(psiElement: PsiElement?): String { return psiElement?.containingFile?.text?.lines()?.size.toString() } fun calculateComplexityCount(psiElement: PsiElement?): String { if (psiElement?.language == null) { return "0" } return ComplexityProvider.provide(psiElement.language)?.process(psiElement).toString() } companion object { private val languageExtension: LanguageExtension = LanguageExtension("com.phodal.shirePsiVariableProvider") fun provide(language: Language): PsiContextVariableProvider { return languageExtension.forLanguage(language) ?: DefaultPsiContextVariableProvider() } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/variable/ShireQLInterpreter.kt ================================================ package com.phodal.shirecore.provider.variable import com.intellij.lang.Language import com.intellij.lang.LanguageExtension import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement /** * For [com.phodal.shirelang.compiler.hobbit.execute.PsiQueryStatementProcessor] */ interface ShireQLInterpreter { fun supportsMethod(language: Language, methodName: String): List /** * clazz.getName() or clazz.extensions */ fun resolveCall(element: PsiElement, methodName: String, arguments: List): Any /** * parentOf or childOf or anyOf ? */ fun resolveOfTypedCall(project: Project, methodName: String, arguments: List): Any companion object { private val languageExtension: LanguageExtension = LanguageExtension("com.phodal.shirePsiQLInterpreter") fun provide(language: Language): ShireQLInterpreter? { return languageExtension.forLanguage(language) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/variable/ToolchainVariableProvider.kt ================================================ package com.phodal.shirecore.provider.variable import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.phodal.shirecore.provider.variable.model.ToolchainVariable interface ToolchainVariableProvider : VariableProvider { fun isResolvable(variable: ToolchainVariable, psiElement: PsiElement?, project: Project): Boolean companion object { private val EP_NAME: ExtensionPointName = ExtensionPointName("com.phodal.shireToolchainVariableProvider") fun all(): List { return EP_NAME.extensionList } fun provide(variable: ToolchainVariable, element: PsiElement?, project: Project): ToolchainVariableProvider? { return EP_NAME.extensionList.firstOrNull { it.isResolvable(variable, element, project) } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/variable/VariableProvider.kt ================================================ package com.phodal.shirecore.provider.variable import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement interface VariableProvider { fun resolve(variable: T, project: Project, editor: Editor, psiElement: PsiElement?,): Any } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/variable/impl/DefaultPsiContextVariableProvider.kt ================================================ package com.phodal.shirecore.provider.variable.impl import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.phodal.shirecore.provider.variable.PsiContextVariableProvider import com.phodal.shirecore.provider.variable.model.PsiContextVariable import com.phodal.shirecore.provider.variable.model.PsiContextVariable.* import com.phodal.shirecore.psi.CodeSmellCollector class DefaultPsiContextVariableProvider : PsiContextVariableProvider { override fun resolve( variable: PsiContextVariable, project: Project, editor: Editor, psiElement: PsiElement?, ): String { return when (variable) { FRAMEWORK_CONTEXT -> return collectFrameworkContext(psiElement, project) CHANGE_COUNT -> return calculateChangeCount(psiElement) LINE_COUNT -> return calculateLineCount(psiElement) COMPLEXITY_COUNT -> return calculateComplexityCount(psiElement) CODE_SMELL -> return CodeSmellCollector.collectElementProblemAsSting(psiElement!!, project, editor) else -> "" } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/variable/model/ConditionPsiVariable.kt ================================================ package com.phodal.shirecore.provider.variable.model enum class ConditionPsiVariable( override val variableName: String, override val description: String, override var value: Any? = null, ) : Variable { FILE_PATH("filePath", "The path of the file"), FILE_NAME("fileName", "The name of the file"), FILE_EXTENSION("fileExtension", "The extension of the file"), FILE_CONTENT("fileContent", "The content of the file") ; companion object { fun from(variableName: String): ConditionPsiVariable? { return values().find { it.variableName == variableName } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/variable/model/ContextVariable.kt ================================================ package com.phodal.shirecore.provider.variable.model enum class ContextVariable( override val variableName: String, override val description: String, override var value: Any? = "", ) : Variable { SELECTION("selection", "User selection code/element's in text"), SELECTION_WITH_NUM("selectionWithNum", "User selection code/element's in text with line number"), BEFORE_CURSOR("beforeCursor", "All the text before the cursor"), AFTER_CURSOR("afterCursor", "All the text after the cursor"), FILE_NAME("fileName", "The name of the file"), FILE_PATH("filePath", "The path of the file"), METHOD_NAME("methodName", "The name of the method"), LANGUAGE("language", "The language of the current file, will use IntelliJ's language ID"), COMMENT_SYMBOL("commentSymbol", "The comment symbol of the language, for example, `//` in Java"), ALL("all", "All the text") ; companion object { fun from(variableName: String): ContextVariable? { return values().find { it.variableName == variableName } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/variable/model/PsiContextVariable.kt ================================================ package com.phodal.shirecore.provider.variable.model /** * Represents the context variables that can be used in the code structure generation process. * * @property variableName the name of the context variable */ enum class PsiContextVariable( override val variableName: String, override val description: String, override var value: Any? = null, ) : Variable { /** * Represents the PsiNameIdentifierOwner of the current class, used to retrieve the class name. */ CURRENT_CLASS_NAME("currentClassName", "The name of the current class"), /** * Represents the input and output of PsiElement and PsiFile. */ CURRENT_CLASS_CODE("currentClassCode", "The code of the current class"), CURRENT_METHOD_NAME("currentMethodName", "The name of the current method"), CURRENT_METHOD_CODE("currentMethodCode", "The code of the current method"), /** * Represents the input and output of PsiElement and PsiFile. */ RELATED_CLASSES("relatedClasses", "The related classes based on the AST analysis"), /** * Uses TfIDF to search for similar test cases in the code. */ SIMILAR_TEST_CASE("similarTestCase", "The similar test cases based on the TfIDF analysis"), /** * Represents the import statements required for the code structure. */ IMPORTS("imports", "The import statements required for the code structure"), /** * Flag indicating whether the code structure is being generated in a new file. */ IS_NEED_CREATE_FILE( "isNeedCreateFile", "Flag indicating whether the code structure is being generated in a new file" ), /** * The name of the target test file where the code structure will be generated. */ TARGET_TEST_FILE_NAME( "targetTestFileName", "The name of the target test file where the code structure will be generated" ), /** * underTestMethod */ UNDER_TEST_METHOD_CODE("underTestMethodCode", "The code of the method under test"), /** * Represents the framework information required for the code structure. */ FRAMEWORK_CONTEXT("frameworkContext", "The framework information in dependencies of current project"), /** * codeSmell */ CODE_SMELL("codeSmell", "Include psi error and warning"), METHOD_CALLER("methodCaller", "The method that initiates the current call"), CALLED_METHOD("calledMethod", "The method that is being called by the current method"), SIMILAR_CODE("similarCode", "Recently 20 files similar code based on the tf-idf search"), STRUCTURE("structure", "The structure of the current class, for programming language will be in UML format."), /** * Represents the number of changes in the current file. */ CHANGE_COUNT("changeCount", "The number of changes in the current file"), /** * Represents the number of lines in the current file. */ LINE_COUNT("lineCount", "The number of lines in the current file"), /** * Represents the complexity of the current file. */ COMPLEXITY_COUNT("complexityCount", "The complexity of the current file") ; companion object { /** * Returns the PsiVariable with the given variable name. * * @param variableName the variable name to search for * @return the PsiVariable with the given variable name */ fun from(variableName: String): PsiContextVariable? { return entries.firstOrNull { it.variableName == variableName } } fun all(): List = entries } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/variable/model/SystemInfoVariable.kt ================================================ package com.phodal.shirecore.provider.variable.model import com.intellij.openapi.application.ApplicationInfo import java.util.* enum class SystemInfoVariable( override val variableName: String, override val description: String, ) : ToolchainVariable { OS_NAME("os.name", "The name of the operating system") { override var value: Any? get() = System.getProperty("os.name") set(_) {} }, OS_VERSION("os.version", "The version of the operating system") { override var value: Any? get() = System.getProperty("os.version") set(_) {} }, OS_ARCH("os.arch", "The architecture of the operating system") { override var value: Any? get() = System.getProperty("os.arch") set(_) {} }, IDE_NAME("ide.name", "The name of the IDE") { override var value: Any? get() = System.getProperty("idea.platform.prefix", "idea") set(_) {} }, IDE_VERSION("ide.version", "The version of the IDE") { override var value: Any? get() = ApplicationInfo.getInstance().build.asString() set(_) {} }, IDE_CODE("ide.code", "The code of the IDE") { override var value: Any? get() = ApplicationInfo.getInstance().build.productCode set(_) {} }, TIMEZONE("timezone", "The timezone") { override var value: Any? get() = TimeZone.getDefault().displayName set(_) {} }, DATE("date", "The current date") { override var value: Any? get() = Calendar.getInstance().time set(_) {} }, TODAY("today", "Today's date") { override var value: Any? get() = Calendar.getInstance().time set(_) {} }, NOW("now", "The current time in milliseconds") { override var value: Any? get() = System.currentTimeMillis() set(_) {} }, LOCALE("locale", "The default locale") { override var value: Any? get() = Locale.getDefault().toString() set(_) {} }; companion object { fun from(variableName: String): SystemInfoVariable? { return values().firstOrNull { it.variableName == variableName } } fun all(): List { return values().toList() } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/variable/model/ToolchainVariable.kt ================================================ package com.phodal.shirecore.provider.variable.model import org.reflections.Reflections import kotlin.reflect.KClass import kotlin.reflect.full.companionObjectInstance import kotlin.reflect.full.declaredFunctions import kotlin.reflect.full.functions interface ToolchainVariable : Variable { companion object { private val subclasses: Set> by lazy { val reflections = Reflections("com.phodal.shirecore.provider.variable.model") reflections.getSubTypesOf(ToolchainVariable::class.java) .map { it.kotlin } .toSet() } fun from(variableName: String): ToolchainVariable? { for (subclass in subclasses) { val companion = subclass.companionObjectInstance ?: continue val fromFunction = companion::class.declaredFunctions.find { it.name == "from" } ?: continue val result = fromFunction.call(companion, variableName) as? ToolchainVariable if (result != null) { return result } } return null } fun all(): List { val allVariables = mutableListOf() for (subclass in subclasses) { val valuesFunction = subclass.functions.find { it.name == "values" } ?: continue val enumConstants = valuesFunction.call() as Array allVariables.addAll(enumConstants) } return allVariables } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/variable/model/Variable.kt ================================================ package com.phodal.shirecore.provider.variable.model import com.phodal.shirecore.provider.variable.model.toolchain.* interface Variable { val variableName: String val description: String var value: Any? } data class DebugValue( override val variableName: String, override var value: Any?, override val description: String, ) : Variable { companion object { fun description(key: String): String { return PsiContextVariable.from(key)?.description ?: ContextVariable.from(key)?.description ?: SystemInfoVariable.from(key)?.description // ?: ConditionPsiVariable.from(key)?.description /// ... ?: DatabaseToolchainVariable.from(key)?.description ?: TerminalToolchainVariable.from(key)?.description ?: VcsToolchainVariable.from(key)?.description ?: BuildToolchainVariable.from(key)?.description ?: SonarqubeVariable.from(key)?.description ?: "Unknown" } fun all(): List { val allVariables = mutableListOf() allVariables.addAll(ContextVariable.values()) allVariables.addAll(PsiContextVariable.all()) allVariables.addAll(SystemInfoVariable.values()) // allVariables.addAll(ConditionPsiVariable.values()) /// ... allVariables.addAll(DatabaseToolchainVariable.values()) allVariables.addAll(TerminalToolchainVariable.values()) allVariables.addAll(VcsToolchainVariable.values()) allVariables.addAll(BuildToolchainVariable.values()) allVariables.addAll(SonarqubeVariable.values()) return allVariables } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/variable/model/toolchain/BuildToolchainVariable.kt ================================================ package com.phodal.shirecore.provider.variable.model.toolchain import com.phodal.shirecore.provider.variable.model.ToolchainVariable /** * Enum representing variables used in the generation of code structures. */ enum class BuildToolchainVariable( override val variableName: String, override var value: Any? = null, override val description: String = "", ) : ToolchainVariable { ProjectDependencies("projectDependencies", description = "The dependencies of the project"), ; companion object { /** * Returns the PsiVariable with the given variable name. * * @param variableName the variable name to search for * @return the PsiVariable with the given variable name */ fun from(variableName: String): BuildToolchainVariable? { return values().firstOrNull { it.variableName == variableName } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/variable/model/toolchain/DatabaseToolchainVariable.kt ================================================ package com.phodal.shirecore.provider.variable.model.toolchain import com.phodal.shirecore.provider.variable.model.ToolchainVariable enum class DatabaseToolchainVariable( override val variableName: String, override var value: Any? = null, override val description: String = "", ) : ToolchainVariable { DatabaseInfo("databaseInfo", description = "The database information"), Databases("databases", description = "The databases in the database"), Tables("tables", description = "The tables in the database"), Columns("columns", description = "The columns in the database") ; companion object { /** * Returns the PsiVariable with the given variable name. * * @param variableName the variable name to search for * @return the PsiVariable with the given variable name */ fun from(variableName: String): DatabaseToolchainVariable? { return DatabaseToolchainVariable.values().firstOrNull { it.variableName == variableName } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/variable/model/toolchain/SonarqubeVariable.kt ================================================ package com.phodal.shirecore.provider.variable.model.toolchain import com.phodal.shirecore.provider.variable.model.ToolchainVariable /** * Enum representing variables used in the generation of code structures. */ enum class SonarqubeVariable( override val variableName: String, override var value: Any? = null, override val description: String = "", ) : ToolchainVariable { Issue("sonarIssue", null, "the issue of current file"), Results("sonarResults", null, "the results of current file") ; companion object { /** * Returns the PsiVariable with the given variable name. * * @param variableName the variable name to search for * @return the PsiVariable with the given variable name */ fun from(variableName: String): SonarqubeVariable? { return values().firstOrNull { it.variableName == variableName } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/variable/model/toolchain/TerminalToolchainVariable.kt ================================================ package com.phodal.shirecore.provider.variable.model.toolchain import com.phodal.shirecore.provider.variable.model.ToolchainVariable /** * Enum representing variables used in the generation of code structures. */ enum class TerminalToolchainVariable( override val variableName: String, override var value: Any? = null, override val description: String = "", ) : ToolchainVariable { SHELL_PATH("shellPath", "/bin/bash", "The path to the shell executable"), PWD("pwd", null, "The current working directory"), ; companion object { /** * Returns the PsiVariable with the given variable name. * * @param variableName the variable name to search for * @return the PsiVariable with the given variable name */ fun from(variableName: String): TerminalToolchainVariable? { return values().firstOrNull { it.variableName == variableName } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/provider/variable/model/toolchain/VcsToolchainVariable.kt ================================================ package com.phodal.shirecore.provider.variable.model.toolchain import com.phodal.shirecore.provider.variable.model.ToolchainVariable /** * Enum representing variables used in the generation of code structures. */ enum class VcsToolchainVariable( override val variableName: String, override var value: Any? = null, override val description: String = "", ) : ToolchainVariable { CurrentChanges("currentChanges", description = "The code changes in the current working directory"), CurrentBranch("currentBranch", description = "The name of the current branch"), HistoryCommitMessages("historyCommitMessages", description = "The commit messages in the history"), Diff("diff", description = "The diff of the current changes") ; companion object { /** * Returns the PsiVariable with the given variable name. * * @param variableName the variable name to search for * @return the PsiVariable with the given variable name */ fun from(variableName: String): VcsToolchainVariable? { return values().firstOrNull { it.variableName == variableName } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/psi/CodeSmellCollector.kt ================================================ package com.phodal.shirecore.psi import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx import com.intellij.lang.LanguageCommenters import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement object CodeSmellCollector { /** * Collects all the problems found in the given `project`, within the specified `editor` and `element`. * * @param project The project in which the problems are to be collected. * @param editor The editor that is associated with the element. * @param element The PsiElement for which the problems are to be collected. * @return A string containing all the problems found, separated by new lines, or `null` if no problems were found. */ private fun collectProblems(project: Project, editor: Editor, element: PsiElement): String? { val range = element.textRange val document = editor.document var errors: MutableList = mutableListOf() DaemonCodeAnalyzerEx.processHighlights(document, project, null, range.startOffset, range.endOffset) { if (it.description != null) { errors.add(it.description) } true } val commentSymbol = commentPrefix(element) // remove dupcliated descriptions errors = errors.distinct().toMutableList() return errors.joinToString("\n") { "$commentSymbol - $it" } } /** * Collects the problems related to the given PsiElement and returns them as a formatted string. * * @param element the PsiElement for which problems need to be collected * @param project the Project in which the element exists * @param editor the Editor used for displaying the problems * @return a formatted string containing the problems related to the element, along with any relevant code snippets */ fun collectElementProblemAsSting( element: PsiElement, project: Project, editor: Editor ): String { return collectProblems(project, editor, element) ?: "" } private fun commentPrefix(element: PsiElement): String { return LanguageCommenters.INSTANCE.forLanguage(element.language)?.lineCommentPrefix ?: "//" } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/psi/PsiErrorCollector.kt ================================================ package com.phodal.shirecore.psi import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx import com.intellij.lang.annotation.HighlightSeverity import com.intellij.openapi.Disposable import com.intellij.openapi.application.runReadAction import com.intellij.openapi.editor.Document import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.TextRange import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement import com.intellij.psi.PsiErrorElement import com.intellij.psi.PsiFile import com.intellij.util.messages.MessageBusConnection import com.phodal.shirecore.ast.PsiSyntaxCheckingVisitor import java.util.concurrent.CompletableFuture import java.util.concurrent.TimeUnit object PsiErrorCollector { fun collectSyntaxError( psiFile: PsiFile, project: Project, ): List { var errors: List = listOf() collectSyntaxError(psiFile, psiFile.virtualFile, project) { errors = it } return errors } /** * This function is used to collect syntax errors in a given source file using the PSI (Program Structure Interface) of the file. * It takes the source file, a callback function to run after collecting errors, an output file, and the project as parameters. * * @param sourceFile The PSI file from which syntax errors need to be collected. * @param runAction A callback function that takes a list of errors as input and performs some action. * @param outputFile The virtual file where the errors will be collected. * @param project The project to which the files belong. */ fun collectSyntaxError( sourceFile: PsiFile, outputFile: VirtualFile, project: Project, runAction: ((errors: List) -> Unit)?, ) { val collectPsiError = sourceFile.collectPsiError() if (collectPsiError.isNotEmpty()) { return runAction?.invoke(collectPsiError) ?: Unit } val document = runReadAction { FileDocumentManager.getInstance().getDocument(outputFile) } ?: return val range = TextRange(0, document.textLength) val errors = mutableListOf() DaemonCodeAnalyzerEx.getInstance(project).restart(sourceFile) val hintDisposable = Disposer.newDisposable() val busConnection: MessageBusConnection = project.messageBus.connect(hintDisposable) val future: CompletableFuture> = CompletableFuture() busConnection.subscribe( DaemonCodeAnalyzer.DAEMON_EVENT_TOPIC, SimpleCodeErrorListener(document, project, range, errors, busConnection, hintDisposable) { future.complete(it) } ) future.get(30, TimeUnit.SECONDS) } class SimpleCodeErrorListener( private val document: Document, private val project: Project, private val range: TextRange, private val errors: MutableList, private val busConnection: MessageBusConnection, private val hintDisposable: Disposable, private val runAction: ((errors: List) -> Unit)?, ) : DaemonCodeAnalyzer.DaemonListener { override fun daemonFinished() { DaemonCodeAnalyzerEx.processHighlights( document, project, HighlightSeverity.ERROR, range.startOffset, range.endOffset ) { if (it.description != null) { errors.add(it.description) } true } runAction?.invoke(errors) busConnection.disconnect() Disposer.dispose(hintDisposable) } } } /** * This function is an extension function for PsiFile class in Kotlin. * It collects syntax errors present in the PsiFile and returns a list of error messages. * It creates a PsiSyntaxCheckingVisitor object to visit each element in the PsiFile. * If the element is a PsiErrorElement, it adds a message to the errors list with the error description and position. * Finally, it returns the list of error messages. */ fun PsiFile.collectPsiError(): MutableList { val errors = mutableListOf() val visitor = object : PsiSyntaxCheckingVisitor() { override fun visitElement(element: PsiElement) { if (element is PsiErrorElement) { errors.add("Syntax error at position ${element.textRange.startOffset}: ${element.errorDescription}") } super.visitElement(element) } } this.accept(visitor) return errors } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/runner/ConfigurationRunner.kt ================================================ package com.phodal.shirecore.runner import com.intellij.execution.* import com.intellij.execution.configurations.RunConfiguration import com.intellij.execution.executors.DefaultRunExecutor import com.intellij.execution.impl.ExecutionManagerImpl import com.intellij.execution.process.ProcessAdapter import com.intellij.execution.process.ProcessEvent import com.intellij.execution.process.ProcessOutputType import com.intellij.execution.runners.ExecutionEnvironmentBuilder import com.intellij.execution.runners.ProgramRunner import com.intellij.execution.testframework.sm.runner.SMTRunnerEventsAdapter import com.intellij.execution.testframework.sm.runner.SMTRunnerEventsListener import com.intellij.openapi.application.runInEdt import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Key import com.intellij.util.messages.MessageBusConnection import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit interface ConfigurationRunner { fun runnerId() = DefaultRunExecutor.EXECUTOR_ID fun executeRunConfigurations( project: Project, settings: RunnerAndConfigurationSettings, testEventsListener: SMTRunnerEventsAdapter? = null, indicator: ProgressIndicator? = null, ) { val runContext = createRunContext() executeRunConfigures(project, settings, runContext, testEventsListener, indicator) } fun executeRunConfigures( project: Project, settings: RunnerAndConfigurationSettings, runContext: RunContext, testEventsListener: SMTRunnerEventsAdapter?, indicator: ProgressIndicator?, ) { val connection: MessageBusConnection? = project.messageBus.connect() try { return executeRunConfigurations(connection, settings, runContext, testEventsListener, indicator) } finally { connection?.disconnect() } } /** * This function is responsible for executing run configurations with the given parameters. * * @param connection The message bus connection to use. * @param configurations The runner and configuration settings to execute. * @param runContext The run context for the execution. * @param testEventsListener The listener for test events. * @param indicator The progress indicator for the execution. */ fun executeRunConfigurations( connection: MessageBusConnection?, configurations: RunnerAndConfigurationSettings, runContext: RunContext, testEventsListener: SMTRunnerEventsListener?, indicator: ProgressIndicator?, ) { testEventsListener?.let { connection?.subscribe(SMTRunnerEventsListener.TEST_STATUS, it) } connection?.let { Disposer.register(runContext, connection) } runInEdt { try { configurations.startRunConfigurationExecution(runContext) val handler = CheckExecutionListener(runnerId(), runContext) connection?.subscribe(ExecutionManager.EXECUTION_TOPIC, handler) } catch (e: ExecutionException) { logger().warn("Failed to start run configuration: ${configurations.name}") runContext.latch.countDown() } } // todo: find a better way if (indicator != null) { while (!indicator.isCanceled) { val result = runContext.latch.await(100, TimeUnit.MILLISECONDS) if (result) break } if (indicator.isCanceled) { Disposer.dispose(runContext) } } } fun createRunContext(): RunContext { val stderr = StringBuilder() val processListener = object : OutputListener() { override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) { if (ProcessOutputType.isStderr(outputType)) { stderr.append(event.text) } } } val runContext = RunContext(processListener, null, CountDownLatch(1)) return runContext } /** * This function defines a process run completion action to be executed once a process run by the program runner completes. * It is designed to handle the aftermath of a process execution, including stopping the process and notifying the run context. * * @param runContext The context in which the run operation is being executed. It provides the necessary information * and handles to manage the run process, including a latch to synchronize the completion of the run. * The run context is also responsible for disposing of resources once the run completes. * * Note: This function uses the 'return@Callback' syntax to exit the lambda expression early in case of a null descriptor. */ fun processRunCompletionAction(runContext: RunContext) = ProgramRunner.Callback { descriptor -> // Descriptor can be null in some cases. // For example, IntelliJ Rust's test runner provides null here if compilation fails if (descriptor == null) { runContext.latch.countDown() return@Callback } Disposer.register(runContext) { ExecutionManagerImpl.stopProcess(descriptor) } val processHandler = descriptor.processHandler if (processHandler != null) { processHandler.addProcessListener(object : ProcessAdapter() { override fun processTerminated(event: ProcessEvent) { runContext.latch.countDown() } }) runContext.processListener?.let { processHandler.addProcessListener(it) } } } @Throws(ExecutionException::class) fun RunnerAndConfigurationSettings.startRunConfigurationExecution(runContext: RunContext): Boolean { val runner = ProgramRunner.getRunner(runnerId(), configuration) val env = ExecutionEnvironmentBuilder.create(DefaultRunExecutor.getRunExecutorInstance(), this) .activeTarget() .build(processRunCompletionAction(runContext)) if (runner == null || env.state == null) { runContext.latch.countDown() return false } runContext.environments.add(env) try { runner.execute(env) } catch (e: ExecutionException) { runContext.latch.countDown() throw e } return true } fun executeRunConfigurations(project: Project, configuration: RunConfiguration) { val runManager = RunManager.getInstance(project) val settings = runManager.findConfigurationByTypeAndName(configuration.type, configuration.name) if (settings == null) { logger().warn("Failed to find test configuration for: ${configuration.name}") return } executeRunConfigurations(project, settings) } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/runner/RunContext.kt ================================================ package com.phodal.shirecore.runner import com.intellij.execution.ExecutionListener import com.intellij.execution.Executor import com.intellij.execution.process.ProcessListener import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.execution.runners.ProgramRunner import com.intellij.openapi.Disposable import java.util.concurrent.CountDownLatch class RunContext( val processListener: ProcessListener?, val executionListener: ExecutionListener?, val latch: CountDownLatch, val executor: Executor? = null, val runner: ProgramRunner<*>? = null, ) : Disposable { val environments: MutableList = mutableListOf() override fun dispose() {} } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/runner/RunServiceExt.kt ================================================ // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.phodal.shirecore.runner import com.intellij.execution.ExecutionListener import com.intellij.execution.process.ProcessHandler import com.intellij.execution.runners.ExecutionEnvironment class CheckExecutionListener( private val executorId: String, private val runContext: RunContext, ) : ExecutionListener { override fun processStartScheduled(executorId: String, env: ExecutionEnvironment) { checkAndExecute(executorId, env) { runContext.executionListener?.processStartScheduled(executorId, env) } } override fun processNotStarted(executorId: String, env: ExecutionEnvironment) { checkAndExecute(executorId, env) { runContext.latch.countDown() runContext.executionListener?.processNotStarted(executorId, env) } } override fun processStarting(executorId: String, env: ExecutionEnvironment) { checkAndExecute(executorId, env) { runContext.executionListener?.processStarting(executorId, env) } } override fun processStarted(executorId: String, env: ExecutionEnvironment, handler: ProcessHandler) { checkAndExecute(executorId, env) { runContext.executionListener?.processStarted(executorId, env, handler) } } override fun processTerminating(executorId: String, env: ExecutionEnvironment, handler: ProcessHandler) { checkAndExecute(executorId, env) { runContext.executionListener?.processTerminating(executorId, env, handler) } } override fun processTerminated( executorId: String, env: ExecutionEnvironment, handler: ProcessHandler, exitCode: Int ) { checkAndExecute(executorId, env) { runContext.executionListener?.processTerminated(executorId, env, handler, exitCode) } } private fun checkAndExecute(executorId: String, env: ExecutionEnvironment, action: () -> Unit) { if (this.executorId == executorId && env in runContext.environments) { action() } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/runner/RunServiceTask.kt ================================================ // Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.phodal.shirecore.runner import com.intellij.execution.ExecutionListener import com.intellij.execution.ExecutionManager import com.intellij.execution.ExecutionManager.Companion.EXECUTION_TOPIC import com.intellij.execution.RunnerAndConfigurationSettings import com.intellij.execution.executors.DefaultRunExecutor import com.intellij.execution.process.* import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.execution.runners.ExecutionEnvironmentBuilder import com.intellij.execution.runners.ProgramRunner import com.intellij.execution.testframework.Filter import com.intellij.execution.testframework.sm.runner.SMTRunnerEventsAdapter import com.intellij.execution.testframework.sm.runner.SMTestProxy import com.intellij.execution.ui.ExecutionUiService import com.intellij.execution.ui.RunContentDescriptor import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.invokeAndWaitIfNeeded import com.intellij.openapi.application.runInEdt import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.progress.Task import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Key import com.intellij.openapi.util.NlsSafe import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement import com.intellij.util.text.nullize import com.phodal.shirecore.ShireCoreBundle import com.phodal.shirecore.ShirelangNotifications import com.phodal.shirecore.provider.shire.FileRunService import java.util.concurrent.CompletableFuture open class RunServiceTask( private val project: Project, private val virtualFile: VirtualFile, private val testElement: PsiElement?, private val fileRunService: FileRunService, private val runner: ProgramRunner<*>? = null, private val future: CompletableFuture? = null, ) : ConfigurationRunner, Task.Backgroundable( project, ShireCoreBundle.message("progress.run.task"), true ) { override fun runnerId() = runner?.runnerId ?: DefaultRunExecutor.EXECUTOR_ID override fun run(indicator: ProgressIndicator) { if (future != null) { runInBackgroundAndCollectToFuture() } else { runAndCollectTestResults(indicator) } } private fun runInBackgroundAndCollectToFuture() { val settings: RunnerAndConfigurationSettings = fileRunService.createRunSettings(project, virtualFile, testElement) ?: throw IllegalStateException("No run configuration found for file: ${virtualFile.path}") runAnCollectStdOutput(settings, project, future!!) } /** * This function is responsible for executing a run configuration and returning the corresponding check result. * It is used within the test framework to run tests and report the results back to the user. * * @param indicator A progress indicator that is used to track the progress of the execution. * @return The check result of the executed run configuration, or `null` if no run configuration could be created. */ private fun runAndCollectTestResults(indicator: ProgressIndicator?): RunnerResult? { val settings: RunnerAndConfigurationSettings? = fileRunService.createRunSettings(project, virtualFile, testElement) if (settings == null) { logger().warn("No run configuration found for file: ${virtualFile.path}") return null } settings.isActivateToolWindowBeforeRun = false val testRoots = mutableListOf() val testEventsListener = object : SMTRunnerEventsAdapter() { override fun onTestingStarted(testsRoot: SMTestProxy.SMRootTestProxy) { testRoots += testsRoot } } val runContext = createRunContext() executeRunConfigures(project, settings, runContext, testEventsListener, indicator) @Suppress("UnstableApiUsage") invokeAndWaitIfNeeded { } val testResults = testRoots.mapNotNull { it.toCheckResult() } if (testResults.isEmpty()) return RunnerResult.noTestsRun val firstFailure = testResults.firstOrNull { it.status != RunnerStatus.Solved } val result = firstFailure ?: testResults.first() return result } private fun SMTestProxy.SMRootTestProxy.toCheckResult(): RunnerResult? { if (finishedSuccessfully()) return RunnerResult(RunnerStatus.Solved, "CONGRATULATIONS") val failedChildren = collectChildren(object : Filter() { override fun shouldAccept(test: SMTestProxy): Boolean = test.isLeaf && !test.finishedSuccessfully() }) val firstFailedTest = failedChildren.firstOrNull() if (firstFailedTest == null) { ShirelangNotifications.warn(project, "Testing failed although no failed tests found") return null } val diff = firstFailedTest.diffViewerProvider?.let { CheckResultDiff(it.left, it.right, it.diffTitle) } val message = if (diff != null) getComparisonErrorMessage(firstFailedTest) else getErrorMessage(firstFailedTest) val details = firstFailedTest.stacktrace return RunnerResult( RunnerStatus.Failed, removeAttributes(fillWithIncorrect(message)), diff = diff, details = details ) } private fun SMTestProxy.finishedSuccessfully(): Boolean { return !hasErrors() && (isPassed || isIgnored) } /** * Some testing frameworks add attributes to be shown in console (ex. Jest - ANSI color codes) * which are not supported in Task Description, so they need to be removed */ private fun removeAttributes(text: String): String { val buffer = StringBuilder() AnsiEscapeDecoder().escapeText(text, ProcessOutputTypes.STDOUT) { chunk, _ -> buffer.append(chunk) } return buffer.toString() } /** * Returns message for test error that will be shown to a user in Check Result panel */ @Suppress("UnstableApiUsage") @NlsSafe private fun getErrorMessage(node: SMTestProxy): String = node.errorMessage ?: "Execution failed" /** * Returns message for comparison error that will be shown to a user in Check Result panel */ private fun getComparisonErrorMessage(node: SMTestProxy): String = getErrorMessage(node) private fun fillWithIncorrect(message: String): String = message.nullize(nullizeSpaces = true) ?: "Incorrect" companion object { fun runAnCollectStdOutput( settings: RunnerAndConfigurationSettings, project: Project, completableFuture: CompletableFuture, ) { val executorInstance = DefaultRunExecutor.getRunExecutorInstance() val env = ExecutionEnvironmentBuilder .createOrNull(executorInstance, settings.configuration) ?.build() ?: throw IllegalStateException("Failed to create execution environment") val runContentManager = ExecutionManager.getInstance(project).getContentManager() val processAdapter = object : ProcessAdapter() { val stdout = StringBuilder() val stderr = StringBuilder() override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) { when (outputType) { ProcessOutputTypes.STDOUT -> stdout.append(event.text) ProcessOutputTypes.STDERR -> stderr.append(event.text) ProcessOutputTypes.SYSTEM -> { // ignore system output } else -> {} } } override fun processTerminated(event: ProcessEvent) { when (event.exitCode) { 0 -> completableFuture.complete(stdout.toString()) else -> completableFuture.completeExceptionally(IllegalStateException("$stderr\nProcess terminated with non-zero exit code: ${event.exitCode}")) } } } settings.isActivateToolWindowBeforeRun = false settings.isTemporary = true settings.isFocusToolWindowBeforeRun = false val disposable = Disposer.newDisposable() val connection = ApplicationManager.getApplication().messageBus.connect(disposable) connection.subscribe(EXECUTION_TOPIC, object : ExecutionListener { override fun processStarting(executorId: String, env: ExecutionEnvironment, handler: ProcessHandler) { handler.addProcessListener(processAdapter) } override fun processTerminated( executorId: String, env: ExecutionEnvironment, handler: ProcessHandler, exitCode: Int, ) { super.processTerminated(executorId, env, handler, exitCode) connection.disconnect() if (exitCode != 0) { completableFuture.completeExceptionally(IllegalStateException("Process terminated with non-zero exit code: $exitCode")) } else { val content = runContentManager.getReuseContent(env) ?: return runInEdt { runContentManager.removeRunContent(executorInstance, content) } } } }) ExecutionManager.getInstance(project).restartRunProfile( project, executorInstance, env.executionTarget, settings, null ) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/runner/RunnerResult.kt ================================================ // Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.phodal.shirecore.runner import org.jetbrains.annotations.Nls class RunnerResult( val status: RunnerStatus, @Nls(capitalization = Nls.Capitalization.Sentence) val message: String = "", val details: String? = null, val diff: CheckResultDiff? = null, ) { companion object { val noTestsRun: RunnerResult get() = RunnerResult( RunnerStatus.Unchecked, "check.no.tests.with.help.guide", ) } } data class CheckResultDiff(val expected: String, val actual: String, val title: String = "") ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/runner/RunnerResultSeverity.kt ================================================ // Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.phodal.shirecore.runner enum class RunnerResultSeverity { Info, Warning, Error; fun isWaring() = this == Warning fun isInfo() = this == Info } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/runner/RunnerStatus.kt ================================================ // Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.phodal.shirecore.runner enum class RunnerStatus(val rawStatus: String) { Unchecked("UNCHECKED"), Solved("CORRECT"), Failed("WRONG"); companion object { fun String.toCheckStatus(): RunnerStatus = when (this) { "CORRECT" -> Solved "WRONG" -> Failed else -> Unchecked } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/runner/ShireProcessHandler.kt ================================================ package com.phodal.shirecore.runner import com.intellij.build.process.BuildProcessHandler import com.intellij.execution.process.AnsiEscapeDecoder import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.externalSystem.util.DiscardingInputStream import com.intellij.openapi.util.Key import java.io.* import java.nio.channels.Channels import java.nio.channels.Pipe class ShireProcessHandler(private val myExecutionName: String) : BuildProcessHandler() { private var myProcessInputWriter: OutputStream? = null private var myProcessInputReader: InputStream? = null private val myAnsiEscapeDecoder = AnsiEscapeDecoder() init { try { val pipe = Pipe.open() myProcessInputReader = BufferedInputStream(Channels.newInputStream(pipe.source())) myProcessInputWriter = BufferedOutputStream(Channels.newOutputStream(pipe.sink())) } catch (_: IOException) { } } override fun detachIsDefault(): Boolean = true override fun destroyProcessImpl() = Unit override fun detachProcessImpl() { try { notifyProcessDetached() } catch (e: Exception) { // ignore logger().warn(e) } finally { notifyProcessTerminated(0) } } fun exitWithError() = notifyProcessTerminated(-1) override fun notifyTextAvailable(text: String, outputType: Key<*>) { myAnsiEscapeDecoder.escapeText( text, outputType ) { decodedText: String?, attributes: Key<*>? -> super.notifyTextAvailable( decodedText!!, attributes!! ) } } override fun getProcessInput(): OutputStream? = myProcessInputWriter override fun getExecutionName(): String = myExecutionName } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/runner/console/CustomFlowWrapper.kt ================================================ package com.phodal.shirecore.runner.console import com.intellij.execution.ui.ConsoleView import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.cancellable /** * This class supports cancel callback for [Flow], * the flow consumer cancels the flow collection after callback is called, * which can be used to skip processing useless data. * * @author lk */ class CustomFlowWrapper(private val delegate: Flow) : Flow { private var isCanceled = false private var _cancelCallback: ((String) -> Unit)? = null override suspend fun collect(collector: FlowCollector) { if (!isCanceled) { delegate.collect(collector) } } fun cancelCallback(callback: (String) -> Unit) { _cancelCallback = callback } fun cancel(message: String) { check(isCanceled == false) { "This flow has been canceled" } isCanceled = true _cancelCallback?.invoke(message) } } fun Flow.cancelWithConsole(consoleView: ConsoleView?): Flow = cancelHandler { consoleView?.addCancelCallback(it) } /** * Please use [CustomFlowWrapper] to call it, and it won't work if it's CancellableFlow, * so you need to call it before calling [cancellable] */ fun Flow.cancelHandler(handle: ((String) -> Unit) -> Unit): Flow = apply { if (this is CustomFlowWrapper) handle({ cancel(it) }) } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/runner/console/ShireConsoleViewBase.kt ================================================ package com.phodal.shirecore.runner.console import com.intellij.execution.console.ConsoleViewWrapperBase import com.intellij.execution.ui.ConsoleView /** * This class provides cancel callbacks for the console view * and stop the tasks related to it when it is closed, * * @author lk */ open class ShireConsoleViewBase(executionConsole: ConsoleView) : ConsoleViewWrapperBase(executionConsole) { open fun cancelCallback(callback: (String) -> Unit) = Unit open fun isCanceled() = false } fun ConsoleView.addCancelCallback(callback: (String) -> Unit) { if (this is ShireConsoleViewBase) { cancelCallback(callback) } } fun ConsoleView.isCanceled(): Boolean { if (this is ShireConsoleViewBase) { return isCanceled() } return false } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/schema/SecretPatternFileProvider.kt ================================================ package com.phodal.shirecore.schema import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VirtualFile import com.jetbrains.jsonSchema.extension.JsonSchemaFileProvider import com.jetbrains.jsonSchema.extension.SchemaType import com.phodal.shirecore.ShireCoreBundle import org.jetbrains.annotations.NonNls @NonNls internal const val SECRET_PATTERN_EXTENSION = "shireSecretPattern.yml" class ShireSecretPatternSchemaFileProvider(project: Project) : JsonSchemaFileProvider { @NonNls private val DOT_EXTENSION = ".$SECRET_PATTERN_EXTENSION" @NonNls private val SCHEMA = "/schemas/shireSecretPattern.schema.json" override fun isAvailable(file: VirtualFile): Boolean = file.nameSequence.endsWith(DOT_EXTENSION) override fun getName(): String = ShireCoreBundle.message("schema.pattern.json.display.name") override fun getSchemaFile(): VirtualFile? = VfsUtil.findFileByURL(javaClass.getResource(SCHEMA)!!) override fun getSchemaType(): SchemaType = SchemaType.embeddedSchema } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/schema/ShireCustomAgentSchemaFileProvider.kt ================================================ package com.phodal.shirecore.schema import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VirtualFile import com.jetbrains.jsonSchema.extension.JsonSchemaFileProvider import com.jetbrains.jsonSchema.extension.SchemaType import com.phodal.shirecore.ShireCoreBundle import org.jetbrains.annotations.NonNls @NonNls internal const val CUSTOM_AGENT_JSON_EXTENSION = "shireCustomAgent.json" class ShireCustomAgentSchemaFileProvider(project: Project) : JsonSchemaFileProvider { @NonNls private val DOT_EXTENSION = ".$CUSTOM_AGENT_JSON_EXTENSION" @NonNls private val CUSTOM_AGENT_SCHEMA = "/schemas/shireCustomAgent.schema.json" override fun isAvailable(file: VirtualFile): Boolean = file.nameSequence.endsWith(DOT_EXTENSION) override fun getName(): String = ShireCoreBundle.message("schema.custom-agent.json.display.name") override fun getSchemaFile(): VirtualFile? = VfsUtil.findFileByURL(javaClass.getResource(CUSTOM_AGENT_SCHEMA)!!) override fun getSchemaType(): SchemaType = SchemaType.embeddedSchema } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/schema/ShireEnvFileProvider.kt ================================================ package com.phodal.shirecore.schema import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VirtualFile import com.jetbrains.jsonSchema.extension.JsonSchemaFileProvider import com.jetbrains.jsonSchema.extension.SchemaType import com.phodal.shirecore.ShireCoreBundle import org.jetbrains.annotations.NonNls @NonNls internal const val SHIRE_ENV_PATTERN_EXTENSION = "shireEnv.json" class ShireEnvFileProvider(project: Project) : JsonSchemaFileProvider { @NonNls private val DOT_EXTENSION = ".$SHIRE_ENV_PATTERN_EXTENSION" @NonNls private val SCHEMA = "/schemas/shireEnv.schema.json" override fun isAvailable(file: VirtualFile): Boolean = file.nameSequence.endsWith(DOT_EXTENSION) override fun getName(): String = ShireCoreBundle.message("schema.env.json.display.name") override fun getSchemaFile(): VirtualFile? = VfsUtil.findFileByURL(javaClass.getResource(SCHEMA)!!) override fun getSchemaType(): SchemaType = SchemaType.embeddedSchema } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/schema/ShireJsonSchemaProviderFactory.kt ================================================ package com.phodal.shirecore.schema import com.intellij.openapi.project.Project import com.jetbrains.jsonSchema.extension.JsonSchemaFileProvider import com.jetbrains.jsonSchema.extension.JsonSchemaProviderFactory class ShireJsonSchemaProviderFactory : JsonSchemaProviderFactory { override fun getProviders(project: Project): MutableList { return mutableListOf( ShireSecretPatternSchemaFileProvider(project), ShireCustomAgentSchemaFileProvider(project), ShireEnvFileProvider(project) ) } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/search/algorithm/BM25Similarity.kt ================================================ package com.phodal.shirecore.search.algorithm import kotlin.math.log10 /** * BM25Similarity is a class that computes the similarity between a given query and a list of documents (chunks). * It uses the BM25 algorithm, which is a probabilistic information retrieval model. * The BM25 algorithm considers term frequency, inverse document frequency, and document length to compute relevance scores. * * @property k1 The term frequency saturation parameter. * @property b The document length normalization parameter. * * The class contains two main functions: * 1. computeInputSimilarity: Computes the BM25 similarity scores between the query and each document. * 2. computeIDF: Computes the inverse document frequency (IDF) for each term in the corpus. */ class BM25Similarity : Similarity { private val k1 = 1.5 private val b = 0.75 override fun computeInputSimilarity(query: String, chunks: List>): List> { val docCount = chunks.size val avgDocLength = chunks.map { it.size }.average() val idfMap = computeIDF(chunks, docCount) // Tokenize the query val queryTerms = tokenize(query).groupBy { it }.mapValues { it.value.size } return chunks.map { doc -> val docLength = doc.size queryTerms.map { (term, queryTermFreq) -> val tf = doc.count { it == term }.toDouble() val idf = idfMap[term] ?: 0.0 val numerator = tf * (k1 + 1) val denominator = tf + k1 * (1 - b + b * (docLength / avgDocLength)) idf * (numerator / denominator) * queryTermFreq } } } fun computeIDF(chunks: List>, docCount: Int): Map { val termDocCount = mutableMapOf() chunks.forEach { doc -> doc.toSet().forEach { term -> termDocCount[term] = termDocCount.getOrDefault(term, 0) + 1 } } return termDocCount.mapValues { (_, count) -> log10((docCount - count + 0.5) / (count + 0.5) + 1.0) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/search/algorithm/JaccardSimilarity.kt ================================================ package com.phodal.shirecore.search.algorithm open class JaccardSimilarity : Similarity { /** * The `tokenLevelJaccardSimilarity` method calculates the Jaccard similarity between a query string and an array of string * arrays (chunks). The Jaccard similarity is a measure of the similarity between two sets and is defined as the size of * the intersection divided by the size of the union of the two sets. * * @param query The query string to compare against the chunks. * @param chunks An array of string arrays (chunks) to compare against the query. * @return A two-dimensional array representing the Jaccard similarity scores between the query and each chunk. */ override fun computeInputSimilarity(query: String, chunks: List>): List> { val currentFileTokens = tokenize(query) return chunks.map { list -> list.map { it -> val tokenizedFile = tokenize(it) similarityScore(currentFileTokens, tokenizedFile) } } } fun similarityScore(set1: Set, set2: Set): Double { val intersectionSize = set1.intersect(set2).size val unionSize = set1.union(set2).size return intersectionSize.toDouble() / unionSize } /** * Calculates the similarity score between a given path and a set of strings. * * @param path The path to calculate similarity for. * @param sets The set of strings to compare with the path. * @return A number representing the similarity score between the path and the set of strings. */ fun pathSimilarity(path: String, sets: Set): Double { val splitPath = path.split('/') val set1 = splitPath.map(::tokenize) .reduce { acc, it -> acc.union(it) } val set2 = sets.map(::tokenize) .reduce { acc, it -> acc.union(it) } return similarityScore(set1, set2) } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/search/algorithm/Similarity.kt ================================================ package com.phodal.shirecore.search.algorithm import com.phodal.shirecore.search.tokenizer.StopwordsBasedTokenizer interface Similarity { fun tokenize(input: String): Set { return StopwordsBasedTokenizer.instance().tokenize(input).toSet() } fun computeInputSimilarity(query: String, chunks: List>): List> } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/search/algorithm/TfIdf.kt ================================================ /* Copyright (c) 2011, Rob Ellis, Chris Umbel Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package com.phodal.shirecore.search.algorithm import com.phodal.shirecore.search.tokenizer.RegexpTokenizer import com.phodal.shirecore.search.tokenizer.Tokenizer import com.phodal.shirecore.search.tokenizer.WordTokenizer import kotlin.math.ln var ourStopwords = listOf( "about", "above", "after", "again", "all", "also", "am", "an", "and", "another", "any", "are", "as", "at", "be", "because", "been", "before", "being", "below", "between", "both", "but", "by", "came", "can", "cannot", "come", "could", "did", "do", "does", "doing", "during", "each", "few", "for", "from", "further", "get", "got", "has", "had", "he", "have", "her", "here", "him", "himself", "his", "how", "if", "in", "into", "is", "it", "its", "itself", "like", "make", "many", "me", "might", "more", "most", "much", "must", "my", "myself", "never", "now", "of", "on", "only", "or", "other", "our", "ours", "ourselves", "out", "over", "own", "said", "same", "see", "she", "should", "since", "so", "some", "still", "such", "take", "than", "that", "the", "their", "theirs", "them", "themselves", "then", "there", "these", "they", "this", "those", "through", "to", "too", "under", "until", "up", "very", "was", "way", "we", "well", "were", "what", "where", "when", "which", "while", "who", "whom", "with", "would", "why", "you", "your", "yours", "yourself", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "$", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "_" ) typealias DocumentType = Any typealias TfIdfCallback = (index: Int, measure: Double, key: Any?) -> Unit class TfIdf { private val documents: MutableList = mutableListOf() private var _idfCache: MutableMap = mutableMapOf() var tokenizer: RegexpTokenizer = WordTokenizer() companion object { fun tf(term: String, document: DocumentType): Int { return (document as? Map<*, *>)?.get(term) as? Int ?: 0 } } fun idf(term: String, force: Boolean = false): Double { if (_idfCache[term] != null && !force) { return _idfCache[term]!! } val docsWithTerm = documents.count { documentHasTerm(term, it) } val idf = 1 + ln((documents.size.toDouble()) / (1 + docsWithTerm)) _idfCache[term] = idf return idf } private fun documentHasTerm(term: String, document: DocumentType): Boolean { return ((document as? Map<*, *>)?.get(term) as? Int ?: 0) > 0 } fun buildDocument(text: DocumentType, key: Any? = null): MutableMap { val stopOut: Boolean val doc: MutableMap when (text) { is String -> { val tokens = tokenizer.tokenize(text) stopOut = true doc = tokens.fold(mutableMapOf("__key" to key)) { acc, term -> if (term !in ourStopwords) { acc[term] = (acc[term] as? Int ?: 0) + 1 } acc } } is List<*> -> { stopOut = false doc = text.filterIsInstance() .fold(mutableMapOf("__key" to key)) { acc, term -> acc[term] = (acc[term] as? Int ?: 0) + 1 acc } } else -> { stopOut = false doc = (text as MutableMap) } } // remove "__key" if (doc.containsKey("__key")) { doc.remove("__key") } return doc } fun addDocument(document: DocumentType, key: Any? = null, restoreCache: Boolean = false) { documents.add(buildDocument(document, key)) if (restoreCache) { for (term in _idfCache.keys) { idf(term, true) } } else { _idfCache = mutableMapOf() } } fun addDocuments(documents: List) { documents.forEach { addDocument(it) } } fun tfidf(terms: Any, d: Int): Double { val termsList = if (terms is String) { tokenizer.tokenize(terms) } else { terms as List } return termsList.fold(0.0) { value, term -> val idf = idf(term) value + (tf(term, documents[d]) * if (idf == Double.POSITIVE_INFINITY) 0.0 else idf) } } fun listTerms(index: Int): List { val terms = mutableListOf() for ((term, value) in documents[index] as Map) { if (term != "__key") { terms.add(TermData(term, tf(term, documents[index]), idf(term), tfidf(term, index))) } } return terms.sortedByDescending { it.tfidf } } data class TermData(val term: String, val tf: Int, val idf: Double, val tfidf: Double) /** * This function calculates the Term Frequency-Inverse Document Frequency (TF-IDF) for each document in the collection. * TF-IDF is a numerical statistic that reflects how important a word is to a document in a collection or corpus. * * @param terms The terms for which the TF-IDF is to be calculated. This can be a single term or a collection of terms. * @param callback An optional callback function that is invoked for each document. The callback function is passed three parameters: * - The index of the current document. * - The calculated TF-IDF value for the current document. * - The key of the current document (if it exists). * The callback function can be used to perform additional processing or logging for each document. * * @return A list of Double values representing the calculated TF-IDF values for each document in the collection. The order of the values in the list corresponds to the order of the documents in the collection. */ fun tfidfs(terms: Any, callback: TfIdfCallback? = null): List { val tfidfs = MutableList(documents.size) { 0.0 } for (i in documents.indices) { tfidfs[i] = tfidf(terms, i) callback?.invoke(i, tfidfs[i], (documents[i] as? Map)?.get("__key")) } return tfidfs } fun setTokenizer(t: Any) { if (t !is Tokenizer) { throw IllegalArgumentException("Expected a valid Tokenizer") } tokenizer = t as RegexpTokenizer } fun setStopwords(customStopwords: List): Boolean { ourStopwords = customStopwords return true } fun search(query: String): List { return tfidfs(query, null) } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/search/function/ScoreText.kt ================================================ package com.phodal.shirecore.search.function import com.intellij.openapi.vfs.VirtualFile data class ScoredText( val text: String, val similarity: Double = 0.0, var index: Int = 0, var count: Int = 0, val file: VirtualFile? = null, var embedding: FloatArray? = null, ) { override fun toString(): String { return "Similarity: ${similarity}, Text: $text" } override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as ScoredText if (text != other.text) return false if (index != other.index) return false if (count != other.count) return false if (similarity != other.similarity) return false if (file != other.file) return false if (embedding != null) { if (other.embedding == null) return false if (!embedding.contentEquals(other.embedding)) return false } else if (other.embedding != null) return false return true } override fun hashCode(): Int { var result = text.hashCode() result = 31 * result + index result = 31 * result + count result = 31 * result + similarity.hashCode() result = 31 * result + (file?.hashCode() ?: 0) result = 31 * result + (embedding?.contentHashCode() ?: 0) return result } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/search/function/SemanticService.kt ================================================ package com.phodal.shirecore.search.function import cc.unitmesh.document.parser.MdDocumentParser import cc.unitmesh.document.parser.MsOfficeDocumentParser import cc.unitmesh.document.parser.PdfDocumentParser import cc.unitmesh.document.parser.TextDocumentParser import cc.unitmesh.rag.document.DocumentType import com.intellij.openapi.application.PathManager import com.intellij.openapi.components.Service import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.util.io.FileUtilRt import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.toNioPathOrNull import com.phodal.shirecore.search.indices.DiskSynchronizedEmbeddingSearchIndex import com.phodal.shirecore.search.indices.EmbeddingSearchIndex import com.phodal.shirecore.search.indices.InMemoryEmbeddingSearchIndex import com.phodal.shirecore.search.rank.Reranker import kotlinx.coroutines.* import java.io.File import java.nio.file.Path @Service(Service.Level.PROJECT) class SemanticService(val project: Project) { private var lastQuery: String = "" private var lastSearchChunks: List = emptyList() private var index: EmbeddingSearchIndex = InMemoryEmbeddingSearchIndex(cacheDir()) private val logger = Logger.getInstance(SemanticService::class.java) suspend fun embed(chunk: String): FloatArray { TODO("see in issue: https://github.com/phodal/shire/issues/84") } suspend fun embedList(chunk: Array): List { return chunk.mapIndexed { index, text -> ScoredText( index = index, count = text.length, text = text, file = null, embedding = embed(text) ) } } suspend fun embedding(chunks: List): List { val ids = chunks.map { it.text } val embeddings = chunks.mapNotNull { entry -> entry.embedding ?: run { entry.embedding = embed(entry.text) entry } entry.embedding } index.addEntries(ids zip embeddings) index.saveToDisk() return chunks } suspend fun searching(input: String, threshold: Double = 0.5): List { lastQuery = input val inputEmbedding = embed(input) val findClosest = index.findClosest(inputEmbedding, 10) return findClosest.filter { it.similarity > threshold } } suspend fun reranking(type: String): List { return Reranker.create(type, project).rerank(lastQuery, lastSearchChunks) } suspend fun splitting(path: List): List = withContext(Dispatchers.IO) { path.map { file -> val inputStream = file.inputStream val extension = file.extension ?: return@map emptyList() val parser = when (val documentType = DocumentType.of(extension)) { DocumentType.TXT -> TextDocumentParser(documentType) DocumentType.PDF -> PdfDocumentParser() DocumentType.HTML -> TextDocumentParser(documentType) DocumentType.DOC -> MsOfficeDocumentParser(documentType) DocumentType.XLS -> MsOfficeDocumentParser(documentType) DocumentType.PPT -> MsOfficeDocumentParser(documentType) DocumentType.MD -> MdDocumentParser() null -> { if (!file.canBeAdded()) { logger.warn("File ${file.path} can't be added to the index") return@map emptyList() } TextDocumentParser(DocumentType.TXT) } } parser.parse(inputStream).mapIndexed { index, document -> // filter document.text.length < 0 if (document.text.isBlank()) { return@mapIndexed null } ScoredText( text = document.text, embedding = null, index = index, count = document.text.length, file = file, ) }.filterNotNull() }.flatten() } suspend fun configCache(text: String): Any { val type = SemanticStorageType.fromString(text) return when (type) { SemanticStorageType.MEMORY -> { index.loadFromDisk() } SemanticStorageType.DISK -> { if (index is DiskSynchronizedEmbeddingSearchIndex) { index.loadFromDisk() } else { index = DiskSynchronizedEmbeddingSearchIndex(cacheDir()) index.loadFromDisk() } } } } private fun cacheDir(): Path { return project.guessProjectDir() ?.toNioPathOrNull() ?.resolve(".shire-cache") ?.resolve("semantic") ?: systemPath() } private fun systemPath(): Path = File(PathManager.getSystemPath()) .resolve("shire-cache") .resolve("semantic").toPath() } fun VirtualFile.canBeAdded(): Boolean { if (!this.isValid || this.isDirectory) return false if (this.fileType.isBinary || FileUtilRt.isTooLarge(this.length)) return false return true } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/search/function/SemanticStorageType.kt ================================================ package com.phodal.shirecore.search.function enum class SemanticStorageType(val value: String) { MEMORY("memory"), DISK("disk"), ; companion object { fun fromString(value: String): SemanticStorageType { return values().firstOrNull { it.value == value } ?: MEMORY } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/search/indices/DiskSynchronizedEmbeddingSearchIndex.kt ================================================ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.phodal.shirecore.search.indices import com.intellij.concurrency.ConcurrentCollectionFactory import com.intellij.util.containers.CollectionFactory import com.phodal.shirecore.search.function.ScoredText import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.ensureActive import java.nio.file.Path import java.util.concurrent.locks.ReentrantReadWriteLock import kotlin.concurrent.read import kotlin.concurrent.write /** * Concurrent [EmbeddingSearchIndex] that synchronizes all index change operations with disk and * allows simultaneous read operations from multiple consumers. * Incremental operations do not rewrite the whole storage file with embeddings. * Instead, they change only the corresponding sections in the file. */ class DiskSynchronizedEmbeddingSearchIndex(val root: Path, limit: Int? = null) : EmbeddingSearchIndex { private var indexToId: MutableMap = CollectionFactory.createSmallMemoryFootprintMap() private var idToEntry: MutableMap = CollectionFactory.createSmallMemoryFootprintMap() private val uncheckedIds: MutableSet = ConcurrentCollectionFactory.createConcurrentSet() var changed: Boolean = false private val lock = ReentrantReadWriteLock() private val fileManager = LocalEmbeddingIndexFileManager(root) override var limit = limit set(value) = lock.write { if (value != null) { // Shrink index if necessary: while (idToEntry.size > value) { delete(indexToId[idToEntry.size - 1]!!, all = true, shouldSaveIds = false) } saveIds() } field = value } internal data class IndexEntry( var index: Int, var count: Int, val embedding: FloatArray ) override val size: Int get() = lock.read { idToEntry.size } override operator fun contains(id: String): Boolean = lock.read { uncheckedIds.remove(id) id in idToEntry } override fun clear() = lock.write { indexToId.clear() idToEntry.clear() uncheckedIds.clear() changed = false } override fun onIndexingStart() { uncheckedIds.clear() uncheckedIds.addAll(idToEntry.keys) } override fun onIndexingFinish() = lock.write { if (uncheckedIds.size > 0) changed = true uncheckedIds.forEach { delete(it, all = true, shouldSaveIds = false) } uncheckedIds.clear() } override suspend fun addEntries(values: Iterable>, shouldCount: Boolean) = coroutineScope { lock.write { for ((id, embedding) in values) { ensureActive() val entry = idToEntry.getOrPut(id) { changed = true if (limit != null && idToEntry.size >= limit!!) return@write val index = idToEntry.size indexToId[index] = id IndexEntry(index, 0, embedding) } if (shouldCount || entry.count == 0) { entry.count += 1 } } } } override suspend fun saveToDisk() = lock.read { save() } override suspend fun loadFromDisk() = coroutineScope { val (ids, embeddings) = fileManager.loadIndex() ?: return@coroutineScope val idToIndex = ids.withIndex().associate { it.value to it.index } val idToEmbedding = (ids zip embeddings).toMap() ensureActive() lock.write { ensureActive() indexToId = CollectionFactory.createSmallMemoryFootprintMap(ids.withIndex().associate { it.index to it.value }) idToEntry = CollectionFactory.createSmallMemoryFootprintMap( ids.associateWith { IndexEntry(idToIndex[it]!!, 0, idToEmbedding[it]!!) } ) } } override fun findClosest(searchEmbedding: FloatArray, topK: Int, similarityThreshold: Double?): List = lock.read { return idToEntry.mapValues { it.value.embedding }.findClosest(searchEmbedding, topK, similarityThreshold) } override fun streamFindClose(searchEmbedding: FloatArray, similarityThreshold: Double?): Sequence { return LockedSequenceWrapper(lock::readLock) { this.idToEntry // manually use the receiver here to make sure the property is not captured by reference .asSequence() .map { it.key to it.value.embedding } .streamFindClose(searchEmbedding, similarityThreshold) } } override fun estimateMemoryUsage() = fileManager.embeddingSizeInBytes.toLong() * size override fun estimateLimitByMemory(memory: Long): Int { return (memory / fileManager.embeddingSizeInBytes).toInt() } override fun checkCanAddEntry(): Boolean = lock.read { return limit == null || idToEntry.size < limit!! } private suspend fun save() = coroutineScope { val ids = idToEntry.toList().sortedBy { it.second.index }.map { it.first } val embeddings = ids.map { idToEntry[it]!!.embedding } fileManager.saveIndex(ids, embeddings) } fun deleteEntry(id: String) = lock.write { delete(id) } fun addEntry(id: String, embedding: FloatArray) = lock.write { add(id, embedding) } /* Optimization for consequent delete and add operations */ fun updateEntry(id: String, newId: String, embedding: FloatArray) = lock.write { if (id !in idToEntry) return if (idToEntry[id]!!.count == 1 && newId !in this) { val index = idToEntry[id]!!.index fileManager[index] = embedding idToEntry.remove(id) idToEntry[newId] = IndexEntry(index, 1, embedding) indexToId[index] = newId saveIds() } else { // Do not apply optimization delete(id) add(newId, embedding) } } private fun add(id: String, embedding: FloatArray, shouldCount: Boolean = false) { val entry = idToEntry.getOrPut(id) { changed = true if (limit != null && idToEntry.size >= limit!!) return@add val index = idToEntry.size fileManager[index] = embedding indexToId[index] = id IndexEntry(index, 0, embedding) } if (shouldCount || entry.count == 0) { entry.count += 1 if (entry.count == 1) { saveIds() } } } private fun delete(id: String, all: Boolean = false, shouldSaveIds: Boolean = true) { val entry = idToEntry[id] ?: return entry.count -= 1 if (!all && entry.count > 0) return val lastIndex = idToEntry.size - 1 val index = entry.index val movedId = indexToId[lastIndex]!! fileManager.removeAtIndex(index) indexToId[index] = movedId indexToId.remove(lastIndex) idToEntry[movedId]!!.index = index idToEntry.remove(id) if (shouldSaveIds) saveIds() } private fun saveIds() { fileManager.saveIds(idToEntry.toList().sortedBy { it.second.index }.map { it.first }) } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/search/indices/EmbeddingSearchIndex.kt ================================================ // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.phodal.shirecore.search.indices import com.phodal.shirecore.search.function.ScoredText import kotlin.collections.asSequence import kotlin.math.sqrt import kotlin.sequences.filter import kotlin.sequences.map import kotlin.sequences.sortedByDescending import kotlin.sequences.take import kotlin.sequences.toList import kotlin.to interface EmbeddingSearchIndex { val size: Int var limit: Int? operator fun contains(id: String): Boolean fun clear() fun onIndexingStart() fun onIndexingFinish() suspend fun addEntries(values: Iterable>, shouldCount: Boolean = false) suspend fun saveToDisk() suspend fun loadFromDisk() fun findClosest(searchEmbedding: FloatArray, topK: Int, similarityThreshold: Double? = null): List fun streamFindClose(searchEmbedding: FloatArray, similarityThreshold: Double? = null): Sequence fun estimateMemoryUsage(): Long fun estimateLimitByMemory(memory: Long): Int fun checkCanAddEntry(): Boolean } internal fun Map.findClosest( searchEmbedding: FloatArray, topK: Int, similarityThreshold: Double?, ): List { return asSequence() .map { it.key to searchEmbedding.times(it.value) } .filter { (_, similarity) -> if (similarityThreshold != null) similarity > similarityThreshold else true } .sortedByDescending { (_, similarity) -> similarity } .take(topK) .map { (id, similarity) -> ScoredText(id, similarity.toDouble()) } .toList() } internal fun Sequence>.streamFindClose( queryEmbedding: FloatArray, similarityThreshold: Double?, ): Sequence { return map { (id, embedding) -> id to queryEmbedding.times(embedding) } .filter { similarityThreshold == null || it.second > similarityThreshold } .map { (id, similarity) -> ScoredText(id, similarity.toDouble()) } } fun FloatArray.times(other: FloatArray): Float { require(this.size == other.size) { "Embeddings must have the same size, but got ${this.size} and ${other.size}" } return this.zip(other).map { (a, b) -> a * b }.sum() } fun FloatArray.normalized(): FloatArray { val norm = sqrt(this.times(this)) val normalizedValues = this.map { it / norm } return normalizedValues.toFloatArray() } fun FloatArray.cosine(other: FloatArray): Float { require(this.size == other.size) { "Embeddings must have the same size" } val dot = this.times(other) val norm = sqrt(this.times(this)) * sqrt(other.times(other)) return dot / norm } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/search/indices/InMemoryEmbeddingSearchIndex.kt ================================================ // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.phodal.shirecore.search.indices import com.intellij.concurrency.ConcurrentCollectionFactory import com.intellij.util.containers.CollectionFactory import com.phodal.shirecore.search.function.ScoredText import java.nio.file.Path import java.util.concurrent.locks.ReentrantReadWriteLock import kotlin.collections.asSequence import kotlin.collections.contains import kotlin.collections.forEach import kotlin.collections.putAll import kotlin.collections.take import kotlin.collections.toList import kotlin.collections.toMap import kotlin.collections.toMutableMap import kotlin.collections.unzip import kotlin.collections.zip import kotlin.concurrent.read import kotlin.concurrent.write import kotlin.sequences.map import kotlin.to /** * Concurrent [com.phodal.shirecore.search.indices.EmbeddingSearchIndex] that stores all embeddings in the memory and allows * simultaneous read operations from multiple consumers. * Can be persisted to disk. */ class InMemoryEmbeddingSearchIndex(root: Path, limit: Int? = null) : EmbeddingSearchIndex { private var idToEmbedding: MutableMap = CollectionFactory.createSmallMemoryFootprintMap() private val uncheckedIds: MutableSet = ConcurrentCollectionFactory.createConcurrentSet() private val lock = ReentrantReadWriteLock() private val fileManager = LocalEmbeddingIndexFileManager(root) override var limit = limit set(value) = lock.write { // Shrink index if necessary: if (value != null && value < idToEmbedding.size) { idToEmbedding = idToEmbedding.toList().take(value).toMap().toMutableMap() } field = value } override val size: Int get() = lock.read { idToEmbedding.size } override operator fun contains(id: String): Boolean = lock.read { uncheckedIds.remove(id) id in idToEmbedding } override fun clear() = lock.write { idToEmbedding.clear() uncheckedIds.clear() } override fun onIndexingStart() { uncheckedIds.clear() uncheckedIds.addAll(idToEmbedding.keys) } override fun onIndexingFinish() = lock.write { uncheckedIds.forEach { idToEmbedding.remove(it) } uncheckedIds.clear() } override suspend fun addEntries(values: Iterable>, shouldCount: Boolean) = lock.write { if (limit != null) { val list = values.toList() idToEmbedding.putAll(list.take(minOf(limit!! - idToEmbedding.size, list.size))) } else { idToEmbedding.putAll(values) } } override suspend fun saveToDisk() = lock.read { save() } override suspend fun loadFromDisk() = lock.write { val (ids, embeddings) = fileManager.loadIndex() ?: return idToEmbedding = (ids zip embeddings).toMap().toMutableMap() } override fun findClosest(searchEmbedding: FloatArray, topK: Int, similarityThreshold: Double?): List = lock.read { return idToEmbedding.findClosest(searchEmbedding, topK, similarityThreshold) } override fun streamFindClose(searchEmbedding: FloatArray, similarityThreshold: Double?): Sequence { return LockedSequenceWrapper(lock::readLock) { this.idToEmbedding // manually use the receiver here to make sure the property is not captured by reference .asSequence() .map { it.key to it.value } .streamFindClose(searchEmbedding, similarityThreshold) } } override fun estimateMemoryUsage() = fileManager.embeddingSizeInBytes.toLong() * size override fun estimateLimitByMemory(memory: Long): Int { return (memory / fileManager.embeddingSizeInBytes).toInt() } override fun checkCanAddEntry(): Boolean = lock.read { return limit == null || idToEmbedding.size < limit!! } private suspend fun save() { val (ids, embeddings) = idToEmbedding.toList().unzip() fileManager.saveIndex(ids, embeddings) } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/search/indices/LocalEmbeddingIndexFileManager.kt ================================================ // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.phodal.shirecore.search.indices import com.fasterxml.jackson.core.JsonProcessingException import com.fasterxml.jackson.core.util.DefaultIndenter import com.fasterxml.jackson.core.util.DefaultPrettyPrinter import com.fasterxml.jackson.databind.SerializationFeature import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue import com.intellij.util.io.outputStream import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.ensureActive import java.io.IOException import java.io.RandomAccessFile import java.nio.ByteBuffer import java.nio.file.Files import java.nio.file.Path import java.util.concurrent.locks.ReentrantReadWriteLock import kotlin.also import kotlin.apply import kotlin.collections.forEach import kotlin.collections.map import kotlin.collections.toMutableList import kotlin.concurrent.read import kotlin.concurrent.write import kotlin.io.buffered import kotlin.io.path.exists import kotlin.io.path.inputStream import kotlin.io.use import kotlin.text.contains import kotlin.text.intern import kotlin.text.lowercase import kotlin.to class LocalEmbeddingIndexFileManager(root: Path, private val dimensions: Int = DEFAULT_DIMENSIONS) { private val lock = ReentrantReadWriteLock() private val mapper = jacksonObjectMapper().enable(SerializationFeature.INDENT_OUTPUT) private val prettyPrinter = DefaultPrettyPrinter().apply { indentArraysWith(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE) } private val rootPath = root get() = field.also { Files.createDirectories(field) } private val idsPath get() = rootPath.resolve(IDS_FILENAME) private val embeddingsPath get() = rootPath.resolve(EMBEDDINGS_FILENAME) val embeddingSizeInBytes = dimensions * EMBEDDING_ELEMENT_SIZE /** Provides reading access to the embedding vector at the specified index * without reading the whole file into memory */ operator fun get(index: Int): FloatArray = lock.read { RandomAccessFile(embeddingsPath.toFile(), "r").use { input -> input.seek(getIndexOffset(index)) val buffer = ByteArray(EMBEDDING_ELEMENT_SIZE) FloatArray(dimensions) { input.read(buffer) ByteBuffer.wrap(buffer).getFloat() } } } /** Provides writing access to embedding vector at the specified index * without writing the other vectors */ operator fun set(index: Int, embedding: FloatArray) = lock.write { RandomAccessFile(embeddingsPath.toFile(), "rw").use { output -> output.seek(getIndexOffset(index)) val buffer = ByteBuffer.allocate(EMBEDDING_ELEMENT_SIZE) embedding.forEach { output.write(buffer.putFloat(0, it).array()) } } } /** * Removes the embedding vector at the specified index. * To do so, replaces this vector with the last vector in the file and shrinks the file size. */ fun removeAtIndex(index: Int) = lock.write { RandomAccessFile(embeddingsPath.toFile(), "rw").use { file -> if (file.length() < embeddingSizeInBytes) return if (file.length() - embeddingSizeInBytes != getIndexOffset(index)) { file.seek(file.length() - embeddingSizeInBytes) val array = ByteArray(EMBEDDING_ELEMENT_SIZE) val embedding = FloatArray(dimensions) { file.read(array) ByteBuffer.wrap(array).getFloat() } file.seek(getIndexOffset(index)) val buffer = ByteBuffer.allocate(EMBEDDING_ELEMENT_SIZE) embedding.forEach { file.write(buffer.putFloat(0, it).array()) } } file.setLength(file.length() - embeddingSizeInBytes) } } suspend fun loadIndex(): Pair, List>? = coroutineScope { ensureActive() lock.read { ensureActive() if (!idsPath.exists() || !embeddingsPath.exists()) return@coroutineScope null val ids = try { mapper.readValue>(idsPath.toFile()).map { it.intern() }.toMutableList() } catch (e: JsonProcessingException) { return@coroutineScope null } val buffer = ByteArray(EMBEDDING_ELEMENT_SIZE) embeddingsPath.inputStream().buffered().use { input -> ids to ids.map { ensureActive() FloatArray(dimensions) { input.read(buffer) ByteBuffer.wrap(buffer).getFloat() } } } } } fun saveIds(ids: List) = lock.write { withNotEnoughSpaceCheck { idsPath.outputStream().buffered().use { output -> mapper.writer(prettyPrinter).writeValue(output, ids) } } } suspend fun saveIndex(ids: List, embeddings: List) = coroutineScope { ensureActive() lock.write { ensureActive() withNotEnoughSpaceCheck { idsPath.outputStream().buffered().use { output -> mapper.writer(prettyPrinter).writeValue(output, ids) } } val buffer = ByteBuffer.allocate(EMBEDDING_ELEMENT_SIZE) withNotEnoughSpaceCheck { embeddingsPath.outputStream().buffered().use { output -> embeddings.forEach { embedding -> ensureActive() embedding.forEach { output.write(buffer.putFloat(0, it).array()) } } } } } } private fun getIndexOffset(index: Int): Long = index.toLong() * embeddingSizeInBytes private fun withNotEnoughSpaceCheck(task: () -> Unit) { try { task() } catch (e: IOException) { if (e.message?.lowercase()?.contains("space") == true) { idsPath.toFile().delete() embeddingsPath.toFile().delete() } else throw e } } companion object { const val DEFAULT_DIMENSIONS = 384 const val EMBEDDING_ELEMENT_SIZE = 4 private const val IDS_FILENAME = "ids.json" private const val EMBEDDINGS_FILENAME = "embeddings.bin" } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/search/indices/LockedSequenceWrapper.kt ================================================ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.phodal.shirecore.search.indices import java.util.concurrent.locks.Lock /** * Wrapper around [delegateSequenceProvider] that performs iteration under a single lock provided by [lockProvider]. * To make sure the lock is acquired and released in the same thread, * iteration over this sequence should happen in a single thread. * To achieve this behavior in the coroutine context, run iteration with [kotlinx.coroutines.newSingleThreadContext] */ class LockedSequenceWrapper(private val lockProvider: () -> Lock, private val delegateSequenceProvider: () -> Sequence) : Sequence { override fun iterator(): Iterator { val lock = lockProvider() lock.lock() var delegate: Iterator? = null try { delegate = delegateSequenceProvider().iterator() } finally { if (delegate == null) { lock.unlock() } } return object : Iterator { override fun hasNext(): Boolean { var delegateHasNext = false try { delegateHasNext = delegate!!.hasNext() } finally { if (!delegateHasNext) { // exception or no next element lock.unlock() } } return delegateHasNext } override fun next(): T { lateinit var delegateNext: T var success = false try { delegateNext = delegate!!.next() success = true } finally { if (!success) { // exception lock.unlock() } } return delegateNext } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/search/rank/LlmReRanker.kt ================================================ package com.phodal.shirecore.search.rank import com.intellij.openapi.components.Service import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.phodal.shirecore.llm.LlmProvider import com.phodal.shirecore.search.function.ScoredText import kotlinx.coroutines.runBlocking import kotlinx.coroutines.flow.cancellable fun RERANK_PROMPT(query: String, documentId: String, document: String): String { return """ You are an expert software developer responsible for helping detect whether the retrieved snippet of code is relevant to the query. For a given input, you need to output a single word: "Yes" or "No" indicating the retrieved snippet is relevant to the query. Query: Where is the FastAPI server? Snippet: ```/Users/andrew/Desktop/server/main.py from fastapi import FastAPI app = FastAPI() @app.get("/") fun read_root(): Map { return mapOf("Hello" to "World") } ``` Relevant: Yes Query: Where in the documentation does it talk about the UI? Snippet: ```/Users/andrew/Projects/bubble_sort/src/lib.rs fn bubble_sort(arr: &mut [T]) { for i in 0..arr.size { for j in 1 until arr.size - i { if (arr[j - 1] > arr[j]) { arr.swap(j - 1, j) } } } } ``` Relevant: No Query: $query Snippet: ```$documentId $document ``` Relevant: """.trimIndent() } @Service(Service.Level.PROJECT) class LLMReranker(val project: Project) : Reranker { override val name = "llmReranker" private suspend fun scoreChunk(chunk: ScoredText, query: String): Double { val prompt = RERANK_PROMPT(query, getBasename(chunk.file), chunk.text) val stream = LlmProvider.provider(project)?.stream(prompt, "", false)!! var completion: String = "" runBlocking { stream.cancellable().collect { completion += it } } if (completion.isBlank()) { return 0.0 } val answer = completion .trim() .lowercase() .replace("\"", "") .replace("'", "") return when (answer) { "yes" -> 1.0 "no" -> 0.0 else -> { println("Unexpected response from single token reranker: \"$answer\". Expected \"yes\" or \"no\".") 0.0 } } } private fun getBasename(file: VirtualFile?): String { return file?.path?.substringAfterLast("/") ?: "unknown" } override suspend fun rerank(query: String, chunks: List): List { return chunks.map { chunk -> chunk.copy(similarity = scoreChunk(chunk, query)) }.filter { it.similarity > 0.5 } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/search/rank/LostInTheMiddleRanker.kt ================================================ package com.phodal.shirecore.search.rank import com.phodal.shirecore.search.function.ScoredText class LostInTheMiddleRanker() : Reranker { override val name = "lostInTheMiddleRanker" override suspend fun rerank(query: String, chunks: List): List { val sortedChunks = chunks.sortedBy { it.similarity } val result = mutableListOf() for (i in sortedChunks.indices) { if (i % 2 == 0) { result.add(sortedChunks[i / 2]) } else { result.add(sortedChunks[sortedChunks.size - i / 2 - 1]) } } return result } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/search/rank/Reranker.kt ================================================ package com.phodal.shirecore.search.rank import com.intellij.openapi.project.Project import com.phodal.shirecore.search.function.ScoredText interface Reranker { val name: String suspend fun rerank(query: String, chunks: List): List companion object { fun create(name: String, project: Project): Reranker { return when (name) { "lostInTheMiddleRanker" -> LostInTheMiddleRanker() "llmReranker" -> LLMReranker(project) else -> throw IllegalArgumentException("Unknown reranker: $name") } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/search/similar/SimilarChunkContext.kt ================================================ package com.phodal.shirecore.search.similar import com.intellij.lang.Language import com.intellij.lang.LanguageCommenters class SimilarChunkContext(val language: Language, val paths: List?, val chunks: List?) { fun format(): String { val commentPrefix = commentPrefix(language) ?: return "" if (paths == null || chunks == null) return "" val filteredPairs = paths.zip(chunks).filter { it.second.isNotEmpty() } val queryBuilder = StringBuilder() for ((path, chunk) in filteredPairs) { val commentedCode = commentCode(chunk, commentPrefix) queryBuilder.append("$commentPrefix Compare this snippet from $path:\n") queryBuilder.append(commentedCode).append("\n") } return queryBuilder.toString().trim() } private fun commentCode(code: String, commentSymbol: String?): String { if (commentSymbol == null) return code return code.split("\n").joinToString("\n") { "$commentSymbol $it" } } companion object { fun commentPrefix(language: Language): String? { val commenter = LanguageCommenters.INSTANCE.forLanguage(language) val commentPrefix = commenter.lineCommentPrefix return commentPrefix } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/search/similar/SimilarChunkSearcher.kt ================================================ package com.phodal.shirecore.search.similar import com.intellij.openapi.application.runReadAction import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.fileEditor.impl.EditorHistoryManager import com.intellij.openapi.fileTypes.FileType import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.roots.ProjectRootManager import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement import com.intellij.psi.PsiManager import com.phodal.shirecore.search.algorithm.JaccardSimilarity import java.io.File class SimilarChunksSearch(private var snippetLength: Int = 60, private var maxRelevantFiles: Int = 20) : JaccardSimilarity() { fun similarChunksWithPaths(element: PsiElement): SimilarChunkContext { val mostRecentFiles = getMostRecentFiles(element) val mostRecentFilesRelativePaths = mostRecentFiles.mapNotNull { relativePathTo(it, element) } val chunks = extractChunks(element, mostRecentFiles) val jaccardSimilarities = computeInputSimilarity(element.text, chunks) val similarChunks: List> = jaccardSimilarities.mapIndexedNotNull { fileIndex, jaccardList -> val maxIndex = jaccardList.indexOf(jaccardList.maxOrNull()) val targetChunk = chunks[fileIndex][maxIndex] if (targetChunk.isNotEmpty()) { mostRecentFilesRelativePaths[fileIndex] to targetChunk } else { null } } val (paths, chunksText) = similarChunks.unzip() return SimilarChunkContext(element.language, paths, chunksText) } private fun relativePathTo(relativeFile: VirtualFile, element: PsiElement): String? { val fileIndex: ProjectFileIndex = ProjectRootManager.getInstance(element.project).fileIndex var contentRoot: VirtualFile? = runReadAction { fileIndex.getContentRootForFile(relativeFile) } if (contentRoot == null) { contentRoot = fileIndex.getClassRootForFile(relativeFile) } return contentRoot?.let { VfsUtilCore.getRelativePath(relativeFile, it, File.separatorChar) } } private fun extractChunks(element: PsiElement, mostRecentFiles: List): List> { val psiManager: PsiManager = PsiManager.getInstance(element.project) return mostRecentFiles.mapNotNull { file -> val psiFile = psiManager.findFile(file) psiFile?.text ?.split("\n", limit = snippetLength) ?.filter { !it.trim().startsWith("import ") && !it.trim().startsWith("package ") } } } private fun getMostRecentFiles(element: PsiElement): List { val fileType: FileType = element.containingFile?.fileType ?: return emptyList() val recentFiles: List = EditorHistoryManager.getInstance(element.project).fileList.filter { file -> file.isValid && file.fileType == fileType && file != element.containingFile.virtualFile } val start = (recentFiles.size - maxRelevantFiles + 1).coerceAtLeast(0) val end = (recentFiles.size - 1).coerceAtLeast(0) return recentFiles.subList(start, end) } companion object { val INSTANCE: SimilarChunksSearch = SimilarChunksSearch() fun createQuery(element: PsiElement, chunkSize: Int = 60): String? { if (element.language.displayName.lowercase() == "markdown") { return null } return runReadAction { try { val similarChunksSearch = SimilarChunksSearch(chunkSize).similarChunksWithPaths(element) if (similarChunksSearch.paths?.isEmpty() == true || similarChunksSearch.chunks?.isEmpty() == true) { return@runReadAction null } // todo: change to count query by size val query = similarChunksSearch.format() if (query.length < 10) { return@runReadAction null } if (query.length > 1024) { logger().warn("Query size is too large: ${query.length}") // split to 1024 return@runReadAction query.substring(0, 1024) } return@runReadAction query } catch (e: Exception) { return@runReadAction null } } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/search/tokenizer/CodeNamingTokenizer.kt ================================================ package com.phodal.shirecore.search.tokenizer class CodeNamingTokenizer(opts: RegexTokenizerOptions? = null) : RegexpTokenizer(opts) { init { whitespacePattern = Regex( "(?<=[a-z])(?=[A-Z])|" + // camelCase 分词 "(?<=[A-Z])(?=[A-Z][a-z])|" + // PascalCase 分词 "(?<=[A-Za-z])(?=[0-9])|" + // 字母和数字分词 "(?<=[0-9])(?=[A-Za-z])|" + // 数字和字母分词 "_+" // under_score 分词 ) } override fun tokenize(input: String): List { val results = whitespacePattern.split(input) return if (discardEmpty) { without(results, "", " ").map { it.lowercase() } } else { results.map { it.lowercase() } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/search/tokenizer/RegexpTokenizer.kt ================================================ package com.phodal.shirecore.search.tokenizer interface RegexTokenizerOptions { val pattern: Regex? val discardEmpty: Boolean val gaps: Boolean? } open class RegexpTokenizer(opts: RegexTokenizerOptions? = null) : Tokenizer { var whitespacePattern = Regex("\\s+") var discardEmpty: Boolean = true private var _gaps: Boolean? = null init { val options = opts ?: object : RegexTokenizerOptions { override val pattern: Regex? = null override val discardEmpty: Boolean = true override val gaps: Boolean? = null } whitespacePattern = options.pattern ?: whitespacePattern discardEmpty = options.discardEmpty _gaps = options.gaps if (_gaps == null) { _gaps = true } } override fun tokenize(input: String): List { val results: List val output = if (_gaps == true) { results = input.split(whitespacePattern) if (discardEmpty) without(results, "", " ") else results } else { results = whitespacePattern.findAll(input).map { it.value }.toList() results.ifEmpty { emptyList() } } return output } fun without(arr: List, vararg values: String): List { return arr.filter { it !in values } } } class WordTokenizer(options: RegexTokenizerOptions? = null) : RegexpTokenizer(options) { init { whitespacePattern = Regex("[^A-Za-zА-Яа-я0-9_]+") } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/search/tokenizer/StopwordsBasedTokenizer.kt ================================================ package com.phodal.shirecore.search.tokenizer import com.phodal.shirecore.search.tokenizer.TermSplitter.splitTerms val CHINESE_STOP_WORDS = listOf( "的", "地", "得", "和", "跟", "与", "及", "向", "并", "等", "更", "已", "含", "做", "我", "你", "他", "她", "们", "某", "该", "各", "每", "这", "那", "哪", "什", "么", "谁", "年", "月", "日", "时", "分", "秒", "几", "多", "来", "在", "就", "又", "很", "呢", "吧", "吗", "了", "嘛", "哇", "儿", "哼", "啊", "嗯", "是", "着", "都", "不", "说", "也", "看", "把", "还", "个", "有", "小", "到", "一", "为", "中", "于", "对", "会", "之", "第", "此", "或", "共", "按", "请" ) class StopwordsBasedTokenizer private constructor() : Tokenizer { companion object { private var instance_: StopwordsBasedTokenizer? = null @JvmStatic fun instance(): StopwordsBasedTokenizer { if (instance_ == null) { instance_ = StopwordsBasedTokenizer() } return instance_!! } } private val stopWords = listOf("we", "our", "you", "it", "its", "they", "them", "their", "this", "that", "these", "those", "is", "are", "was", "were", "be", "been", "being", "have", "has", "had", "having", "do", "does", "did", "doing", "can", "don", "t", "s", "will", "would", "should", "what", "which", "who", "when", "where", "why", "how", "a", "an", "the", "and", "or", "not", "no", "but", "because", "as", "until", "again", "further", "then", "once", "here", "there", "all", "any", "both", "each", "few", "more", "most", "other", "some", "such", "above", "below", "to", "during", "before", "after", "of", "at", "by", "about", "between", "into", "through", "from", "up", "down", "in", "out", "on", "off", "over", "under", "only", "own", "same", "so", "than", "too", "very", "just", "now") private val programmingKeywords = listOf("if", "then", "else", "for", "while", "with", "def", "function", "return", "TODO", "import", "try", "catch", "raise", "finally", "repeat", "switch", "case", "match", "assert", "continue", "break", "const", "class", "enum", "struct", "static", "new", "super", "this", "var") private val javaKeywords = listOf("public", "private", "protected", "static", "final", "abstract", "interface", "implements", "extends", "throws", "throw", "try", "catch", "finally", "synchronized") private val chineseStopWords = CHINESE_STOP_WORDS private val stopWordsSet = setOf( *stopWords.toTypedArray(), *programmingKeywords.toTypedArray(), *javaKeywords.toTypedArray(), *chineseStopWords.toTypedArray() ) override fun tokenize(input: String): List { return splitTerms(input).toList().filter { !stopWordsSet.contains(it) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/search/tokenizer/TermSplitter.kt ================================================ package com.phodal.shirecore.search.tokenizer /** * The `TermSplitter` object provides methods for splitting an input string into terms based on different naming styles of identifiers. * It supports three naming styles: CamelCase, Numeric, and underscore_case. * * The `splitTerms` method is a static generator function that splits the input string into terms based on the naming style of the identifiers. * It matches all patterns in the input string, creates a set to store unique terms, and adds lowercase versions of the matches to this set. * It then splits the matches into terms based on CamelCase, underscore_case, and Numeric naming styles, and adds these terms to the terms array. * If the term is longer than 2 characters and matches the alphabetic pattern, it is added to the set of unique terms. * Finally, it yields each unique term as a sequence. * * The `syncSplitTerms` method synchronously splits the input string into terms and returns a list of unique terms. */ object TermSplitter { private val allPattern = Regex("(? { return sequence { val matchAll = allPattern.findAll(input) for (match in matchAll) { val matchValue = match.value val uniqueTerms = mutableSetOf() uniqueTerms.add(matchValue.lowercase()) val terms = mutableListOf() val camelCaseSplits = matchValue.split(camelCasePattern) if (camelCaseSplits.size > 1) { terms.addAll(camelCaseSplits) } val underscoreSplit = matchValue.split('_') if (underscoreSplit.size > 1) { terms.addAll(underscoreSplit) } val numberSuffixMatch = numericPattern.find(matchValue) numberSuffixMatch?.let { terms.add(it.groupValues[1]) } terms.filter { term -> term.length > 2 && alphaNumericPattern.containsMatchIn(term) } .forEach { uniqueTerms.add(it.lowercase()) } yieldAll(uniqueTerms) } }.distinct() } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/search/tokenizer/Tokenizer.kt ================================================ package com.phodal.shirecore.search.tokenizer interface Tokenizer { fun tokenize(input: String): List fun trim(array: MutableList): List { while (array.last() == "") { array.removeAt(array.lastIndex) } while (array.first() == "") { array.removeAt(0) } return array } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/sketch/LangSketch.kt ================================================ package com.phodal.shirecore.sketch import com.intellij.lang.Language import com.intellij.openapi.Disposable import com.intellij.openapi.actionSystem.ActionGroup import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.ActionPlaces import com.intellij.openapi.actionSystem.ActionToolbar import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.colors.EditorColorsListener import com.intellij.openapi.editor.colors.EditorColorsManager import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.project.Project import com.intellij.util.messages.Topic import javax.swing.JComponent interface LangSketch : Disposable { fun getViewText(): String fun updateViewText(text: String) fun getComponent(): JComponent fun updateLanguage(language: Language?, originLanguage: String?) fun doneUpdateText(text: String) {} fun setupActionBar(project: Project, editor: Editor) { val toolbar = actionToolbar() ?: return if (editor is EditorEx) { toolbar.component.setBackground(editor.backgroundColor) } toolbar.component.setOpaque(true) toolbar.targetComponent = editor.contentComponent editor.headerComponent = toolbar.component val connect = project.messageBus.connect(this) val topic: Topic = EditorColorsManager.TOPIC connect.subscribe(topic, EditorColorsListener { if (editor is EditorEx) { toolbar.component.setBackground(editor.backgroundColor) } }) } fun actionToolbar(): ActionToolbar? { val toolbarActionGroup = ActionManager.getInstance().getAction("Shire.ToolWindow.Toolbar") as? ActionGroup ?: return null val toolbar = ActionManager.getInstance() .createActionToolbar(ActionPlaces.MAIN_TOOLBAR, toolbarActionGroup, true) return toolbar } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/sketch/highlight/CodeHighlightSketch.kt ================================================ package com.phodal.shirecore.sketch.highlight import com.intellij.lang.Language import com.intellij.openapi.Disposable import com.intellij.openapi.actionSystem.DataProvider import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ReadAction import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.EditorFactory import com.intellij.openapi.editor.EditorKind import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.editor.ex.EditorMarkupModel import com.intellij.openapi.editor.ex.FocusChangeListener import com.intellij.openapi.editor.ex.MarkupModelEx import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.fileTypes.PlainTextLanguage import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.text.StringUtil import com.intellij.openapi.vfs.VirtualFile import com.intellij.testFramework.LightVirtualFile import com.intellij.ui.JBColor import com.intellij.ui.components.JBPanel import com.intellij.util.concurrency.annotations.RequiresReadLock import com.intellij.util.ui.JBEmptyBorder import com.intellij.util.ui.JBUI import com.phodal.shirecore.sketch.LangSketch import com.phodal.shirecore.utils.markdown.CodeFenceLanguage import java.awt.BorderLayout import java.util.concurrent.atomic.AtomicBoolean import javax.swing.JComponent class CodeHighlightSketch( val project: Project, val text: String, private var ideaLanguage: Language?, val editorLineThreshold: Int = 6, ) : JBPanel(BorderLayout()), DataProvider, LangSketch { private var textLanguage: String? = null var editorFragment: EditorFragment? = null private var hasSetupAction = false init { if (text.isEmpty() && (ideaLanguage?.displayName != "Markdown" && ideaLanguage != PlainTextLanguage.INSTANCE)) { initEditor(text) } } fun initEditor(text: String) { if (hasSetupAction) return hasSetupAction = true val editor = createCodeViewerEditor(project, text, ideaLanguage, this) border = JBEmptyBorder(8) layout = BorderLayout(JBUI.scale(8), 0) background = JBColor(0xEAEEF7, 0x2d2f30) editor.component.isOpaque = true editorFragment = EditorFragment(editor, editorLineThreshold) add(editorFragment!!.getContent(), BorderLayout.CENTER) if (textLanguage != null && textLanguage?.lowercase() != "markdown") { setupActionBar(project, editor) } } override fun getViewText(): String { return editorFragment?.editor?.document?.text ?: "" } override fun updateLanguage(language: Language?, originLanguage: String?) { if (ideaLanguage == null || ideaLanguage == PlainTextLanguage.INSTANCE) { ideaLanguage = language textLanguage = originLanguage } } override fun updateViewText(text: String) { if (!hasSetupAction && text.isNotEmpty()) { initEditor(text) } WriteCommandAction.runWriteCommandAction(project) { val document = editorFragment?.editor?.document val normalizedText = StringUtil.convertLineSeparators(text) document?.replaceString(0, document.textLength, normalizedText) } } override fun getComponent(): JComponent = this override fun getData(dataId: String): Any? = null companion object { private val LINE_NO_REGEX = Regex("^\\d+:") fun createCodeViewerEditor( project: Project, text: String, ideaLanguage: Language?, disposable: Disposable, ): EditorEx { var editorText = text val language = ideaLanguage ?: CodeFenceLanguage.findLanguage("Plain text") val ext = if (language.displayName == "Plain text") { CodeFenceLanguage.lookupFileExt(language.displayName) } else { language.associatedFileType?.defaultExtension ?: "Unknown" } /// check text easyline starts with Lineno and :, for example: 1: var isShowLineNo = true editorText.lines().forEach { if (!it.matches(LINE_NO_REGEX)) { isShowLineNo = false return@forEach } } if (isShowLineNo) { val newLines = text.lines().map { it.replace(LINE_NO_REGEX, "") } editorText = newLines.joinToString("\n") } val file = LightVirtualFile("shire.${ext}", language, editorText) val document: Document = file.findDocument() ?: throw IllegalStateException("Document not found") return createCodeViewerEditor(project, file, document, disposable, isShowLineNo) } fun createCodeViewerEditor( project: Project, file: LightVirtualFile, document: Document, disposable: Disposable, isShowLineNo: Boolean? = false, ): EditorEx { val editor: EditorEx = ReadAction.compute { EditorFactory.getInstance().createViewer(document, project, EditorKind.PREVIEW) as EditorEx } disposable.whenDisposed(disposable) { EditorFactory.getInstance().releaseEditor(editor) } editor.setFile(file) editor.setCaretEnabled(true) val highlighter = ApplicationManager.getApplication() .getService(EditorHighlighterFactory::class.java) .createEditorHighlighter(project, file) editor.highlighter = highlighter val markupModel: MarkupModelEx = editor.markupModel (markupModel as EditorMarkupModel).isErrorStripeVisible = false val settings = editor.settings.also { it.isDndEnabled = false it.isLineNumbersShown = isShowLineNo ?: false it.additionalLinesCount = 0 it.isLineMarkerAreaShown = false it.isFoldingOutlineShown = false it.isRightMarginShown = false it.isShowIntentionBulb = false it.isUseSoftWraps = true it.isRefrainFromScrolling = true it.isAdditionalPageAtBottom = false it.isCaretRowShown = false } editor.addFocusListener(object : FocusChangeListener { override fun focusGained(focusEditor: Editor) { settings.isCaretRowShown = true } override fun focusLost(focusEditor: Editor) { settings.isCaretRowShown = false editor.markupModel.removeAllHighlighters() } }) return editor } } override fun dispose() { // do nothing } } @RequiresReadLock fun VirtualFile.findDocument(): Document? { return ReadAction.compute { FileDocumentManager.getInstance().getDocument(this) } } fun Disposable.whenDisposed(listener: () -> Unit) { Disposer.register(this) { listener() } } fun Disposable.whenDisposed( parentDisposable: Disposable, listener: () -> Unit, ) { val isDisposed = AtomicBoolean(false) val disposable = Disposable { if (isDisposed.compareAndSet(false, true)) { listener() } } Disposer.register(this, disposable) Disposer.register(parentDisposable, Disposable { if (isDisposed.compareAndSet(false, true)) { Disposer.dispose(disposable) } }) } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/sketch/highlight/EditorFragment.kt ================================================ package com.phodal.shirecore.sketch.highlight import com.intellij.icons.AllIcons import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.event.CaretEvent import com.intellij.openapi.editor.event.CaretListener import com.intellij.openapi.editor.ex.EditorEx import com.intellij.ui.components.JBLabel import com.intellij.util.ui.JBUI import com.intellij.util.ui.components.BorderLayoutPanel import java.awt.Color import java.awt.Dimension import java.awt.Insets import java.awt.event.MouseAdapter import java.awt.event.MouseEvent import javax.swing.Box import javax.swing.JComponent class EditorPadding(private val editor: Editor, pad: Int) : Box.Filler(Dimension(pad, pad), Dimension(pad, pad), Dimension(pad, pad)) { init { setOpaque(true) editor.caretModel.addCaretListener(object : CaretListener { override fun caretPositionChanged(event: CaretEvent) { this@EditorPadding.repaint() } }) } override fun getBackground(): Color = editor.contentComponent.getBackground() } class EditorFragment(var editor: EditorEx, private val editorLineThreshold: Int = 6) { private val expandCollapseTextLabel: JBLabel = JBLabel("", 0).apply { isOpaque = true isVisible = false } private val content: BorderLayoutPanel = createContentPanel() private var collapsed = false private fun createContentPanel(): BorderLayoutPanel { return object : BorderLayoutPanel() { override fun getPreferredSize(): Dimension { val preferredSize = super.getPreferredSize() if (editor.document.lineCount > editorLineThreshold && collapsed) { val lineHeight = editor.lineHeight val insets = editor.scrollPane.insets val editorMaxHeight = calculateMaxHeight(lineHeight, insets) return Dimension(preferredSize.width, editorMaxHeight) } return preferredSize } private fun calculateMaxHeight(lineHeight: Int, insets: Insets): Int { val height = lineHeight * editorLineThreshold + insets.height val headerHeight = editor.headerComponent?.preferredSize?.height ?: 0 val labelHeight = expandCollapseTextLabel.preferredSize.height return height + headerHeight + labelHeight + insets.height } }.apply { isOpaque = true addToLeft(EditorPadding(editor, 5)) addToRight(EditorPadding(editor, 5)) addToCenter(editor.component) addToBottom(expandCollapseTextLabel) } } init { expandCollapseTextLabel.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent?) { toggleCollapsedState() } }) } fun getContent(): JComponent = content fun isCollapsed(): Boolean = collapsed fun setCollapsed(value: Boolean) { if (collapsed != value) { collapsed = value updateExpandCollapseLabel() } } private fun toggleCollapsedState() { setCollapsed(!collapsed) } fun updateExpandCollapseLabel() { val linesCount = editor.document.lineCount expandCollapseTextLabel.isVisible = linesCount > editorLineThreshold expandCollapseTextLabel.text = if (collapsed) "More lines" else "" expandCollapseTextLabel.icon = if (collapsed) AllIcons.General.ChevronDown else AllIcons.General.ChevronUp } } val Insets.width: Int get() = left + right val Insets.height: Int get() = top + bottom ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/sketch/highlight/toolbar/ShireCopyToClipboardAction.kt ================================================ package com.phodal.shirecore.sketch.highlight.toolbar import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.project.DumbAwareAction import java.awt.Toolkit import java.awt.datatransfer.StringSelection class ShireCopyToClipboardAction : DumbAwareAction() { override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT override fun actionPerformed(e: AnActionEvent) { val editor = e.getData(com.intellij.openapi.actionSystem.PlatformDataKeys.EDITOR) ?: return val document = editor.document val text = document.text val selection = StringSelection(text) val clipboard = Toolkit.getDefaultToolkit().systemClipboard clipboard.setContents(selection, null) } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/sketch/highlight/toolbar/ShireInsertCodeAction.kt ================================================ package com.phodal.shirecore.sketch.highlight.toolbar import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.PlatformDataKeys import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.DumbAwareAction import com.intellij.psi.PsiDocumentManager import com.intellij.psi.codeStyle.CodeStyleManager class ShireInsertCodeAction : DumbAwareAction() { override fun actionPerformed(e: AnActionEvent) { val project = e.project ?: return val editor = e.getData(PlatformDataKeys.EDITOR) ?: return val selectionModel = if (editor.selectionModel.hasSelection()) editor.selectionModel else null val newText = selectionModel?.selectedText ?: editor.document.text.trimEnd() val textEditor = FileEditorManager.getInstance(project).selectedTextEditor ?: return val currentSelection = textEditor.selectionModel WriteCommandAction.writeCommandAction(project).compute { val offset: Int val document = textEditor.document if (currentSelection.hasSelection()) { offset = currentSelection.selectionStart document.replaceString(currentSelection.selectionStart, currentSelection.selectionEnd, newText) } else { offset = textEditor.caretModel.offset document.insertString(offset, newText) } val psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document) ?: return@compute PsiDocumentManager.getInstance(project).commitDocument(document) CodeStyleManager.getInstance(project).reformatText(psiFile, offset, offset + newText.length) } } override fun update(e: AnActionEvent) { val project = e.project if (project == null) { e.presentation.isEnabled = false return } val selectedTextEditor = FileEditorManager.getInstance(project).selectedTextEditor if (selectedTextEditor == null || !selectedTextEditor.document.isWritable) { e.presentation.isEnabled = false } else { e.presentation.isEnabledAndVisible = true } } override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/sketch/highlight/toolbar/ShireLanguageLabelAction.kt ================================================ package com.phodal.shirecore.sketch.highlight.toolbar import com.intellij.openapi.actionSystem.* import com.intellij.openapi.actionSystem.ex.CustomComponentAction import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.util.Key import com.intellij.testFramework.LightVirtualFile import com.intellij.ui.components.JBLabel import com.intellij.util.ui.UIUtil import javax.swing.JComponent class ShireLanguageLabelAction: DumbAwareAction(), CustomComponentAction { override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT override fun createCustomComponent(presentation: Presentation, place: String): JComponent { val languageId = presentation.getClientProperty(SHIRE_LANGUAGE_LABEL_KEY) ?: "" val label = JBLabel(languageId) label.setOpaque(false) label.foreground = UIUtil.getLabelInfoForeground() return label } override fun updateCustomComponent(component: JComponent, presentation: Presentation) { if (component !is JBLabel) return val languageId = presentation.getClientProperty(SHIRE_LANGUAGE_LABEL_KEY) ?: "" if (languageId.isNotBlank() && component.text.isBlank()) { component.text = languageId } } override fun actionPerformed(e: AnActionEvent) { } override fun update(e: AnActionEvent) { val editor = e.dataContext.getData(CommonDataKeys.EDITOR) ?: return val lightVirtualFile = FileDocumentManager.getInstance().getFile(editor.document) as? LightVirtualFile ?: return val language = lightVirtualFile.language ?: return e.presentation.putClientProperty(SHIRE_LANGUAGE_LABEL_KEY, language.displayName) } companion object { val SHIRE_LANGUAGE_LABEL_KEY: Key = Key.create("ShireLanguagePresentationKey") } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/sketch/highlight/toolbar/ShireRunCodeAction.kt ================================================ package com.phodal.shirecore.sketch.highlight.toolbar import com.intellij.ide.scratch.ScratchRootType import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.vfs.readText import com.intellij.psi.PsiManager import com.phodal.shirecore.provider.shire.FileRunService class ShireRunCodeAction : DumbAwareAction() { override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT override fun update(e: AnActionEvent) { val project = e.project ?: return val editor = e.getData(com.intellij.openapi.actionSystem.PlatformDataKeys.EDITOR) ?: return val document = editor.document val file = FileDocumentManager.getInstance().getFile(document) if (file != null) { val psiFile = PsiManager.getInstance(project).findFile(file) if (psiFile != null) { e.presentation.isEnabled = FileRunService.provider(project, file) != null return } } e.presentation.isEnabled = false } override fun actionPerformed(e: AnActionEvent) { val editor = e.getData(com.intellij.openapi.actionSystem.PlatformDataKeys.EDITOR) ?: return val project = e.project ?: return val document = editor.document val file = FileDocumentManager.getInstance().getFile(document) ?: return val psiFile = PsiManager.getInstance(project).findFile(file) ?: return val scratchFile = ScratchRootType.getInstance() .createScratchFile(project, file.name, psiFile.language, file.readText()) ?: return try { FileRunService.provider(project, file)?.runFile( project, scratchFile, psiFile, ) } finally { // runWriteAction { // scratchFile.delete(this) // } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/sketch/lint/SketchCodeInspection.kt ================================================ package com.phodal.shirecore.sketch.lint import com.intellij.analysis.AnalysisScope import com.intellij.codeInsight.daemon.impl.DaemonProgressIndicator import com.intellij.codeInspection.InspectionEngine import com.intellij.codeInspection.InspectionManager import com.intellij.codeInspection.ProblemDescriptor import com.intellij.codeInspection.ex.GlobalInspectionContextBase import com.intellij.codeInspection.ex.LocalInspectionToolWrapper import com.intellij.lang.annotation.HighlightSeverity import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.intellij.openapi.ui.popup.JBPopup import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.openapi.vfs.VirtualFile import com.intellij.profile.codeInspection.InspectionProjectProfileManager import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.intellij.ui.JBColor import com.intellij.ui.components.JBLabel import com.intellij.ui.components.JBScrollPane import com.intellij.ui.table.JBTable import com.intellij.util.PairProcessor import com.phodal.shirecore.ShireCoreBundle import java.awt.Dimension import java.awt.FlowLayout import java.awt.event.MouseAdapter import java.awt.event.MouseEvent import javax.swing.BorderFactory import javax.swing.JPanel import javax.swing.JTable import javax.swing.table.DefaultTableModel object SketchCodeInspection { fun showErrors(errors: List, panel: JPanel) { val columnNames = arrayOf("Line", "Description", "Highlight Type") val data = errors.map { arrayOf(it.lineNumber, it.description, it.highlightType.toString()) }.toTypedArray() val tableModel = DefaultTableModel(data, columnNames) val table = JBTable(tableModel).apply { autoResizeMode = JTable.AUTO_RESIZE_ALL_COLUMNS } val scrollPane = JBScrollPane(table).apply { preferredSize = Dimension(480, 400) } val errorLabel = JBLabel(ShireCoreBundle.message("sketch.lint.error", errors.size)).apply { border = BorderFactory.createEmptyBorder(5, 5, 5, 5) addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent?) { createPopup(scrollPane, table, errors).showInCenterOf(panel) } override fun mouseEntered(e: MouseEvent) { toolTipText = ShireCoreBundle.message("sketch.lint.error.tooltip") } }) } val errorPanel = JPanel().apply { background = JBColor.WHITE layout = FlowLayout(FlowLayout.LEFT) add(errorLabel) } panel.add(errorPanel) } private fun createPopup( scrollPane: JBScrollPane, table: JBTable, errors: List ): JBPopup = JBPopupFactory.getInstance() .createComponentPopupBuilder(scrollPane, table) .setTitle("Found Lint Issues: ${errors.size}") .setResizable(true) .setMovable(true) .setRequestFocus(true) .createPopup() fun runInspections(project: Project, psiFile: PsiFile, originFile: VirtualFile): List { val globalContext = InspectionManager.getInstance(project).createNewGlobalContext() as? GlobalInspectionContextBase ?: return emptyList() val originPsi = runReadAction { PsiManager.getInstance(project).findFile(originFile) } ?: return emptyList() globalContext.currentScope = AnalysisScope(originPsi) val toolsCopy = collectTools(project, psiFile, globalContext) if (toolsCopy.isEmpty()) { return emptyList() } return runReadAction { val indicator = DaemonProgressIndicator() val result: Map> = InspectionEngine.inspectEx( toolsCopy, psiFile, psiFile.textRange, psiFile.textRange, false, false, true, indicator, PairProcessor.alwaysTrue() ) val problems = result.values.flatten() return@runReadAction problems .sortedBy { it.lineNumber } .distinctBy { it.lineNumber }.map { SketchInspectionError.Companion.from(it) } } } private fun collectTools( project: Project, psiFile: PsiFile, globalContext: GlobalInspectionContextBase ): MutableList { val inspectionProfile = InspectionProjectProfileManager.getInstance(project).currentProfile val toolWrappers = inspectionProfile.getInspectionTools(psiFile) .filter { it.isApplicable(psiFile.language) && it.defaultLevel.severity == HighlightSeverity.ERROR } toolWrappers.forEach { it.initialize(globalContext) } val toolsCopy: MutableList = ArrayList(toolWrappers.size) for (tool in toolWrappers) { if (tool is LocalInspectionToolWrapper) { toolsCopy.add(tool.createCopy()) } } return toolsCopy } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/sketch/lint/SketchInspectionError.kt ================================================ package com.phodal.shirecore.sketch.lint import com.intellij.codeInspection.ProblemDescriptor import com.intellij.codeInspection.ProblemHighlightType data class SketchInspectionError( val lineNumber: Int, val description: String, val highlightType: ProblemHighlightType, ) { companion object { fun from(problemDescriptor: ProblemDescriptor): SketchInspectionError { return SketchInspectionError( problemDescriptor.lineNumber, problemDescriptor.descriptionTemplate, problemDescriptor.highlightType ) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/sketch/patch/DiffLangSketch.kt ================================================ package com.phodal.shirecore.sketch.patch import com.intellij.icons.AllIcons import com.intellij.lang.Language import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.command.undo.UndoManager import com.intellij.openapi.diff.impl.patch.PatchReader import com.intellij.openapi.diff.impl.patch.TextFilePatch import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.fileEditor.FileEditorProvider import com.intellij.openapi.project.Project import com.intellij.openapi.ui.DialogWrapper import com.intellij.openapi.vcs.changes.patch.AbstractFilePatchInProgress import com.intellij.openapi.vcs.changes.patch.ApplyPatchDefaultExecutor import com.intellij.openapi.vcs.changes.patch.MatchPatchPaths import com.intellij.openapi.vfs.VirtualFile import com.intellij.testFramework.LightVirtualFile import com.intellij.ui.JBColor import com.intellij.ui.components.panels.VerticalLayout import com.intellij.util.containers.MultiMap import com.intellij.util.ui.JBUI import com.phodal.shirecore.ShireCoreBundle import com.phodal.shirecore.ShirelangNotifications import com.phodal.shirecore.findFile import com.phodal.shirecore.provider.sketch.ExtensionLangSketch import java.awt.BorderLayout import javax.swing.BoxLayout import javax.swing.JButton import javax.swing.JComponent import javax.swing.JPanel class DiffLangSketch(private val myProject: Project, private var patchContent: String) : ExtensionLangSketch { private val mainPanel: JPanel = JPanel(VerticalLayout(5)) private val myHeaderPanel: JPanel = JPanel(BorderLayout()) private val shelfExecutor = ApplyPatchDefaultExecutor(myProject) private val myReader = PatchReader(patchContent).also { try { it.parseAllPatches() } catch (e: Exception) { ShirelangNotifications.error(myProject, "Failed to parse patch: ${e.message}") } } private val filePatches: MutableList = myReader.textPatches init { val header = createHeaderAction() myHeaderPanel.add(header, BorderLayout.EAST) mainPanel.add(myHeaderPanel) mainPanel.border = JBUI.Borders.compound( JBUI.Borders.empty(0, 10), JBUI.Borders.customLine(JBColor.border(), 1, 1, 1, 1) ) ApplicationManager.getApplication().invokeAndWait { if (filePatches.isEmpty()) { ShirelangNotifications.error(myProject, "PatchProcessor: no patches found") return@invokeAndWait } filePatches.forEachIndexed { _, patch -> val diffPanel = when { patch.beforeFileName != null -> { val originFile = myProject.findFile(patch.beforeFileName!!) ?: return@forEachIndexed SingleFileDiffView(myProject, originFile, patch) } patch.afterFileName != null -> { val content = patch.singleHunkPatchText val virtualFile = LightVirtualFile(patch.afterFileName!!, content) SingleFileDiffView(myProject, virtualFile, patch) } else -> { val content = patch.singleHunkPatchText val virtualFile = LightVirtualFile("ErrorPatchFile", content) SingleFileDiffView(myProject, virtualFile, patch) } } mainPanel.add(diffPanel.getComponent()) } } } private fun createHeaderAction(): JComponent { val acceptButton = JButton(ShireCoreBundle.message("sketch.patch.action.accept")).apply { icon = AllIcons.Actions.SetDefault toolTipText = ShireCoreBundle.message("sketch.patch.action.accept.tooltip") addActionListener { handleAcceptAction() } } val rejectButton = JButton(ShireCoreBundle.message("sketch.patch.action.reject")).apply { this.icon = AllIcons.Actions.Rollback this.toolTipText = ShireCoreBundle.message("sketch.patch.action.reject.tooltip") addActionListener { handleRejectAction() } } val viewDiffButton = JButton(ShireCoreBundle.message("sketch.patch.action.viewDiff")).apply { this.toolTipText = ShireCoreBundle.message("sketch.patch.action.viewDiff.tooltip") this.icon = AllIcons.Actions.ListChanges addActionListener { handleViewDiffAction() } } val panel = JPanel() panel.layout = BoxLayout(panel, BoxLayout.X_AXIS) panel.add(acceptButton) panel.add(rejectButton) panel.add(viewDiffButton) panel.background = JBColor(0xF5F5F5, 0x333333) return panel } private fun handleAcceptAction() { ApplicationManager.getApplication().invokeAndWait { val matchedPatches = MatchPatchPaths(myProject).execute(filePatches, true) val patchGroups = MultiMap>() for (patchInProgress in matchedPatches) { patchGroups.putValue(patchInProgress.base, patchInProgress) } if (filePatches.isEmpty()) { ShirelangNotifications.error(myProject, "PatchProcessor: no patches found") return@invokeAndWait } val pathsFromGroups = ApplyPatchDefaultExecutor.pathsFromGroups(patchGroups) val additionalInfo = myReader.getAdditionalInfo(pathsFromGroups) shelfExecutor.apply(filePatches, patchGroups, null, "LlmGen.diff", additionalInfo) } } private fun handleRejectAction() { val undoManager = UndoManager.getInstance(myProject) val fileEditor = FileEditorManager.getInstance(myProject).selectedEditor ?: return if (undoManager.isUndoAvailable(fileEditor)) { undoManager.undo(fileEditor) } } private fun handleViewDiffAction() { val beforeFileNames = filePatches.mapNotNull { it.beforeFileName } if (beforeFileNames.size > 1) { return defaultView() } else { val editorProvider = FileEditorProvider.EP_FILE_EDITOR_PROVIDER.extensionList.firstOrNull { it.javaClass.simpleName == "DiffPatchFileEditorProvider" } if (editorProvider != null) { val virtualFile = LightVirtualFile("diff.diff", patchContent) val editor = editorProvider.createEditor(myProject, virtualFile) object : DialogWrapper(myProject) { init { title = "Diff Preview" setOKButtonText("Accept") init() } override fun doOKAction() { handleAcceptAction() super.doOKAction() } override fun createCenterPanel(): JComponent { return editor.component } }.show() } else { return defaultView() } } } private fun defaultView() { MyApplyPatchFromClipboardDialog(myProject, patchContent).show() } override fun getExtensionName(): String = "patch" override fun getViewText(): String = patchContent override fun updateViewText(text: String) { this.patchContent = text } override fun getComponent(): JComponent = mainPanel override fun updateLanguage(language: Language?, originLanguage: String?) {} override fun dispose() {} } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/sketch/patch/DiffLangSketchProvider.kt ================================================ package com.phodal.shirecore.sketch.patch import com.intellij.openapi.project.Project import com.phodal.shirecore.provider.sketch.ExtensionLangSketch import com.phodal.shirecore.provider.sketch.LanguageSketchProvider class DiffLangSketchProvider : LanguageSketchProvider { override fun isSupported(lang: String): Boolean = lang == "diff" || lang == "patch" override fun create(project: Project, content: String): ExtensionLangSketch = DiffLangSketch(project, content) } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/sketch/patch/MyApplyPatchFromClipboardDialog.kt ================================================ package com.phodal.shirecore.sketch.patch import com.intellij.openapi.project.Project import com.intellij.openapi.vcs.VcsApplicationSettings import com.intellij.openapi.vcs.VcsBundle import com.intellij.openapi.vcs.changes.patch.ApplyPatchDefaultExecutor import com.intellij.openapi.vcs.changes.patch.ApplyPatchDifferentiatedDialog import com.intellij.openapi.vcs.changes.patch.ApplyPatchMode import com.intellij.testFramework.LightVirtualFile import java.awt.event.ActionEvent import java.awt.event.KeyEvent import javax.swing.JCheckBox import javax.swing.JComponent class MyApplyPatchFromClipboardDialog(project: Project, clipboardText: String) : ApplyPatchDifferentiatedDialog( project, ApplyPatchDefaultExecutor(project), emptyList(), ApplyPatchMode.APPLY_PATCH_IN_MEMORY, LightVirtualFile("clipboardPatchFile", clipboardText), null, null, //NON-NLS null, null, null, false ) { override fun createDoNotAskCheckbox(): JComponent = createAnalyzeOnTheFlyOptionPanel() companion object { private fun createAnalyzeOnTheFlyOptionPanel(): JCheckBox { val removeOptionCheckBox = JCheckBox(VcsBundle.message("patch.apply.analyze.from.clipboard.on.the.fly.checkbox")) removeOptionCheckBox.mnemonic = KeyEvent.VK_L removeOptionCheckBox.isSelected = VcsApplicationSettings.getInstance().DETECT_PATCH_ON_THE_FLY removeOptionCheckBox.addActionListener { e: ActionEvent? -> VcsApplicationSettings.getInstance().DETECT_PATCH_ON_THE_FLY = removeOptionCheckBox.isSelected } return removeOptionCheckBox } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/sketch/patch/SingleFileDiffView.kt ================================================ package com.phodal.shirecore.sketch.patch import com.intellij.diff.DiffContentFactoryEx import com.intellij.diff.chains.SimpleDiffRequestChain import com.intellij.diff.chains.SimpleDiffRequestProducer import com.intellij.diff.editor.ChainDiffVirtualFile import com.intellij.diff.editor.DiffEditorTabFilesManager import com.intellij.diff.requests.SimpleDiffRequest import com.intellij.icons.AllIcons import com.intellij.lang.Language import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.runInEdt import com.intellij.openapi.application.runReadAction import com.intellij.openapi.command.undo.UndoManager import com.intellij.openapi.diff.impl.patch.TextFilePatch import com.intellij.openapi.diff.impl.patch.apply.GenericPatchApplier import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.Project import com.intellij.openapi.ui.DialogPanel import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.readText import com.intellij.psi.PsiManager import com.intellij.testFramework.LightVirtualFile import com.intellij.ui.DarculaColors import com.intellij.ui.JBColor import com.intellij.ui.components.JBLabel import com.intellij.ui.components.panels.VerticalLayout import com.intellij.ui.dsl.builder.AlignX import com.intellij.ui.dsl.builder.RightGap import com.intellij.ui.dsl.builder.panel import com.intellij.util.LocalTimeCounter import com.phodal.shirecore.ShireCoreBundle import com.phodal.shirecore.sketch.LangSketch import com.phodal.shirecore.sketch.lint.SketchCodeInspection import java.awt.BorderLayout import java.awt.event.MouseAdapter import java.awt.event.MouseEvent import javax.swing.BorderFactory import javax.swing.JButton import javax.swing.JComponent import javax.swing.JPanel class SingleFileDiffView( private val myProject: Project, private val currentFile: VirtualFile, val patch: TextFilePatch, ) : LangSketch { private val mainPanel: JPanel = JPanel(VerticalLayout(5)) private val myHeaderPanel: JPanel = JPanel(BorderLayout()) private var filePanel: DialogPanel? = null var diffFile: ChainDiffVirtualFile? = null private val oldCode = currentFile.readText() private val appliedPatch = GenericPatchApplier.apply(oldCode, patch.hunks) private val newCode = appliedPatch?.patchedText ?: "" init { val contentPanel = JPanel(BorderLayout()) val actions = createActionButtons() val filepathLabel = JBLabel(currentFile.name).apply { icon = currentFile.fileType.icon border = BorderFactory.createEmptyBorder(2, 10, 2, 10) addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent?) { val isShowDiffSuccess = showDiff() if (isShowDiffSuccess) return FileEditorManager.getInstance(myProject).openFile(currentFile, true) } override fun mouseEntered(e: MouseEvent) { foreground = JBColor.WHITE filePanel?.background = JBColor(DarculaColors.BLUE, DarculaColors.BLUE) } override fun mouseExited(e: MouseEvent) { foreground = JBColor.BLACK filePanel?.background = JBColor.PanelBackground } }) } filePanel = panel { row { cell(filepathLabel).align(AlignX.FILL).resizableColumn() actions.forEachIndexed { index, action -> cell(action).align(AlignX.LEFT) if (index < actions.size - 1) { this@panel.gap(RightGap.SMALL) } } } }.apply { background = JBColor.PanelBackground } val fileContainer = JPanel(BorderLayout(10, 10)).also { it.add(filePanel) } contentPanel.add(fileContainer, BorderLayout.CENTER) mainPanel.add(myHeaderPanel) mainPanel.add(contentPanel) ApplicationManager.getApplication().executeOnPooledThread { lintCheckForNewCode(currentFile) } } fun lintCheckForNewCode(currentFile: VirtualFile) { if (newCode.isEmpty()) return val newFile = LightVirtualFile(currentFile, newCode, LocalTimeCounter.currentTime()) val psiFile = runReadAction { PsiManager.getInstance(myProject).findFile(newFile) } ?: return val errors = SketchCodeInspection.runInspections(myProject, psiFile, currentFile) if (errors.isNotEmpty()) { SketchCodeInspection.showErrors(errors, this@SingleFileDiffView.mainPanel) } } private fun showDiff(): Boolean { if (diffFile != null) { showDiffFile(diffFile!!) return true } val document = FileDocumentManager.getInstance().getDocument(currentFile) ?: return false val appliedPatch = GenericPatchApplier.apply(document.text, patch.hunks) ?: return false val newText = appliedPatch.patchedText val diffFactory = DiffContentFactoryEx.getInstanceEx() val currentDocContent = diffFactory.create(myProject, currentFile) val newDocContent = diffFactory.create(newText) val diffRequest = SimpleDiffRequest( "Shire Diff - ${patch.beforeFileName}", currentDocContent, newDocContent, "Original", "AI generated" ) val producer = SimpleDiffRequestProducer.create(currentFile.path) { diffRequest } val chain = SimpleDiffRequestChain.fromProducer(producer) runInEdt { diffFile = ChainDiffVirtualFile(chain, "Diff") showDiffFile(diffFile!!) } return true } private val diffEditorTabFilesManager = DiffEditorTabFilesManager.getInstance(myProject) private fun showDiffFile(diffFile: ChainDiffVirtualFile) { diffEditorTabFilesManager.showDiffFile(diffFile, true) } private fun createActionButtons(): List { val undoManager = UndoManager.getInstance(myProject) val fileEditor = FileEditorManager.getInstance(myProject).getSelectedEditor(currentFile) val rollback = JButton(AllIcons.Actions.Rollback).apply { toolTipText = ShireCoreBundle.message("sketch.patch.action.rollback.tooltip") isEnabled = undoManager.isUndoAvailable(fileEditor) border = null isFocusPainted = false isContentAreaFilled = false addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent?) { if (undoManager.isUndoAvailable(fileEditor)) { undoManager.undo(fileEditor) } } }) } return listOf(rollback) } override fun getViewText(): String = currentFile.readText() override fun updateViewText(text: String) {} override fun getComponent(): JComponent = mainPanel override fun updateLanguage(language: Language?, originLanguage: String?) {} override fun dispose() {} fun openDiffView() { showDiff() } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/sse/CustomAgentSSEExecutor.kt ================================================ package com.phodal.shirecore.sse import com.intellij.openapi.components.Service import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.phodal.shirecore.agent.AuthType import com.phodal.shirecore.agent.CustomAgent import com.phodal.shirecore.agent.CustomAgentResponseAction import com.phodal.shirecore.llm.ChatMessage import com.phodal.shirecore.llm.ChatRole import com.phodal.shirecore.llm.CustomRequest import kotlinx.coroutines.flow.Flow import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import java.util.concurrent.TimeUnit @Service(Service.Level.PROJECT) class CustomAgentSSEExecutor(val project: Project) : CustomSSEHandler() { private var client = OkHttpClient() private val logger = logger() private val messages: MutableList = mutableListOf() override var requestFormat: String = "" override var responseFormat: String = "" fun execute(promptText: String, agent: CustomAgent): Flow { messages.add(ChatMessage(ChatRole.user, promptText)) this.requestFormat = agent.connector?.requestFormat ?: this.requestFormat this.responseFormat = agent.connector?.responseFormat ?: this.responseFormat val customRequest = CustomRequest(listOf(ChatMessage(ChatRole.user, promptText))) val request = if (requestFormat.isNotEmpty()) { customRequest.updateCustomFormat(requestFormat) } else { Json.encodeToString(customRequest) } val body = request.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()) val builder = Request.Builder() val auth = agent.auth when (auth?.type) { AuthType.Bearer -> { builder.addHeader("Authorization", "Bearer ${auth.token}") builder.addHeader("Content-Type", "application/json") } null -> { logger.info("No auth type found for agent ${agent.name}") } } client = client.newBuilder() .connectTimeout(agent.defaultTimeout, TimeUnit.MINUTES) .readTimeout(agent.defaultTimeout, TimeUnit.MINUTES).build() val call = client.newCall(builder.url(agent.url).post(body).build()) return when (agent.responseAction) { CustomAgentResponseAction.Stream -> { streamSSE(call, messages = messages, project) } else -> { streamJson(call, messages) } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/sse/CustomSSEHandler.kt ================================================ package com.phodal.shirecore.sse import com.fasterxml.jackson.databind.ObjectMapper import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.nfeld.jsonpathkt.JsonPath import com.nfeld.jsonpathkt.extension.read import com.phodal.shirecore.sse.io.ChatCompletionResult import com.phodal.shirecore.sse.io.JSONBodyResponseCallback import com.phodal.shirecore.sse.io.ResponseBodyCallback import com.phodal.shirecore.sse.io.SSE import com.phodal.shirecore.runner.console.CustomFlowWrapper import com.phodal.shirecore.llm.ChatMessage import com.phodal.shirecore.llm.ChatRole import com.phodal.shirecore.llm.CustomRequest import com.phodal.shirecore.provider.streaming.OnStreamingService import io.reactivex.rxjava3.core.BackpressureStrategy import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.FlowableEmitter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.withContext import kotlinx.serialization.encodeToString import kotlinx.serialization.json.* import okhttp3.Call import okhttp3.Request import org.jetbrains.annotations.VisibleForTesting /** * The `CustomSSEProcessor` class is responsible for processing server-sent events (SSE) in a custom manner. * It provides functions to stream JSON and SSE data from a given `Call` instance, and exposes properties for request and response formats. * * @property hasSuccessRequest A boolean flag indicating whether the request was successful. * @property requestFormat A string representing the format of the request. * @property responseFormat A string representing the format of the response. * @property logger An instance of the logger for logging purposes. * * @constructor Creates an instance of `CustomSSEProcessor`. */ open class CustomSSEHandler { open var hasSuccessRequest: Boolean = false private var parseFailedResponses: MutableList = mutableListOf() open val requestFormat: String = "" open val responseFormat: String = "\$.choices[0].delta.content" private val logger = logger() fun streamJson(call: Call, messages: MutableList): Flow = callbackFlow { call.enqueue(JSONBodyResponseCallback(responseFormat) { withContext(Dispatchers.IO) { send(it) } messages += ChatMessage(ChatRole.assistant, it) close() }) awaitClose() } @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) fun streamSSE(call: Call, messages: MutableList, project: Project): Flow { var emit: FlowableEmitter? = null val sseFlowable = Flowable .create({ emitter: FlowableEmitter -> emit = emitter.apply { call.enqueue(ResponseBodyCallback(emitter, true)) } }, BackpressureStrategy.BUFFER) val service = project.getService(OnStreamingService::class.java) try { var output = "" return CustomFlowWrapper(callbackFlow { withContext(Dispatchers.IO) { sseFlowable .doOnError { it.printStackTrace() trySend(it.message ?: "Error occurs") close() } .runCatching { blockingForEach { sse -> if (responseFormat.isNotEmpty()) { // {"id":"cmpl-a22a0d78fcf845be98660628fe5d995b","object":"chat.completion.chunk","created":822330,"model":"moonshot-v1-8k","choices":[{"index":0,"delta":{},"finish_reason":"stop","usage":{"prompt_tokens":434,"completion_tokens":68,"total_tokens":502}}]} // in some case, the response maybe not equal to our response format, so we need to ignore it // {"id":"cmpl-ac26a17e","object":"chat.completion.chunk","created":1858403,"model":"yi-34b-chat","choices":[{"delta":{"role":"assistant"},"index":0}],"content":"","lastOne":false} val chunk: String? = JsonPath.parse(sse!!.data)?.read(responseFormat) // new JsonPath lib caught the exception, so we need to handle when it is null if (chunk == null) { if (sse.data == "[DONE]") { return@blockingForEach } parseFailedResponses.add(sse.data) logger.warn("Failed to parse response.origin response is: ${sse.data}, response format: $responseFormat") } else { hasSuccessRequest = true output += chunk service?.onStreaming(project, chunk) trySend(chunk) } } else { val result: ChatCompletionResult = ObjectMapper().readValue(sse!!.data, ChatCompletionResult::class.java) val completion = result.choices[0].message if (completion?.content != null) { output += completion.content service?.onStreaming(project, completion.content) trySend(completion.content) } } } } // when stream finished, check if any response parsed succeeded // if not, notice user check response format if (!hasSuccessRequest) { val errorMsg = """ |**Failed** to parse response.please check your response format: |**$responseFormat** origin responses is: |- ${parseFailedResponses.joinToString("\n- ")} |""".trimMargin() // TODO add refresh feature // don't use trySend, it may be ignored by 'close()` op service?.onStreamingError() send(errorMsg) } messages += ChatMessage(ChatRole.assistant, output) close() } awaitClose() }).also { it.cancelCallback { emit?.onComplete() } } } catch (e: Exception) { if (hasSuccessRequest) { logger.info("Failed to stream", e) } else { logger.error("Failed to stream", e) } return callbackFlow { close() } } finally { parseFailedResponses.clear() } } } @VisibleForTesting fun Request.Builder.appendCustomHeaders(customRequestHeader: String): Request.Builder = apply { runCatching { Json.parseToJsonElement(customRequestHeader) .jsonObject["customHeaders"].let { customFields -> customFields?.jsonObject?.forEach { (key, value) -> header(key, value.jsonPrimitive.content) } } }.onFailure { logger().warn("Failed to parse custom request header", it) } } @VisibleForTesting fun JsonObject.updateCustomBody(customRequest: String): JsonObject { return runCatching { buildJsonObject { // copy origin object val customRequestJson = Json.parseToJsonElement(customRequest).jsonObject customRequestJson["fields"]?.jsonObject?.let { fieldsObj -> val messages: JsonArray = this@updateCustomBody["messages"]?.jsonArray ?: buildJsonArray {} val contentOfFirstMessage = if (messages.isNotEmpty()) { messages.last().jsonObject["content"]?.jsonPrimitive?.content ?: "" } else "" fieldsObj.forEach { (fieldKey, fieldValue) -> if (fieldValue is JsonObject) { put(fieldKey, buildJsonObject { fieldValue.forEach { (subKey, subValue) -> if (subValue is JsonPrimitive && subValue.content == "\$content") { put(subKey, JsonPrimitive(contentOfFirstMessage)) } else { put(subKey, subValue) } } }) } else if (fieldValue is JsonPrimitive && fieldValue.content == "\$content") { put(fieldKey, JsonPrimitive(contentOfFirstMessage)) } else { put(fieldKey, fieldValue) } } return@buildJsonObject } this@updateCustomBody.forEach { u, v -> put(u, v) } customRequestJson["customFields"]?.let { customFields -> customFields.jsonObject.forEach { (key, value) -> put(key, value) } } // TODO clean code with magic literals var roleKey = "role" var contentKey = "content" customRequestJson.jsonObject["messageKeys"]?.let { roleKey = it.jsonObject["role"]?.jsonPrimitive?.content ?: "role" contentKey = it.jsonObject["content"]?.jsonPrimitive?.content ?: "content" } val messages: JsonArray = this@updateCustomBody["messages"]?.jsonArray ?: buildJsonArray { } this.put("messages", buildJsonArray { messages.forEach { message -> val role: String = message.jsonObject["role"]?.jsonPrimitive?.content ?: "user" val content: String = message.jsonObject["content"]?.jsonPrimitive?.content ?: "" add(buildJsonObject { put(roleKey, role) put(contentKey, content) }) } }) } }.getOrElse { logger().error("Failed to parse custom request body", it) this } } fun CustomRequest.updateCustomFormat(format: String): String { val requestContentOri = Json.encodeToString(this) return Json.parseToJsonElement(requestContentOri) .jsonObject.updateCustomBody(format).toString() } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/sse/io/JSONBodyResponseCallback.kt ================================================ package com.phodal.shirecore.sse.io import com.nfeld.jsonpathkt.JsonPath import com.nfeld.jsonpathkt.extension.read import kotlinx.coroutines.runBlocking import okhttp3.Call import okhttp3.Callback import okhttp3.Response import java.io.IOException class JSONBodyResponseCallback(private val responseFormat: String,private val callback: suspend (String)->Unit): Callback { override fun onFailure(call: Call, e: IOException) { runBlocking { callback("error. ${e.message}") } } override fun onResponse(call: Call, response: Response) { val responseBody: String? = response.body?.string() if (responseFormat.isEmpty()) { runBlocking { callback(responseBody ?: "") } return } val responseContent: String = JsonPath.parse(responseBody)?.read(responseFormat) ?: "" runBlocking() { callback(responseContent) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/sse/io/OpenAIDto.kt ================================================ package com.phodal.shirecore.sse.io import com.fasterxml.jackson.annotation.JsonAlias import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.JsonNode data class ChatFunctionCall( val name: String? = null, val arguments: JsonNode? = null, ) data class ChatMessage( val role: String, @JsonInclude val content: String? = null, val name: String? = null, @JsonProperty("function_call") val functionCall: ChatFunctionCall? = null, ) data class ChatCompletionChoice( val index: Int? = null, @JsonAlias("delta") val message: ChatMessage? = null, @JsonProperty("finish_reason") val finishReason: String? = null, ) data class Usage( @JsonProperty("prompt_tokens") val promptTokens: Long = 0, @JsonProperty("completion_tokens") val completionTokens: Long = 0, @JsonProperty("total_tokens") val totalTokens: Long = 0, ) data class ChatCompletionResult( val id: String? = null, val `object`: String? = null, val created: Long = 0, val model: String? = null, val choices: List, val usage: Usage? = null, ) ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/sse/io/ResponseBodyCallback.kt ================================================ // MIT License // //Copyright (c) [year] [fullname] // //Permission is hereby granted, free of charge, to any person obtaining a copy //of this software and associated documentation files (the "Software"), to deal //in the Software without restriction, including without limitation the rights //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell //copies of the Software, and to permit persons to whom the Software is //furnished to do so, subject to the following conditions: // //The above copyright notice and this permission notice shall be included in all //copies or substantial portions of the Software. // //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE //SOFTWARE. package com.phodal.shirecore.sse.io import com.intellij.openapi.diagnostic.logger import io.reactivex.rxjava3.core.FlowableEmitter import okhttp3.Call import okhttp3.Callback import okhttp3.Response import java.io.BufferedReader import java.io.IOException import java.io.InputStreamReader import java.nio.charset.StandardCharsets /** * Callback to parse Server Sent Events (SSE) from raw InputStream and * emit the events with io.reactivex.FlowableEmitter to allow streaming of * SSE. */ class ResponseBodyCallback(private val emitter: FlowableEmitter, private val emitDone: Boolean) : Callback { override fun onResponse(call: Call, response: Response) { var reader: BufferedReader? = null try { if (!response.isSuccessful) { if (response.body == null) { throw ShireHttpException("Response body is null", response.code) } else { throw ShireHttpException(response.body?.string() ?: "", response.code) } } val inputStream = response.body!!.byteStream() reader = BufferedReader(InputStreamReader(inputStream, StandardCharsets.UTF_8)) var line: String? = null var sse: SSE? = null while (!emitter.isCancelled && reader.readLine().also { line = it } != null) { sse = when { line!!.startsWith("data:") -> { val data = line!!.substring(5).trim { it <= ' ' } SSE(data) } line == "" && sse != null -> { if (sse.isDone) { if (emitDone) { emitter.onNext(sse) } break } emitter.onNext(sse) null } // starts with event: line!!.startsWith("event:") -> { // https://github.com/sysid/sse-starlette/issues/16 val eventName = line!!.substring(6).trim { it <= ' ' } if (eventName == "ping") { // skip ping event and data emitter.onNext(sse!!) emitter.onNext(sse) } null } // skip `: ping` comments for: https://github.com/sysid/sse-starlette/issues/16 line!!.startsWith(": ping") -> { null } else -> { when { // sometimes the server maybe returns empty line line == "" -> { null } // : is comment // https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream line!!.startsWith(":") -> { null } else -> { throw SSEFormatException("Invalid sse format! '$line'") } } } } } emitter.onComplete() } catch (t: Throwable) { logger().error("Error while reading SSE", t) onFailure(call, IOException(t)) } finally { if (reader != null) { try { reader.close() } catch (e: IOException) { // do nothing } } } } override fun onFailure(call: Call, e: IOException) { emitter.onError(e) } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/sse/io/SSE.kt ================================================ package com.phodal.shirecore.sse.io class SSE(val data: String) { fun toBytes(): ByteArray { return String.format("data: %s\n\n", this.data).toByteArray() } val isDone: Boolean get() = DONE_DATA.equals(this.data, ignoreCase = true) companion object { private const val DONE_DATA = "[DONE]" } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/sse/io/SSEFormatException.kt ================================================ package com.phodal.shirecore.sse.io class SSEFormatException(msg: String?) : Throwable(msg) ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/sse/io/ShireHttpException.kt ================================================ package com.phodal.shirecore.sse.io class ShireHttpException(val error: String, private val statusCode: Int) : RuntimeException(error) { override fun toString(): String { return "ShireHttpException(statusCode=$statusCode, message=$message)" } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/task/Graph.kt ================================================ package com.phodal.shirecore.task data class Node(val id: Int, val name: String) data class Edge(val from: Node, val to: Node) class Graph { private val nodes = mutableListOf() private val edges = mutableListOf() private val adjList = mutableMapOf>() fun addNode(node: Node) { nodes.add(node) adjList[node] = mutableListOf() } fun addEdge(edge: Edge) { edges.add(edge) adjList[edge.from]?.add(edge.to) } fun getNodes(): List = nodes fun getEdges(): List = edges fun getAdjList(): Map> = adjList fun topologicalSort(graph: Graph): List { val visited = mutableSetOf() val stack = mutableListOf() val adjList = graph.getAdjList() fun dfs(node: Node) { if (node !in visited) { visited.add(node) adjList[node]?.forEach { dfs(it) } stack.add(node) } } graph.getNodes().forEach { if (it !in visited) dfs(it) } return stack.reversed() } fun hasCycle(graph: Graph): Boolean { val visited = mutableSetOf() val recStack = mutableSetOf() val adjList = graph.getAdjList() fun dfs(node: Node): Boolean { if (node !in visited) { visited.add(node) recStack.add(node) adjList[node]?.forEach { if (it !in visited && dfs(it)) { return true } else if (it in recStack) { return true } } } recStack.remove(node) return false } return graph.getNodes().any { dfs(it) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/ui/CustomProgressBar.kt ================================================ package com.phodal.shirecore.ui import com.intellij.icons.AllIcons import com.intellij.ui.components.JBLabel import com.intellij.util.ui.JBUI import java.awt.BorderLayout import java.awt.event.MouseAdapter import java.awt.event.MouseEvent import javax.swing.JPanel import javax.swing.JProgressBar class CustomProgressBar(private val view: ShirePanelView) : JPanel(BorderLayout()) { private val progressBar: JProgressBar = JProgressBar() var isIndeterminate = progressBar.isIndeterminate set(value) { progressBar.isIndeterminate = value field = value } private val cancelLabel = JBLabel(AllIcons.Actions.CloseHovered) init { cancelLabel.setBorder(JBUI.Borders.empty(0, 5)) cancelLabel.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent?) { view.cancel("This progressBar is canceled") } }) add(progressBar, BorderLayout.CENTER) add(cancelLabel, BorderLayout.EAST) } override fun setVisible(visible: Boolean) { super.setVisible(visible) progressBar.isVisible = visible cancelLabel.isVisible = visible } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/ui/ShirePanelView.kt ================================================ package com.phodal.shirecore.ui import com.intellij.openapi.application.runInEdt import com.intellij.openapi.fileTypes.PlainTextLanguage import com.intellij.openapi.project.Project import com.intellij.openapi.ui.DialogPanel import com.intellij.openapi.ui.NullableComponent import com.intellij.openapi.ui.SimpleToolWindowPanel import com.intellij.ui.components.JBScrollPane import com.intellij.ui.components.panels.VerticalLayout import com.intellij.ui.dsl.builder.AlignX import com.intellij.ui.dsl.builder.AlignY import com.intellij.ui.dsl.builder.Cell import com.intellij.ui.dsl.builder.panel import com.intellij.util.ui.JBUI import com.intellij.util.ui.UIUtil import com.phodal.shirecore.provider.sketch.ExtensionLangSketch import com.phodal.shirecore.provider.sketch.LanguageSketchProvider import com.phodal.shirecore.sketch.LangSketch import com.phodal.shirecore.sketch.highlight.CodeHighlightSketch import com.phodal.shirecore.ui.input.ShireChatBoxInput import com.phodal.shirecore.utils.markdown.CodeFence import com.phodal.shirecore.utils.markdown.CodeFenceLanguage import java.awt.BorderLayout import javax.swing.JComponent import javax.swing.JPanel import javax.swing.ScrollPaneConstants import javax.swing.SwingUtilities class ShirePanelView(val project: Project, showInput: Boolean = true) : SimpleToolWindowPanel(true, true), NullableComponent { private var progressBar: CustomProgressBar = CustomProgressBar(this) private var shireInput: ShireChatBoxInput = ShireChatBoxInput(project) private var myList = JPanel(VerticalLayout(JBUI.scale(0))).apply { this.isOpaque = true this.background = UIUtil.getLabelBackground() } private var userPrompt: JPanel = JPanel(BorderLayout()).apply { this.isOpaque = true this.background = JBUI.CurrentTheme.CustomFrameDecorations.titlePaneInactiveBackground() this.border = JBUI.Borders.empty(10, 0) } private var contentPanel = JPanel(BorderLayout()).apply { this.isOpaque = true this.background = UIUtil.getLabelBackground() } private var panelContent: DialogPanel = panel { row { cell(progressBar).fullWidth() } row { cell(userPrompt).fullWidth().fullHeight() } row { cell(myList).fullWidth().fullHeight() } } private val scrollPanel: JBScrollPane = JBScrollPane( panelContent, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER ).apply { this.verticalScrollBar.autoscrolls = true } var handleCancel: ((String) -> Unit)? = null init { contentPanel.add(scrollPanel, BorderLayout.CENTER) if (showInput) { shireInput.also { border = JBUI.Borders.empty(8) } contentPanel.add(shireInput, BorderLayout.SOUTH) } setContent(contentPanel) } fun onStart() { initializePreAllocatedBlocks(project) progressBar.isIndeterminate = true } private val blockViews: MutableList = mutableListOf() private fun initializePreAllocatedBlocks(project: Project) { repeat(16) { runInEdt { val codeBlockViewer = CodeHighlightSketch(project, "", PlainTextLanguage.INSTANCE) blockViews.add(codeBlockViewer) myList.add(codeBlockViewer) } } } fun addRequestPrompt(text: String) { runInEdt { val codeBlockViewer = CodeHighlightSketch(project, text, CodeFenceLanguage.findLanguage("Markdown")).apply { initEditor(text) } codeBlockViewer.editorFragment?.setCollapsed(true) codeBlockViewer.editorFragment!!.updateExpandCollapseLabel() val panel = panel { row { cell(codeBlockViewer).fullWidth() } }.also { it.border = JBUI.Borders.empty(10, 0) } userPrompt.add(panel, BorderLayout.CENTER) this.revalidate() this.repaint() } } fun onUpdate(text: String) { val codeFenceList = CodeFence.parseAll(text) runInEdt { codeFenceList.forEachIndexed { index, codeFence -> if (index < blockViews.size) { var langSketch: ExtensionLangSketch? = null if (codeFence.originLanguage != null && codeFence.isComplete && blockViews[index] !is ExtensionLangSketch) { langSketch = LanguageSketchProvider.provide(codeFence.originLanguage) ?.create(project, codeFence.text) } if (langSketch != null) { val oldComponent = blockViews[index] blockViews[index] = langSketch myList.remove(index) myList.add(langSketch.getComponent(), index) oldComponent.dispose() } else { blockViews[index].apply { updateLanguage(codeFence.ideaLanguage, codeFence.originLanguage) updateViewText(codeFence.text) } } } else { val codeBlockViewer = CodeHighlightSketch(project, codeFence.text, PlainTextLanguage.INSTANCE) blockViews.add(codeBlockViewer) myList.add(codeBlockViewer.getComponent()) } } while (blockViews.size > codeFenceList.size) { val lastIndex = blockViews.lastIndex blockViews.removeAt(lastIndex) myList.remove(lastIndex) } myList.revalidate() myList.repaint() scrollToBottom() } } fun onFinish(text: String) { runInEdt { blockViews.filter { it.getViewText().isNotEmpty() }.forEach { it.doneUpdateText(text) } blockViews.filter { it.getViewText().isEmpty() }.forEach { myList.remove(it.getComponent()) } } progressBar.isIndeterminate = false progressBar.isVisible = false scrollToBottom() } private fun scrollToBottom() { SwingUtilities.invokeLater { val verticalScrollBar = scrollPanel.verticalScrollBar verticalScrollBar.value = verticalScrollBar.maximum } } fun resize(maxHeight: Int = 480) { val height = myList.components.sumOf { it.height } if (height < maxHeight) { this.minimumSize = JBUI.size(800, height) } else { this.minimumSize = JBUI.size(800, maxHeight) } } override fun isNull(): Boolean { return !isVisible } fun cancel(s: String) = runCatching { handleCancel?.invoke(s) } } fun Cell.fullWidth(): Cell { return this.align(AlignX.FILL) } fun Cell.fullHeight(): Cell { return this.align(AlignY.FILL) } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/ui/input/DarculaNewUIUtil.kt ================================================ // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.phodal.shirecore.ui.input import com.intellij.ide.ui.laf.darcula.DarculaUIUtil import com.intellij.util.ui.JBInsets import com.intellij.util.ui.MacUIUtil import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval import java.awt.* import java.awt.geom.Path2D import java.awt.geom.RoundRectangle2D import kotlin.math.max @ApiStatus.Internal object DarculaNewUIUtil { /** * Paints rounded border for a focusable component. Non focused border rect is inside [rect], focused/outlined border can come outside */ fun paintComponentBorder(g: Graphics, rect: Rectangle, outline: DarculaUIUtil.Outline?, focused: Boolean, enabled: Boolean, bw: Int = DarculaUIUtil.BW.get(), arc: Float = DarculaUIUtil.COMPONENT_ARC.float) { val g2 = g.create() as Graphics2D try { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, if (MacUIUtil.USE_QUARTZ) RenderingHints.VALUE_STROKE_PURE else RenderingHints.VALUE_STROKE_NORMALIZE) val lw = DarculaUIUtil.LW.get() when { enabled && outline != null -> { outline.setGraphicsColor(g2, focused) paintRectangle(g2, rect, arc, bw) } focused -> { DarculaUIUtil.Outline.focus.setGraphicsColor(g2, true) paintRectangle(g2, rect, arc, bw) } else -> { g2.color = DarculaUIUtil.getOutlineColor(enabled, focused) paintRectangle(g2, rect, arc, lw) } } } finally { g2.dispose() } } /** * Will be removed after the next 25.1 release */ @ApiStatus.Internal @Deprecated("Use drawRoundedRectangle instead") @ScheduledForRemoval fun paintComponentBorder(g: Graphics, rect: Rectangle, color: Color, arc: Float, thick: Int) { drawRoundedRectangle(g, rect, color, arc, thick) } fun drawRoundedRectangle(g: Graphics, rect: Rectangle, color: Color, arc: Float, thick: Int) { val g2 = g.create() as Graphics2D try { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, if (MacUIUtil.USE_QUARTZ) RenderingHints.VALUE_STROKE_PURE else RenderingHints.VALUE_STROKE_NORMALIZE) g2.color = color paintRectangle(g2, rect, arc, thick) } finally { g2.dispose() } } @Deprecated("Use fillInsideComponentBorder instead") fun fillInsideComponentBorder(g: Graphics, rect: Rectangle, color: Color) { fillInsideComponentBorder(g, rect, color, DarculaUIUtil.COMPONENT_ARC.float) } /** * Fills part of a component inside the border. The resulting rectangle is a little bit smaller than [rect], * so inside background doesn't protrude outside the line border (can be visible near rounded corners). * This method should be used together with [paintComponentBorder],[drawRoundedRectangle], or other methods that draw lined border. */ fun fillInsideComponentBorder(g: Graphics, rect: Rectangle, color: Color, arc: Float = DarculaUIUtil.COMPONENT_ARC.float) { if (rect.width <= 0 || rect.height <= 0) { return } val g2 = g.create() as Graphics2D try { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, if (MacUIUtil.USE_QUARTZ) RenderingHints.VALUE_STROKE_PURE else RenderingHints.VALUE_STROKE_NORMALIZE) val border = Path2D.Float(Path2D.WIND_EVEN_ODD) // Reduce size a little bit, so inside background is not protruded outside the border near rounded corners border.append(RoundRectangle2D.Float(0.5f, 0.5f, rect.width - 1f, rect.height - 1f, arc, arc), false) g2.translate(rect.x, rect.y) g2.color = color g2.fill(border) } finally { g2.dispose() } } fun fillRoundedRectangle(g: Graphics, rect: Rectangle, color: Color, arc: Float = DarculaUIUtil.COMPONENT_ARC.float) { if (rect.width <= 0 || rect.height <= 0) { return } val g2 = g.create() as Graphics2D try { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, if (MacUIUtil.USE_QUARTZ) RenderingHints.VALUE_STROKE_PURE else RenderingHints.VALUE_STROKE_NORMALIZE) val border = Path2D.Float(Path2D.WIND_EVEN_ODD) border.append(RoundRectangle2D.Float(0f, 0f, rect.width.toFloat(), rect.height.toFloat(), arc, arc), false) g2.translate(rect.x, rect.y) g2.color = color g2.fill(border) } finally { g2.dispose() } } /** * Using DarculaUIUtil.doPaint and similar methods doesn't give good results when line thickness is 1 (right corners too thin) */ private fun paintRectangle(g: Graphics2D, rect: Rectangle, arc: Float, thick: Int) { val addToRect = thick - DarculaUIUtil.LW.get() if (addToRect > 0) { @Suppress("UseDPIAwareInsets") JBInsets.addTo(rect, Insets(addToRect, addToRect, addToRect, addToRect)) } val w = thick.toFloat() val border = Path2D.Float(Path2D.WIND_EVEN_ODD) border.append(RoundRectangle2D.Float(0f, 0f, rect.width.toFloat(), rect.height.toFloat(), arc, arc), false) val innerArc = max(arc - thick * 2, 0.0f) border.append(RoundRectangle2D.Float(w, w, rect.width - w * 2, rect.height - w * 2, innerArc, innerArc), false) g.translate(rect.x, rect.y) g.fill(border) } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/ui/input/ShireChatBoxInput.kt ================================================ package com.phodal.shirecore.ui.input import com.intellij.codeInsight.lookup.LookupManagerListener import com.intellij.icons.AllIcons import com.intellij.lang.Language import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.fileEditor.FileEditorManagerEvent import com.intellij.openapi.fileEditor.FileEditorManagerListener import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.intellij.ui.components.JBList import com.intellij.ui.components.JBScrollPane import com.intellij.util.ui.JBUI import com.phodal.shirecore.ShireCoreBundle import com.phodal.shirecore.provider.psi.RelatedClassesProvider import com.phodal.shirecore.provider.shire.FileCreateService import com.phodal.shirecore.provider.shire.FileRunService import com.phodal.shirecore.relativePath import java.awt.BorderLayout import java.awt.Component import java.awt.Dimension import java.awt.FlowLayout import java.awt.event.MouseAdapter import java.awt.event.MouseEvent import javax.swing.* data class modelWrapper(val psiElement: PsiElement, var panel: JPanel? = null, var namePanel: JPanel? = null) class ShireChatBoxInput(val project: Project) : JPanel(BorderLayout()), Disposable { private var scratchFile: VirtualFile? = null private val listModel = DefaultListModel() private val elementsList = JBList(listModel) private var inputSection: ShireInputSection init { setupElementsList() inputSection = ShireInputSection(project, this) inputSection.addListener(object : ShireInputListener { override fun onStop(component: ShireInputSection) { inputSection.showSendButton() } override fun onSubmit(component: ShireInputSection, trigger: ShireInputTrigger) { val prompt = component.text component.text = "" if (prompt.isEmpty() || prompt.isBlank()) { component.showTooltip(ShireCoreBundle.message("chat.input.empty.tips")) return } val virtualFile = createShireFile(prompt) this@ShireChatBoxInput.scratchFile = virtualFile FileRunService.provider(project, virtualFile!!) ?.runFile(project, virtualFile, null) listModel.clear() elementsList.clearSelection() } }) this.border = BorderFactory.createEmptyBorder(10, 10, 10, 10) this.add(inputSection, BorderLayout.CENTER) this.add(elementsList, BorderLayout.NORTH) setupEditorListener() setupRelatedListener() } private fun setupEditorListener() { project.messageBus.connect(this).subscribe( FileEditorManagerListener.FILE_EDITOR_MANAGER, object : FileEditorManagerListener { override fun selectionChanged(event: FileEditorManagerEvent) { val file = event.newFile ?: return val psiFile = PsiManager.getInstance(project).findFile(file) ?: return RelatedClassesProvider.provide(psiFile.language) ?: return ApplicationManager.getApplication().invokeLater { listModel.addIfAbsent(psiFile) } } } ) } private fun setupRelatedListener() { project.messageBus.connect(this) .subscribe(LookupManagerListener.TOPIC, ShireInputLookupManagerListener(project) { ApplicationManager.getApplication().invokeLater { val relatedElements = RelatedClassesProvider.provide(it.language)?.lookup(it) updateElements(relatedElements) } }) } private fun setupElementsList() { elementsList.selectionMode = ListSelectionModel.SINGLE_SELECTION elementsList.layoutOrientation = JList.HORIZONTAL_WRAP elementsList.visibleRowCount = 2 elementsList.cellRenderer = ElementListCellRenderer() elementsList.setEmptyText("") val scrollPane = JBScrollPane(elementsList) scrollPane.preferredSize = Dimension(-1, 80) scrollPane.horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS scrollPane.verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED elementsList.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { val list = e.source as JBList<*> val index = list.locationToIndex(e.point) if (index != -1) { val wrapper = listModel.getElementAt(index) val cellBounds = list.getCellBounds(index, index) wrapper.panel?.components?.firstOrNull { it.contains(e.x - cellBounds.x - it.x, it.height - 1) }?.let { when { it is JPanel -> { listModel.removeElement(wrapper) wrapper.psiElement.containingFile?.let { psiFile -> val relativePath = psiFile.virtualFile.relativePath(project) inputSection.appendText("\n/" + "file" + ":${relativePath}") listModel.indexOf(wrapper.psiElement).takeIf { it != -1 }?.let { listModel.remove(it) } val relatedElements = RelatedClassesProvider.provide(psiFile.language)?.lookup(psiFile) updateElements(relatedElements) } } it is JLabel && it.icon == AllIcons.Actions.Close -> listModel.removeElement(wrapper) else -> list.clearSelection() } } ?: list.clearSelection() } } }) add(scrollPane, BorderLayout.NORTH) } private fun updateElements(elements: List?) { elements?.forEach { listModel.addIfAbsent(it) } } private fun createShireFile(prompt: String): VirtualFile? { val findLanguageByID = Language.findLanguageByID("Shire") ?: throw IllegalStateException("Shire language not found") val provide = FileCreateService.provide(findLanguageByID) ?: throw IllegalStateException("FileCreateService not found") return provide.createFile(prompt, project) } override fun dispose() { scratchFile?.delete(this) } } private fun DefaultListModel.addIfAbsent(psiFile: PsiElement) { val isValid = when (psiFile) { is PsiFile -> psiFile.isValid else -> true } if (!isValid) return if (elements().asIterator().asSequence().none { it.psiElement == psiFile }) { addElement(modelWrapper(psiFile)) } } private class ElementListCellRenderer : ListCellRenderer { override fun getListCellRendererComponent( list: JList, value: modelWrapper, index: Int, isSelected: Boolean, cellHasFocus: Boolean, ): Component { val psiElement = value.psiElement val panel = value.panel ?: JPanel(FlowLayout(FlowLayout.LEFT, 3, 0)).apply { accessibleContext.accessibleName = "Element Panel" border = JBUI.Borders.empty(2, 5) val namePanel = JPanel(layout) val iconLabel = JLabel(psiElement.containingFile?.fileType?.icon ?: AllIcons.FileTypes.Unknown) namePanel.add(iconLabel) val nameLabel = JLabel(psiElement.containingFile?.name ?: "Unknown") namePanel.add(nameLabel) add(namePanel) val closeLabel = JLabel(AllIcons.Actions.Close) closeLabel.border = JBUI.Borders.empty() add(closeLabel, BorderLayout.EAST) value.panel = this value.namePanel = namePanel } val namePanel = value.namePanel if (isSelected) { namePanel?.background = list.selectionBackground namePanel?.foreground = list.selectionForeground } else { namePanel?.background = list.background namePanel?.foreground = list.foreground } return panel } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/ui/input/ShireCoolBorder.kt ================================================ package com.phodal.shirecore.ui.input import com.intellij.ide.ui.laf.darcula.DarculaUIUtil import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.editor.ex.FocusChangeListener import com.intellij.openapi.ui.ErrorBorderCapable import com.intellij.util.ui.JBInsets import com.intellij.util.ui.UIUtil import java.awt.* import java.awt.geom.Path2D import java.awt.geom.Rectangle2D import java.awt.geom.RoundRectangle2D import javax.swing.JComponent import javax.swing.border.Border import javax.swing.border.LineBorder class ShireCoolBorder(private val editor: EditorEx, parent: JComponent) : Border, ErrorBorderCapable { init { editor.addFocusListener(object : FocusChangeListener { override fun focusGained(editor2: Editor) { parent.repaint() } override fun focusLost(editor2: Editor) { parent.repaint() } }) } override fun paintBorder(c: Component, g: Graphics, x: Int, y: Int, width: Int, height: Int) { val r = Rectangle(x, y, width, height) JBInsets.removeFrom(r, JBInsets.create(1, 1)) DarculaNewUIUtil.fillInsideComponentBorder(g, r, c.background) val enabled = c.isEnabled val hasFocus = UIUtil.isFocusAncestor(c) DarculaNewUIUtil.paintComponentBorder(g, r, DarculaUIUtil.getOutline(c as JComponent), hasFocus, enabled) } override fun getBorderInsets(c: Component): Insets = JBInsets.create(Insets(3, 8, 3, 3)).asUIResource() override fun isBorderOpaque(): Boolean = true } class ShireLineBorder(color: Color, thickness: Int, roundedCorners: Boolean, val radius: Int) : LineBorder(color, thickness, roundedCorners) { override fun paintBorder(component: Component?, graphics: Graphics?, x: Int, y: Int, width: Int, height: Int) { if (thickness > 0 && graphics is Graphics2D) { val oldColor = graphics.color graphics.color = lineColor val offs = thickness val size = offs + offs val outer: Shape val inner: Shape if (roundedCorners) { val arc: Float = (radius * 2).toFloat() outer = RoundRectangle2D.Float(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), arc, arc) inner = RoundRectangle2D.Float( (x + offs).toFloat(), (y + offs).toFloat(), (width - size).toFloat(), (height - size).toFloat(), arc, arc ) } else { outer = Rectangle2D.Float(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat()) inner = Rectangle2D.Float( (x + offs).toFloat(), (y + offs).toFloat(), (width - size).toFloat(), (height - size).toFloat() ) } val shape = Path2D.Float(0) shape.append(outer, false) shape.append(inner, false) graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) graphics.fill(shape) graphics.color = oldColor } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/ui/input/ShireInputListener.kt ================================================ package com.phodal.shirecore.ui.input import com.intellij.openapi.editor.ex.EditorEx import java.util.* interface ShireInputListener : EventListener { fun editorAdded(editor: EditorEx) {} fun onSubmit(component: ShireInputSection, trigger: ShireInputTrigger) {} fun onStop(component: ShireInputSection) {} } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/ui/input/ShireInputLookupManagerListener.kt ================================================ package com.phodal.shirecore.ui.input import com.intellij.codeInsight.lookup.Lookup import com.intellij.codeInsight.lookup.LookupEvent import com.intellij.codeInsight.lookup.LookupListener import com.intellij.codeInsight.lookup.LookupManagerListener import com.intellij.codeInsight.lookup.impl.LookupImpl import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.phodal.shirecore.completion.ShireLookupElement class ShireInputLookupManagerListener( private val project: Project, private val callback: ((PsiFile) -> Unit)? = null, ) : LookupManagerListener { override fun activeLookupChanged(oldLookup: Lookup?, newLookup: Lookup?) { if (newLookup !is LookupImpl) return newLookup.addLookupListener(object : LookupListener { override fun itemSelected(event: LookupEvent) { if (event.item !is ShireLookupElement<*>) return val lookupElement = event.item as ShireLookupElement<*> runReadAction { val file = lookupElement.getFile() val psiFile = PsiManager.getInstance(project).findFile(file) if (psiFile != null) { callback?.invoke(psiFile) } } } }) } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/ui/input/ShireInputSection.kt ================================================ package com.phodal.shirecore.ui.input import com.intellij.icons.AllIcons import com.intellij.ide.IdeTooltip import com.intellij.ide.IdeTooltipManager import com.intellij.openapi.Disposable import com.intellij.openapi.actionSystem.Presentation import com.intellij.openapi.actionSystem.impl.ActionButton import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.editor.event.DocumentEvent import com.intellij.openapi.editor.event.DocumentListener import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.Project import com.intellij.openapi.ui.ComponentValidator import com.intellij.openapi.ui.popup.Balloon.Position import com.intellij.openapi.wm.IdeFocusManager import com.intellij.openapi.wm.impl.InternalDecorator import com.intellij.ui.HintHint import com.intellij.util.EventDispatcher import com.intellij.util.ui.JBEmptyBorder import com.intellij.util.ui.JBUI import com.intellij.util.ui.UIUtil import com.intellij.util.ui.components.BorderLayoutPanel import com.phodal.shirecore.ShireCoreBundle import com.phodal.shirecore.config.interaction.task.InsertUtil.insertStringAndSaveChange import java.awt.CardLayout import java.awt.Color import java.awt.Dimension import java.awt.Point import java.awt.event.MouseAdapter import java.awt.event.MouseEvent import javax.swing.Box import javax.swing.JComponent import javax.swing.JPanel import kotlin.math.max import kotlin.math.min class ShireInputSection(private val project: Project, val disposable: Disposable?) : BorderLayoutPanel() { private val input: ShireInputTextField private val documentListener: DocumentListener private val sendButtonPresentation: Presentation private val stopButtonPresentation: Presentation private val sendButton: ActionButton private val stopButton: ActionButton private val buttonPanel = JPanel(CardLayout()) val editorListeners = EventDispatcher.create(ShireInputListener::class.java) var text: String get() { return input.text } set(text) { input.recreateDocument() input.text = text } init { val sendButtonPresentation = Presentation(ShireCoreBundle.message("chat.panel.send")) sendButtonPresentation.icon = AllIcons.Actions.Execute this.sendButtonPresentation = sendButtonPresentation sendButton = ActionButton( DumbAwareAction.create { editorListeners.multicaster.onSubmit(this@ShireInputSection, ShireInputTrigger.Button) }, this.sendButtonPresentation, "", Dimension(20, 20) ) val stopButtonPresentation = Presentation("Stop") stopButtonPresentation.icon = AllIcons.Actions.Suspend this.stopButtonPresentation = stopButtonPresentation stopButton = ActionButton( DumbAwareAction.create { editorListeners.multicaster.onStop(this@ShireInputSection) }, this.stopButtonPresentation, "", Dimension(20, 20) ) input = ShireInputTextField(project, listOf(), disposable, this) documentListener = object : DocumentListener { override fun documentChanged(event: DocumentEvent) { val inputHeight = input.preferredSize?.height if (inputHeight == input.height) return revalidate() } } input.addDocumentListener(documentListener) input.recreateDocument() input.border = JBEmptyBorder(4) addToCenter(input) val layoutPanel = BorderLayoutPanel() val horizontalGlue = Box.createHorizontalGlue() horizontalGlue.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent?) { IdeFocusManager.getInstance(project).requestFocus(input, true) input.caretModel.moveToOffset(input.text.length - 1) } }) layoutPanel.setOpaque(false) buttonPanel.add(sendButton, "Send") buttonPanel.add(stopButton, "Stop") layoutPanel.addToCenter(horizontalGlue) layoutPanel.addToRight(buttonPanel) addToBottom(layoutPanel) ComponentValidator(disposable!!).installOn((this)).revalidate() addListener(object : ShireInputListener { override fun editorAdded(editor: EditorEx) { this@ShireInputSection.initEditor() } }) } fun showStopButton() { (buttonPanel.layout as? CardLayout)?.show(buttonPanel, "Stop") stopButton.isEnabled = true } fun showTooltip(text: String) { showTooltip(input, Position.above, text) } fun showTooltip(component: JComponent, position: Position, text: String) { val point = Point(component.x, component.y) val tipComponent = IdeTooltipManager.initPane( text, HintHint(component, point).setAwtTooltip(true).setPreferredPosition(position), null ) val tooltip = IdeTooltip(component, point, tipComponent) IdeTooltipManager.getInstance().show(tooltip, true) } fun showSendButton() { (buttonPanel.layout as? CardLayout)?.show(buttonPanel, "Send") buttonPanel.isEnabled = true } fun initEditor() { val editorEx = this.input.editor as? EditorEx ?: return setBorder(ShireCoolBorder(editorEx, this)) UIUtil.setOpaqueRecursively(this, false) this.revalidate() } override fun getPreferredSize(): Dimension { val result = super.getPreferredSize() result.height = max(min(result.height, maxHeight), minimumSize.height) return result } fun appendText(text: String) { WriteCommandAction.runWriteCommandAction( project, "Append text", "intentions.write.action", { insertStringAndSaveChange(project, text, input.editor!!.document, input.editor!!.document.textLength, false) }) } /** * Set the content of the input field. */ fun setContent(trimMargin: String) { val focusManager = IdeFocusManager.getInstance(project) focusManager.requestFocus(input, true) this.input.recreateDocument() this.input.text = trimMargin } override fun getBackground(): Color? { // it seems that the input field is not ready when this method is called if (this.input == null) return super.getBackground() val editor = input.editor ?: return super.getBackground() return editor.colorsScheme.defaultBackground } override fun setBackground(bg: Color?) {} fun addListener(listener: ShireInputListener) { editorListeners.addListener(listener) } fun moveCursorToStart() { input.caretModel.moveToOffset(0) } private val maxHeight: Int get() { val decorator = UIUtil.getParentOfType(InternalDecorator::class.java, this) val contentManager = decorator?.contentManager ?: return JBUI.scale(200) return contentManager.component.height / 2 } val focusableComponent: JComponent get() = input } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/ui/input/ShireInputTextField.kt ================================================ package com.phodal.shirecore.ui.input import com.intellij.openapi.Disposable import com.intellij.openapi.actionSystem.* import com.intellij.openapi.actionSystem.ex.AnActionListener import com.intellij.openapi.command.CommandProcessor import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.EditorModificationUtil import com.intellij.openapi.editor.actions.EnterAction import com.intellij.openapi.editor.actions.IncrementalFindAction import com.intellij.openapi.editor.event.DocumentListener import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider import com.intellij.openapi.fileTypes.FileTypes import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.Project import com.intellij.testFramework.LightVirtualFile import com.intellij.ui.EditorTextField import com.intellij.util.EventDispatcher import com.intellij.util.messages.MessageBusConnection import com.intellij.util.ui.JBUI import com.phodal.shirecore.sketch.highlight.findDocument import com.phodal.shirecore.utils.markdown.CodeFenceLanguage import java.awt.Color import java.awt.event.KeyEvent import java.util.* import javax.swing.KeyStroke class ShireInputTextField( project: Project, private val listeners: List, val disposable: Disposable?, val inputSection: ShireInputSection, ) : EditorTextField(project, FileTypes.PLAIN_TEXT), Disposable { private var editorListeners: EventDispatcher = inputSection.editorListeners init { isOneLineMode = false setFontInheritedFromLAF(true) addSettingsProvider { it.putUserData(IncrementalFindAction.SEARCH_DISABLED, true) it.colorsScheme.lineSpacing = 1.2f it.settings.isUseSoftWraps = true it.isEmbeddedIntoDialogWrapper = true it.contentComponent.setOpaque(false) } DumbAwareAction.create { object : AnAction() { override fun actionPerformed(actionEvent: AnActionEvent) { val editor = editor ?: return CommandProcessor.getInstance().executeCommand(project, { val eol = "\n" val caretOffset = editor.caretModel.offset editor.document.insertString(caretOffset, eol) editor.caretModel.moveToOffset(caretOffset + eol.length) EditorModificationUtil.scrollToCaret(editor) }, null, null) } } }.registerCustomShortcutSet( CustomShortcutSet( KeyboardShortcut(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.CTRL_DOWN_MASK), null), KeyboardShortcut(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.META_DOWN_MASK), null) ), this ) val connect: MessageBusConnection = project.messageBus.connect(disposable ?: this) val topic = AnActionListener.TOPIC connect.subscribe(topic, object : AnActionListener { override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) { if (event.dataContext.getData(CommonDataKeys.EDITOR) === editor && action is EnterAction) { editorListeners.multicaster.onSubmit(inputSection, ShireInputTrigger.Key) } } }) listeners.forEach { listener -> document.addDocumentListener(listener) } } override fun onEditorAdded(editor: Editor) { editorListeners.multicaster.editorAdded((editor as EditorEx)) } public override fun createEditor(): EditorEx { val editor = super.createEditor() editor.setVerticalScrollbarVisible(true) setBorder(JBUI.Borders.empty()) editor.setShowPlaceholderWhenFocused(true) editor.caretModel.moveToOffset(0) editor.scrollPane.setBorder(border) editor.contentComponent.setOpaque(false) return editor } override fun getBackground(): Color { val editor = editor ?: return super.getBackground() return editor.colorsScheme.defaultBackground } override fun getData(dataId: String): Any? { if (!PlatformCoreDataKeys.FILE_EDITOR.`is`(dataId)) { return super.getData(dataId) } val currentEditor = editor ?: return super.getData(dataId) return TextEditorProvider.getInstance().getTextEditor(currentEditor) } override fun dispose() { listeners.forEach { editor?.document?.removeDocumentListener(it) } } fun recreateDocument() { val id = UUID.randomUUID() val language = CodeFenceLanguage.findLanguage("Shire") val file = LightVirtualFile("ShireInput-$id", language, "") val document = file.findDocument() ?: throw IllegalStateException("Can't create in-memory document") initializeDocumentListeners(document) setDocument(document) inputSection.initEditor() } private fun initializeDocumentListeners(inputDocument: Document) { listeners.forEach { listener -> inputDocument.addDocumentListener(listener) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/ui/input/ShireInputTrigger.kt ================================================ package com.phodal.shirecore.ui.input enum class ShireInputTrigger { Button, Key } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/utils/markdown/CodeFence.kt ================================================ package com.phodal.shirecore.utils.markdown import ai.grazie.nlp.utils.length import com.intellij.lang.Language import com.phodal.shirecore.utils.markdown.CodeFenceLanguage.findLanguage import com.phodal.shirecore.utils.markdown.CodeFenceLanguage.lookupFileExt class CodeFence( val ideaLanguage: Language, val text: String, var isComplete: Boolean, val extension: String?, val originLanguage: String? = null, ) { companion object { private var lastTxtBlock: CodeFence? = null val shireStartRegex = Regex("") val shireEndRegex = Regex("") fun parse(content: String): CodeFence { val markdownRegex = Regex("```([\\w#+\\s]*)") val lines = content.replace("\\n", "\n").lines() // 检查是否存在 shire 开始标签 val startMatch = shireStartRegex.find(content) if (startMatch != null) { val endMatch = shireEndRegex.find(content) val isComplete = endMatch != null // 提取内容:如果有结束标签就截取中间内容,没有就取整个后续内容 val shireContent = if (isComplete) { content.substring(startMatch.range.last + 1, endMatch.range.first).trim() } else { content.substring(startMatch.range.last + 1).trim() } return CodeFence(findLanguage("Shire"), shireContent, isComplete, "shire", "Shire") } // 原有的 Markdown 代码块解析逻辑 var codeStarted = false var codeClosed = false var languageId: String? = null val codeBuilder = StringBuilder() for (line in lines) { if (!codeStarted) { val matchResult: MatchResult? = markdownRegex.find(line.trimStart()) if (matchResult != null) { val substring = matchResult.groups[1]?.value languageId = substring codeStarted = true } } else if (line.startsWith("```")) { codeClosed = true break } else { codeBuilder.append(line).append("\n") } } val trimmedCode = codeBuilder.trim().toString() val language = findLanguage(languageId ?: "") val extension = language.associatedFileType?.defaultExtension ?: lookupFileExt(languageId ?: "txt") return if (trimmedCode.isEmpty()) { CodeFence(language, "", codeClosed, extension, languageId) } else { CodeFence(language, trimmedCode, codeClosed, extension, languageId) } } fun parseAll(content: String): List { val codeFences = mutableListOf() var currentIndex = 0 val startMatches = shireStartRegex.findAll(content) for (startMatch in startMatches) { // 处理标签前的文本 if (startMatch.range.first > currentIndex) { val beforeText = content.substring(currentIndex, startMatch.range.first) if (beforeText.isNotEmpty()) { parseMarkdownContent(beforeText, codeFences) } } // 处理 shire 标签内容 val searchRegion = content.substring(startMatch.range.first) val endMatch = shireEndRegex.find(searchRegion) val isComplete = endMatch != null val shireContent = if (isComplete) { searchRegion.substring(startMatch.range.length, endMatch!!.range.first).trim() } else { searchRegion.substring(startMatch.range.length).trim() } codeFences.add(CodeFence(findLanguage("Shire"), shireContent, isComplete, "shire", "Shire")) currentIndex = if (isComplete) { startMatch.range.first + endMatch!!.range.last + 1 } else { content.length } } // 处理最后剩余的内容 if (currentIndex < content.length) { val remainingContent = content.substring(currentIndex) parseMarkdownContent(remainingContent, codeFences) } return codeFences } private fun parseMarkdownContent(content: String, codeFences: MutableList) { val regex = Regex("```([\\w#+\\s]*)") val lines = content.replace("\\n", "\n").lines() var codeStarted = false var languageId: String? = null val codeBuilder = StringBuilder() val textBuilder = StringBuilder() for (line in lines) { if (!codeStarted) { val matchResult = regex.find(line.trimStart()) if (matchResult != null) { if (textBuilder.isNotEmpty()) { val textBlock = CodeFence(findLanguage("markdown"), textBuilder.trim().toString(), true, "txt") lastTxtBlock = textBlock codeFences.add(textBlock) textBuilder.clear() } languageId = matchResult.groups[1]?.value codeStarted = true } else { textBuilder.append(line).append("\n") } } else { if (line.startsWith("```")) { val codeContent = codeBuilder.trim().toString() val codeFence = parse("```$languageId\n$codeContent\n```") codeFences.add(codeFence) codeBuilder.clear() codeStarted = false languageId = null } else { codeBuilder.append(line).append("\n") } } } val ideaLanguage = findLanguage(languageId ?: "markdown") if (textBuilder.isNotEmpty()) { val normal = CodeFence(ideaLanguage, textBuilder.trim().toString(), true, null, languageId) codeFences.add(normal) } if (codeStarted) { val codeContent = codeBuilder.trim().toString() if (codeContent.isNotEmpty()) { val codeFence = parse("```$languageId\n$codeContent\n") codeFences.add(codeFence) } else { val defaultLanguage = CodeFence(ideaLanguage, codeContent, false, null, languageId) codeFences.add(defaultLanguage) } } } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/utils/markdown/CodeFenceLanguage.kt ================================================ package com.phodal.shirecore.utils.markdown import com.intellij.lang.Language import com.intellij.openapi.fileTypes.PlainTextLanguage object CodeFenceLanguage { /** * Returns the corresponding file extension for a given programming language identifier. * * This function maps a language identifier (e.g., "Java", "Python") to its typical file extension (e.g., "java", "py"). * If the language identifier is not recognized, the function returns the input string as the file extension. * * @param languageId The identifier of the programming language (case-insensitive). * @return The file extension corresponding to the given language identifier. */ fun lookupFileExt(languageId: String): String { return when (languageId.lowercase()) { "c#" -> "cs" "c++" -> "cpp" "c" -> "c" "java" -> "java" "javascript" -> "js" "kotlin" -> "kt" "python" -> "py" "ruby" -> "rb" "swift" -> "swift" "typescript" -> "ts" "markdown" -> "md" "sql" -> "sql" "plantuml" -> "puml" "shell" -> "sh" "objective-c" -> "m" "objective-c++" -> "mm" "go" -> "go" "html" -> "html" "css" -> "css" "dart" -> "dart" "scala" -> "scala" "rust" -> "rs" "http request" -> "http" else -> languageId } } /** * Searches for a language by its name and returns the corresponding [Language] object. If the language is not found, * [PlainTextLanguage.INSTANCE] is returned. * * @param languageName The name of the language to find. * @return The [Language] object corresponding to the given name, or [PlainTextLanguage.INSTANCE] if the language is not found. */ fun findLanguage(languageName: String): Language { val fixedLanguage = when (languageName) { "csharp" -> "c#" "cpp" -> "c++" "shell" -> "Shell Script" "sh" -> "Shell Script" "http" -> "HTTP Request" else -> languageName } val languages = Language.getRegisteredLanguages() val registeredLanguages = languages .filter { it.displayName.isNotEmpty() } return registeredLanguages.find { it.displayName.equals(fixedLanguage, ignoreCase = true) } ?: PlainTextLanguage.INSTANCE } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/utils/markdown/MarkdownUtil.kt ================================================ // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.phodal.shirecore.utils.markdown import org.intellij.markdown.IElementType import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor import org.intellij.markdown.html.HtmlGenerator import org.intellij.markdown.parser.MarkdownParser // https://github.com/JetBrains/markdown/issues/72 private val embeddedHtmlType = IElementType("ROOT") object MarkdownUtil { fun toHtml(markdownText: String): String { val flavour = GFMFlavourDescriptor() val parsedTree = MarkdownParser(flavour).parse(embeddedHtmlType, markdownText) return HtmlGenerator(markdownText, parsedTree, flavour).generateHtml() } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/utils/markdown/PostCodeProcessor.kt ================================================ package com.phodal.shirecore.utils.markdown class PostCodeProcessor( private val prefixCode: String, private val suffixCode: String, private val completeCode: String, private val indentSize: Int = 4 ) { private val indent = " ".repeat(indentSize) private val javaMethodPattern = Regex("^(?:^|\\s+)(?:@[A-Z]\\w+|(?:(?:public|private|protected)\\s+)?.*\\{)") private val endLinePattern = Regex("\n\\s+\n\\s+|\n\\s+\n|\n\n\\s+") fun execute(): String { if (completeCode.isEmpty()) { return completeCode } var code = completeCode val prefix = prefixCode.trim() if (completeCode.startsWith(prefix)) { code = completeCode.substring(prefix.length) } var lines: MutableList = code.split("\n").toMutableList() val isFirstLineNeedIndent = !prefix.endsWith(indent) if (javaMethodPattern.matches(lines[0])) { if (isFirstLineNeedIndent && lines[0].startsWith(indent)) { lines[0] = lines[0].substring(indent.length) } } if (!lines.last().startsWith(indent)) { lines = lines.map { indent + it }.toMutableList() } val results = lines.joinToString("\n") val leftBraceCount = (prefix + code + suffixCode).count { it == '{' } val rightBraceCount = (prefix + code + suffixCode).count { it == '}' } val reversed = results.reversed() var toRemoveBrace = rightBraceCount - leftBraceCount val stringBuilder = StringBuilder() for (i in reversed.indices) { if (toRemoveBrace > 0 && (reversed[i] == '}' || reversed[i] == '\n' || reversed[i] == ' ')) { if (reversed[i] == '}') { toRemoveBrace-- } else { stringBuilder.append(reversed[i]) } } else { stringBuilder.append(reversed[i]) } } val output = stringBuilder.reverse().toString() return endLinePattern.replace(output, "\n") } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/variable/frontend/Component.kt ================================================ package com.phodal.shirecore.variable.frontend import kotlinx.serialization.Serializable /** * the Design System Component */ @Serializable data class Component( val name: String, val path: String, val signature: String = "", val props: List = emptyList(), ) { fun format(): String { return """ |component name: $name |component path: $path |input signature: $signature |component props: $props """.trimMargin() } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/variable/frontend/ComponentProvider.kt ================================================ package com.phodal.shirecore.variable.frontend interface ComponentProvider { fun getPages(): List fun getComponents(): List fun getRoutes(): Map } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/variable/template/TemplateContext.kt ================================================ package com.phodal.shirecore.variable.template interface TemplateContext { } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/variable/template/VariableActionEventDataHolder.kt ================================================ package com.phodal.shirecore.variable.template import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.util.Key import com.intellij.openapi.util.UserDataHolderBase /** * The VariableActionEventDataHolder class serves as a temporary storage for data context related to VCS variable actions. * It uses a data class structure to encapsulate the data and provides a companion object to manage the data storage and retrieval. * * This class is designed to be used with a key-value pair mechanism where the data context is stored against a specific key. * The companion object provides static methods to put and get the data context. * * Usage: * * To store data: * ```Kotlin * val eventData = VariableActionEventDataHolder(dataContext) * VariableActionEventDataHolder.putData(eventData) * ``` * * To retrieve data: * ```Kotlin * val eventData = VariableActionEventDataHolder.getData() * ``` * * @param dataContext The DataContext object that holds the relevant information for VCS variable actions. * It is optional and can be null if no data needs to be stored initially. */ data class VariableActionEventDataHolder(val dataContext: DataContext? = null) { companion object { private val DATA_KEY: Key = Key.create(VariableActionEventDataHolder::class.java.name) private val dataHolder = UserDataHolderBase() fun putData(context: VariableActionEventDataHolder) { dataHolder.putUserData(DATA_KEY, context) } fun getData(): VariableActionEventDataHolder? { return dataHolder.getUserData(DATA_KEY) } } } ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/variable/toolchain/buildsystem/BuildSystemContext.kt ================================================ package com.phodal.shirecore.variable.toolchain.buildsystem import com.phodal.shirecore.variable.template.TemplateContext class BuildSystemContext( val buildToolName: String, val buildToolVersion: String, val languageName: String, val languageVersion: String, /** * The `taskString` variable represents a string that can be used to store various types of task information or commands. * * This variable can be used in different scenarios, such as: * - Parsing the `package.json` file and retrieving the scripts field. * - Running a Gradle task to obtain a list of all available tasks. * - Storing any other task-related information or commands. * * The content of the `taskString` variable will depend on the specific use case and can be customized accordingly. * * Example usage: * ``` * val taskString: String = "npm run build" * ``` * In this example, the `taskString` variable is assigned the value `"npm run build"`, which represents a command to run the `build` script defined in the `package.json` file. * * Please note that the actual content and usage of the `taskString` variable will vary depending on the context and requirements of the application or system using it. */ val taskString: String = "", val libraries: List = emptyList() ) : TemplateContext ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/variable/toolchain/refactoring/RefactorInstElement.kt ================================================ package com.phodal.shirecore.variable.toolchain.refactoring data class RefactorInstElement( val isClass: Boolean, val isMethod: Boolean, val methodName: String, val canonicalName: String, val className: String, val pkgName: String ) ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/variable/toolchain/unittest/AutoTestingPromptContext.kt ================================================ package com.phodal.shirecore.variable.toolchain.unittest import com.intellij.lang.Language import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement import com.phodal.shirecore.variable.template.TemplateContext data class AutoTestingPromptContext( val isNewFile: Boolean, val outputFile: VirtualFile, val relatedClasses: List = emptyList(), val testClassName: String?, val language: Language, /** * In Java, it is the current class. * In Kotlin, it is the current class or current function. * In JavaScript, it is the current class or current function. */ val currentObject: String? = null, val imports: List = emptyList(), /** Since 1.5.4, since some languages have different test code insertion strategies, * we need to pass in the test element text */ val testElement: PsiElement? = null, ) : TemplateContext ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/variable/vcs/ShireFileBranch.kt ================================================ package com.phodal.shirecore.variable.vcs data class ShireFileBranch( val name: String, override val count: Int, override val commits: List ) : CommitModel(count, commits) ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/variable/vcs/ShireFileCommit.kt ================================================ package com.phodal.shirecore.variable.vcs data class ShireFileCommit( val filename: String, val path: String, val status: String, override val count: Int, override val commits: List ) : CommitModel(count, commits) ================================================ FILE: core/src/main/kotlin/com/phodal/shirecore/variable/vcs/ShireGitCommit.kt ================================================ package com.phodal.shirecore.variable.vcs sealed class GitEntity // Base class for models containing commits sealed class CommitModel( open val count: Int, open val commits: List ) : GitEntity() data class ShireGitCommit( val hash: String, val authorName: String, val authorEmail: String, val authorDate: Long, val committerName: String, val committerEmail: String, val committerDate: Long, val message: String, val fullMessage: String ) : GitEntity() ================================================ FILE: core/src/main/resources/com.phodal.shirecore.xml ================================================ messages.ShireCoreBundle ================================================ FILE: core/src/main/resources/messages/ShireCoreBundle.properties ================================================ name=Shire progress.run.task=Running task shire.ref.loading=Loading schema.custom-agent.json.display.name=Shire CustomAgent Schema intentions.request.background.process.title=Your LLM handle generate file intentions.chat.code.complete.name=Code complete intentions.step.prepare-context=Prepare context shell.command.suggestion.action.default.text=How to checkout a branch? schema.pattern.json.display.name=Shire Secret Pattern schema.env.json.display.name=Shire Env Schema shire.llm.notfound=No LLM provider found intentions.assistant.name=Shire intention action intentions.assistant.popup.title=Shire Intention Action sketch.patch.action.accept=Accept sketch.patch.action.accept.tooltip=Accept the change sketch.patch.action.reject=Reject sketch.patch.action.reject.tooltip=Reject the change sketch.patch.action.viewDiff=View Diff sketch.patch.action.viewDiff.tooltip=View the diff # rollback sketch.patch.action.rollback=Rollback sketch.patch.action.rollback.tooltip=Rollback the change chat.panel.send=Send chat.input.empty.tips=Input cannot be empty sketch.lint.error.tooltip=Click to view all errors sketch.lint.error=Found Lint issue: {0} ================================================ FILE: core/src/main/resources/schemas/shireCustomAgent.schema.json ================================================ { "$schema": "http://json-schema.org/draft-07/schema#", "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" }, "description": { "type": "string" }, "url": { "type": "string", "format": "uri" }, "auth": { "type": "object", "properties": { "type": { "type": "string", "enum": [ "Bearer" ] }, "token": { "type": "string" } }, "required": [ "type", "token" ] }, "connector": { "type": "object", "properties": { "requestFormat": { "type": "string", "description": "JSONPath expression to specify the format of the request" }, "responseFormat": { "type": "string", "description": "JSONPath expression to specify the format of the request" } } }, "responseAction": { "type": "string", "enum": [ "Direct", "TextChunk", "WebView", "Shire" ] }, "defaultTimeout": { "type": "number" }, "enabled": { "type": "boolean" } }, "required": [ "name", "url", "description", "responseAction" ], "additionalProperties": true } } ================================================ FILE: core/src/main/resources/schemas/shireEnv.schema.json ================================================ { "title": "Shire Environment JSON schema", "$schema": "https://json-schema.org/draft-07/schema#", "type": "object", "properties": { "models": { "type": "array", "items": { "type": "object", "properties": { "title": { "type": "string" }, "provider": { "type": "string", "enum": [ "openai" ] }, "apiBase": { "type": "string" }, "apiKey": { "type": "string" }, "model": { "type": "string" }, "temperature": { "type": "number" }, "maxTokens": { "type": "number" } }, "required": [ "title", "apiKey", "model" ] } } }, "patternProperties": { "^models$": { "not": { "type": "object", "properties": { "Security": { "type": "object", "properties": { "Auth": { "type": "object", "patternProperties": { ".+": { "properties": { "Type": { "enum": [ "OAuth2", "Mock" ] }, "Grant Type": { "enum": [ "Authorization Code", "Implicit", "Password", "Client Credentials", "Device Authorization" ] }, "Auth URL": { "type": "string" }, "Token URL": { "type": "string" }, "Redirect URL": { "type": "string" }, "Client ID": { "type": "string" }, "Client Secret": { "type": "string" }, "Client Credentials": { "anyOf": [ { "type": "boolean" }, { "enum": [ "basic", "in body", "none" ] } ] }, "Username": { "type": "string" }, "Password": { "type": "string" }, "Acquire Automatically": { "type": "boolean" }, "Scope": { "type": "string" }, "State": { "type": "string" }, "PKCE": { "anyOf": [ { "type": "boolean" }, { "type": "object", "properties": { "Code Challenge Method": { "enum": [ "SHA-256", "Plain" ] }, "Code Verifier": { "type": "string", "pattern": "^[A-Za-z0-9.\\-~]{43,128}$" } } } ] }, "Open Complete URI": { "type": "boolean" }, "Device Auth URL": { "type": "string" }, "Start Polling After Browser": { "type": "boolean" }, "Use ID Token": { "type": "boolean" }, "Custom Request Parameters": { "type": "object", "properties": { "resource": { "$ref": "#/$defs/customParameter" }, "audience": { "$ref": "#/$defs/customParameter" }, ".+": { "$ref": "#/$defs/customParameter" } } }, "Token": { "type": "string" }, "ID Token": { "type": "string" } }, "required": [ "Type" ] } } } } } }, "patternProperties": { ".+": { "anyOf": [ { "type": "boolean" }, { "type": "string" } ], "description": "User defined variable" } }, "description": "Environment" } } }, "$defs": { "customParameterScalar": { "oneOf": [ { "type": "string" }, { "type": "object", "properties": { "Value": { "type": "string" }, "Use": { "enum": [ "In Auth Request", "In Token Request", "Everywhere" ] } }, "required": [ "Value" ] } ] }, "customParameter": { "oneOf": [ { "$ref": "#/$defs/customParameterScalar" }, { "type": "array", "items": { "$ref": "#/$defs/customParameterScalar" } } ] } } } ================================================ FILE: core/src/main/resources/schemas/shireSecretPattern.schema.json ================================================ { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "patterns": { "type": "array", "items": { "type": "object", "properties": { "pattern": { "type": "object", "properties": { "name": { "type": "string", "description": "Name of the pattern" }, "regex": { "type": "string", "description": "Must be a regular expression, optionally but recommended to be quoted, and must be surrounded with '/'. Example: '/Code coverage: \\d+\\.\\d+/'", "format": "regex" }, "confidence": { "type": "string", "enum": ["high", "medium", "low"] } }, "required": ["name", "regex", "confidence"] } }, "required": ["pattern"] } } }, "required": ["patterns"] } ================================================ FILE: core/src/main/resources/secrets/default.shireSecretPattern.yml ================================================ patterns: - pattern: name: times regex: \d{1,2}:\d{2} ?(?:[ap]\.?m\.?)?|\d[ap]\.?m\.? confidence: high - pattern: name: phones regex: ((?:(?\\s*?[\\S\\s]*?[\\S\\s]*?<\\/pwentry>\\s*?<\\/pwlist>" confidence: high - pattern: name: Large number of US Phone Numbers regex: "\\d{3}-\\d{3}-\\d{4}|\\(\\d{3}\\)\\ ?\\d{3}-?\\d{4}" confidence: high - pattern: name: Large number of US Zip Codes regex: "^(\\d{5}-\\d{4}|\\d{5})$" confidence: high - pattern: name: Lightweight Directory Access Protocol regex: "(?:dn|cn|dc|sn):\\s*[a-zA-Z0-9=, ]*" confidence: high - pattern: name: Metasploit Module regex: "require\\ 'msf/core'|class\\ Metasploit|include\\ Msf::Exploit::\\w+::\\w+" confidence: high - pattern: name: MySQL database dump regex: "DROP DATABASE IF EXISTS(?:.|\\n){5,300}CREATE DATABASE(?:.|\\n){5,300}DROP TABLE IF EXISTS(?:.|\\n){5,300}CREATE TABLE" confidence: high - pattern: name: MySQLite database dump regex: "DROP\\ TABLE\\ IF\\ EXISTS\\ \\[[a-zA-Z]*\\];|CREATE\\ TABLE\\ \\[[a-zA-Z]*\\];" confidence: high - pattern: name: Network Proxy Auto-Config regex: "proxy\\.pac|function\\ FindProxyForURL\\(\\w+,\\ \\w+\\)" confidence: high - pattern: name: Nmap Scan Report regex: "Nmap\\ scan\\ report\\ for\\ [a-zA-Z0-9.]+" confidence: high - pattern: name: PGP Header regex: "-{5}(?:BEGIN|END)\\ PGP\\ MESSAGE-{5}" confidence: high - pattern: name: PGP Private Key Block regex: "-----BEGIN PGP PRIVATE KEY BLOCK-----(?:.|\\s)+?-----END PGP PRIVATE KEY BLOCK-----" confidence: high - pattern: name: PKCS7 Encrypted Data regex: "(?:Signer|Recipient)Info(?:s)?\\ ::=\\ \\w+|[D|d]igest(?:Encryption)?Algorithm|EncryptedKey\\ ::= \\w+" confidence: high - pattern: name: Password etc passwd regex: "[a-zA-Z0-9\\-]+:[x|\\*]:\\d+:\\d+:[a-zA-Z0-9/\\- \"]*:/[a-zA-Z0-9/\\-]*:/[a-zA-Z0-9/\\-]+" confidence: high - pattern: name: Password etc shadow regex: "[a-zA-Z0-9\\-]+:(?:(?:!!?)|(?:\\*LOCK\\*?)|\\*|(?:\\*LCK\\*?)|(?:\\$.*\\$.*\\$.*?)?):\\d*:\\d*:\\d*:\\d*:\\d*:\\d*:" confidence: high - pattern: name: PlainText Private Key regex: "-----BEGIN PRIVATE KEY-----(?:.|\\s)+?-----END PRIVATE KEY-----" confidence: high - pattern: name: PuTTY SSH DSA Key regex: "PuTTY-User-Key-File-2: ssh-dss\\s*Encryption: none(?:.|\\s?)*?Private-MAC:" confidence: high - pattern: name: PuTTY SSH RSA Key regex: "PuTTY-User-Key-File-2: ssh-rsa\\s*Encryption: none(?:.|\\s?)*?Private-MAC:" confidence: high - pattern: name: Public Key Cryptography System (PKCS) regex: "protocol=\"application/x-pkcs[0-9]{0,2}-signature\"" confidence: high - pattern: name: Public encrypted key regex: "-----BEGIN PUBLIC KEY-----(?:.|\\s)+?-----END PUBLIC KEY-----" confidence: high - pattern: name: RSA Private Key regex: "-----BEGIN RSA PRIVATE KEY-----(?:[a-zA-Z0-9\\+\\=\\/\"']|\\s)+?-----END RSA PRIVATE KEY-----" confidence: high - pattern: name: SSL Certificate regex: "-----BEGIN CERTIFICATE-----(?:.|\\n)+?\\s-----END CERTIFICATE-----" confidence: high - pattern: name: SWIFT Codes regex: "[A-Za-z]{4}(?:GB|US|DE|RU|CA|JP|CN)[0-9a-zA-Z]{2,5}$" confidence: high - pattern: name: Samba Password config file regex: "[a-z]*:\\d{3}:[0-9a-zA-Z]*:[0-9a-zA-Z]*:\\[U\\ \\]:.*" confidence: high - pattern: name: Slack 2FA Backup Codes regex: "Two-Factor\\s*\\S*Authentication\\s*\\S*Backup\\s*\\S*Codes(?:.|\\n)*[Ss]lack(?:.|\\n)*\\d{9}" confidence: high - pattern: name: UK Drivers License Numbers regex: "[A-Z]{5}\\d{6}[A-Z]{2}\\d{1}[A-Z]{2}" confidence: high - pattern: name: UK Passport Number regex: "\\d{10}GB[RP]\\d{7}[UMF]{1}\\d{9}" confidence: high - pattern: name: USBank Routing Numbers - California regex: "^12(?:1122676|2235821)$" confidence: high - pattern: name: United Bank Routing Number - California regex: "^122243350$" confidence: high - pattern: name: Wells Fargo Routing Numbers - California regex: "^121042882$" confidence: high - pattern: name: aws_access_key regex: "((access[-_]?key[-_]?id)|(ACCESS[-_]?KEY[-_]?ID)|([Aa]ccessKeyId)|(access[_-]?id)).{0,20}AKIA[a-zA-Z0-9+/]{16}[^a-zA-Z0-9+/]" confidence: high - pattern: name: aws_credentials_context regex: "access_key_id|secret_access_key|AssetSync.configure" confidence: high - pattern: name: aws_secret_key regex: "((secret[-_]?access[-_]?key)|(SECRET[-_]?ACCESS[-_]?KEY|(private[-_]?key))|([Ss]ecretAccessKey)).{0,20}[^a-zA-Z0-9+/][a-zA-Z0-9+/]{40}\\b" confidence: high - pattern: name: facebook_secret regex: "(facebook_secret|FACEBOOK_SECRET|facebook_app_secret|FACEBOOK_APP_SECRET)[a-z_ =\\s\"'\\:]{0,5}[^a-zA-Z0-9][a-f0-9]{32}[^a-zA-Z0-9]" confidence: high - pattern: name: github_key regex: "(GITHUB_SECRET|GITHUB_KEY|github_secret|github_key|github_token|GITHUB_TOKEN|github_api_key|GITHUB_API_KEY)[a-z_ =\\s\"'\\:]{0,10}[^a-zA-Z0-9][a-zA-Z0-9]{40}[^a-zA-Z0-9]" confidence: high - pattern: name: google_two_factor_backup regex: "(?:BACKUP VERIFICATION CODES|SAVE YOUR BACKUP CODES)[\\s\\S]{0,300}@" confidence: high - pattern: name: heroku_key regex: "(heroku_api_key|HEROKU_API_KEY|heroku_secret|HEROKU_SECRET)[a-z_ =\\s\"'\\:]{0,10}[^a-zA-Z0-9-]\\w{8}(?:-\\w{4}){3}-\\w{12}[^a-zA-Z0-9\\-]" confidence: high - pattern: name: microsoft_office_365_oauth_context regex: "https://login.microsoftonline.com/common/oauth2/v2.0/token|https://login.windows.net/common/oauth2/token" confidence: high - pattern: name: pgSQL Connection Information regex: "(?:postgres|pgsql)\\:\\/\\/" confidence: high - pattern: name: slack_api_key regex: "(slack_api_key|SLACK_API_KEY|slack_key|SLACK_KEY)[a-z_ =\\s\"'\\:]{0,10}[^a-f0-9][a-f0-9]{32}[^a-f0-9]" confidence: high - pattern: name: slack_api_token regex: "(xox[pb](?:-[a-zA-Z0-9]+){4,})" confidence: high - pattern: name: ssh_dss_public regex: "ssh-dss [0-9A-Za-z+/]+[=]{2}" confidence: high - pattern: name: ssh_rsa_public regex: "ssh-rsa AAAA[0-9A-Za-z+/]+[=]{0,3} [^@]+@[^@]+" confidence: high - pattern: name: IBAN regex: '[a-zA-Z]{2}[0-9]{2}[a-zA-Z0-9]{4}[0-9]{7}([a-zA-Z0-9]?){0,16}' confidence: high - pattern: name: GPS Data regex: '^([-+]?)([\d]{1,2})(((\.)(\d+)(,)))(\s*)(([-+]?)([\d]{1,3})((\.)(\d+))?)' confidence: high - pattern: name: Blood Type regex: '^(A|B|AB|O)[-+]$' confidence: high - pattern: name: Date of Birth - 2 regex: '^([1-9]|[12][0-9]|3[01])(\/?\.\-?\-?\s?)(0[1-9]|1[12])(\/?\.?\-?\s?)(19[0-9][0-9]|20[0][0-9]|20[1][0-8])$' confidence: high - pattern: name: Tax Number regex: '^[0-9]{10}$' confidence: high - pattern: name: Bitcoin Address regex: '^[13][a-km-zA-HJ-NP-Z0-9]{26,33}$' confidence: high ================================================ FILE: core/src/main/resources/tokenizers-engine.properties ================================================ tokenizers_version=0.19.1-0.28.0 ================================================ FILE: core/src/test/kotlin/com/phodal/shirecore/diff/model/StreamDiffTest.kt ================================================ package com.phodal.shirecore.diff.model import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals import org.junit.Test class StreamDiffTest { @Test fun `add multiple lines`() = runBlocking { val oldLines = listOf("first item", "fourth val") val newLines = flowOf("first item", "second arg", "third param", "fourth val") val diffResults = streamDiff(oldLines, newLines).toList() assertEquals( listOf( DiffLine.Same("first item"), DiffLine.New("second arg"), DiffLine.New("third param"), DiffLine.Same("fourth val") ), diffResults ) } @Test fun `remove multiple lines`() = runBlocking { val oldLines = listOf("first item", "second arg", "third param", "fourth val") val newLines = flowOf("first item", "fourth val") val diffResults = streamDiff(oldLines, newLines).toList() assertEquals( listOf( DiffLine.Same("first item"), DiffLine.Old("second arg"), DiffLine.Old("third param"), DiffLine.Same("fourth val") ), diffResults ) } @Test fun `empty old lines`() = runBlocking { val oldLines = emptyList() val newLines = flowOf("first item", "second arg") val diffResults = streamDiff(oldLines, newLines).toList() assertEquals( listOf( DiffLine.New("first item"), DiffLine.New("second arg") ), diffResults ) } @Test fun `empty new lines`() = runBlocking { val oldLines = listOf("first item", "second arg") val newLines = flowOf() // 空的新行流 val diffResults = streamDiff(oldLines, newLines).toList() assertEquals( listOf( DiffLine.Old("first item"), DiffLine.Old("second arg") ), diffResults ) } } ================================================ FILE: core/src/test/kotlin/com/phodal/shirecore/guard/model/SecretPatternsScannerTest.kt ================================================ package com.phodal.shirecore.guard.model import com.intellij.testFramework.LightPlatformTestCase import com.phodal.shirecore.function.guard.model.SecretPattern import com.phodal.shirecore.function.guard.scanner.SecretPatternsScanner /** * Unit tests for the SecretPatternsManager class. */ class SecretPatternsScannerTest : LightPlatformTestCase() { // fun `should add a new pattern to the list of patterns`() { fun testShouldAddNewPatternToListOfPatterns() { val secretPatterns: SecretPatternsScanner = SecretPatternsScanner(project) // Given val newPattern = SecretPattern("Email", "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}", "medium") // When secretPatterns.addPattern(newPattern) val updatedPatterns = secretPatterns.getPatterns() // Then assertTrue(updatedPatterns.contains(newPattern)) } // fun `should remove a pattern from the list of patterns`() { fun testShouldRemovePatternFromListOfPatterns() { val secretPatterns: SecretPatternsScanner = SecretPatternsScanner(project) val patternToRemove = SecretPattern("Credit Card", "[0-9]{4} [0-9]{4} [0-9]{4} [0-9]{4}", "high") // When secretPatterns.removePattern(patternToRemove) val remainingPatterns = secretPatterns.getPatterns() // Then assertFalse(remainingPatterns.contains(patternToRemove)) } // fun `should find patterns that match the text`() { fun testShouldFindPatternsThatMatchText() { val secretPatterns: SecretPatternsScanner = SecretPatternsScanner(project) val testText = "My email is example@example.com and my phone number is 123-456-7890." // When val output = secretPatterns.maskInput(testText) // Then assertEquals("My email is **** and my phone number is ****.", output) } } ================================================ FILE: core/src/test/kotlin/com/phodal/shirecore/middleware/builtin/SaveFileProcessorTest.kt ================================================ package com.phodal.shirecore.middleware.builtin import com.intellij.testFramework.fixtures.BasePlatformTestCase class FileNameTest : BasePlatformTestCase() { fun testReturnTimestampWithExtensionWhenFilePathIsBlank() { val ext = "txt" val filePath = "" val result = getValidFilePath(filePath, ext) // Check if the result matches a timestamp followed by the correct extension assertTrue(result.matches(Regex("""\d+\.txt"""))) } fun testReturnValidFilePathIfItMatchesRegex() { val ext = "txt" val filePath = "C:\\Users\\John\\Documents\\file.txt" val result = getValidFilePath(filePath, ext) // Since the path is valid, it should return the same file path assertEquals(filePath, result) } // "docs/api.yml" fun testReturnValidFilePathIfItMatchesRegexForLinux() { val ext = "yml" val filePath = "docs/api.yml" val result = getValidFilePath(filePath, ext) // Since the path is valid, it should return the same file path assertEquals(filePath, result) } fun testReturnTimestampWithExtensionIfPathIsInvalid() { val ext = "txt" val filePath = "Invalid\\Path\\test" val result = getValidFilePath(filePath, ext) assertEquals(result, filePath) } fun testReturnTimestampWithExtensionIfPathIsInvalidForLinux() { val ext = "txt" val filePath = "/home/user/Documents/file.shire" val result = getValidFilePath(filePath, ext) assertEquals(result, filePath) } fun testReturnParsedTextWhenFilePathContainsCodeFence() { val ext = "txt" val filePath = "```\nsome code block\n```" val parsedText = "some code block" // Mock or simulate the CodeFence.parse method val result = getValidFilePath(filePath, ext) // Assuming the CodeFence.parse method extracts "some code block" assertEquals(parsedText, result) } } ================================================ FILE: core/src/test/kotlin/com/phodal/shirecore/provider/impl/MarkdownPsiContextVariableProviderTest.kt ================================================ package com.phodal.shirecore.provider.impl import com.intellij.testFramework.fixtures.BasePlatformTestCase class MarkdownPsiContextVariableProviderTest: BasePlatformTestCase() { fun testShouldSuccessParseMarkdownHeading() { val markdownText = """# Hello World | sample | ## h2 | ### h3 | #### h4 """.trimMargin() val html = MarkdownPsiContextVariableProvider().toHtml(markdownText) assertEquals("# Hello World\n## h2\n### h3\n#### h4", html) } } ================================================ FILE: core/src/test/kotlin/com/phodal/shirecore/search/TfIdfTest.kt ================================================ package com.phodal.shirecore.search import com.phodal.shirecore.search.algorithm.TfIdf import junit.framework.TestCase.assertEquals import org.junit.Test class TfIdfTest { @Test fun shouldBuildDocumentFromString() { // given val tfIdf = TfIdf() val text = "apple orange apple banana" val expectedDocument = mapOf("apple" to 2, "orange" to 1, "banana" to 1) // when val document = tfIdf.buildDocument(text) // then assertEquals(expectedDocument, document) } @Test fun shouldReturnTfIdfValuesForAGivenQuery() { // given val tfIdf = TfIdf() val chunks = listOf("chunk1", "chunk2", "chunk3") val query = "chunk1" tfIdf.addDocuments(chunks) // when val result = tfIdf.search(query) // then assertEquals(3, result.size) } @Test fun shouldExecuteTheCallbackFunctionIfProvided() { // given val tfIdf = TfIdf() val chunks = listOf("chunk1", "chunk2", "chunk3") val query = "chunk1" tfIdf.addDocuments(chunks) // when tfIdf.search(query) } } ================================================ FILE: core/src/test/kotlin/com/phodal/shirecore/search/algorithm/BM25SimilarityTest.kt ================================================ package com.phodal.shirecore.search.algorithm import junit.framework.TestCase.* import org.junit.Test import kotlin.math.log10 /** * Unit tests for BM25Similarity class. */ class BM25SimilarityTest { @Test fun testComputeInputSimilarity() { val bm25 = BM25Similarity() val query = "sample query" val chunks = listOf( listOf("this", "is", "a", "sample", "document"), listOf("this", "document", "is", "another", "sample"), listOf("one", "more", "sample", "document") ) val similarity = bm25.computeInputSimilarity(query, chunks) assertNotNull(similarity) assertEquals(3, similarity.size) // We have 3 documents similarity.forEach { docSim -> assertEquals(2, docSim.size) // Our query has 2 terms } // Print similarity for manual inspection (not a part of actual tests) similarity.forEachIndexed { index, docSim -> println("Document $index: $docSim") } } @Test fun testComputeIDF() { val bm25 = BM25Similarity() val chunks = listOf( listOf("this", "is", "a", "sample", "document"), listOf("this", "document", "is", "another", "sample"), listOf("one", "more", "sample", "document") ) val docCount = chunks.size val idfMap = bm25.computeIDF(chunks, docCount) assertNotNull(idfMap) assertTrue(idfMap.isNotEmpty()) assertEquals(8, idfMap.size) // There are 8 unique terms // Validate some IDF values manually val expectedIDFThis = log10((docCount - 2 + 0.5) / (2 + 0.5) + 1.0) val expectedIDFSample = log10((docCount - 3 + 0.5) / (3 + 0.5) + 1.0) val expectedIDFOne = log10((docCount - 1 + 0.5) / (1 + 0.5) + 1.0) assertEquals(expectedIDFThis, idfMap["this"]) assertEquals(expectedIDFSample, idfMap["sample"]) assertEquals(expectedIDFOne, idfMap["one"]) // Print IDF map for manual inspection (not a part of actual tests) idfMap.forEach { (term, idf) -> println("Term: $term, IDF: $idf") } } @Test fun `should compute similarity for query and documents correctly`() { val similarity = BM25Similarity() val chunks = listOf( listOf("apple", "banana", "apple"), listOf("banana", "cherry"), listOf("apple", "cherry") ) val query = "apple banana" val result = similarity.computeInputSimilarity(query, chunks) assertNotNull(result) assertEquals(3, result.size) // Ensure one result per document assertEquals(2, result[0].size) // Ensure one score per term in the query // Check if the computed similarity values are non-negative val allPositive = result.flatten().all { it >= 0.0 } assertTrue(allPositive) } @Test fun `should handle query term not present in documents`() { val similarity = BM25Similarity() val chunks = listOf( listOf("apple", "banana", "apple"), listOf("banana", "cherry"), listOf("apple", "cherry") ) val query = "apple orange" val result = similarity.computeInputSimilarity(query, chunks) assertNotNull(result) assertEquals(3, result.size) // Ensure one result per document assertEquals(2, result[0].size) // Ensure one score per term in the query // Check if the score for the term 'orange' is 0.0 as it is not present in the documents assertEquals(0.0, result[0][1]) } } ================================================ FILE: core/src/test/kotlin/com/phodal/shirecore/search/algorithm/JaccardSimilarityTest.kt ================================================ package com.phodal.shirecore.search.algorithm import org.junit.Test import junit.framework.TestCase.assertEquals class JaccardSimilarityTest { @Test fun should_calculate_token_level_jaccard_similarity() { // given val jaccardSimilarity = JaccardSimilarity() val query = "test query" val chunks = listOf(listOf("test", "query"), listOf("another", "test"), listOf("query", "test")) // when val result = jaccardSimilarity.computeInputSimilarity(query, chunks) // then assertEquals(3, result.size) assertEquals(0.5, result[0][0]) assertEquals(0.0, result[1][0]) assertEquals(0.5, result[2][0]) } @Test fun should_return_a_set_of_tokens() { // given val jaccardSimilarity = JaccardSimilarity() val input = "test input" // when val result = jaccardSimilarity.tokenize(input) // then assertEquals(2, result.size) } fun should_return_correct_similarity_score() { // given val jaccardSimilarity = JaccardSimilarity() val set1 = setOf("test", "query") val set2 = setOf("query", "another") // when val result = jaccardSimilarity.similarityScore(set1, set2) // then assertEquals(0.33, result, 0.01) } @Test fun should_return_the_correct_similarity_score() { // given val jaccardSimilarity = JaccardSimilarity() val path1 = "folder1/folder2/file1" val set2 = setOf("folder1", "folder2", "file2") val expectedScore: Double = 2.0 / 3.0 // when val actualScore = jaccardSimilarity.pathSimilarity(path1, set2) // then assertEquals(expectedScore, actualScore) } } ================================================ FILE: core/src/test/kotlin/com/phodal/shirecore/search/function/LocalEmbeddingTest.kt ================================================ package com.phodal.shirecore.search.function import com.phodal.shirecore.search.indices.normalized import org.junit.Assert.assertArrayEquals import org.junit.Test class FloatArrayExtensionTest { @Test fun should_return_normalized_float_array() { // Given val inputArray = floatArrayOf(1.0f, 2.0f, 3.0f) // When val normalizedArray = inputArray.normalized() // Then val expectedArray = floatArrayOf(0.26726124f, 0.5345225f, 0.8017837f) assertArrayEquals(expectedArray, normalizedArray, 0.0001f) } } ================================================ FILE: core/src/test/kotlin/com/phodal/shirecore/search/tokenizer/TermSplitterTest.kt ================================================ package com.phodal.shirecore.search.tokenizer import com.phodal.shirecore.search.tokenizer.TermSplitter.splitTerms import junit.framework.TestCase.assertEquals import org.junit.Test class TermSplitterTest { @Test fun should_splitTerms_when_inputIsCamelCase() { // Given val input = "HelloWorld_helloWorld123" // When val result = splitTerms(input).toList() // Then val expected = listOf( "helloworld_helloworld123", "hello", "world_hello", "world123", "helloworld", "helloworld123", "helloworld_helloworld" ) assertEquals(expected, result) } @Test fun should_splitTerms_when_inputIsUnderscoreCase() { // Given val input = "underscore_case_example" // When val result = splitTerms(input).toList() // Then val expected = listOf("underscore_case_example", "underscore", "case", "example") assertEquals(expected, result) } @Test fun should_splitTerms_when_inputContainsNumericSuffix() { // Given val input = "example123" // When val result = splitTerms(input).toList() // Then val expected = listOf("example123", "example") assertEquals(expected, result) } @Test fun should_splitTerms_when_inputIsMixedNamingStyles() { // Given val input = "CamelCase_with123Numbers" // When val result = splitTerms(input).toList() // Then val expected = listOf("camelcase_with123numbers", "camel", "case_with123numbers", "camelcase", "with123numbers") assertEquals(expected, result) } @Test fun should_syncSplitTerms_when_inputIsCamelCase() { // Given val input = "CamelCaseExample" // When val result = splitTerms(input).toList() // Then val expected = listOf("camelcaseexample", "camel", "case", "example") assertEquals(expected, result) } @Test fun should_syncSplitTerms_when_inputIsUnderscoreCase() { // Given val input = "underscore_case_example" // When val result = splitTerms(input).toList() // Then val expected = listOf("underscore_case_example", "underscore", "case", "example") assertEquals(expected, result) } @Test fun should_syncSplitTerms_when_inputContainsNumericSuffix() { // Given val input = "example123" // When val result = splitTerms(input).toList() // Then val expected = listOf("example123", "example") assertEquals(expected, result) } @Test fun should_handle_java_controller_in_real_world() { // Given val input = """ @PostMapping @ResponseStatus(code = HttpStatus.CREATED) public String postTicket(@RequestBody TicketCreateRequest ticketCreateRequest){ Ticket ticket = ticketMapper.toEntity(ticketCreateRequest); List foods = ticketCreateRequest.getFood().stream() .map((foodId) -> foodService.findById(foodId)) .collect(Collectors.toList()); ticket.setFoods(foods); return ticketService.postTicket(ticket); } """.trimMargin() // When val result = splitTerms(input).toList() // Then val expected = listOf( "postmapping", "post", "mapping", "responsestatus", "response", "status", "code", "httpstatus", "http", "created", "public", "string", "postticket", "ticket", "requestbody", "request", "body", "ticketcreaterequest", "create", "ticketmapper", "mapper", "toentity", "entity", "list", "food", "foods", "getfood", "get", "stream", "map", "foodid", "foodservice", "service", "findbyid", "find", "collect", "collectors", "tolist", "setfoods", "set", "return", "ticketservice" ) assertEquals(expected, result) } } ================================================ FILE: core/src/test/kotlin/com/phodal/shirecore/task/GraphTest.kt ================================================ package com.phodal.shirecore.task import org.junit.Assert.* import org.junit.Test class GraphTest { @Test fun should_addNode_toGraph_when_addNode() { // given val graph = Graph() val node = Node(1, "Node1") // when graph.addNode(node) // then assertEquals(listOf(node), graph.getNodes()) } @Test fun should_addEdge_toGraph_when_addEdge() { // given val graph = Graph() val node1 = Node(1, "Node1") val node2 = Node(2, "Node2") val edge = Edge(node1, node2) // when graph.addNode(node1) graph.addNode(node2) graph.addEdge(edge) // then assertEquals(listOf(edge), graph.getEdges()) } @Test fun should_returnTopologicalSort_when_topologicalSort() { // given val graph = Graph() val node1 = Node(1, "Node1") val node2 = Node(2, "Node2") val node3 = Node(3, "Node3") val edge1 = Edge(node1, node2) val edge2 = Edge(node2, node3) graph.addNode(node1) graph.addNode(node2) graph.addNode(node3) graph.addEdge(edge1) graph.addEdge(edge2) // when val result = graph.topologicalSort(graph) // then assertEquals(listOf(node1, node2, node3), result) } @Test fun should_returnTrue_when_hasCycle() { // given val graph = Graph() val node1 = Node(1, "Node1") val node2 = Node(2, "Node2") val node3 = Node(3, "Node3") val edge1 = Edge(node1, node2) val edge2 = Edge(node2, node3) val edge3 = Edge(node3, node1) graph.addNode(node1) graph.addNode(node2) graph.addNode(node3) graph.addEdge(edge1) graph.addEdge(edge2) graph.addEdge(edge3) // when val result = graph.hasCycle(graph) // then assertTrue(result) } @Test fun should_returnFalse_when_noCycle() { // given val graph = Graph() val node1 = Node(1, "Node1") val node2 = Node(2, "Node2") val node3 = Node(3, "Node3") val edge1 = Edge(node1, node2) val edge2 = Edge(node2, node3) graph.addNode(node1) graph.addNode(node2) graph.addNode(node3) graph.addEdge(edge1) graph.addEdge(edge2) // when val result = graph.hasCycle(graph) // then assertFalse(result) } } ================================================ FILE: core/src/test/kotlin/com/phodal/shirecore/utils/markdown/CodeFenceTest.kt ================================================ package com.phodal.shirecore.utils.markdown import com.intellij.testFramework.fixtures.BasePlatformTestCase import kotlin.collections.get class CodeFenceTest : BasePlatformTestCase() { fun testShould_handle_code_not_complete_from_markdown() { val markdown = """ |```java |public class HelloWorld { | public static void main(String[] args) { | System.out.println("Hello, World"); """.trimMargin() val code = CodeFence.parse(markdown) assertEquals( code.text, """ |public class HelloWorld { | public static void main(String[] args) { | System.out.println("Hello, World"); """.trimMargin() ) assertTrue(!code.isComplete) } fun testShould_handle_code_for_when_code() { val markdown = """ |下面是 |```java """.trimMargin() val code = CodeFence.parse(markdown) assertEquals( code.text, """ """.trimMargin() ) assertTrue(!code.isComplete) } fun testShould_handle_pure_markdown_content() { val content = "```markdown\\nGET /wp/v2/posts\\n```" val code = CodeFence.parse(content) assertEquals(code.text, "GET /wp/v2/posts") } fun testShould_handle_http_request() { val content = "```http request\\nGET /wp/v2/posts\\n```" val code = CodeFence.parse(content) assertEquals(code.text, "GET /wp/v2/posts") } fun testShould_parse_code_from_markdown_java_hello_world() { val markdown = """ |Java Hello, world |```java |public class HelloWorld { | public static void main(String[] args) { | System.out.println("Hello, World"); | } |} |``` | |Python Hello, world | |```http request |DELETE /api/blog/1 |Content-Type: application/json """.trimMargin() val codeFences = CodeFence.parseAll(markdown) assertEquals(codeFences.size, 4) val code = codeFences[1] assertEquals( code.text, """ |public class HelloWorld { | public static void main(String[] args) { | System.out.println("Hello, World"); | } |} """.trimMargin() ) assertTrue(code.isComplete) val last = codeFences.last() assertEquals(last.text, "DELETE /api/blog/1\nContent-Type: application/json") assertEquals(last.ideaLanguage.displayName, "HTTP Request") assertEquals(false, last.isComplete) } fun testShould_parse_code_for_http_request() { val markdown = """ |Java Hello, world |```http request |GET /api """.trimMargin() val codeFences = CodeFence.parseAll(markdown) assertEquals(codeFences.size, 2) val last = codeFences.last() assertEquals(last.text, "GET /api") assertEquals(last.ideaLanguage.displayName, "HTTP Request") assertEquals(false, last.isComplete) } fun testShould_parse_code_for_empty_http_request() { val markdown = """ |Java Hello, world |```http request """.trimMargin() val codeFences = CodeFence.parseAll(markdown) assertEquals(codeFences.size, 2) val last = codeFences.last() assertEquals(last.text, "") assertEquals(last.ideaLanguage.displayName, "HTTP Request") assertEquals(false, last.isComplete) } fun testSupportMultipleLanguage() { val markdown = """ |Java Hello, world |```http request |GET /api |``` |HELLO """.trimMargin() val codeFences = CodeFence.parseAll(markdown) assertEquals(codeFences.size, 3) val last = codeFences.last() assertEquals(last.text, "HELLO") assertEquals(last.ideaLanguage.displayName, "Markdown") assertEquals(true, last.isComplete) } fun testShouldParseHtmlCode() { val content = """ // patch to call tools for step 3 with DevIns language, should use DevIns code fence /patch:src/main/index.html ```patch // the index.html code ``` """.trimIndent() val code = CodeFence.parse(content) assertEquals( code.text, """ /patch:src/main/index.html ```patch // the index.html code ``` """.trimIndent() ) assertTrue(code.isComplete) } fun testShouldParseUndoneHtmlCode() { val content = """ // patch to call tools for step 3 with DevIns language, should use DevIns code fence /patch:src/main/index.html ```patch // the index.html code ``` """.trimIndent() val code = CodeFence.parse(content) assertFalse(code.isComplete) assertEquals( code.text, """ /patch:src/main/index.html ```patch // the index.html code ``` """.trimIndent() ) } /// parse all with shires fun testShouldParseAllWithDevin() { val content = """ | |// the index.html code | | |```java |public class HelloWorld { | public static void main(String[] args) { | System.out.println("Hello, World"); | } |} |``` """.trimMargin() val codeFences = CodeFence.parseAll(content) assertEquals(codeFences.size, 3) assertEquals( codeFences[0].text, """ |// the index.html code """.trimMargin() ) assertTrue(codeFences[0].isComplete) assertEquals( codeFences[2].text, """ |public class HelloWorld { | public static void main(String[] args) { | System.out.println("Hello, World"); | } |} """.trimMargin() ) assertTrue(codeFences[2].isComplete) } } ================================================ FILE: core/src/test/resources/META-INF/plugin.xml ================================================ com.phodal.shire ================================================ FILE: docs/CNAME ================================================ shire.phodal.com ================================================ FILE: docs/_config.yml ================================================ remote_theme: pmarsceill/just-the-docs title: Shire - AI coding agent language description: Shire offers a straightforward AI coding agent language that enables communication between an LLM and control IDE for automated programming. heading_anchors: true footer_content: "This code is distributed under the MPL 2.0 license. See `LICENSE` in this directory." # Footer last edited timestamp last_edit_timestamp: true # show or hide edit time - page must have `last_modified_date` defined in the frontmatter last_edit_time_format: "%b %e %Y at %I:%M %p" # uses ruby's time format: https://ruby-doc.org/stdlib-2.7.0/libdoc/time/rdoc/Time.html # Footer "Edit this page on GitHub" link text gh_edit_link: true # show or hide edit this page link gh_edit_link_text: "Edit this page on GitHub." gh_edit_repository: "https://github.com/phodal/shire" # the github URL for your repo gh_edit_branch: "master" # the branch that your docs are served from gh_edit_source: docs # the source that your files originate from gh_edit_view_mode: "tree" # "tree" or "edit" if you want the user to jump into the editor immediately callouts_level: quiet # or loud callouts: highlight: color: yellow important: title: Important color: blue new: title: New color: green note: title: Note color: purple warning: title: Warning color: red # Enable or disable the site search # Supports true (default) or false search_enabled: true search: # Split pages into sections that can be searched individually # Supports 1 - 6, default: 2 heading_level: 4 # Maximum amount of previews per search result # Default: 3 previews: 3 # Maximum amount of words to display before a matched word in the preview # Default: 5 preview_words_before: 5 # Maximum amount of words to display after a matched word in the preview # Default: 10 preview_words_after: 10 # Set the search token separator # Default: /[\s\-/]+/ # Example: enable support for hyphenated search words tokenizer_separator: /[\s/]+/ # Display the relative url in search results # Supports true (default) or false rel_url: true # Enable or disable the search button that appears in the bottom right corner of every page # Supports true or false (default) button: false # Back to top link back_to_top: true back_to_top_text: "Back to top" # Google Analytics Tracking (optional) # e.g, UA-1234567-89 #ga_tracking: UA-1234567-89 ga_tracking_anonymize_ip: true # Use GDPR compliant Google Analytics settings (true by default) url: "https://shire.phodal.com" # the base hostname & protocol for your site # Aux links for the upper right navigation aux_links: "View in on GitHub": - "//github.com/phodal/shire" # Makes Aux links open in a new tab. Default is false aux_links_new_tab: true # #mermaid: # # Version of mermaid library # # Pick an available version from https://cdn.jsdelivr.net/npm/mermaid/ # version: "10.4.0" plugins: - jekyll-seo-tag - jekyll-sitemap ================================================ FILE: docs/_includes/head_custom.html ================================================ ================================================ FILE: docs/_includes/js/custom.js ================================================ function shire_lang(hljs) { let regex = hljs.regex const TEMPLATE_VARIABLES = { className: "template-variable", variants: [ { // jinja templates Ansible begin: /\{\{/, end: /\}\}/, }, { // Ruby i18n begin: /%\{/, end: /\}/, }, ], } const STRING = { className: "string", relevance: 0, variants: [ { begin: /'/, end: /'/, }, { begin: /"/, end: /"/, }, // { begin: /\S+/ } ], contains: [ hljs.BACKSLASH_ESCAPE, TEMPLATE_VARIABLES, ], } const DATE_RE = "[0-9]{4}(-[0-9][0-9]){0,2}" const TIME_RE = "([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?" const FRACTION_RE = "(\\.[0-9]*)?" const ZONE_RE = "([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?" const TIMESTAMP = { className: "number", begin: "\\b" + DATE_RE + TIME_RE + FRACTION_RE + ZONE_RE + "\\b", } let FRONTMATTER = { className: "meta", begin: "^---\\s*$", end: "^---\\s*$", contains: [ { className: "attr", variants: [ // added brackets support { begin: /\w[\w :()\./-]*:(?=[ \t]|$)/, }, { // double quoted keys - with brackets begin: /"\w[\w :()\./-]*":(?=[ \t]|$)/, }, { // single quoted keys - with brackets begin: /'\w[\w :()\./-]*':(?=[ \t]|$)/, }, ], }, TIMESTAMP, // numbers are any valid C-style number that // sit isolated from other words { className: "number", begin: hljs.C_NUMBER_RE + "\\b", relevance: 0, }, STRING, ], } let INLINE_HTML = { begin: /<\/?[A-Za-z_]/, end: ">", subLanguage: "xml", relevance: 0, } let HORIZONTAL_RULE = { begin: "^[-\\*]{3,}", end: "$", } let CODE = { className: "code", variants: [ // TODO: fix to allow these to work with sublanguage also { begin: "(`{3,})[^`](.|\\n)*?\\1`*[ ]*" }, { begin: "(~{3,})[^~](.|\\n)*?\\1~*[ ]*" }, // needed to allow markdown as a sublanguage to work { begin: "```", end: "```+[ ]*$", }, { begin: "~~~", end: "~~~+[ ]*$", }, { begin: "`.+?`" }, { begin: "(?=^( {4}|\\t))", // use contains to gobble up multiple lines to allow the block to be whatever size // but only have a single open/close tag vs one per line contains: [ { begin: "^( {4}|\\t)", end: "(\\n)$", }, ], relevance: 0, }, ], } let LIST = { className: "bullet", begin: "^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)", end: "\\s+", excludeEnd: true, } let LINK_REFERENCE = { begin: /^\[[^\n]+\]:/, returnBegin: true, contains: [ { className: "symbol", begin: /\[/, end: /\]/, excludeBegin: true, excludeEnd: true, }, { className: "link", begin: /:\s*/, end: /$/, excludeBegin: true, }, ], } let URL_SCHEME = /[A-Za-z][A-Za-z0-9+.-]*/ let LINK = { variants: [ // too much like nested array access in so many languages // to have any real relevance { begin: /\[.+?\]\[.*?\]/, relevance: 0, }, // popular internet URLs { begin: /\[.+?\]\(((data|javascript|mailto):|(?:http|ftp)s?:\/\/).*?\)/, relevance: 2, }, { begin: regex.concat(/\[.+?\]\(/, URL_SCHEME, /:\/\/.*?\)/), relevance: 2, }, // relative urls { begin: /\[.+?\]\([./?&#].*?\)/, relevance: 1, }, // whatever else, lower relevance (might not be a link at all) { begin: /\[.*?\]\(.*?\)/, relevance: 0, }, ], returnBegin: true, contains: [ { // empty strings for alt or link text match: /\[(?=\])/, }, { className: "string", relevance: 0, begin: "\\[", end: "\\]", excludeBegin: true, returnEnd: true, }, { className: "link", relevance: 0, begin: "\\]\\(", end: "\\)", excludeBegin: true, excludeEnd: true, }, { className: "symbol", relevance: 0, begin: "\\]\\[", end: "\\]", excludeBegin: true, excludeEnd: true, }, ], } let BOLD = { className: "strong", contains: [], // defined later variants: [ { begin: /_{2}(?!\s)/, end: /_{2}/, }, { begin: /\*{2}(?!\s)/, end: /\*{2}/, }, ], } let ITALIC = { className: "emphasis", contains: [], // defined later variants: [ { begin: /\*(?![*\s])/, end: /\*/, }, { begin: /_(?![_\s])/, end: /_/, relevance: 0, }, ], } // 3 level deep nesting is not allowed because it would create confusion // in cases like `***testing***` because where we don't know if the last // `***` is starting a new bold/italic or finishing the last one let BOLD_WITHOUT_ITALIC = hljs.inherit(BOLD, { contains: [] }) let ITALIC_WITHOUT_BOLD = hljs.inherit(ITALIC, { contains: [] }) BOLD.contains.push(ITALIC_WITHOUT_BOLD) ITALIC.contains.push(BOLD_WITHOUT_ITALIC) let CONTAINABLE = [ INLINE_HTML, LINK, ]; [ BOLD, ITALIC, BOLD_WITHOUT_ITALIC, ITALIC_WITHOUT_BOLD, ].forEach(m => { m.contains = m.contains.concat(CONTAINABLE) }) CONTAINABLE = CONTAINABLE.concat(BOLD, ITALIC) let HEADER = { className: "section", variants: [ { begin: "^#{1,6}", end: "$", contains: CONTAINABLE, }, { begin: "(?=^.+?\\n[=-]{2,}$)", contains: [ { begin: "^[=-]*$" }, { begin: "^", end: "\\n", contains: CONTAINABLE, }, ], }, ], } let BLOCKQUOTE = { className: "quote", begin: "^>\\s+", contains: CONTAINABLE, end: "$", } let ENTITY = { //https://spec.commonmark.org/0.31.2/#entity-references scope: "literal", match: /&([a-zA-Z0-9]+|#[0-9]{1,7}|#[Xx][0-9a-fA-F]{1,6});/, } let COMMAND_KEYWORDS = [ "file", "rev", "refactor", "symbol", "write", "run", "commit", "file-func", "browse", "refactor", ] const keywordPattern = COMMAND_KEYWORDS.join("|") let COMMAND = { className: "command", begin: new RegExp(`/(?:${keywordPattern})`), contains: [ { className: "symbol", begin: /:/, // 匹配语义号 }, { className: "url", begin: /(https?:\/\/[^\s]+)/, // 匹配 URL }, { className: "file", begin: /[a-zA-Z0-9_\-\/\.]+/, // 匹配文件路径 relevance: 0, }, // HEAD~1 { className: "git-rev", begin: /HEAD~[0-9]+/, }, { className: "string", begin: /"/, end: /"/, // 匹配字符串 contains: [ { begin: /\\./, // 匹配转义字符 }, ], }, ], } return { name: "shire", aliases: [ "sre", "shire", ], keywords: { keyword: COMMAND_KEYWORDS, }, contains: [ COMMAND, FRONTMATTER, HEADER, INLINE_HTML, LIST, BOLD, ITALIC, BLOCKQUOTE, CODE, HORIZONTAL_RULE, LINK, LINK_REFERENCE, ENTITY, ], } } document.addEventListener("DOMContentLoaded", (event) => { document.querySelectorAll("pre code").forEach((el) => { let langs = hljs.listLanguages() if (!langs.includes("shire")) { hljs.registerLanguage("shire", function() { console.log("registering shire") return shire_lang(hljs) }) hljs.highlightAll() } }) }) ================================================ FILE: docs/_sass/custom/custom.scss ================================================ iframe { border: none; } .site-title { font-size: 14px !important; } pre { background: #f5f6fa; /* 代码块背景色 */ } .hljs { color: #000000; /* 普通文本 */ line-height: 1.5; white-space: preserve; } span.hljs-meta, span.hljs-variable { color: #6897bb; /* 元数据和变量 */ } span.hljs-keyword { color: #cc7832; /* 关键字 */ } span.hljs-title.class_ { color: #a9b7c6; /* 类名 */ } span.hljs-title { color: #ffc66d; /* 函数名 */ } span.hljs-symbol, span.hljs-file, span.hljs-git-rev, span.hljs-string { color: #6a8759; /* 字符串 */ } span.hljs-number { color: #6897bb; /* 数字 */ } span.hljs-url { color: #7253ed; /* URL */ } span.hljs-command { color: #c00; /* URL */ } span.hljs-property, span.hljs-attr, span.hljs-params .hljs-title.class_ { color: #a9b7c6; /* 属性、属性名、参数名 */ } span.hljs-built_in, span.hljs-literal, span.hljs-params { color: #a9b7c6; /* 内置函数、文字、参数 */ } span.hljs-hex_number { color: #6897bb; /* 十六进制数字 */ } span.hljs-comment { color: #808080; /* 注释 */ } code.hljs.language-undefined { color: #000000; /* 未定义语言代码 */ } ================================================ FILE: docs/cloud/cloud.md ================================================ --- layout: default title: Cloud nav_order: 8 has_children: true permalink: /cloud --- ================================================ FILE: docs/cloud/http-api-tool.md ================================================ --- layout: default title: Http API Tool nav_order: 2 parent: Cloud --- 在 [#11](https://github.com/phodal/shire/issues/11) 中,我们引入了一个远程调用的能力,即你可以在 Shire 中调用远程 API,作为上下文 的一部分。 ## Quick Start 先看个例子: ```shire --- variables: "demo": /demo.md/ { thread(".shire/toolchain/bigmodel.curl.sh") } --- hi $demo ``` 在这个例子中,我们定义了一个变量 `demo`,我们调用 `bigmodel.curl.sh` 来获取一个远程的 API 数据。 如下是 `bigmodel.curl.sh` 的内容: ```shell curl --location 'https://open.bigmodel.cn/api/paas/v4/chat/completions' \ --header 'Authorization: Bearer ${apiKey}' \ --header 'Content-Type: application/json' \ --data '{ "model": "glm-4", "messages": [ { "role": "user", "content": "你好" } ] }' ``` 这里我们使用了一个变量 `apiKey`,它可以通过 `*.shireEnv.json` 文件来设置 ```json { "development": { "apiKey": "123456" } } ``` 当前,只支持简单的环境变量,即上面的 `development` 为环境名,`apiKey` 为变量名。 ### `.shireEnv.json` 文件 `.shireEnv.json` 用于存储环境变量,Shire 将会自动加载这种文件,当前只支持 `development` 环境。 ### cURL.sh 在 Shire 中,我们使用 cURL 来调用远程 API,以简化调用的过程。 注意: - Shire 通过 JetBrains 的 HttpClient 来转换 cURL 脚本,因此,不一定支持所有的 cURL 语法。 - Shire 只支持 `${xxx}` 形式的变量替换,不支持 `$xxx` 形式的变量替换。 - Shire 使用 OkHttpClient 来调用远程 API,因此,不一定支持所有的 cURL 语法。 ### 结合 JsonPath > JSONPath 是一种类似于 XPath 的语法,用于从 JSON 文档中选择数据。在 Shire 中,我们可以使用 JsonPath 来选择我们需要的数据。 ```shire --- variables: "api": /sampl.sh/ { thread(".shire/toolchain/bigmodel.curl.sh") | jsonpath("$.choices[0].message.content") } --- hi $api ``` 输出示例: ```bash Prepare for running httpClient.shire... Shire Script: /Users/phodal/IdeaProjects/shire-demo/.shire/toolchain/httpClient.shire Shire Script Compile output: hi 你好👋!我是人工智能助手智谱清言,可以叫我小智🤖,很高兴见到你,欢迎问我任何问题。 -------------------- 你好!很高兴见到你。如果你有任何问题或需要帮助,请随时告诉我。我在这里为你提供信息和支持。 Process finished with exit code 0 ``` ================================================ FILE: docs/cloud/remote-agent.md ================================================ --- layout: default title: Remote AI Agent nav_order: 1 parent: Cloud --- 远程 AI Agent ================================================ FILE: docs/data-privacy/data-privacy.md ================================================ --- layout: default title: Data Privacy nav_order: 6 has_children: true permalink: /data-privacy --- Shire 在 AI 数据安全上提供了一些保护机制,用于对 AI 模型的输入、输出数据进行保护。 **Pipeline 函数保护数据**通过自定义规则对数据进行处理,诸如: - PatternAction - 使用 `replace` 函数,将敏感信息替换为占位符。 - PsiMask - 使用 `mask` 函数,对敏感信息进行脱敏处理。 **AI 数据安全保护函数**用于对 AI 模型的输入、输出数据进行保护。主要是用在 AI 模型的输入输出数据中,对敏感信息进行保护,保护的方式包括: - `beforeStreaming`,在 Streaming 开始前对生成的内容进行处理,将敏感信息替换为占位符。 - `onStreaming`,在 Streaming 过程中对生成的内容,检查是否有敏感信息。 ================================================ FILE: docs/data-privacy/guarding-functions.md ================================================ --- layout: default title: AI 数据安全保护函数 nav_order: 2 parent: Data Privacy --- 数据安全保护函数(AI Data Guarding Functions)是用于进行对与模型交互的数据进行数据保护、去敏感化等操作的一种机制。 - NER 命名实体识别 (Named-entity recognition) Scanner - Pattern/Regex Scanner ## `redact` 函数 在 redact 函数中, 我们使用 [db/pii-stable.yml](https://github.com/mazen160/secrets-patterns-db/blob/master/db/pii-stable.yml) 作为敏感数据的配置文件, 用于对数据进行脱敏处理。 普通变量使用示例: ```shire --- variables: "phoneNumber": "086-1234567890" "var2": /.*ple.shire/ { cat | redact } --- ``` ## 使用自定义 `.shireSecretPattern.yaml` 在 Shire 中支持与 [Secrets Patterns DB](https://github.com/mazen160/secrets-patterns-db) 相似的配置文件,用于对数据进行脱敏处理。 你可以在项目中新建一个 `.shireSecretPattern.yaml`结尾的文件,用于定义自定义的敏感数据规则,如:`Phodal.shireSecretPattern.yaml`。 在该文件中,你可以定义一些敏感数据的规则,如: ```yaml patterns: - pattern: name: Slack Token regex: "(xox[pborsa]-[0-9]{12}-[0-9]{12}-[0-9]{12}-[a-z0-9]{32})" confidence: high ``` 随后,Shire 将会在处理数据时,自动对匹配到的数据进行脱敏处理。 ================================================ FILE: docs/data-privacy/pipeline-guarding.md ================================================ --- layout: default title: Pipeline 函数 nav_order: 1 parent: Data Privacy --- ### 使用 Sed 函数保护数据 Basic Sed Example ```shire --- variables: "var2": /.*ple.shire/ { cat | find("fileName") | sed("\"fileName\"", "hello.kt") } --- Summary webpage: $var2 ``` OpenAI Example: ```shire --- variables: "openai": "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" "var2": /.*ple.shire/ { cat | find("openai") | sed("(?i)\b(sk-[a-zA-Z0-9]{20}T3BlbkFJ[a-zA-Z0-9]{20})(?:['|\"|\n|\r|\s|\x60|;]|${'$'})", "sk-***") } --- Summary webpage: $var2 ``` ## 相关资源 ### [Secrets Patterns DB](https://github.com/mazen160/secrets-patterns-db) Secrets Patterns DB 包含了用于检测秘密、API 密钥、密码、令牌等的正则表达式模式的最大开源数据库。 示例:[db/pii-stable.yml](https://github.com/mazen160/secrets-patterns-db/blob/master/db/pii-stable.yml) 部分内容如下: ```yaml patterns: - pattern: name: times regex: \d{1,2}:\d{2} ?(?:[ap]\.?m\.?)?|\d[ap]\.?m\.? confidence: high - pattern: name: phones regex: ((?:(? 语言接口是一种使用自然语言作为领域特定语言(DSL)或与系统进行交互的接口。它通过解析、处理和分析自然语言,指导系统的设计、开发和执行。 > 其设计目的是提高开发效率、准确性和用户体验,使用户能够使用自然语言描述系统需求、执行任务并获取系统生成的结果。 在 Shire 中,我们采纳了一种语言接口设计原则,将自然语言作为领域特定语言(DSL)或与系统进行交互的接口。这种设计原则涵盖以下几个方面: - **领域特定语言(DSL)**。DSL 是一种专门用于解决特定领域问题的语言,其通过简单、直观的语法和语义,让开发者更容易地描述和执行特定任务。在 Shire 中,我们引入了一种类似于 YAML 的 HobbitHole,用于描述数据处理流程和 IDE 交互逻辑。通过这种 DSL,开发者可以定义数据处理流程、交互逻辑和输出结果。 - **自然语言即 LLM 接口**。对于大型语言模型(LLM)而言,自然语言是一种重要的接口,用于描述和执行代码生成任务。开发者可以通过自然语言描述, 定义代码生成模板、变量和条件,以及执行代码生成任务。这种设计原则让开发者能够更直观、灵活地描述和执行代码生成任务,提升了代码生成的效率和准确性。 示例: ```shire --- name: "AutoTest" description: "AutoTest" interaction: AppendCursor actionLocation: ContextMenu --- 为以下的 ${context.language} 代码编写单元测试。 ... ``` 通过上述配置,我们定义了一个名为 "AutoTest" 的 HobbitHole,用于生成自动化测试代码。通过这种 DSL + 自然语言的结合,我们可以定义 IDE 中的交互类型、操作位置和其他属性,进而实现代码生成任务的自动化和智能化。 ## 原则 3:原子功能单元(Atomic Functional Units) 原子功能单元(Atomic Functional Units, AFUs)是一种设计方法,旨在将复杂系统分解为独立且功能明确的最小操作单元。这种设计原则受到 Linux 设计思想的启发,强调模块化、独立性和简洁性。 1. **原子性和模块化**。每个原子功能单元(AFU)都是独立且不可再分割的基本操作单元,采用模块化设计,能够独立执行特定任务,并可以自由组合和重用。 2. **简单接口与管道式处理**。AFUs 暴露简单明确的输入和输出接口,通过管道连接,形成高效且连贯的数据处理流程,隐藏内部实现细节,简化用户操作。 3. **高内聚低耦合**。每个 AFU 内部专注于特定任务(高内聚),与其他单元通过简单接口进行通信(低耦合),提升系统的灵活性、可维护性和扩展性。 以下是一个示例,展示如何对Java文件进行Embedding,以提供模板中的变量,作为LLM(大型语言模型)的上下文: ```shire --- variables: "searchResult": /*.docx/ { splitting | embedding | searching("API 设计范式") } --- 根据如下的内容,总结一下 API 如何设计: $searchResult ``` 在这个示例中: - splitting:将文档拆分为独立的部分,每个部分可以单独处理。 - embedding:对每个部分进行 Embedding,以便在 LLM 中使用。 - searching:在 Embedding 的内容中搜索特定的关键词或概念。 通过这种方式,我们可以将复杂的任务分解为独立的原子功能单元,每个单元都是独立的,可以自由组合和重用,从而提高系统的灵活性和可维护性。 ================================================ FILE: docs/development/development.md ================================================ --- layout: default title: Development nav_order: 8 has_children: true permalink: /development --- {: .note } Due to my Sabbatical Leave and responsibilities in caring for my child, updates to this document may not be as frequent. 核心概念: - ShireCompiler - HobbitHole,the frontmatter config will convert to a HobbitHole, which is a data structure for the IDE action. ## IDE Build issue ### Notes: Kotlin 2.0 -> 1.9.24 ```bash Internal API usages (179): #Internal class kotlinx.serialization.UnknownFieldException reference Internal class kotlinx.serialization.UnknownFieldException is referenced in com.phodal.shirecore.guard.model.SecretPatternItem$.serializer.deserialize(Decoder) : SecretPatternItem. This class is marked with Kotlin `internal` visibility modifier, indicating that it is not supposed to be referenced in client code outside the declaring module. Internal class kotlinx.serialization.UnknownFieldException is referenced in com.phodal.shirecore.llm.ChatMessage$.serializer.deserialize(Decoder) : ChatMessage. This class is marked with Kotlin `internal` visibility modifier, indicating that it is not supposed to be referenced in client code outside the declaring module. #Internal class kotlinx.serialization.internal.StringSerializer reference Internal class kotlinx.serialization.internal.StringSerializer is referenced in com.phodal.shirecore.agent.CustomFlowTransition$.serializer.childSerializers() : KSerializer[]. This class is marked with Kotlin `internal` visibility modifier, indicating that it is not supposed to be referenced in client code outside the declaring module. Internal class kotlinx.serialization.internal.StringSerializer is referenced in com.phodal.shirecore.agent.CustomAgent$.serializer.childSerializers() : KSerializer[]. This class is marked with Kotlin `internal` visibility modifier, indicating that it is not supposed to be referenced in client code outside the declaring module. ``` - Kaml 0.60.0 -> build with Kotlin 2.0.0, but it's not compatible with Kotlin 1.9.24. ================================================ FILE: docs/development/ide-note.md ================================================ --- layout: default title: IDE Note nav_order: 999 parent: Development --- ## CoroutineScope issue 示例: ```kotlin ShireCoroutineScope.scope(context.project).launch { val suggestion = StringBuilder() flow?.cancelWithConsole(context.console)?.cancellable()?.collect { char -> suggestion.append(char) invokeLater { context.console?.print(char, ConsoleViewContentType.NORMAL_OUTPUT) } } postExecute.invoke(suggestion.toString(), null) } ``` ## Data Context ### 1. 从 AnAction 中获取 DataContext 如果你正在实现一个继承自 `AnAction` 的动作类,可以通过 `AnActionEvent` 直接获取 `DataContext`。如下所示: ```java public class MyAction extends AnAction { @Override public void actionPerformed(AnActionEvent e) { DataContext dataContext = e.getDataContext(); // 你可以从 DataContext 获取更多数据 } } ``` ### 2. 从一个组件中获取 DataContext 如果你有一个 UI 组件,比如一个按钮,你可以使用 `DataManager` 来获取该组件的 `DataContext`。例如: ```java JComponent component = ...; // 你的组件 DataContext dataContext = DataManager.getInstance().getDataContext(component); ``` ### 3. 从当前焦点的组件获取 DataContext 如果你需要从当前焦点的组件获取 `DataContext`,可以这样做: ```java DataContext dataContext = DataManager.getInstance().getDataContextFromFocus().getResult(); ``` ### 4. 使用 PlatformDataKeys 获取 `DataContext` 后,你可以使用 `PlatformDataKeys` 来提取特定的数据。例如,获取当前的 `Editor`: ```java Editor editor = CommonDataKeys.EDITOR.getData(dataContext); Project project = CommonDataKeys.PROJECT.getData(dataContext); VirtualFile file = CommonDataKeys.VIRTUAL_FILE.getData(dataContext); ``` ### 5. 存储 Event 的 DataContext 在某些情况下,你可能需要在动作执行之前存储 `DataContext`。你可以使用 `VariableActionEventDataHolder` 来存储数据。例如: ```kotlin class ShireSonarLintAction : AnAction() { // ... override fun actionPerformed(e: AnActionEvent) { val project = e.project ?: return VariableActionEventDataHolder.putData(VariableActionEventDataHolder(e.dataContext)) val config = shireActionConfigs(project).firstOrNull() ?: return ShireRunFileAction.executeShireFile(project, config, null) } } ``` 使用 `VariableActionEventDataHolder` 存储 `DataContext` 后,你可以在动作执行时获取数据。例如: ```kotlin fun getCommitWorkflowUi(): CommitWorkflowUi? { VariableActionEventDataHolder.getData()?.vcsDataContext?.let { val commitWorkflowUi = it.getData(VcsDataKeys.COMMIT_WORKFLOW_UI) return commitWorkflowUi as CommitWorkflowUi? } val dataContext = DataManager.getInstance().dataContextFromFocus.result val commitWorkflowUi = dataContext?.getData(VcsDataKeys.COMMIT_WORKFLOW_UI) return commitWorkflowUi } ``` ## 自定义 ContextMenu 位置 ### 普通方式 参考:`ShireActionStartupActivity` 中的实现,如:`attachTerminalAction` 方法 ```kotlin private fun attachTerminalAction() { val actionManager = ActionManager.getInstance() val toolsMenu = actionManager.getAction("TerminalToolwindowActionGroup") as? DefaultActionGroup ?: return val action = actionManager.getAction("ShireTerminalAction") if (!toolsMenu.containsAction(action)) { toolsMenu.add(action) } } ``` 1. 从 ActionManager 中获取目标 ActionGroup 2. 将自定义 Action 添加到目标 ActionGroup 中 ### 动态方式 对于不对外提供的插件,可以尝试监听是否有对应的事件,诸如 Sonarlint 使用的是 Panel,因此可以监听 Panel 的事件,然后动态添加 Action。 ```kotlin private fun attachSonarLintAction(project: Project) { project.messageBus.connect().subscribe(ToolWindowManagerListener.TOPIC, SonarLintToolWindowListener(project)); } ``` 对应的 `SonarLintToolWindowListener` 实现如下: ```kotlin class SonarLintToolWindowListener(private val project: Project) : ToolWindowManagerListener { override fun toolWindowShown(toolWindow: ToolWindow) { if (toolWindow.id != "SonarLint") return val action = ActionManager.getInstance().getAction("ShireSonarLintAction") val contentManager = toolWindow.contentManager val content = contentManager.getContent(0) ?: return val simpleToolWindowPanel = content.component as? SimpleToolWindowPanel val actionToolbar = simpleToolWindowPanel?.toolbar?.components?.get(0) as? ActionToolbar ?: return val actionGroup = actionToolbar.actionGroup as? DefaultActionGroup if (actionGroup?.containsAction(action) == false) { actionGroup.add(action) } } } ``` ================================================ FILE: docs/development/language-spec.md ================================================ --- layout: default title: Language Specification parent: Development nav_order: 2 --- See in [ShireParser.bnf] for latest version. ## Hobbit Hole Design ### Normal type Example: ```shire --- key: "value" --- ``` All | ValueType | Description | |------------|------------------------------| | String | "value" | | IDENTIFIER | enum key in Shire soure code | | Number | 123 | | Boolean | true | | Array | [1, 2, 3] | | Object | {key: "value"} | ### Function Type Example: ```shire --- when: { $selection.length() >= 0 } --- ``` All | ValueType | Description | Example | |----------------------|-----------------------------------|-----------------------------------------------------------------------------| | Pattern Action | /regex/ { functionBlock } | /.*.java/ { $selection.length() >= 0 } | | Function | { functionBlock } | { $selection.length() >= 0 } | | Ast Query Expression | use `from`, `select`, `where`, | see in `Ast Query Expression` | | Case Block | case "variable" { functionBlock } | case "$0" { default { find("ERROR") \| sort \| xargs("notify_admin") } } | | Flags Block | flags { flagBlock } | flags { "ignore": { } } | #### Ast Query Expression ```shire --- variables: "allController": { from { PsiClass clazz // the class } where { clazz.extends("org.springframework.web.bind.annotation.RestController") and clazz.getAnAnnotation() == "org.springframework.web.bind.annotation.RequestMapping" } select { clazz.id, clazz.name, "code" } } --- ``` ================================================ FILE: docs/development/shire-sketch.md ================================================ --- layout: default title: Shire Sketch parent: Development nav_order: 3 --- Shire Sketch 是 Shire 提供的 IDE 画布功能,旨在通过其丰富的文本代码(源码、Patch、UML、架构图等)二次处理、渲染组件,进一步简化交互成本, 以提升开发者在 IDE 中的体验。 无论是单个文件的显示、渲染操作,还是多文件协作、修复,Shire Sketch 都能提供强大的支持。 - **实时流式代码高亮**:实时显示代码高亮的流式视图。 - **内置差异(Patch 语言)**:显示代码差异的内置视图。 - **实时流式差异(StreamDiff)**:实时显示代码差异的流式视图,基于 Continue 的 UI 修改。 - **Mermaid 流程图**:支持 Mermaid 流程图的渲染,与双向绑定的代码编辑器。(要求启用 Mermaid 插件) - **PlantUML 图表**:支持 PlantUML 图表的渲染,与双向绑定的代码编辑器。(要求安装 `PlantUML integration` 插件) ## Sketch 示例 ### Diff Sketch 示例:Prompt: ```shire 请使用 patch 的方式完成需求,并使用 markdown patch 代码格式返回。 ``` ![](https://shire.run/images/shire-sketch-diff.png) ### Mermaid Sketch 示例 Prompt: ```shire 请使用 mermaid Code 格式,语言 `mermaid`,请根据用户故事绘制 mermaid 时序图,返回设计的 mermaid 代码。 ``` ![](https://shire.run/images/shire-sketch-mermaid.png) ### PlantUml Sketch 示例 Prompt: ```shire 请使用 plantuml Code 格式,语言 `plantuml`,请根据用户故事绘制 PUML 时序图,返回设计的 PUML 代码。 ``` ![](https://shire.run/images/shire-sketch-plantuml.png) ## 创建新 Sketch ```kotlin interface LanguageSketchProvider { fun isSupported(lang: String): Boolean fun create(project: Project, content: String): ExtensionLangSketch companion object { private val EP_NAME: ExtensionPointName = ExtensionPointName("com.phodal.shireLangSketchProvider") fun provide(language: String): LanguageSketchProvider? { return EP_NAME.extensionList.firstOrNull { it.isSupported(language) } } } } ``` 示例: XML 声明: ```xml ``` 实现代码: ```kotlin class DiffLangSketchProvider : LanguageSketchProvider { override fun isSupported(lang: String): Boolean = lang == "diff" || lang == "patch" override fun create(project: Project, content: String): ExtensionLangSketch = DiffLangSketch(project, content) } ``` ================================================ FILE: docs/examples/auto-test.md ================================================ --- layout: default title: AI AutoTest parent: Shire Examples nav_order: 2 --- --- name: "AutoTest" description: "AutoTest" interaction: AppendCursor actionLocation: ContextMenu when: { $fileName.contains(".java") && $filePath.contains("src/main/java") } variables: "frameworkContext": /.*/build\.gradle\.kts/ { find("org.springframework.boot:spring-boot-starter-jdbc") | print("This project use Spring Framework")} --- Write unit test for following ${context.language} code. ${frameworkContext} #if($context.relatedClasses.length() > 0 ) Here is the relate code maybe you can use ${context.relatedClasses} #end #if($context.currentClass.length() > 0 ) This is the class where the source code resides: ${context.currentClass} #end Here is the source code to be tested: ```${context.language} ${context.imports} ${context.selection} ``` #if($context.isNewFile) Should include package and imports. Start method test code with Markdown code block here: #else Should include package and imports. Start ${context.targetTestFileName} test code with Markdown code block here: #end ================================================ FILE: docs/examples/batch-execute.md ================================================ --- layout: default title: Batch Execute parent: Shire Examples nav_order: 10 --- `batch` 函数可以用来批量执行一个脚本。这个函数接受两个参数,第一个参数是要执行的脚本,第二个参数是要执行的文件列表。 ```shire --- name: "Generate Swagger Doc" variables: "controllers": /.*.Controller.java/ { print } "gen-swagger": /any/ { batch("controller-with-swagger.shire", $controllers) } beforeStreaming: { stop } --- ``` ================================================ FILE: docs/examples/cli-copilot.md ================================================ --- layout: default title: CLI Copilot parent: Shire Examples nav_order: 5 --- ```shire --- name: "Terminal" description: "Generate Cli" interaction: AppendCursor actionLocation: TerminalMenu --- Return only the command to be executed as a raw string, no string delimiters wrapping it, no yapping, no markdown, no fenced code blocks, what you return will be passed to subprocess.check_output() directly. - Today is: $today, user system is: $os, - User current directory is: $cwd, user use is: $shellPath, according the tool to create the command. For example, if the user asks: undo last git commit You return only line command: git reset --soft HEAD~1 User asks: $input ``` 注释: - `TerminalMenu`:在 Terminal Window 中添加 Shire 入口 ================================================ FILE: docs/examples/code-comment.md ================================================ --- layout: default title: Code Comments parent: Shire Examples nav_order: 4 --- 示例:生成注释 --- name: "生成注释" interaction: InsertBeforeSelection actionLocation: ContextMenu onStreamingEnd: { insertNewline | formatCode } --- 为如下的代码编写注释,使用 javadoc 风格: ```$language $selection ``` 只返回注释 解释: - `InsertBeforeSelection`:在选中的代码之前插入注释 - `insertNewLine`:在插入注释后换行 - `formatCode`:格式化代码 ================================================ FILE: docs/examples/code-refactoring.md ================================================ --- layout: default title: Code Refactoring parent: Shire Examples nav_order: 3 --- ```shire --- name: "Refactoring" actionLocation: ContextMenu interaction: ReplaceSelection --- 请你这段代码建议适当的重构。提高代码的可读性、质量,使代码更加有组织和易懂。你的回答应包含重构描述和一个代码片段,展示重构后的结果。 使用一些众所周知的重构技巧,比如以下列表中的一个: - 重命名 - 修改签名、声明 - 提取或引入变量、函数、常量、参数、类型参数 - 提取类、接口、超类 - 内联类、函数、变量等 - 移动字段、函数、语句等 - 上移构造函数、字段、方法 - 下移字段、方法 请勿生成多个代码片段,尝试将所有更改都整合到一个代码片段中。 请勿生成包含虚构周围类、方法的代码。不要模拟缺失的依赖项。 提供的代码已经整合到正确且可编译的代码中,不要在其周围添加额外的类。 以下是静态代码分析的 Code Smell 结果: $codeSmell 请重构以下代码: $selection ``` ================================================ FILE: docs/examples/commit-message-gen.md ================================================ --- layout: default title: AI Commit Message parent: Shire Examples nav_order: 2 --- ```shire --- name: "Commit message" interaction: AppendCursor actionLocation: CommitMenu --- 为给定的变更(Diff)编写一个连贯但具有描述性的代码提交信息。 - 确保包含修改了什么以及为什么。 - 以不超过 50 个字符的祈使句形式开头。 - 然后留下一个空行,如有必要,继续详细说明。 - 说明应该少于 200 个字符。 遵循常规提交规范,例如: - fix(authentication): 修复密码正则表达式模式问题 - feat(storage): 添加对S3存储的支持 - test(java): 修复用户控制器的测试用例 - docs(architecture): 在主页添加架构图 Diff: $currentChanges ``` ================================================ FILE: docs/examples/examples.md ================================================ --- layout: default title: Shire Examples nav_order: 9 has_children: true permalink: /examples --- Sample Projects: - [Shire Java/Spring Demo](https://github.com/shire-lang/shire-spring-java-demo) ================================================ FILE: docs/examples/inline-chat.md ================================================ --- layout: default title: Custom Inline Chat parent: Shire Examples nav_order: 11 --- Inline Chat 可以在当用户选中内容时,在选中行的左侧显示一个 Icon,用户点击这个 Icon 后,可以在当前页面内进行对话。 ![Inline Chat](https://shire.run/images/shire-inline-chat.png) ```shire --- name: "shire multiple file edit" description: "Shire Multiple File Edit" interaction: StreamDiff actionLocation: InlineChat --- 根据用户的要求和现有的代码编写 Java 代码。要求: 1. 使用 diff patch 的方式。 2. 如果是新文件也使用 patch 的格式。 3. 每个文件的修改也请用 diff 的形式给出。 现有代码如下: $all 用户的需求如下: $chatPrompt ``` ================================================ FILE: docs/examples/multiple-file-edit.md ================================================ --- layout: default title: Multiple File Edit parent: Shire Examples nav_order: 12 --- ChatBox 是在 Shire ToolWindow 中的一个输入框,用户可以在这里输入内容,然后调用大语言模型。使用事项如下: - 默认使用 `RigthPanel` 作为展示位置,使用 `ChatBox` 作为 `actionLocation`。 - 当用户创建了 `actionLocation: ChatBox` 的 Shire 代码时,将会读取用户的输入作为提示词的一部分。 ```shire --- name: "shire multiple file edit" description: "Shire Multiple File Edit" onStreaming: { logging } interaction: RightPanel actionLocation: ChatBox --- 根据用户的要求和现有的代码编写 Java 代码。要求: 1. 使用 diff patch 的方式。 2. 如果是新文件也使用 patch 的格式。 3. 每个文件的修改也请用 diff 的形式给出。 用户的需求如下: $chatPrompt ``` ================================================ FILE: docs/examples/on-paste-modify.md ================================================ --- layout: default title: On Paste modify parent: Shire Examples nav_order: 9 --- `OnPaste` 可以在用户粘贴代码时,根据上下文,对用户粘贴的代码进行优化。 ```shire --- name: "PasteMaster" interaction: OnPaste --- 优化待复制代码。根据当前的代码上下文(光标前后),对用户复制的代码,生成新的代码。 光标前的代码: $beforeCursor 光标后的代码: $afterCursor 用户复制的代码: $text 只根据上下文,优化用户复制的代码 ``` ================================================ FILE: docs/examples/search.md ================================================ --- layout: default title: Semantic Search parent: Shire Examples nav_order: 8 --- ## Semantic Search - splitting, splitting the file - splitting by the file extension - support format: `*.docx`, `*.md`, `*.txt`, `*.pdf`, `*.xlsx`, `*.pptx` or other text-based file - embedding, embedding full path of the file - embedding to InMemory: [InMemoryEmbeddingSearchIndex] - searching, searching for a specific string in the file, which will - embedding the input string - execute relevant search - return result - caching, caching the search result (Should be in first of calling) - support for local, remote, or InMemory cache? ### Basic Example ```shire --- name: "AutoTest" description: "AutoTest" interaction: AppendCursor variables: "*.docx": /.*/build\.gradle\.kts/ { splitting | embedding | searching("hello") } --- Write unit test for following ${context.language} code. ``` ### Cache Example ```shire --- name: "Search" variables: "testTemplate": /.*.java/ { caching("disk") | splitting | embedding | searching("comment") } --- 根据如下的代码,回答用户的问题:博客创建的流程 $testTemplate ``` ================================================ FILE: docs/faq.md ================================================ --- layout: default title: FAQ nav_order: 999 has_children: true permalink: /faq --- ## `java.net.SocketException: No route to localhost:0 port is out of range` **原因分析**: 此错误通常是因为IDE(例如IntelliJ IDEA)设置了手动代理,而错误信息中的“localhost:0”是配置项中的主机名和端口号参数所导致的。 参考[Issue93](https://github.com/phodal/shire/issues/93)来解决该问题。 **解决步骤**: - 检查IDE的代理设置是否正确。 - 确认代理服务器的主机名和端口是否可用。 - 如果不需要代理,尝试关闭IDE的代理设置。 - 根据具体情况调整IDE配置或代理服务器配置。 ## `RunCode: No run service found for file` 可能原因: - 缺少对应语言的 IDE 插件:诸如 [HttpClient](https://plugins.jetbrains.com/plugin/13121-http-client)、JavaScript 插件等。 - 缺少对应语言的 FileRunService 实现。 ================================================ FILE: docs/index.md ================================================ --- layout: default title: Home description: Shire offers a straightforward AI Coding Agent Language that enables communication between an LLM and control IDE for automated programming. nav_order: 1 permalink: / ---

logo

Shire - AI Coding Agent Language

Build Version Downloads

## English Introduction **Shire** offers a seamless AI coding agent language, enabling large language models (LLMs) to engage in fluid dialogue with integrated development environments (IDEs) to achieve automated programming. > The concept of Shire has its roots in [AutoDev](https://github.com/unit-mesh/auto-dev), a subproject of > [UnitMesh](https://unitmesh.cc/). Within AutoDev, we envisioned an AI-driven integrated development environment > for developers, which included Shire’s predecessor, DevIns. DevIns was designed to empower users to create custom AI > agents tailored to their own IDEs, thus forging a personalized AI-powered development realm. ## Chinese Introduction PS:the Shire 一词来自于《魔戒》(LOTR)中的(夏尔)Shire,是霍比特人(Hobbit)的家园。 Shire 提供了一种简便的 AI 编码智能体语言,使大型语言模型(LLM)能够与集成开发环境(IDE)无缝交互,实现编程自动化。 > Shire 的概念起源于 [AutoDev](https://github.com/unit-mesh/auto-dev),这是 [UnitMesh](https://unitmesh.cc/) 的一个子项目。在 AutoDev 中,我们设计了一个面向开发者的 AI 驱动集成开发环境(IDE),其中包括 Shire 的前身 DevIns。DevIns 旨在让用户能够为他们自己的 IDE 创建定制的 AI 代理,从而构建个性化的 AI 驱动开发环境。 Shire example Project: [Java example](https://github.com/shire-lang/shire-spring-java-demo) ### SDLC - [Code change analysis](https://github.com/shire-lang/shire-demo/blob/master/.shire/requirement/crud/analysis-requirements.shire) use LLM to analysis requirements, then choose the best files to change. - [Requirement + AutoCRUD](https://github.com/shire-lang/shire-demo/blob/master/.shire/requirement/analysis-requirements.shire) analysis requirements, then auto generate CRUD code. - [Dify + OpenAPI/Swagger](https://github.com/shire-lang/shire-demo/blob/master/.shire/api/design/design-rest-api.shire) interactive with Dify agent to design REST API - [Add Spring doc to project](https://github.com/shire-lang/shire-demo/blob/master/.shire/api/setup-dep/setup-spring-doc-openapi.shire) add Spring doc to project. - [Generate RestAssured Test](https://github.com/shire-lang/shire-demo/blob/master/.shire/api/verify/rest-assure.shire) AI to generate RestAssured test code. - [Generate JavaDoc](https://github.com/shire-lang/shire-demo/blob/master/.shire/documentation/javadoc.shire) use LLM to generate JavaDoc. - [Complexity Analysis](https://github.com/shire-lang/shire-demo/blob/master/.shire/git/complexity.shire) calculate code complexity. - [PlantUML: fetch Github issue for analysis](https://github.com/shire-lang/shire-demo/blob/master/.shire/requirement/visual/mindmap.shire) fetch GitHub issue to generate mindmap. FrontEnd: - [Frontend + HTML mockup](https://github.com/shire-lang/shire-demo/blob/master/.shire/frontend/html-mock-up.shire) use LLM to generate HTML mockup and show in WebView. - [Mobile + Ionic](https://github.com/shire-lang/shire-demo/blob/master/.shire/frontend/mobile-mock-up.shire) use LLM to generate mobile mockup with Ionic, show in WebView. - [Mobile + React](https://github.com/shire-lang/shire-demo/blob/master/.shire/frontend/react-mock-up.shire) use LLM to generate mobile mockup with React, show in WebView. - [JavaScript Auto Unittest](https://github.com/shire-lang/shire-demo/blob/master/.shire/frontend/js-test.shire) use LLM to generate JavaScript test code. Test: - [E2E Test: Playwright](https://github.com/shire-lang/shire-demo/blob/master/.shire/api/e2e/playwright.shire) AI to use Playwright to test the API and auto execute test. - [API Test: Java](https://github.com/shire-lang/shire-demo/blob/master/.shire/test/java/api-test.shire) use LLM to generate Java API test code. - [Unit Test: Java](https://github.com/shire-lang/shire-demo/blob/master/.shire/test/java/autotest.shire) use LLM to generate Java unit test code. - [Unit Test: Python](https://github.com/shire-lang/shire-demo/blob/master/.shire/test/python/AutoTest.shire) use LLM to generate Python unit test code. - [Unit Test: Golang](https://github.com/shire-lang/shire-demo/blob/master/.shire/test/go/AutoTest.shire) use LLM to generate Golang unit test code. ### Workflow & IDE Integration - [Capture web pages and generate report](https://github.com/shire-lang/shire-demo/blob/master/.shire/research/research.shire) capture web pages and generate report. - [approvalExecute](https://github.com/shire-lang/shire-demo/blob/master/.shire/approve/approve.shire) waiting for approval to execute next shire code - [Custom InlineChat](https://github.com/shire-lang/shire-demo/blob/master/.shire/chatbox/inline-chat.shire) custom inline chat - [Custom ChatBox](https://github.com/shire-lang/shire-demo/blob/master/.shire/chatbox/wrapper-chat.shire) custom prompt to use right panel chat box - [Python as Foreign Function Interface](https://github.com/shire-lang/shire-demo/blob/master/.shire/ffi/python-shell-thread.shire) use Python to run shell command in thread. - [Quick Input](https://github.com/shire-lang/shire-demo/blob/master/.shire/miscs/quick-input.shire) show quick input dialog. - [Terminal Agent](https://github.com/shire-lang/shire-demo/blob/master/.shire/miscs/terminal.shire) use terminal agent to run shell command. ### EcoSystem - [Git: Auto push code](https://github.com/shire-lang/shire-demo/blob/master/.shire/git/auto-push.shire) auto commit and push code to server. - [Git: diff AI changed code](https://github.com/shire-lang/shire-demo/blob/master/.shire/git/diff-example.shire) diff AI changed code. - [Git: Commit message](https://github.com/shire-lang/shire-demo/blob/master/.shire/git/login-commit-message.shire) generate commit message. - [Git: Commit ID with Jira](https://github.com/shire-lang/shire-demo/blob/master/.shire/git/commit-message.shire) generate commit message with Jira ID. - [Database: GitHub issue + Design Database Schema](https://github.com/shire-lang/shire-demo/blob/master/.shire/database/design-db.shire) fetch GitHub issue as context to design database schema - [Database: Run SQL in Database](https://github.com/shire-lang/shire-demo/blob/master/.shire/database/command.shire) run SQL with `/database` command. - [OpenRewrite: generate refactoring code](https://github.com/shire-lang/shire-demo/blob/master/.shire/refactor/openRewrite.shire) use OpenRewrite to generate refactoring code. - [MockServer: WireMock](https://github.com/shire-lang/shire-demo/blob/master/.shire/api/mock/gen-mock.shire) AI to generate mock server with WireMock and auto start mock server. - [PlantUML: with remote Agent](https://github.com/shire-lang/shire-demo/blob/master/.shire/toolchain/puml/plantuml-remote.shire) use remote agent to generate PlantUML code. - [Mermaid: with remote Agent](https://github.com/shire-lang/shire-demo/blob/master/.shire/toolchain/mermaid.shire) use remote agent to generate Mermaid code. - [Sonarlint: fix issue](https://github.com/shire-lang/shire-demo/blob/master/.shire/toolchain/sonarfix.shire) use Sonarlint to fix issue. ## Shire Resources Shire Cheatsheet ![Shire Cheatsheet](docs/images/shire-sheet.svg) Shire Data Architecture: ![Shire Data Architecture](docs/images/shire-data-flow.svg) Shire Resources - Documentation: [Shire AI Coding Agent Language](https://shire.phodal.com/) - [Shire Book: AI for software-engineering](https://aise.phodal.com/) (Chinese only) - [Shire.Run - the shareable AI coding agent](https://shire.run/) ## Demo Video Youtube: [![Shire AI Coding Agent Language](https://img.youtube.com/vi/z1ijWOL1rFY/0.jpg)](https://www.youtube.com/watch?v=z1ijWOL1rFY) Bilibili [![Shire AI Coding Agent Language](https://img.youtube.com/vi/z1ijWOL1rFY/0.jpg)](https://www.bilibili.com/video/BV1Lf421q7S7/) ================================================ FILE: docs/lifecycle/after-streaming.md ================================================ --- layout: default title: afterStreaming parent: Lifecycle nav_order: 3 --- `afterStreaming` 会在执行完 `onStreamingEnd` 后执行,用于下一步的处理,因此也叫 `TaskRoutes`。 `TaskRoutes` 顾名思义,是一系列的任务处理路由,将根据条件执行不同的任务。 ### 单条件 如下的代码,用于在 `onStreamingEnd` 后输出结果: ```shire --- afterStreaming: { case condition { default { print($output) } } } --- hi ``` 其中的 `output` 值是 LLM 处理了 hi 后的结果,可能是:`Hello, I'm xxx ...`。 ### 多条件 ```shire --- afterStreaming: { condition { "error" { output.length < 1 } "json-result" { jsonpath("${'$'}.store.*") } } case condition { "error" { notify("Failed to Generate JSON") } "json-result" { execute("sample.shire") } /* go to execute sample.shire */ default { notify("Failed to Generate JSON") /* mean nothing */ } } } --- ``` 当 LLM 返回的结果是 JSON 时: ```json { "store": { "book": [ { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 } ] } } ``` 可以匹配到 `json-result` 条件,然后执行 `sample.shire`。 ================================================ FILE: docs/lifecycle/before-streaming.md ================================================ --- layout: default title: beforeStreaming parent: Lifecycle nav_order: 1 --- `beforeStreaming` 即在 Streaming 开始前对生成的内容进行处理。 ### 示例:启动 MockServer ```shire --- name: "Blog.sample" beforeStreaming: { mock("docs/mock_v0-stubs.json") } --- ``` ### 示例:执行 Gradle Task ```shire --- name: "Blog.sample" beforeStreaming: { execute(":bootRun") } onStreamingEnd: { parseCode | saveFile | openFile | runCode } --- hi ``` ================================================ FILE: docs/lifecycle/lifecycle.md ================================================ --- layout: default title: Lifecycle nav_order: 5 has_children: true permalink: /lifecycle --- # Lifecycle Shire 的生命周期是围绕 IDE 与 LLM 生成过程所设计的。Shire 的生命周期包括: - beforeStreaming: 在 Streaming 开始前对生成的内容进行处理。 - onStreaming:在 Streaming 过程中对生成的内容进行处理。(TBD) - onStreamingDone:在 Streaming 完成后通过一系列的后处理器对生成的内容进行处理。 - afterStreaming:在 Streaming 完成后,根据条件执行后续操作,诸如执行新的 Shire 指令、调用其它工具等等。 ================================================ FILE: docs/lifecycle/on-streaming-done.md ================================================ --- layout: default title: onStreamingDone parent: Lifecycle nav_order: 4 --- `onStreamingDone` 即在 Streaming 完成后通过一系列的后处理器对生成的内容进行处理。 ## 内置 PostHandler 内置的后处理器包括: | 后处理器 | 描述 | |------------------|------------------| | timeMetric | 记录操作耗时。 | | verifyCode | 检查代码错误或 PSI 问题。 | | runCode | 运行生成的文本代码。 | | parseCode | 将文本解析为代码块。 | | saveFile | 将文件保存到磁盘。 | | openFile | 在编辑器中打开文件。 | | insertCode | 在当前光标位置插入代码。 | | formatCode | 格式化代码。 | | parseComment | 解析注释为注释块。 | | insertNewline | 插入新行。 | | append | 将文本追加到文件中。 | | updateEditorText | 更新编辑器文本。 | | patch | 打补丁。 | | diff | 生成 diff view 对比。 | | openWebpage | 打开网页。 | | showWebView | 显示 WebView。 | 最新版本见源码:com.phodal.shirecore.middleware.PostProcessorType ## 示例 ### Hello, world 示例 ```shire --- onStreamingEnd: { parseCode | saveFile | openFile | verifyCode | runCode } --- 生成一个 python hello world,使用 markdown block 返回 ``` 该代码会调用 LLM 生成一个 python hello world,然后将生成的代码块解析,保存到文件,打开文件,检查代码错误或 PSI 问题,最后运行生成的代码。 对应的后处理器有: | 后处理器 | 描述 | |------------|---------------------------------| | parseCode | 从生成的结果中解析生成的代码块。 | | saveFile | 将保存生成的代码到文件。 | | openFile | 打开生成的文件。 | | verifyCode | 检查代码错误或 PSI 问题。 | | runCode | 运行生成的代码(取决于是否存在对应的 RunService)。 | ### 结合变量的示例 ```shire --- name: Summary description: "Generate Summary" interaction: AppendCursor data: ["a", "b"] when: $fileName.matches("/.*.java/") variables: "var2": /.*ple.shire/ { cat | find("fileName") | sort } onStreamingEnd: { append($var2) | saveFile("summary.md") } --- Summary webpage: $fileName ``` 这里的 `var2` 是一个正则表达式,用于匹配文件名中包含 `ple.shire` 的文件,然后将其追加到文件中。 `onStreamingEnd` 会在 Streaming 完成后执行,这里会将 `var2` 的内容追加到 output 中,最终保存到 `summary.md` 文件中。 ### 提交信息生成示例 ```shire --- name: "Commit message" interaction: AppendCursor actionLocation: CommitMenu onStreamingEnd: { parseCode | updateEditorText } --- 请为给定的变更(Diff)编写一个连贯但具有描述性的代码提交信息。 背景信息:我现在使用 Git 编写一本开源电子书《AI 辅助软件工程:AI IDE 插件与编程智能体示例》,我需要为每个提交编写一个简洁但具有描述性的提交信息。 要求: - 确保包含修改了什么以及为什么。 - 以不超过 50 个字符的祈使句形式开头。 - 然后留下一个空行,如有必要,继续详细说明。 - 如果变更是一个 .shire 文件,说明我添加了一个新的示例。 遵循常规提交规范,例如: - fix(authentication): 修复密码正则表达式模式问题 - feat(storage): 添加对S3存储的支持 - test(java): 修复用户控制器的测试用例 - docs(architecture): 在主页添加架构图 Diff: $currentChanges ``` ================================================ FILE: docs/lifecycle/on-streaming.md ================================================ --- layout: default title: onStreaming parent: Lifecycle nav_order: 2 --- {: .note } > **Note:** This page is reserved for future use. It will be used to describe the `onStreaming` lifecycle event. 示例: ```shire --- onStreaming: { logging } --- ``` 当前支持的函数: - logging, - timing, - profiling, ### logging > 记录 LLM prompt 日志。 - `.shire-output/logging.log` 是 LLM 日志文件 - `.shire-output/logging.jsonl` 是含历史 LLM 日志的 JSON 格式文件。 详细见:LoggingStreamingService ### timing > 记录生成 LLM prompt 时间。 ### profiling > 记录 LLM prompt 的性能分析,主要包含内存。 ================================================ FILE: docs/quick-start.md ================================================ --- layout: default title: Quick Start nav_order: 3 --- ## Installation - Using the IDE built-in plugin system: Settings/Preferences > Plugins > Marketplace > Search for "shire" > Install - Get from Marketplace: - Manually: Download the [latest release](https://github.com/phodal/shire/releases/latest) and install it manually using Settings/Preferences > Plugins > ⚙️ > Install plugin from disk... Examples: [https://github.com/shire-lang/shire-spring-java-demo](https://github.com/shire-lang/shire-spring-java-demo) ## Usage ### "Hello World" example 1. create a new file, like `hi.shire`, with content: ```shire hi ``` 2. Run file with `Shire` plugin, and you will see AI response result. ``` Prepare for running hi.shire... Shire Script: /Volumes/source/ai/shire/hi.shire Shire Script Compile output: Used model: gpt-4o hi -------------------- Hello! How's it going? Process finished with exit code 0 ``` ### Example 2 1. first create a new file, like `sample.shire`, with content: ```shire Explain code /file:src/main/java/com/example/Controller.java ``` 2. Run file with `Shire` plugin, and you will see AI response result. ### Use in IDE Example: ```shire --- name: "AutoTest" description: "AutoTest" actionLocation: ContextMenu interaction: AppendCursor when: { $fileName.contains(".java") && $filePath.contains("src/main/java") } --- @ext-context.autotest Write unit test for following ${context.language} code. ${context.frameworkContext} /file:src/main/kotlin/com/phodal/blog/controller/UserController.kt ``` ## Config LLM Current we support OpenAI, GLM, 零一万物, Moonshot AI, DeepSeek AI. You need to configure your API Token and Model in `Settings` -> `Tools` -> `Shire`. ### OpenAI - LLM API Host: Empty - ModelName: gpt-4o - Engine Token: your key ### GLM 示例 - LLM API Host: https://open.bigmodel.cn/api/paas/v4/chat/completions - ModelName: glm-4 - Engine Token: your key ### 零一万物 - LLM API Host: https://api.lingyiwangwu.com/v1/chat/completions - ModelName: yi-34b-chat - Engine Token: your key ### Moonshot AI - LLM API Host: https://api.moonshot.cn/v1/chat/completions - ModelName: moonshot-v1-8k - Engine Token: your key ### DeepSeek AI - LLM API Host: https://api.deepseek.com/v1/chat/completions - ModelName: deepseek-chat - Engine Token: your key ================================================ FILE: docs/scene/ai-for-doc.md ================================================ --- layout: default title: AI 增强技术文档写作 parent: Shire Scene nav_order: 2 --- ## Why: 经典文档工程的解决思路 过去在编写文档的一些痛点,诸如于: - 文档代码不同步。即文档的 API 变化可能落后于代码,导致 API 与文档出现不一致。 - 频繁的 API 变更。API 变更时,文档需要手动进行更新,不能自动化同步。 - 概念不统一。对于同一个概念,文档的不同地方描述不一致。 - 重复的文档块。文档需要重复引用某一部分的文档,不能像代码一样引用。 - 代码无法运行。按照文档的步骤下来编写的代码、复制的代码,是不能运行的。 几年前,为了提供技术框架文档的质量,我研究了市面上主流的文档生成工具、框架文档构建等,也总结了一些文档生成的最佳实践,诸如: - 《[文档代码化](https://www.phodal.com/blog/isomorphism-document/)》 - 《[文档同构:如何实现文档与代码的双向绑定?](https://www.phodal.com/blog/isomorphism-document/)》 - 《[文档工程体验设计:重塑开发者体验](https://www.phodal.com/blog/documentation-enginnering-experience-design/)》 - 《[API 库的文档体系支持:主流编程语言的文档设计](https://www.phodal.com/blog/api-ducumentation-design-dsl-base/)》 但是,这些工具都无法满足我的需求,所以在过去我也编写了一系列的文档生成工具,诸如:Forming ( https://github.com/inherd/forming ) ```Rust // doc-code: file("src/lib.rs").line()[2, 5] // 读取 "src/lib.rs" 文件的第 2 到第 5 行 // doc-section: file("src/lib.rs").section("section1") // 读取 "src/lib.rs" 文件中的 section1 相关的代码块 ``` 但是,这并不是一个完美的解决方案,因为你经常要因为代码的变化而去更新文档。 ## WHAT: AI 增强技术文档写作体验 > AI 增强的技术文档写作体验是一种创新的方法,将先进的人工智能技术与文档编写和管理深度融合。它通过自动化工具和智能分析,简化了文档创建、 > 更新和维护的流程,显著提高了文档的质量、准确性和一致性。 作为一个实验项目,我们开始使用 Shire 来生成和维护技术文档。以下是几个主要场景示例: - 代码注释生成:通过分析代码内容,自动生成相应的文档注释,确保文档与代码同步更新,并减少手动维护的需求。 - 自动化内容生成:基于已有的代码注释,自动生成完整的文档内容,包括 API 说明、使用示例等,显著降低了手动编写和更新文档的工作量。 - 代码示例生成:自动读取项目中的测试用例,并将其作为文档中的示例代码展示,帮助读者更好地理解代码的实际应用场景。 - 动态内容检索:根据特定关键词,智能检索文档内容,帮助用户快速定位所需信息,并自动生成相关文档段落。 通过智能自动化的介入,文档编写变得更加高效和轻松,开发者能够专注于核心开发任务,同时确保文档始终与最新的代码和功能保持同步。 ## HOW: Shire 智能体语言示例 在这里,我们主要会使用 Shire 语言的三个基本能力: - 借助 IDE 与项目和 LLM 进行交互 - 基于 pattern-action 的变量定义和生成 - 基于 RAG 函数的内容检索 相关的示例,可以直接阅读 Shire 中的代码:https://github.com/phodal/shire ### 基础能力:生成自定义风格注释 为了更好的让 LLM 理解代码的函数,我们需要先使用 Shire 编写一个生成注释的指令。如下代码所示: --- name: "生成注释" interaction: InsertBeforeSelection actionLocation: ContextMenu when: $fileName.contains(".kt") && $filePath.contains("src/main/kotlin") onStreamingEnd: { insertNewline | formatCode } --- 为如下的代码编写注释,使用 KDoc 风格: ```$language $selection ``` 只返回注释 在这里,我们定义了一个专用于生成 Kotlin 代码注释的指令,通过右键菜单触发。当用户在 Kotlin 文件中选择代码后,Shire 会自动为选中的代码生成相应的注释, 并插入到代码之前。 ### 读取与生成:借助 pipeline 函数,自动生成文档文件 随后,我们就可以根据目标文档路径,诸如 `docs/shire/shire-builtin-variable.md` 编写对应的生成逻辑。诸如于: ```shire --- name: "Context Variable" description: "Here is a description of the action." interaction: RunPanel variables: "contextVariable": /ContextVariable\.kt/ { cat } "psiContextVariable": /PsiContextVariable\.kt/ { cat } onStreamingEnd: { parseCode | saveFile("docs/shire/shire-builtin-variable.md") } --- 根据如下的信息,编写对应的 ContextVariable 相关信息的 markdown 文档。 你所需要包含的 ContextVariable 信息如下: $contextVariable ... ``` 在这里,我们定义了一个变量 `contextVariable`,它的值是读取所有的 `ContextVariable.kt` 文件的结果。在运行的时候,Shire 会将这个变量的值 编译到 prompt 中,并发送给 LLM,以生成对应的文档。当 LLM 生成的文档返回后,我们会解析出其中的代码块,并保存到指定的文件中。 除此,当代码库中包含有测试用例时,我们就可以配置示例作为代码示例: ```shire --- name: "Hobbit Hole" description: "Here is a description of the action." interaction: RunPanel variables: "currentCode": /HobbitHole\.kt/ { cat } "testCode": /ShireCompileTest\.kt/ { cat } onStreamingEnd: { saveFile("docs/shire/shire-hobbit-hole.md") } --- 根据如下的代码用例、文档,编写对应的 HobbitHole 相关信息的 markdown 文档。 ... ``` 当然了,也可以直接读取原来的文档,然后进行更新。 ### 示例:结合 RAG 技术,自动化分析文档 对于更复杂的场景,则可以直接结合 RAG 与 Shire 的 workflow 来实现。如下所示: ```shire --- name: "Semantic Search" variables: "code": /.*.kt/ { splitting | embedding } "input": "博客创建流程" "lang": "java" afterStreaming: { case condition { default { searching($output) | execute("SummaryQuestion.shire", $output, $input, $lang) } } } --- You are a coding assistant who helps the user answer questions about code in their workspace by providing a list of relevant keywords they can search for to answer the question. ... ``` 上述代码中,我们定义了一个变量 `code`,它的值是对所有的 `*.kt` 文件进行分割,并进行向量化。而这里的的 `input` 则是用户输入的问题, 用于搜索相关的文档内容。 在执行时,会将用户的问题发送给 LLM,由其生成关键词,然后在本地进行检索,最后,将结果发送给下一个流程,即 `SummaryQuestion.shire`。 在 `SummaryQuestion.shire` 中,会将检索结果进行总结,然后生成对应的文档。 ## 总结 在这篇文档中,我们分享了使用 Shire 智能体语言来生成和维护技术文档的经验和思考。Shire 是我们在开发中不断探索和改进的一种智能语言工具, 它不仅简化了文档编写的流程,还有效解决了传统文档编写中的诸多痛点。 ================================================ FILE: docs/scene/scene.md ================================================ --- layout: default title: Shire Scene nav_order: 9 has_children: true permalink: /scene --- ================================================ FILE: docs/scene/secondary-research.md ================================================ --- layout: default title: Secondary Research parent: Shire Scene nav_order: 1 --- > Desk Research(又称为二级研究)是一种利用已有的、已发布的信息进行分析和整理的研究方法。与初级研究不同,初级研究主要关注新数据的收集与生成, > 而二级研究则专注于对已有数据和研究成果的总结、整合和综合分析。典型的二级研究来源包括教科书、百科全书、新闻文章、评论文章和元分析等。 这些文献通常不会包含详细的“方法”部分,因为它们依赖的是已有的研究数据,而非原始数据的生成。 ## WebResource ```shire --- variables: "websites": /*\.md/ { capture("docs/crawlSample.md", "link") | crawl() | thread("summary.shire") } "confluence": { thread("confluence.bash", param1, param2) } "pythonNode.js": { thread("python.py", param1, param2) } --- [website]: it will extract all the .md files and crawl them, then thread them with summary.shire, then return the result. ``` - `capture` 函数, 参数 1: Language, 参数 2: AST Node Type - `crawl` 函数, 参数 1: URLs: List - `thread` 函数, 参数 1: `Script File`, 参数...: Parameters:`Array` - 如果 crawl 返回的是一个数组,那么 thread 会对数组中的每一个元素执行一次 ## 真实世界示例:AI 辅助运维桌面研究 详细见如下的代码示例: ### Main.shire ```shire --- variables: "crawl": /crawlSample\.md/ { capture("docs/crawlSample.md", "link") | crawl() | thread(".shire/research/summary.shire") } "article": /crawlSample\.md/ { cat } onStreamingEnd: { saveFile("docs/output.md") } --- 根据如下的草稿和对应的资料,编写一篇对应主题的文章。 文章草稿如下: $article 相关的资料如下: $crawl ``` ### summary.shire ```shire 使用中文总结如下的开源项目。 要求: 1. 给出项目的基本介绍、首页和文档地址 2. 列举 5 个关键特性 $output ```` ### crawlSample.md ```md # AI 辅助软件工程:CLI 命令生成 ## 为什么需要 AI 来辅助 CLI? ## 什么是 CLI 命令生成 ## 行业示例 1. [nvtop](https://github.com/Syllo/nvtop) - NVIDIA GPUs htop like monitoring tool 2. [nvitop](https://github.com/XuehaiPan/nvitop) - An interactive NVIDIA-GPU process viewer and beyond. 3. [aichat](https://github.com/sigoden/aichat) - all-in-one AI powered CLI chat and copilot. 4. [aider](https://github.com/paul-gauthier/aider) - AI pair programming in your terminal 5. [elia](https://github.com/darrenburns/elia) - A TUI ChatGPT client built with Textual 6. [gpterminator](https://github.com/AineeJames/ChatGPTerminator) - A TUI for OpenAI's ChatGPT 7. [gtt](https://github.com/eeeXun/gtt) - A TUI for Google Translate, ChatGPT, DeepL and other AI services. 8. [ollama](https://github.com/ollama/ollama) - get up and running with large language models locally. 9. [oterm](https://github.com/ggozad/oterm) - A text-based terminal client for ollama. 10. [tgpt](https://github.com/aandrew-me/tgpt) - AI Chatbots in the terminal without needing API keys. 11. [yai](https://github.com/ekkinox/yai) - Your AI powered terminal assistant ``` ## API Resource by Bash - Confluence API - Jira API #### Confluence API https://developer.atlassian.com/cloud/confluence/using-the-rest-api/ ```bash curl --request '/rest/api/content/search?limit=1&cql=id!=0 order by lastmodified desc' \ --header 'Accept: application/json' \ --header 'Authorization: Basic ' ``` #### Jira API https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/ ```bash curl --request '?' \ --header 'Accept: application/json' \ Authorization: Basic ' ``` ## Design Pattern ### AutoFeature - autoAnalysis - autoImage - autoDraft - autoSlide - block embedded - Notion like Block for Reference - Daily randomTip - toImage - Icon ### Workflow Design topic - capture - summary - insight - express ================================================ FILE: docs/shire/shire-builtin-variable.md ================================================ --- layout: default title: Shire Builtin Variable parent: Shire Language nav_order: 4 --- 在 Shire 中,我们提供了一些内置变量,以便用户可以更方便地使用。 当前支持的内置变量有: - [ContextVariable](#contextvariable) 用于提供当前文件的上下文信息。 - [PsiContextVariable](#psicontextvariable) 用于提供当前 AST/PSI 语法树的上下文信息。 - [Toolchain Variable](#toolchain-variable) 用于提供项目的工具链信息。 ## ContextVariable | 变量名 | 描述 | |------------------|--------------------------------------------------------| | selection | User selection code/element's in text | | selectionWithNum | User selection code/element's in text with line number | | beforeCursor | All the text before the cursor | | afterCursor | All the text after the cursor | | fileName | The name of the file | | filePath | The path of the file | | methodName | The name of the method | | language | The language of the current file | | commentSymbol | The comment symbol of the language | | all | All the text | ## PsiContextVariable | 变量名 | 描述 | |---------------------|------------------------------------------------------------------------------------| | currentClassName | The name of the current class | | currentClassCode | The code of the current class | | currentMethodName | The name of the current method | | currentMethodCode | The code of the current method | | relatedClasses | The related classes based on the AST analysis | | similarTestCase | The similar test cases based on the TfIDF analysis | | imports | The import statements required for the code structure | | isNeedCreateFile | Flag indicating whether the code structure is being generated in a new file | | targetTestFileName | The name of the target test file where the code structure will be generated | | underTestMethodCode | The code of the method under test | | frameworkContext | The framework information in dependencies of current project | | codeSmell | Include psi error and warning | | methodCaller | The method that initiates the current call | | calledMethod | The method that is being called by the current method | | similarCode | Recently 20 files similar code based on the tf-idf search | | structure | The structure of the current class, for programming language will be in UML format | | changeCount | The number of changes in the current file | | lineCount | The number of lines in the current element | | complexityCount | The complexity of the current element | {: .note } Some languages may not support all the variables, depending on the language support. ================================================ FILE: docs/shire/shire-custom-variable.md ================================================ --- layout: default title: Shire Custom Variable parent: Shire Language nav_order: 5 --- Shire 自定义变量的核心是 Variable -> Pattern-Action 模型。在 Shire 中,Pattern-Action 模型是一种用于处理数据的模式匹配和动作执行模型。 如下图所示: ![](../images/shire-pattern-action.svg) - variable 是用户自定义的变量。 - pattern 是用于筛选输入数据的规则或标准,可以是一组文件名模式、正则表达式,用于识别哪些数据需要进一步处理。 - action 是当数据符合 pattern 时需要执行的任务,由一系列命令组成,描述了如何处理匹配的数据。 - function name 是 Shire 内置的函数,用于处理数据。 - arguments 是函数的参数,用于传递数据。 - pipe(`|`) 是用于连接多个操作的管道操作符,每个操作都是一个函数,其输出作为下一个函数的输入。 Custom Variable 的三种实现方式: ```shire --- variables: "var1": "demo" // Value "var2": /.*.java/ { find("error.log") | sort | xargs("rm")} // Pattern-Action "var3": /.*.log/ { case "$0" { "error" { find("ERROR") | sort | xargs("notify_admin") } "warn" { find("WARN") | sort | xargs("notify_admin") } "info" { find("INFO") | sort | xargs("notify_user") } default { find("ERROR") | sort | xargs("notify_admin") } } } --- ``` 多变量示例: ```shire --- name: "类图分析" variables: "controllers": /.*.java/ { cat } "tokens": /any/ { tokenizer($controllers, "word") } "chinese": /any/ { tokenizer("孩子上了幼儿园 安全防拐教育要做好", "jieba") } --- $controllers ``` 在这个示例中,我们在 tokens 变量中使用了 controllers 变量的值,这样就可以在变量之间传递数据。 ## Shire 常规自定义变量 常规自定义变量,可以用于: - 对应不同类型文件,自定义 prompt。 ## Variable Pattern Action 在Shire中,我们借鉴了 Unix/Linux 的设计理念和 Shell 编程模式,特别是 Pattern-Action 模型。该模型通过定义模式和动作来处理数据: 1. **模式(Pattern)**:这代表用于筛选输入数据的规则或标准。在Unix/Linux中,这可以是一组文件名模式、正则表达式或其他条件,用于识别哪些数据需要进一步处理。 2. **动作(Action)**:这是当数据符合模式时需要执行的任务。它由一系列命令组成,描述了如何处理匹配的数据。 {: .note } 注意:如果 pattern 为 `any` 或者 `null`,表示不进行筛选,直接执行动作。 例如,在Shire中,我们可以这样定义一个 Pattern-Action: ```text /.*.java/ { find("error.log") | print } ``` 这里,`/*.java/` 是模式部分,用于匹配所有以 `.java` 结尾的文件,而 `{ find("error.log") | print }` 是动作部分, 表示对匹配的文件执行一系列操作:首先搜索包含 "error.log" 的行,然后对这些行进行排序,最后将结果输出到标准输出。 在 Shire 中,我们利用了 Intellij 的强大功能,如正则表达式匹配、代码高亮和语法检查,以帮助用户更高效地编写代码。例如, 使用正则表达式 `.*.java` 可以轻松地匹配所有 Java 源文件。 明白了!让我为您完整优化一下文档,包括对示例的详细解释: ### 示例 1:Pattern-Action Pipeline ```shire --- variables: "var2": /.*.java/ { cat | find("error.log") | sort | cat } "extContext": /build\.gradle\.kts/ { cat | find("org.springframework.boot:spring-boot-starter-jdbc") | print("This project use Spring Framework") } --- ``` 在这个示例中: - **`var2` 变量**:匹配所有以 `.java` 结尾的文件。动作部分使用了管道操作符 `|`,依次执行了 `find("error.log")`、`sort` ,然后再次使用 `cat` 输出结果。 - **`extContext` 变量**:匹配所有名为 `build.gradle.kts` 的文件。动作部分执行了 `find("org.springframework.boot:spring-boot-starter-jdbc")`,并输出一条指示该项目使用 Spring Framework 的信息。 ### 示例 2:Pattern-Action 多 CASE ```shire --- variables: "testTemplate": /\(.*\).java/ { case "$1" { "Controller" { cat(".shire/templates/ControllerTest.java") } "Service" { cat(".shire/templates/ServiceTest.java") } default { cat(".shire/templates/DefaultTest.java") } } } --- ``` 在这个示例中: - **`testTemplate` 变量**:匹配所有以 `(.*)` 开头、`.java` 结尾的文件。根据不同的匹配结果执行不同的动作。 - 如果匹配到 `Controller`,则输出 `ControllerTest.java` 的内容。 - 如果匹配到 `Service`,则输出 `ServiceTest.java` 的内容。 - 如果没有匹配到上述任何值(`default`),则输出 `DefaultTest.java` 的内容。 ### 示例 3:变量二次处理 用户自定义变量可以对 Shire 自带变量进行二次处理,例如: ```shire --- name: "添加测试" actionLocation: ContextMenu variables: "sourceCode": /any/ { print($filePath) | sed("src\/test\/", "src/main/") | sed("Test.java", ".java") | cat } onStreamingEnd: { parseCode | patch($filePath, $output) } --- ``` ## Pattern Function | 函数类别 | 功能描述 | 参数 | 示例 | |-----------|------------------|-----------------------------------------------------------------------------|---------------------------------------------| | find | 基于文本搜索 | `text`: 要搜索的文本 | `find("error")` | | grep | 使用模式进行搜索 | `patterns`: 要搜索的模式 | `grep("[a-zA-Z]+Controller")` | | sed | 查找和替换操作 | `pattern`: 要查找的模式
`replacements`: 替换的字符串
`isRegex`: 是否为正则表达式 | `sed("s/old/new/g")` | | sort | 排序操作 | `arguments`: 排序所需的参数 | `sort` | | uniq | 去除重复行 | `texts`: 要处理的文本 | `uniq("line1", "line2", "line1")` | | head | 获取文件的前几行 | `number`: 要获取的行数 | `head(10)` | | tail | 获取文件的末尾几行 | `number`: 要获取的行数 | `tail(5)` | | xargs | 处理变量 | `variables`: 要处理的变量 | `xargs("arg1", "arg2")` | | print | 打印文本 | `texts`: 要打印的文本 | `print("Hello", "World")` | | cat | 连接文件 | `paths`: 要连接的文件路径 | `cat("file1.txt", "file2.txt")` | | notify | 使用 IDE 通知 | `message`: 要显示的通知消息 | `notify("Process completed successfully.")` | | splitting | 分割文本或文件 | `paths`: 要分割的文本或文件路径 | `splitting("file.txt", "file2.txt")` | | embedding | 嵌入文本 | `entries`: 要嵌入的文本条目 | `embedding("entry1", "entry2")` | | searching | 搜索文本 | `text`: 要搜索的文本, threshold: 置信度阈值(string, 默认 0.5) | `searching("pattern")` | | reranking | 重新排序 | `type`: 重排类型,默认 lostInTheMiddle | `reranking("pattern")` | | caching | 缓存语义 | `text`: 要缓存的文本 | `caching("data")` | | redact | 屏蔽敏感数据 | | `redact()` | | jsonpath | 使用 JsonPath 选择数据 | `jsonPath`: JsonPath 表达式,其中 jsonString 为可选 | `jsonpath(jsonString, "$.store.*")` | | batch | 批处理操作 | `fileName`: Shire 文件名,: paths`: 要处理的文件路径 | `batch("file1.shire", "file2.txt")` | | tokenizer | 分词操作 | `text`: 待分词的文本, type: 分词类型(`word`, `naming`, `stopwords`,`jieba`,默认 `word`) | `tokenizer("text") ` | | lineNo | 行号操作 | `text`: 待处理的文本 | `lineNo("text")` | 编排函数 | 函数类别 | 功能描述 | 参数 | 示例 | |----------------|----------------------------|--------------------|----------------------------------| | execute | 异步执行 Shire 脚本、GradleTask 等 | `string`: 要执行的脚本内容 | `execute("next-script.shire")` | | thread | 线程执行(异步) | `path`: 要执行的脚本路径 | `thread("script.shire")` | | approveExecute | 等待确认再执行 | `path`: 要执行的脚本路径 | `approveExecute("script.shire")` | Execute - Shire 脚本执行: `execute("next-script.shire")` - GradleTask 执行: `execute(":bootRun")` - 文件执行: `execute("next-script.sh")` ================================================ FILE: docs/shire/shire-env.md ================================================ --- layout: default title: Shire Environment parent: Shire Language nav_order: 10 --- Shire Environment 用于定义 Shire 的环境变量,用于存储一些敏感信息。 使用方式 `.shireEnv.json` 文件来存储环境变量,Shire 将会自动加载这种文件。 当前 Shire Env 支持两种变量: - `development`:配置 Token、API Key 等信息。 - `models`:配置模型信息(在 `0.7.4` 版本后支持)。 ## `.shireEnv.json` 文件 `.shireEnv.json` 用于存储环境变量,Shire 将会自动加载这种文件,当前只支持 `development` 环境。 ```json { "development": { "apiKey": "xxx" }, "models": [ { "title": "quickModel", "apiKey": "sk-xxx", "model": "gpt-4o-mini", "temperature": 0.3 }, { "title": "gpt4o", "apiKey": "sk-xxx", "model": "gpt-4o" }, { "title": "glm-4-plus", "apiKey": "xxx", "model": "glm-4-plus", "apiBase": "https://open.bigmodel.cn/api/paas/v4/chat/completions" } ] } ``` ### 使用你的 apiKey 在 Shell 中放在 `${}` 中即可: ```shell curl -X POST 'https://api.dify.ai/v1/completion-messages' \ --header "Authorization: Bearer ${apiKey}" \ --header 'Content-Type: application/json' \ --data-raw '{ "inputs": {"feature": "Hello, world!", "story_list": ${storyList}}, "response_mode": "streaming", "user": "phodal" }' ``` ### Model 配置详细示例: ```kotlin class LlmConfig( val title: String, val provider: String = "openai", val apiBase: String = "https://api.openai.com/v1/chat/completions", val apiKey: String, val model: String, val temperature: Double = 0.0, val maxTokens: Int? = 1024 ) ``` ================================================ FILE: docs/shire/shire-foreign-function.md ================================================ --- layout: default title: Shire Foreign Function parent: Shire Language nav_order: 8 --- # Foreign Function {: .note } 注意:在 `0.9` 版本后支持 示例: ```shire --- functions: normal: "defaultOutput.py"(string) output: "multipleOutput.py"(string) -> content, size // TODO, No Implemented special: "accessFunctionIfSupport.py"::resize(string, number, number) -> image // TODO, No Implemented --- ``` 语言支持: - Node.js (`.js` 文件) - Shell (`.sh` 文件) - Python (`.py` 文件) - Kotlin Script (`.kts` 文件) ## Quick Start The Shire code: ```shire --- functions: normal: ".shire/ffi/hello.js"(string) variables: "text": /.*ple.shire/ { normal("world") } --- hello, $text ``` ### JavaScript example ```javascript const args = process.argv.slice(2); console.log(args[0]); ``` ### Kotlin Script example In Kotlin Script mode, you can just use `args` without `main` function. ```kotlin if (args.isNotEmpty()) { println("${args[0]}!") } else { println("No args...") } ``` ================================================ FILE: docs/shire/shire-hobbit-hole.md ================================================ --- layout: default title: Shire Config (Hobbit Hole) parent: Shire Language nav_order: 3 --- Hobbit Hole 用于定义数据处理流程与 IDE 交互逻辑。 ## Hobbit Hole 概述 ```shire --- name: "Summary" description: "Generate Summary" interaction: AppendCursor actionLocation: ContextMenu --- ``` ### 属性说明 - `name`:Shire 命令的显示名称,将显示在 IDE 的 UI 中,基于 [HobbitHole.interaction](#interaction)。 - `description`:操作的提示信息,将显示在 UI 的 Hover tips 中。 - `interaction`:操作的输出可以是编辑器中的流文本,当使用 [InteractionType.AppendCursorStream](#interaction) 时。 - `actionLocation`:操作的位置,应该是 [ShireActionLocation](#actionlocation) 中的一个,默认为 [ShireActionLocation.RUN_PANEL]。 - `selectionStrategy`:选择要应用操作的元素的策略。 - `variables`:用于构建变量的带有 PatternAction 的变量列表。 - `when`:用于 [com.intellij.codeInsight.intention.IntentionAction.isAvailable]、[com.intellij.openapi.project.DumbAwareAction.DumbAwareAction.update] 检查是否显示菜单的条件。 - `ruleBasedFilter`:应用于操作的规则文件列表。 - `onStreamingEnd`:在流处理结束后执行的后中间件操作列表,如日志记录、指标收集、代码验证、运行代码、解析代码等。 - `afterStreaming`:流结束后执行任务的决策,路由到不同的任务。 - `shortcut`:操作的 IDE 快捷键,使用 IntelliJ IDEA 的快捷键格式。 - `enabled`:是否启用操作。 - `model`: 使用的大语言模型,使用 `.shireEnv.json` 定义。 - `userData`:其余数据。 ### 示例代码 ```shire --- name: "Summary" description: "Generate Summary" interaction: AppendCursor actionLocation: ContextMenu when: $fileName.matches("/.*.java/") variables: "var1": "demo" "var2": /**.java/ { find("error.log") | sort | xargs("rm")} --- ``` ## Hobbit Hole 属性 ### Interaction Type ```kotlin enum class InteractionType(val description: String) { AppendCursor("Append content at the current cursor position"), AppendCursorStream("Append content at the current cursor position, stream output"), OutputFile("Output to a new file"), ReplaceSelection("Replace the currently selected content"), ReplaceCurrentFile("Replace the content of the current file"), InsertBeforeSelection("Insert content before the currently selected content"), RunPanel("Show Result in Run panel which is the bottom of the IDE"), OnPaste("Copy the content to the clipboard"), RightPanel("Show Result in Right panel which is the right of the IDE"), StreamDiff("Use streaming diff to show the result") ; } ``` {: .note } 由于性能原因,OnPaste 暂时只支持 Java 和 Kotlin 语言,并且需要行数多于 5 行。 ### Action Location ```kotlin enum class ShireActionLocation(val location: String, val description: String) { CONTEXT_MENU("ContextMenu", "Show in Context Menu by Right Click"), INTENTION_MENU("IntentionMenu", "Show in Intention Menu by Alt+Enter"), TERMINAL_MENU("TerminalMenu", "Show in Terminal panel menu bar"), COMMIT_MENU("CommitMenu", "Show in Commit panel menu bar"), RUN_PANEL("RunPanel", "Show in Run panel which is the bottom of the IDE"), INPUT_BOX("InputBox", "Show in Input Box"), DATABASE_MENU("DatabaseMenu", "Show in Database panel menu bar"), CONSOLE_MENU("ConsoleMenu", "Show in Console panel menu bar"), VCS_LOG_MENU("VcsLogMenu", "Show in VCS Log panel menu bar"), CHAT_BOX("ChatBox", "Show in Chat Box"), // 将默认使用 RigthPanel 作为展示位置 INLINE_CHAT("InlineChat", "Show in Inline Chat"), /// external plugins EXT_SONARQUBE_MENU("ExtSonarQubeMenu", "Show in SonarQube panel menu bar"), ; } ``` {: .note } 当 COMMIT_MENU 项多于一个时,将会用 PopupMenu 显示;当只有一个时,将直接显示在 Commit 菜单中。 #### ChatBox 示例 ChatBox 是在 Shire ToolWindow 中的一个输入框,用户可以在这里输入内容,然后调用大语言模型。使用事项如下: - 默认使用 `RigthPanel` 作为展示位置,使用 `ChatBox` 作为 `actionLocation`。 - 当用户创建了 `actionLocation: ChatBox` 的 Shire 代码时,将会读取用户的输入作为提示词的一部分。 如下是一个自定义 ChatBox 的 Shire 示例: ```shire --- name: "shire-chat-box" description: "Shire Chat Box" interaction: RightPanel actionLocation: ChatBox --- 根据用户的输入生成 Java 代码 $chatPrompt ``` 此时,当用户在 ChatBox 中输入 `create hello world` 时,会将 `hello world` 作为 `chatPrompt` 的值。生成最终的提示词: ```shire 根据用户的输入生成 Java 代码 create hello world ``` ### Inline Chat 示例 Inline Chat 是在当前文件器的行号附近的一个由 Shire Icon 触发的输入框,用户可以在这里输入内容,然后调用大语言模型。使用事项如下: - 用户需要自行创建一个 Shire 文件,然后将 `actionLocation` 设置为 `InlineChat`。 ================================================ FILE: docs/shire/shire-lang.md ================================================ --- layout: default title: Shire Language Introduction parent: Shire Language nav_order: 1 --- Shire 主要由两部分组成: - Hobbit Hole,用于定义数据处理流程与 IDE 交互 逻辑。 - Shire Template,用于编译和生成最终的提示词。 ## Hobbit Hole Here is the detail - Condition Visible: `when` condition to display the code block - Variables: - Context Variables: `context` to get the context of the current file - Pattern Action: use Pattern (Regex) to match the source data, and use Unix-like command to process the data. - PostMiddle code processor - Output Control Flow ## Shire Template We use Velocity Template to generate the final prompt, and you can access the context variables in the template. ```shire Explain follow code $beforeCursor ``` ================================================ FILE: docs/shire/shire-template.md ================================================ --- layout: default title: Shire Command & Template parent: Shire Language nav_order: 2 --- ## Shire Template We use velocity template to generate code. Here is a simple example: Refactor code: ```${context.language} ${context.selection} ``` ## Shire Commands see in: ShireCommand.kt - `/file`: read file content, format: `/file:`, example: `/file:src/main/java/com/example/Controller.java`. - `/write`: write file content, format: `file#L1-L12`, example: `src/main/java/com/example/Controller.java#L1-L12` - `/rev`: read git change by git revision. - `/run`: run code, especially for test file, which is the best way to run code. - `/patch`: apply patches to file. - `/commit`: commit changes to git - `/symbol`: get child by symbol, like get Class by package name, format: `java.lang.String#length`, example: `.#` - `/shell`: run shell command or shell script, like `ls`, `pwd`, etc. - `/browse`: browse web page, like `https://ide.unitmesh.cc` - `/refactor`: refactor code, like `rename`, `delete`, `move` etc. - `/goto`: go to file, like `src/main/java/com/example/Controller.java#L1C1` - `/database`: query database, like `select * from table where id = 1` ### File Command we keep "/" as `File.separator` for macOS, Windows and Unix. Read file content: ```shire Explain code /file:src/main/java/com/example/Controller.java ``` will call LLM to handle it. ### Write Command write content to file: /write:src/main/java/com/example/Controller.java#L1-L12 ```java public class Controller { public void method() { System.out.println("Hello, World!"); } } ``` ### Rev Command Read git change by git revision: ```shire Explain code /rev:HEAD~1 ``` will call LLM to handle it. ### Run Command Run file: ```shire /run:src/main/java/com/example/Controller.java ``` PS: current only support for TestFile, since UnitTest is the best way to run code. ### Symbol Command Get child elements by symbol, like get Class by package name. ```shire /symbol:com.phodal.shire.demo ``` The output will be: ```java com.phodal.shire.demo.MathHelper com.phodal.shire.demo.DemoApplication com.phodal.shire.demo.MathHelperTest com.phodal.shire.demo.DemoApplicationTests ``` Get method will return code: ```shire /symbol:com.phodal.shire.demo.MathHelper.calculateInsurance ``` The output will be: ```java public static double calculateInsurance(double income) { if (income <= 10000) { return income * 0.365; } else { return income * 0.365 + 1000; } } ``` ### Browse Command Browse web page: ```shire /browse:https://ide.unitmesh.cc ``` It will be text inside the body from web page. ### Refactor Command Refactor code: ```shire /refactor:rename /symbol:com.phodal.shire.demo.MathHelper.calculateInsurance to calculateInsuranceTax ``` It will be handled in local. ### Goto Command Go to file: ```shire /goto:src/main/java/com/example/Controller.java#L1C1 ``` Go to symbol: ```shire /goto:com.phodal.shire.demo.MathHelper.calculateInsurance#L1C1 ``` ### Database Command List all tables: /database:table ``` autodev-index.sqlite ``` List all columns: /database:column ``` chunks ``` Execute SQL: /database:query ```sql select * from table ``` ================================================ FILE: docs/shire/shire-toolchain-function.md ================================================ --- layout: default title: Shire Toolchain Function parent: Shire Language nav_order: 7 --- # Toolchain Function > Toolchain 函数默认遵循 Pattern-Action 模式,用于定义数据处理逻辑。在接受参数时,默认的第一个参数为上下文变量,即 `lastResult` ## Git 支持的函数: - commit,提交代码,参数 1:`message` - push,推送代码 ### 示例: ```shire --- name: "Auto Commit and Push" afterStreaming: { case condition { default { print("feat: add auto commit and push sample") | commit | push } } } --- hi ``` ## Database 支持的函数: - `table`,获取数据库表信息,参数 1:`databaseName`,默认获取第一个连接的数据库。 - `column`,获取数据库列信息,参数 1:`tableName`,默认获取第一个表的列信息。 - `query`,执行 SQL 查询,参数 1:`sql`,示例:`query("select * from user")` ### 示例 1 ```shire --- variables: "relatedTableInfo": /./ { column("user", "post", "tag") } --- 根据如下的信息,生成 SQL: $relatedTableInfo ``` ## WireMock 支持的函数: - `mock`,启动 WireMock 服务,参数 1:`filePath`。默认 8080 端口。 ### 示例 ```shire --- name: "sample" variables: "mock": /any/ { mock("samples/mock/blog_v0-stubs.json") } --- ``` 其中的 `samples/mock/blog_v0-stubs.json` 文件内容如下: ```json { "mappings": [ { "request": { "method": "POST", "url": "/blog", "bodyPatterns": [ { "matchesJsonPath": "$.title" }, { "matchesJsonPath": "$.content" }, { "matchesJsonPath": "$.author" } ] }, "response": { "status": 201, "headers": { "Content-Type": "application/json" }, "body": "{\"message\": \"Blog post created successfully\", \"id\": 1}" } } ] } ``` ================================================ FILE: docs/shire/shire-toolchain-variable.md ================================================ --- layout: default title: Shire Toolchain Variable parent: Shire Language nav_order: 6 --- # Toolchain Variable 工具链变量提供诸如语言、框架和其他工具等数据作为变量。这个数据可以在 Shire 变量和模板中使用。 支持的工具链: - Git - SonarQube - Maven, Gradle(TODO) ## Git {: .label .label-red } Builtin plugin: Git4Idea Git 工具链提供以下变量: - `currentChanges`,当前分支的当前更改。 - `currentBranch`,当前分支的名称。 - `historyCommitMessages`,当前提交的提交消息。 - `diff`,当前选中 commit 的 diff 信息。 ## SonarQube {: .label .label-red } Require: [Sonarlint plugin](https://plugins.jetbrains.com/plugin/7973-sonarlint) SonarQube 工具链提供以下变量: - `sonarIssues`,当前文件的 SonarQube 中的问题列表。 - `sonarResults`,当前文件的 SonarQube 中的结果,含有问题的详细信息。 ## ProjectDependencies (TBC) {: .label .label-red } Builtin plugin: Java Maven, Gradle 工具链提供以下变量: - `projectDependencies`,当前文件的 Maven, Gradle 依赖列表。 ## Database {: .label .label-red } Builtin plugin: Database 数据库工具链提供以下变量: - `databases`,当前连接的数据库列表。 - `tables`,当前数据库的表列表。 - `columns`,当前数据库的表的列列表。 ## Protobuf {: .label .label-red } Builtin plugin: [Protocol Buffers](https://plugins.jetbrains.com/plugin/14004-protocol-buffers) ================================================ FILE: docs/shire/shire.md ================================================ --- layout: default title: Shire Language nav_order: 4 has_children: true permalink: /language --- Shire 语法由三部分组成: - Hobbit Hole:用于定义数据结构。 - Pattern-Action:用于定义数据处理逻辑。 - Condition Visible:用于定义可见性条件。 - Output:用于定义输出。 - Variable:用于定义过程变量。 - Template:用于定义 LLM 模板。 - Shire Template:用于定义代码生成模板。 - Life Cycle:用于定义生命周期。 ================================================ FILE: docs/shire/when.md ================================================ --- layout: default title: Condition Visible parent: Shire Language nav_order: 9 --- Activate Menu 是一类 `Condition Visible` 用于定义一个条件,当条件为真时,将显示对应的 Action。 当前支持 Action 类型:ContextMenu(右键)和 Intention Action(意图感知,通过 Alt+Enter)。 ## 语法示例 ```shire --- when: { $filePath.contains("src/main/java") && $fileName.contains(".java") } --- ``` 当当前文件路径包含 `src/main/java` 且文件名包含 `.java` 时,显示对应的 Action。 ## 语法说明 - `when`:条件表达式,当条件为真时,显示对应的 Action。 - `$`:变量引用符号。 - `.`:属性访问符号。 - `&&`:逻辑与。 - `contains`:字符串包含。 ### 支持的变量 - PsiVariables - `filePath`:文件路径 - `fileName`:文件名 - `fileExtension`:文件扩展名 - `fileContent`:文件内容 ### 支持的操作符 - `==`:等于 - `!=`:不等于 - `>`:大于 - `<`:小于 - `>=`:大于等于 - `<=`:小于等于 ### 支持的函数 详细见:[com.phodal.shirelang.compiler.hobbit.ast.ExpressionBuiltInMethod] ```Kotlin enum class ExpressionBuiltInMethod( val methodName: String, val description: String, val postInsertString: String = "()", val moveCaret: Int = 2, ) { LENGTH("length", "The length of the string"), TRIM("trim", "The trimmed string"), CONTAINS("contains", "Check if the string contains a substring", "(\"\")", 2), STARTS_WITH("startsWith", "Check if the string starts with a substring", "(\"\")", 2), ENDS_WITH("endsWith", "Check if the string ends with a substring", "(\"\")", 2), LOWERCASE("lowercase", "The lowercase version of the string"), UPPERCASE("uppercase", "The uppercase version of the string"), IS_EMPTY("isEmpty", "Check if the string is empty"), IS_NOT_EMPTY("isNotEmpty", "Check if the string is not empty"), FIRST("first", "The first character of the string"), LAST("last", "The last character of the string"), MATCHES("matches", "Check if the string matches a regex pattern", "(\"//\")", 3); } ``` ================================================ FILE: docs/shireql/shire-database.md ================================================ --- layout: default title: ShireQL Database Query (TBC) parent: ShireQL nav_order: 4 --- ## ShireQL 查询制品信息 ### Variable 方式 支持的 variables 或者 function - table() - columns() ### ShireQL 示例 ```shire --- variables: "tables": { from { DBTable table } where { table.name == "public" } select { table.name, table.columns } } --- ``` ================================================ FILE: docs/shireql/shire-ql-basic.md ================================================ --- layout: default title: ShireQL Basic parent: ShireQL nav_order: 1 --- ## ShireQL 基本语法 Design ```shire --- variables "var1": query { // 变量声明部分 from {} // datasource,如:`PsiClass`, `GitCommit`, `ProjectDependency` // 条件部分 where {} // AST expand, and functions support for regex, and methods: similar search, embedding search, tf-idf, and other advanced search // 结果部分 select {} // Node, or Node's attribute, or Node's children } --- ``` ## 通用函数 ### 常用函数 - date 函数 ### NLP 函数 (待定) - similarSearch - embeddingSearch - tf-idf ================================================ FILE: docs/shireql/shire-ql-dependency.md ================================================ --- layout: default title: ShireQL Dependency Query (TBC) parent: ShireQL nav_order: 4 --- ## ShireQL 查询制品信息 ### Maven 示例 ```shire --- variables: "mavenDependencies": { from { ProjectDependency dependency } where { dependency.groupId == "org.springframework.boot" and dependency.artifactId == "spring-boot-starter-web" } select { dependency.groupId, dependency.artifactId, dependency.version } } --- ``` ================================================ FILE: docs/shireql/shire-ql-git.md ================================================ --- layout: default title: ShireQL Git Query parent: ShireQL nav_order: 3 --- ## ShireQL 查询版本管理 ### Git 示例 详细见 [#41](https://github.com/phodal/shire/issues/41) ```shire --- variables: "phodalCommits": { from { GitCommit commit } where { commit.authorName == "Phodal Huang" } select { commit.authorName, commit.authorEmail, commit.message } } --- ``` Model: ``` data class ShireGitCommit( val hash: String, val authorName: String, val authorEmail: String, val authorDate: Long, val committerName: String, val committerEmail: String, val committerDate: Long, val message: String, val fullMessage: String ) : GitEntity() ``` Model design for #41 - GitCommit - Usage: support for git commit query - Field: author, authorEmail, committer, committerEmail, hash, date, message, fullMessage - FileCommit - Usage: support for file in history - Field: commit, filename, status, path - Branch - Usage: support for branch query - Field: name, commitCount Ref design: https://github.com/AmrDeveloper/GQL ================================================ FILE: docs/shireql/shire-ql-psi.md ================================================ --- layout: default title: ShireQL PSI Query parent: ShireQL nav_order: 2 --- 示例: ```shire --- variables: "allController": { from { PsiClass clazz // here is a comment } where { clazz.extends("org.springframework.web.bind.annotation.RestController") and clazz.getAnAnnotation() == "org.springframework.web.bind.annotation.RequestMapping" } select { clazz.id, clazz.name, "code" } } --- ``` ## ShireQL 查询抽象语法树 ### Java 语言示例 ```kotlin enum class JvmPsiPqlMethod(val methodName: String, val description: String) { GET_NAME("getName", "Get class name"), NAME("name", "Get class name"), EXTENDS("extends", "Get class extends"), IMPLEMENTS("implements", "Get class implements"), METHOD_CODE_BY_NAME("methodCodeByName", "Get method code by name"), FIELD_CODE_BY_NAME("fieldCodeByName", "Get field code by name"), SUBCLASSES_OF("subclassesOf", "Get subclasses of class"), ANNOTATED_OF("annotatedOf", "Get annotated classes"), SUPERCLASS_OF("superclassOf", "Get superclass of class"), IMPLEMENTS_OF("implementsOf", "Get implemented interfaces of class"), } ``` ================================================ FILE: docs/shireql/shire-ql.md ================================================ --- layout: default title: ShireQL nav_order: 5 has_children: true permalink: /shireql --- ShireQL 是一个基于 IDE 的数据查询语言,它允许你查询当前文件的 AST(抽象语法树)、Git、依赖信息等。它在 Shire 中用于定义当前文件的上下文以及可以在当前文件上执行的操作。 ## 其它相关资源 ### GitQL GQL: [https://github.com/AmrDeveloper/GQL](https://github.com/AmrDeveloper/GQL) ```sql SELECT author_name, COUNT(author_name) AS commit_num FROM commits GROUP BY author_name, author_email ORDER BY commit_num DESC LIMIT 10 SELECT commit_count FROM branches WHERE commit_count BETWEEN 0..10 SELECT * FROM refs WHERE type = "branch" SELECT * FROM refs ORDER BY type SELECT * FROM commits SELECT author_name, author_email FROM commits SELECT author_name, author_email FROM commits ORDER BY author_name DESC, author_email ASC SELECT author_name, author_email FROM commits WHERE name LIKE "%gmail%" ORDER BY author_name SELECT * FROM commits WHERE LOWER(name) = "amrdeveloper" SELECT author_name FROM commits GROUP By author_name SELECT author_name FROM commits GROUP By author_name having author_name = "AmrDeveloper" SELECT * FROM branches SELECT * FROM branches WHERE is_head = true SELECT name, LEN(name) FROM branches SELECT * FROM tags SELECT * FROM tags OFFSET 1 LIMIT 1 ``` ### GitHub CodeQL QL Language Reference: https://codeql.github.com/docs/ql-language-reference/queries/ ```codeql from /* ... variable declarations ... */ where /* ... logical formula ... */ select /* ... expressions ... */ ``` For Example: ```codeql import java from Class c, Class superclass where superclass = c.getASupertype() select c, "This class extends the class $@.", superclass, superclass.getName() ``` Java ```codeql from Person p where parentOf(p) = parentOf("King Basil") and not p = "King Basil" and not p.isDeceased() select p ``` JavaScript ```codeql import javascript from Comment c where c.getText().regexpMatch("(?si).*\\bTODO\\b.*") select c ``` Better Java Example: ```codeql import java from Constructor c, Annotation ann, AnnotationType anntp where ann = c.getAnAnnotation() and anntp = ann.getType() and anntp.hasQualifiedName("java.lang", "SuppressWarnings") select ann, ann.getValue("value") ``` Java 2 ```codeql import java from LTExpr expr where expr.getLeftOperand().getType().hasName("int") and expr.getRightOperand().getType().hasName("long") and exists(LoopStmt l | l.getCondition().getAChildExpr*() = expr) and not expr.getAnOperand().isCompileTimeConstant() select expr ``` ### SourceGraph CodeSearch https://sourcegraph.com/docs/code-search/queries ```query repo:^github\.com/sourcegraph/sourcegraph$ type:diff select:commit.diff.removed TODO type:diff after:"1 week ago" \.subscribe\( lang:typescript repo:github\.com/sourcegraph/sourcegraph$ (test AND http AND NewRequest) lang:go ``` Date function ```bash before:"last thursday" before:"november 1 2019" after:"6 weeks ago" after:"november 1 2019" repo:vscode@*refs/heads/:^refs/heads/master type:diff task ``` ================================================ FILE: docs/workflow/custom-ai-agent.md ================================================ --- layout: default title: Custom AI Agent parent: Workflow nav_order: 3 --- ## Custom AI Agent 1. Create new file with end `.shireCustomAgent.json` in your project, for example: `demo.shireCustomAgent.json`. 2. Fill JSON format config in `demo.shireCustomAgent.json` file. ### Custom Agent Examples ```json [ { "name": "内部 API 集成", "description": "在一个组织或项目中,不同系统或组件之间的通信接口。", "url": "http://127.0.0.1:8765/api/agent/api-market", "responseAction": "Direct" }, { "name": "组件库查询", "description": "从组件库中检索特定的 UI 组件,以便在开发的应用程序中使用。", "url": "http://127.0.0.1:8765/api/agent/component-list", "responseAction": "TextChunk" }, { "name": "页面生成", "description": "使用 React 框架,基于组件和状态来生成页面。", "url": "http://127.0.0.1:8765/api/agent/ux", "auth": { "type": "Bearer", "token": "eyJhbGci" }, "responseAction": "WebView" }, { "name": "DevInInsert", "description": "Update,並指定20秒的timeout時間", "url": "http://127.0.0.1:8765/api/agent/shire-sample", "responseAction": "Shire", "defaultTimeout": 20 }, { "name": "内部 API 集成", "description": "sample", "url": "http://127.0.0.1:8765/api/agent/api-market", "auth": { "type": "Bearer", "token": "eyJhbGci" }, "connector": { "requestFormat": "{\"customFields\": {\"model\": \"yi-34b-chat\", \"stream\": true}}", "responseFormat": "$.choices[0].delta.content" }, "responseAction": "Direct" } ] ``` ================================================ FILE: docs/workflow/rag-flow.md ================================================ --- layout: default title: Shire Workflow 示例 parent: Workflow nav_order: 4 --- ## `execute` 代码解释示例 ### 步骤 1 ```shire --- name: "Search" variables: "testTemplate": /.*.java/ { splitting | embedding } "lang": "java" "input": "博客创建流程" afterStreaming: { case condition { default { searching($output, "0.1") | execute("search.shire") } } } --- ```system``` You are a coding assistant who helps the user answer questions about code in their workspace by providing a list of relevant keywords they can search for to answer the question. The user will provide you with potentially relevant information from the workspace. This information may be incomplete. DO NOT ask the user for additional information or clarification. DO NOT try to answer the user's question directly. **Additional Rules** Think step by step: 1. Read the user's question to understand what they are asking about their workspace. 2. If the question contains pronouns such as 'it' or 'that', try to understand what the pronoun refers to by looking at the rest of the question and the conversation history. 3. If the question contains an ambiguous word such as 'this', try to understand what it refers to by looking at the rest of the question, the user's active selection, and the conversation history. 4. Output a precise version of the question that resolves all pronouns and ambiguous words like 'this' to the specific nouns they stand for. Be sure to preserve the exact meaning of the question by only changing ambiguous pronouns and words like 'this'. 5. Then output a short markdown list of up to 8 relevant keywords that the user could try searching for to answer their question. These keywords could be used as file names, symbol names, abbreviations, or comments in the relevant code. Put the most relevant keywords to the question first. Do not include overly generic keywords. Do not repeat keywords. 6. For each keyword in the markdown list of related keywords, if applicable add a comma-separated list of variations after it. For example, for 'encode', possible variations include 'encoding', 'encoded', 'encoder', 'encoders'. Consider synonyms and plural forms. Do not repeat variations. 7. keywords should be in English **Examples** User: Where is the code for the function that calculates the average of a list of numbers? Response: Where is calculating the average of a list of numbers? - calculate average, average, average calculation // keywords should be in English - calculate, calculation, calculator // keywords should be in English - jisuan, pingjunshu, pingjun // keywords can be user's language ```user``` User: $input Response: ``` ### 步骤 2:返回 Shire 代码 ```shire Use the above code to answer the following question. You should not reference any files outside of what is shown, unless they are commonly known files, like a .gitignore or package.json. Reference the filenames whenever possible. If there isn't enough information to answer the question, suggest where the user might look to learn more. code: $output User's Question: $input ``` ================================================ FILE: docs/workflow/remote-ai-agent.md ================================================ --- layout: default title: Remote AI Agent parent: Workflow nav_order: 2 --- Remote AI Agent 是通过调用远程的 AI Agent 来执行任务。主要实现方式: - `thread` 来调用 `.curl.sh` 脚本,执行远程 AI Agent 的任务 示例: ```shire --- variables: "story": /any/ { thread(".shire/shell/dify-epic-story.curl.sh") | jsonpath("$.answer", true) } --- ``` ## 代码定位示例 入口 Shire 文件 ```shire --- name: "定位待变更代码" variables: "story": /any/ { thread(".shire/shell/dify-user-story-workflow.curl.sh") | jsonpath("$.answer", true) } "controllers": /.*.java/ { cat | grep("class\s+([a-zA-Z]*Controller)") } "services": /.*.java/ { cat | grep("class\s+([a-zA-Z]*Service)") } "firstController": /CinemaController\.java/ { print } "firstService": /CinemaController\.java/ { print } "domainLanguage": /domain-language\.csv/ { cat } onStreamingEnd: { parseCode | openFile } --- 你是一个网站资深的开发人员,能帮助我定位到代码文件。请根据如下的用户故事,以及对应的 controller, service 名称,选择最合适修改的代码文件 用户故事: $story Controller 列表: $controllers Service 列表: $services 这个网站的一些专有名词如下: $domainLanguage 要求: 如果没有合适的 controller,请给出最合适的 controller 和 service 路径。 Controller 示例路径在: $firstController service 示例路径在: $firstService 你只返回文件名,格式如:`src/main/xxx/DemoController.java` 请严格按格式返回,只返回存在的代码文件,只返回文件路径。 ``` `dify-user-story-workflow.curl.sh` 代码: ```bash curl -X POST 'https://api.dify.ai/v1/completion-messages' \ --header "Authorization: Bearer ${singleStoryKey}" \ --header 'Content-Type: application/json' \ --data-raw '{ "inputs": {"feature": "Hello, world!", "story_list": "作为购物中心电影观众,我想要提前预订电影场次相关的食物,以便于节省购买食物的时间,更好地安排观影时间。", "story": "添加零食和饮料至购物车影院观众在购票时添加零食和饮料提前准备好观影期间的零食和饮料"}, "response_mode": "streaming", "user": "phodal" }' ``` 其中的 `singleStoryKey` 可以通过在项目中创建 `xx.shireEnv.json` 来支持,示例: ```json { "development": { "singleStoryKey": "xxxx" } } ``` ================================================ FILE: docs/workflow/response-routing-function.md ================================================ --- layout: default title: Response Routing - afterStreaming parent: Workflow nav_order: 1 --- 在 LLM 处理完后,可以返回后续的路由处理函数,以便在不同的场景下,对返回的结果进行处理。 1. 返回 Shire 代码,可以作为 Shire 语言块来处理。 2. 返回 JSON 数据(待支持) 示例: ```shire --- afterStreaming: { case condition { default { searching($output) | execute("search.shire") } } } --- ``` ================================================ FILE: docs/workflow/workflow.md ================================================ --- layout: default title: Workflow nav_order: 7 has_children: true permalink: /workflow --- # Workflow 主要通过: - `thread` 函数来在多个线程中执行任务 - `execute` 函数,在执行完后,调用额外的函数 - `batch` 函数,批量执行任务 其它: - `mock` 函数,启动 WireMock 服务 ================================================ FILE: gradle/libs.versions.toml ================================================ [versions] # libraries # plugins kotlin = "2.1.20" changelog = "2.2.1" gradleIntelliJPlugin = "2.0.1" qodana = "2025.1.1" kover = "0.9.0" [libraries] json-pathkt = "com.nfeld.jsonpathkt:jsonpathkt:2.0.1" okhttp = "com.squareup.okhttp3:okhttp:4.4.1" okhttp-sse = "com.squareup.okhttp3:okhttp-sse:4.12.0" kaml = "com.charleskorn.kaml:kaml:0.98.0" # org.reflections:reflections:0.10.2 reflections = "org.reflections:reflections:0.10.2" # "org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1" serialization-json = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1" [plugins] changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" } gradleIntelliJPlugin = { id = "org.jetbrains.intellij.platform", version.ref = "gradleIntelliJPlugin" } kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } qodana = { id = "org.jetbrains.qodana", version.ref = "qodana" } serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: gradle-241.properties ================================================ # IntelliJ Platform Artifacts Repositories -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html ideaPlatformVersion=241 ideaVersion=IU-2024.1 # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html pluginSinceBuild = 232 pluginUntilBuild = 252.* # 3rd party plugins # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html platformPlugins = PythonCore:241.14494.240,PlantUML integration:7.10.1-IJ2023.2,com.intellij.mermaid:0.0.22+IJ.232 #platformPlugins = PlantUML integration:7.10.1-IJ2023.2,com.intellij.mermaid:0.0.22+IJ.232 pythonPlugins = PythonCore:241.14494.240 goPlugin=org.jetbrains.plugins.go:241.14494.240 plantUmlPlugin = PlantUML integration:7.10.1-IJ2023.2 wireMockPlugin=com.intellij.wiremock:241.14494.150 testAutomationPlugin=com.intellij.aqua:241.14494.240 mermaidPlugin=com.intellij.mermaid:0.0.22+IJ.232 jsonPlugin= # 3rd party plugins nodejsPlugin=NodeJS:241.14494.155 sonarPlugin = org.sonarlint.idea:10.8.1.79205 protoPlugin = idea.plugin.protoeditor:241.14494.150 ================================================ FILE: gradle-243.properties ================================================ # IntelliJ Platform Artifacts Repositories -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html ideaPlatformVersion=243 ideaVersion=IU-2024.3 # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html pluginSinceBuild = 243 pluginUntilBuild = 243.* # official plugins platformPlugins=PythonCore:243.21565.211,com.intellij.mermaid:0.0.24+IJ.243 pythonPlugins=PythonCore:243.21565.211 mermaidPlugin=com.intellij.mermaid:0.0.24+IJ.243 goPlugin=org.jetbrains.plugins.go:243.15521.24 nodejsPlugin=NodeJS:243.15521.24 wireMockPlugin=com.intellij.wiremock:243.15521.24 testAutomationPlugin=com.intellij.aqua:243.15521.24 jsonPlugin=com.intellij.modules.json:243.15521.24 # 3rd party plugins sonarPlugin=org.sonarlint.idea:10.8.1.79205 plantUmlPlugin=PlantUML integration:7.10.1-IJ2023.2 protoPlugin = idea.plugin.protoeditor:243.15521.24 ================================================ FILE: gradle.properties ================================================ # IntelliJ Platform Artifacts Repositories -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html propertiesPluginEnvironmentNameProperty=ideaPlatformVersion ideaPlatformVersion=241 pluginGroup = com.phodal.shire pluginName = Shire - AI Coding Agent Language pluginRepositoryUrl = https://github.com/phodal/shire # SemVer format -> https://semver.org pluginVersion = 1.3.6 # IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#configuration-intellij-extension platformType = IU platformVersion = 2024.1 # Gradle Releases -> https://github.com/gradle/gradle/releases gradleVersion = 8.10 # Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib kotlin.stdlib.default.dependency = false # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html org.gradle.configuration-cache = true # Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html org.gradle.caching = true javaVersion = 17 kotlin.daemon.jvmargs=-Xmx2g org.gradle.jvmargs =-Xmx2g ================================================ FILE: gradlew ================================================ #!/bin/sh # # Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################################## # # Gradle start up script for POSIX generated by Gradle. # # Important for running: # # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is # noncompliant, but you have some other compliant shell such as ksh or # bash, then to run this script, type that shell name before the whole # command line, like: # # ksh Gradle # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», # «${var#prefix}», «${var%suffix}», and «$( cmd )»; # * compound commands having a testable exit status, especially «case»; # * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # # (2) This script targets any POSIX shell, so it avoids extensions provided # by Bash, Ksh, etc; in particular arrays are avoided. # # The "traditional" practice of packing multiple parameters into a # space-separated string is a well documented source of bugs and security # problems, so this is (mostly) avoided, by progressively accumulating # options in "$@", and eventually passing that to Java. # # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; # see the in-line comments for details. # # There are tweaks for specific operating systems such as AIX, CygWin, # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. # ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link app_path=$0 # Need this for daisy-chained symlinks. while APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path [ -h "$app_path" ] do ls=$( ls -ld "$app_path" ) link=${ls#*' -> '} case $link in #( /*) app_path=$link ;; #( *) app_path=$APP_HOME$link ;; esac done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum warn () { echo "$*" } >&2 die () { echo echo "$*" echo exit 1 } >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "$( uname )" in #( CYGWIN* ) cygwin=true ;; #( Darwin* ) darwin=true ;; #( MSYS* | MINGW* ) msys=true ;; #( NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD=$JAVA_HOME/jre/sh/java else JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD=java if ! command -v java >/dev/null 2>&1 then die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi # Collect all arguments for the java command, stacking in reverse order: # * args from the command line # * the main class name # * -classpath # * -D...appname settings # * --module-path (only if needed) # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) # Now convert the arguments - kludge to limit ourselves to /bin/sh for arg do if case $arg in #( -*) false ;; # don't mess with options #( /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath [ -e "$t" ] ;; #( *) false ;; esac then arg=$( cygpath --path --ignore --mixed "$arg" ) fi # Roll the args list around exactly as many times as the number of # args, so each arg winds up back in the position where it started, but # possibly modified. # # NB: a `for` loop captures its iteration list before it begins, so # changing the positional parameters here affects neither the number of # iterations, nor the values presented in `arg`. shift # remove old arg set -- "$@" "$arg" # push replacement arg done fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ org.gradle.wrapper.GradleWrapperMain \ "$@" # Stop when "xargs" is not available. if ! command -v xargs >/dev/null 2>&1 then die "xargs is not available" fi # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. # # In Bash we could simply go: # # readarray ARGS < <( xargs -n1 <<<"$var" ) && # set -- "${ARGS[@]}" "$@" # # but POSIX shell has neither arrays nor command substitution, so instead we # post-process each arg (as a line of input to sed) to backslash-escape any # character that might be a shell metacharacter, then use eval to reverse # that process (while maintaining the separation between arguments), and wrap # the whole thing up as a single "set" statement. # # This will of course break if any of these variables contains a newline or # an unmatched quote. # eval "set -- $( printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | xargs -n1 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | tr '\n' ' ' )" '"$@"' exec "$JAVACMD" "$@" ================================================ FILE: gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute echo. 1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. 1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! set EXIT_CODE=%ERRORLEVEL% if %EXIT_CODE% equ 0 set EXIT_CODE=1 if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: languages/README.md ================================================ ================================================ FILE: languages/shire-go/src/main/kotlin/com/phodal/shirelang/go/codemodel/GoClassStructureProvider.kt ================================================ package com.phodal.shirelang.go.codemodel import com.goide.psi.GoMethodDeclaration import com.goide.psi.GoTypeDeclaration import com.goide.psi.GoTypeSpec import com.intellij.psi.PsiElement import com.phodal.shirecore.provider.codemodel.ClassStructureProvider import com.phodal.shirecore.provider.codemodel.model.ClassStructure import com.phodal.shirelang.go.util.GoPsiUtil class GoClassStructureProvider : ClassStructureProvider { override fun build(psiElement: PsiElement, gatherUsages: Boolean): ClassStructure? { if (psiElement !is GoTypeDeclaration && psiElement !is GoTypeSpec) { return null } val typeSpecs: List = when (psiElement) { is GoTypeSpec -> listOf(psiElement) is GoTypeDeclaration -> psiElement.typeSpecList else -> emptyList() } val methodPairs = typeSpecs.flatMap { type -> val methods = type.methods methods.map { method -> method to type.name } } val methods = methodPairs.map { it.first } .filterIsInstance() val name = GoPsiUtil.getDeclarationName(psiElement) return ClassStructure( psiElement, psiElement.text, name, name, methods, emptyList(), emptyList(), emptyList() ) } } ================================================ FILE: languages/shire-go/src/main/kotlin/com/phodal/shirelang/go/codemodel/GoFileStructureProvider.kt ================================================ package com.phodal.shirelang.go.codemodel import com.goide.psi.GoFile import com.goide.psi.GoFunctionOrMethodDeclaration import com.goide.psi.GoType import com.intellij.psi.PsiFile import com.intellij.psi.util.PsiTreeUtil import com.phodal.shirecore.provider.codemodel.FileStructureProvider import com.phodal.shirecore.provider.codemodel.model.FileStructure import com.phodal.shirecore.relativePath class GoFileStructureProvider : FileStructureProvider { override fun build(psiFile: PsiFile): FileStructure? { if (psiFile !is GoFile) return null val packageString = psiFile.packageName val path = psiFile.virtualFile.relativePath(psiFile.project) val imports = psiFile.imports val classes = PsiTreeUtil.getChildrenOfTypeAsList(psiFile, GoType::class.java) val methods = PsiTreeUtil.getChildrenOfTypeAsList(psiFile, GoFunctionOrMethodDeclaration::class.java) return FileStructure(psiFile, psiFile.name, path, packageString, imports, classes, methods) } } ================================================ FILE: languages/shire-go/src/main/kotlin/com/phodal/shirelang/go/codemodel/GoMethodStructureProvider.kt ================================================ package com.phodal.shirelang.go.codemodel import com.goide.psi.GoFunctionDeclaration import com.goide.psi.GoFunctionOrMethodDeclaration import com.goide.psi.GoMethodDeclaration import com.intellij.psi.PsiElement import com.phodal.shirecore.provider.codemodel.MethodStructureProvider import com.phodal.shirecore.provider.codemodel.model.MethodStructure class GoMethodStructureProvider : MethodStructureProvider { override fun build(psiElement: PsiElement, includeClassContext: Boolean, gatherUsages: Boolean): MethodStructure? { if (psiElement !is GoFunctionOrMethodDeclaration) { return null } val funcName = psiElement.name ?: "" val functionSignature: String = when (psiElement) { is GoMethodDeclaration -> { psiElement.signature?.text ?: "" } is GoFunctionDeclaration -> { psiElement.signature?.text ?: "" } else -> "" } val returnType = psiElement.signature?.resultType?.text val languages = psiElement.language.displayName val returnTypeText = returnType val parameterNames = psiElement.signature?.parameters?.parameterDeclarationList?.mapNotNull { it.paramDefinitionList.firstOrNull()?.text }.orEmpty() return MethodStructure( psiElement, psiElement.text, funcName, functionSignature, null, languages, returnTypeText, parameterNames, includeClassContext, emptyList() ) } } ================================================ FILE: languages/shire-go/src/main/kotlin/com/phodal/shirelang/go/codemodel/GoVariableStructureProvider.kt ================================================ package com.phodal.shirelang.go.codemodel import com.goide.psi.GoVarOrConstDefinition import com.intellij.psi.PsiElement import com.phodal.shirecore.provider.codemodel.VariableStructureProvider import com.phodal.shirecore.provider.codemodel.model.VariableStructure class GoVariableStructureProvider : VariableStructureProvider { override fun build( psiElement: PsiElement, withMethodContext: Boolean, withClassContext: Boolean, gatherUsages: Boolean, ): VariableStructure? { if (psiElement !is GoVarOrConstDefinition) { return null } val name = psiElement.name return VariableStructure( psiElement, psiElement.text, name, null, null, emptyList(), false, false ) } } ================================================ FILE: languages/shire-go/src/main/kotlin/com/phodal/shirelang/go/util/GoPsiUtil.kt ================================================ package com.phodal.shirelang.go.util import com.goide.psi.* import com.goide.psi.impl.GoPackage.GoPomTargetPsiElement import com.goide.psi.impl.GoPsiImplUtil import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.psi.PsiElement import com.intellij.psi.util.PsiTreeUtil import com.intellij.util.concurrency.annotations.RequiresReadLock object GoPsiUtil { fun getDeclarationName(psiElement: PsiElement): String? { return singleNamedDescendant(psiElement)?.name } /** * Returns the single named descendant of the given [element]. * * @param element the PsiElement to find the single named descendant from * @return the single named descendant of the given [element], or null if there is none */ fun singleNamedDescendant(element: PsiElement): GoNamedElement? { return when (element) { is GoNamedElement -> element is GoTypeDeclaration -> element.typeSpecList.singleOrNull() is GoVarOrConstSpec<*> -> element.definitionList.singleOrNull() is GoVarOrConstDeclaration<*> -> { (element.specList.singleOrNull() as? GoVarOrConstSpec)?.definitionList?.singleOrNull() } is GoImportDeclaration -> element.importSpecList.singleOrNull() else -> null } } @RequiresReadLock fun findRelatedTypes(declaration: GoFunctionOrMethodDeclaration): List { val signature = declaration.signature ?: return emptyList() val parameterTypes = signature.parameters.parameterDeclarationList .mapNotNull { it.type } val resultTypes = when (val resultType = signature.resultType) { is GoTypeList -> resultType.typeList else -> listOf(resultType) } val mentionedTypes = parameterTypes + resultTypes val genericTypes = mentionedTypes .flatMap { it.typeArguments?.types ?: emptyList() } val relatedTypes = genericTypes + mentionedTypes return relatedTypes .mapNotNull { it.resolve(declaration) as? GoTypeSpec } .filter { isProjectContent(it) } } private fun isProjectContent(element: PsiElement): Boolean { val virtualFile = element.containingFile.virtualFile ?: return true return ProjectFileIndex.getInstance(element.project).isInContent(virtualFile) } private fun notInLibrary(element: PsiElement): Boolean { return !(element is GoPomTargetPsiElement || ProjectFileIndex.getInstance(element.project).isInLibrary(element.containingFile.virtualFile)) } private fun parentWithContext(element: PsiElement): PsiElement? { return when (element) { is GoTypeSpec, is GoMethodSpec, is GoFieldDefinition -> parentTypeSpecOrDeclaration(element) is GoMethodDeclaration -> { val resolveTypeSpec = element.resolveTypeSpec() resolveTypeSpec?.let { parentTypeSpecOrDeclaration(it) } } is GoVarOrConstDefinition -> parentVarOrConstSpecOrDeclaration(element) is GoImportSpec -> parentImportList(element) else -> element } } private fun parentImportList(importSpec: GoImportSpec): PsiElement { val importList = PsiTreeUtil.getParentOfType(importSpec, GoImportList::class.java, true) return if (importList?.importDeclarationList?.size == 1) importList else importSpec } private fun parentTypeSpecOrDeclaration(element: PsiElement): PsiElement { val typeSpec = PsiTreeUtil.getParentOfType(element, GoTypeSpec::class.java, false) ?: return element val typeDeclaration = PsiTreeUtil.getParentOfType(typeSpec, GoTypeDeclaration::class.java, true) return if (typeDeclaration?.typeSpecList?.size == 1) typeDeclaration else typeSpec } private fun parentVarOrConstSpecOrDeclaration(element: PsiElement): PsiElement { val varOrConstSpec = PsiTreeUtil.getParentOfType(element, GoVarOrConstSpec::class.java, false) ?: return element val varOrConstDeclaration = PsiTreeUtil.getParentOfType(varOrConstSpec, GoVarOrConstDeclaration::class.java, true) ?: return varOrConstSpec return when { varOrConstDeclaration.specList.size == 1 -> varOrConstDeclaration varOrConstDeclaration is GoConstDeclaration && varOrConstDeclaration.containsIota() -> varOrConstDeclaration else -> varOrConstSpec } } private fun GoExpression.containsIota(): Boolean { val traverser = GoPsiTreeUtil.goTraverser().withRoot(this) for (element in traverser.traverse()) { if (GoPsiImplUtil.isIota(element)) { return true } } return false } private fun GoConstDeclaration.containsIota(): Boolean { return constSpecList .asSequence() .flatMap { it.expressionList.asSequence() } .any { it.containsIota() } } } ================================================ FILE: languages/shire-go/src/main/kotlin/com/phodal/shirelang/go/variable/GoLanguageProvider.kt ================================================ package com.phodal.shirelang.go.variable import com.goide.GoLanguage import com.goide.sdk.GoSdkService import com.goide.sdk.GoTargetSdkVersionProvider import com.goide.util.GoUtil import com.intellij.openapi.application.ReadAction import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.phodal.shirecore.provider.context.LanguageToolchainProvider import com.phodal.shirecore.provider.context.ToolchainContextItem import com.phodal.shirecore.provider.context.ToolchainPrepareContext import java.lang.reflect.Method class GoLanguageProvider : LanguageToolchainProvider { override fun isApplicable(project: Project, context: ToolchainPrepareContext): Boolean { return context.sourceFile?.language is GoLanguage } override suspend fun collect(project: Project, context: ToolchainPrepareContext): List { if (context.element == null) { return emptyList() } val sourceFile = context.sourceFile ?: return emptyList() return ReadAction.compute, Throwable> { val goVersion = GoSdkService.getInstance(project).getSdk(GoUtil.module(sourceFile)).version val targetVersion = getGoVersion(sourceFile) val prompt = "Go Version: $goVersion, Target Version: $targetVersion" val element = ToolchainContextItem(GoLanguageProvider::class, prompt) listOf(element) } } private fun getGoVersion(sourceFile: PsiFile): String { return try { val clazz = Class.forName("com.goide.sdk.GoTargetSdkVersionProvider") val method: Method = clazz.getMethod("getTargetGoSdkVersion", PsiElement::class.java) val result = method.invoke(null, sourceFile) result?.toString() } catch (e: Exception) { e.printStackTrace() null } ?: "" } } ================================================ FILE: languages/shire-go/src/main/kotlin/com/phodal/shirelang/go/variable/GoPsiContextVariableProvider.kt ================================================ package com.phodal.shirelang.go.variable import com.goide.GoLanguage import com.goide.psi.* import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.openapi.roots.TestSourcesFilter import com.intellij.psi.PsiElement import com.intellij.psi.util.PsiTreeUtil import com.intellij.testIntegration.TestFinderHelper import com.phodal.shirecore.provider.variable.PsiContextVariableProvider import com.phodal.shirecore.psi.CodeSmellCollector import com.phodal.shirecore.provider.variable.model.PsiContextVariable import com.phodal.shirecore.search.similar.SimilarChunksSearch import com.phodal.shirelang.go.codemodel.GoClassStructureProvider import com.phodal.shirelang.go.codemodel.GoMethodStructureProvider import com.phodal.shirelang.go.util.GoPsiUtil class GoPsiContextVariableProvider : PsiContextVariableProvider { override fun resolve( variable: PsiContextVariable, project: Project, editor: Editor, psiElement: PsiElement?, ): Any { if (psiElement?.language !is GoLanguage) return "" val underTestElement = getElementForTests(psiElement) val underTestFile = underTestElement?.containingFile as? GoFile ?: return "" return when (variable) { PsiContextVariable.CURRENT_CLASS_NAME -> "" PsiContextVariable.CURRENT_CLASS_CODE -> { when (underTestElement) { is GoTypeDeclaration, is GoTypeSpec, -> { GoClassStructureProvider().build(underTestElement, false)?.format() } is GoFunctionOrMethodDeclaration -> GoMethodStructureProvider() .build(underTestElement, false, false) ?.format() else -> null } } PsiContextVariable.CURRENT_METHOD_NAME -> { when (psiElement) { is GoFunctionOrMethodDeclaration -> psiElement.name else -> psiElement.text } } PsiContextVariable.CURRENT_METHOD_CODE -> psiElement.text PsiContextVariable.RELATED_CLASSES -> { when (underTestElement) { is GoFunctionOrMethodDeclaration -> { GoPsiUtil.findRelatedTypes(underTestElement).map { it.text} } is GoFile -> { val functions = underTestElement.functions val methods = underTestElement.methods (functions + methods).flatMap { GoPsiUtil.findRelatedTypes(it) }.map { it.text} } else -> emptyList() } } PsiContextVariable.SIMILAR_TEST_CASE -> TODO() PsiContextVariable.IMPORTS -> { val importList = PsiTreeUtil.getChildrenOfTypeAsList(underTestFile, GoImportDeclaration::class.java) importList.map { it.text } } PsiContextVariable.IS_NEED_CREATE_FILE -> TODO() PsiContextVariable.TARGET_TEST_FILE_NAME -> { val name = GoPsiUtil.getDeclarationName(underTestElement) ?: return "" toTestFileName(name) } PsiContextVariable.UNDER_TEST_METHOD_CODE -> { when (underTestElement) { is GoFunctionOrMethodDeclaration -> psiElement.text else -> psiElement.text } } PsiContextVariable.FRAMEWORK_CONTEXT -> return collectFrameworkContext(psiElement, project) PsiContextVariable.CODE_SMELL -> CodeSmellCollector.collectElementProblemAsSting( underTestElement, project, editor ) PsiContextVariable.METHOD_CALLER -> { if (psiElement !is GoFunctionOrMethodDeclaration) return "" "" } PsiContextVariable.CALLED_METHOD -> return SimilarChunksSearch.createQuery(psiElement) ?: "" PsiContextVariable.SIMILAR_CODE -> TODO() PsiContextVariable.STRUCTURE -> { when (underTestElement) { is GoTypeDeclaration, is GoTypeSpec, -> { GoClassStructureProvider().build(underTestElement, true)?.toString() ?: "" } is GoFunctionOrMethodDeclaration -> GoMethodStructureProvider() .build(underTestElement, true, true) ?.toString() ?: "" else -> "" } } PsiContextVariable.CHANGE_COUNT -> calculateChangeCount(psiElement) PsiContextVariable.LINE_COUNT -> calculateLineCount(psiElement) PsiContextVariable.COMPLEXITY_COUNT -> calculateComplexityCount(psiElement) } ?: "" } private fun toTestFileName(underTestFileName: String): String = underTestFileName + "_test.go" private fun getElementForTests(elementAtCaret: PsiElement): PsiElement? { val parent = PsiTreeUtil.getParentOfType(elementAtCaret, GoFunctionOrMethodDeclaration::class.java, false) if (parent == null) { val goFile: GoFile = elementAtCaret as? GoFile ?: return null return if (goFile.functions.isNotEmpty() || goFile.methods.isNotEmpty()) { goFile } else { null } } val virtualFile = elementAtCaret.containingFile?.virtualFile ?: return null val project = elementAtCaret.project if (TestSourcesFilter.isTestSources(virtualFile, project) || TestFinderHelper.isTest(parent)) return null return parent } } ================================================ FILE: languages/shire-go/src/main/resources/com.phodal.shirelang.go.xml ================================================ ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/archmeta/SpringLayerCharacteristic.kt ================================================ package com.phodal.shirelang.java.archmeta import com.intellij.psi.PsiClass import kotlinx.serialization.Serializable @Serializable class SpringLayerCharacteristic(val annotation: String, val imports: List, val codeRegex: String, val fileName: String? = null) { companion object { private val controllerCharacteristic = SpringLayerCharacteristic( annotation = "@Controller", imports = listOf( "org.springframework.stereotype.Controller", "org.springframework.web.bind.annotation.RestController" ), codeRegex = "public\\s+class\\s+\\w+Controller", fileName = ".*Controller\\.java" ) private val serviceCharacteristic = SpringLayerCharacteristic( annotation = "@Service", imports = listOf("org.springframework.stereotype.Service"), codeRegex = "public\\s+(class|interface)\\s+\\w+Service", fileName = ".*(Service|ServiceImpl)\\.java" ) private val entityCharacteristic = SpringLayerCharacteristic( annotation = "@Entity", imports = listOf("javax.persistence.Entity"), codeRegex = "public\\s+class\\s+\\w+Entity", ) private val dtoCharacteristic = SpringLayerCharacteristic( annotation = "@Data", imports = listOf("lombok.Data"), codeRegex = "public\\s+class\\s+\\w+(Dto|DTO|Request|Response|Res|Req)", fileName = ".*(Dto|DTO|Request|Response|Res|Req)\\.java" ) private val repositoryCharacteristic = SpringLayerCharacteristic( annotation = "org.springframework.stereotype.Repository", imports = listOf("org.springframework.stereotype.Repository"), codeRegex = "public\\s+(class|interface)\\s+\\w+Repository", fileName = ".*Repository\\.java" ) private val allCharacteristics = mapOf( "controller" to controllerCharacteristic, "service" to serviceCharacteristic, "entity" to entityCharacteristic, "dto" to dtoCharacteristic, "repository" to repositoryCharacteristic ) fun check(code: String, type: String): Boolean { val characteristic = allCharacteristics[type] ?: return false if (code.contains(characteristic.annotation)) { return true } characteristic.imports.forEach { if (code.contains(it)) { return true } } val regex = Regex(characteristic.codeRegex) return regex.containsMatchIn(code) } fun check(code: PsiClass, type: String): Boolean { val characteristic = allCharacteristics[type] ?: return false code.annotations.forEach { if (characteristic.imports.contains(it.qualifiedName)) { return true } } if (characteristic.fileName != null) { val regex = Regex(characteristic.fileName) code.name?.lowercase()?.let { if (regex.containsMatchIn(it)) { return true } } } val regex = Regex(characteristic.codeRegex) return regex.containsMatchIn(code.text) } } } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/archmeta/SpringLibrary.kt ================================================ package com.phodal.shirelang.java.archmeta data class SpringDataLibraryDescriptor(val shortText: String, val coords: List) data class LibraryDescriptor(val shortText: String, val coords: String) object SpringLibrary { // Spring private const val SPRING_MVC_MAVEN = "org.springframework:spring-webmvc" private const val SPRING_WEBFLUX_MAVEN = "org.springframework:spring-webflux" // Spring Data private const val REACTOR_MAVEN = "io.projectreactor:reactor-core" private const val MONGO_REACTIVE_STREAMS_MAVEN = "org.mongodb:mongodb-driver-reactivestreams" private const val SPRING_DATA_COMMONS_MAVEN = "org.springframework.data:spring-data-commons" private const val JPA_MAVEN = "org.springframework.data:spring-data-jpa" private const val CASSANDRA_MAVEN = "org.springframework.data:spring-data-cassandra" private const val COUCHBASE_MAVEN = "org.springframework.data:spring-data-couchbase" private const val JDBC_MAVEN = "org.springframework.data:spring-data-jdbc" private const val MONGO_MAVEN = "org.springframework.data:spring-data-mongodb" private const val NEO4J_MAVEN = "org.springframework.data:spring-data-neo4j" private const val R2DBC_MAVEN = "org.springframework.data:spring-data-r2dbc" private const val REDIS_MAVEN = "org.springframework.data:spring-data-redis" val SPRING_DATA = listOf( SpringDataLibraryDescriptor("JPA ", listOf(JPA_MAVEN)), SpringDataLibraryDescriptor("CASSANDRA", listOf(CASSANDRA_MAVEN)), SpringDataLibraryDescriptor("REACTIVE CASSANDRA", listOf(CASSANDRA_MAVEN, REACTOR_MAVEN)), SpringDataLibraryDescriptor("COUCHBASE", listOf(COUCHBASE_MAVEN)), SpringDataLibraryDescriptor("REACTIVE COUCHBASE", listOf(COUCHBASE_MAVEN, REACTOR_MAVEN)), SpringDataLibraryDescriptor("JDBC", listOf(JDBC_MAVEN)), SpringDataLibraryDescriptor("MONGO", listOf(MONGO_MAVEN)), SpringDataLibraryDescriptor( "REACTIVE MONGO", listOf(MONGO_MAVEN, REACTOR_MAVEN, MONGO_REACTIVE_STREAMS_MAVEN) ), SpringDataLibraryDescriptor("NEO4J", listOf(NEO4J_MAVEN)), SpringDataLibraryDescriptor("R2DBC", listOf(R2DBC_MAVEN)), SpringDataLibraryDescriptor("REDIS", listOf(REDIS_MAVEN)) ) fun canApplySpringData(libName: String) = libName == SPRING_DATA_COMMONS_MAVEN val SPRING_MVC = listOf( LibraryDescriptor("Spring MVC", SPRING_MVC_MAVEN), LibraryDescriptor("Spring WebFlux", SPRING_WEBFLUX_MAVEN) ) fun canApplySpringMvc(libName: String) = libName == SPRING_MVC_MAVEN || libName == SPRING_WEBFLUX_MAVEN } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/codeedit/JavaAutoTestingService.kt ================================================ package com.phodal.shirelang.java.codeedit import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer import com.intellij.execution.RunManager import com.intellij.execution.configurations.RunConfiguration import com.intellij.execution.configurations.RunProfile import com.intellij.lang.java.JavaLanguage import com.intellij.openapi.application.ReadAction import com.intellij.openapi.application.runReadAction import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.externalSystem.service.project.ProjectDataManager import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.psi.* import com.intellij.psi.util.PsiTreeUtil import com.phodal.shirecore.provider.TestingService import com.phodal.shirecore.provider.codemodel.ClassStructureProvider import com.phodal.shirecore.provider.codemodel.model.ClassStructure import com.phodal.shirecore.psi.collectPsiError import com.phodal.shirecore.variable.toolchain.unittest.AutoTestingPromptContext import com.phodal.shirelang.java.util.JavaTypeResolver import org.jetbrains.idea.maven.execution.MavenRunConfiguration import org.jetbrains.idea.maven.execution.MavenRunConfigurationType import org.jetbrains.idea.maven.execution.MavenRunnerParameters import org.jetbrains.idea.maven.project.MavenProject import org.jetbrains.idea.maven.project.MavenProjectsManager import org.jetbrains.plugins.gradle.service.execution.GradleExternalTaskConfigurationType import org.jetbrains.plugins.gradle.service.execution.GradleRunConfiguration import org.jetbrains.plugins.gradle.util.GradleConstants import java.io.File class JavaAutoTestService : TestingService() { private val maxLevelOneClass = 8 override fun runConfigurationClass(project: Project): Class = GradleRunConfiguration::class.java override fun isApplicable(element: PsiElement): Boolean = element.language is JavaLanguage override fun isApplicable(project: Project, file: VirtualFile): Boolean { return file.extension == "java" && PsiManager.getInstance(project).findFile(file) is PsiJavaFile } override fun createConfiguration(project: Project, virtualFile: VirtualFile): RunConfiguration? { val psiFile = runReadAction { PsiManager.getInstance(project).findFile(virtualFile) } as? PsiJavaFile ?: return null if (psiFile.collectPsiError().isNotEmpty()) { return null } return createConfigForJava(virtualFile, project) } override fun findOrCreateTestFile( sourceFile: PsiFile, project: Project, psiElement: PsiElement, ): AutoTestingPromptContext? { val sourceFilePath = sourceFile.virtualFile val parentDir = sourceFilePath.parent val testFileName = sourceFile.name.replace(".java", "") + "Test" val parentDirPath = ReadAction.compute { parentDir?.path } val relatedModels = lookupRelevantClass(project, psiElement).distinctBy { it.name } // Check if the source file is in the src/main/java directory if (!parentDirPath?.contains("/src/main/java/")!!) { log.error("Source file is not in the src/main/java directory: $parentDirPath") return null } var isNewFile = false // Find the test directory val testDirPath = parentDirPath.replace("/src/main/java/", "/src/test/java/") var testDir = LocalFileSystem.getInstance().findFileByPath(testDirPath) if (testDir == null || !testDir.isDirectory) { isNewFile = true // Create the test directory if it doesn't exist val testDirFile = File(testDirPath) if (!testDirFile.exists()) { testDirFile.mkdirs() LocalFileSystem.getInstance().refreshAndFindFileByPath(testDirPath)?.let { refreshedDir -> testDir = refreshedDir } } } val testDirCreated: VirtualFile? = VirtualFileManager.getInstance().refreshAndFindFileByUrl("file://$testDirPath") if (testDirCreated == null) { log.error("Failed to create test directory: $testDirPath") return null } // Test directory already exists, find the corresponding test file val testFilePath = testDirPath + "/" + sourceFile.name.replace(".java", "Test.java") val testFile = LocalFileSystem.getInstance().findFileByPath(testFilePath) project.guessProjectDir()?.refresh(true, true) val imports = runReadAction { val importList = PsiTreeUtil.getChildrenOfTypeAsList(sourceFile, PsiImportList::class.java) importList.flatMap { it.allImportStatements.map { import -> import.text } } } val currentClass = extracted(psiElement) val relatedClasses = relatedModels.map { it.format() } return if (testFile != null) { AutoTestingPromptContext( isNewFile, testFile, relatedClasses, testFileName, sourceFile.language, currentClass, imports ) } else { val targetFile = createTestFile(sourceFile, testDir!!, project) AutoTestingPromptContext( isNewFile = true, targetFile, relatedClasses, "", sourceFile.language, currentClass, imports ) } } private fun extracted(psiElement: PsiElement): String? { var currentClass: ClassStructure? = null; when (psiElement) { is PsiJavaFile -> { currentClass = runReadAction { psiElement.classes.firstOrNull()?.let { ClassStructureProvider.from(it) } } } is PsiClass -> { currentClass = runReadAction { ClassStructureProvider.from(psiElement) } } is PsiMethod -> { currentClass = runReadAction { psiElement.containingClass?.let { ClassStructureProvider.from(it) } } } } return currentClass?.format(); } override fun lookupRelevantClass(project: Project, element: PsiElement): List { return ReadAction.compute, Throwable> { val elements = mutableListOf() val projectPath = project.guessProjectDir()?.path val resolvedClasses: MutableMap = mutableMapOf() resolvedClasses.putAll(JavaTypeResolver.resolveByField(element)) when (element) { is PsiJavaFile -> { element.classes.forEach { psiClass -> resolvedClasses.putAll(JavaTypeResolver.resolveByClass(psiClass)) } } is PsiClass -> { element.methods.forEach { method -> resolvedClasses.putAll(JavaTypeResolver.resolveByMethod(method)) } } is PsiMethod -> { resolvedClasses.putAll(JavaTypeResolver.resolveByMethod(element)) } } if (resolvedClasses.isEmpty()) { return@compute elements } if ((resolvedClasses.size <= maxLevelOneClass) || element is PsiMethod) { // load all second childrens val childClasses: MutableMap = mutableMapOf() resolvedClasses.forEach { (key, value) -> value.fields.forEach { field -> childClasses.putAll(JavaTypeResolver.resolveByType(field.type)) } } resolvedClasses.putAll(childClasses) } // find the class in the same project resolvedClasses.forEach { (_, psiClass) -> val classPath = psiClass.containingFile?.virtualFile?.path if (classPath?.contains(projectPath!!) == true) { elements += ClassStructureProvider.from(psiClass, false) ?: return@forEach } } elements } } override fun tryFixSyntaxError(outputFile: VirtualFile, project: Project, issues: List) { val sourceFile: PsiJavaFile = runReadAction { PsiManager.getInstance(project).findFile(outputFile) as? PsiJavaFile } ?: return val editor = FileEditorManager.getInstance(project).selectedTextEditor ?: return DaemonCodeAnalyzer.getInstance(project).autoImportReferenceAtCursor(editor, sourceFile) } private fun createTestFile(sourceFile: PsiFile, testDir: VirtualFile, project: Project): VirtualFile { val sourceFileName = sourceFile.name val testFileName = sourceFileName.replace(".java", "Test.java") val testFileContent = "" return WriteCommandAction.runWriteCommandAction(project) { val testFile = testDir.createChildData(this, testFileName) val document = FileDocumentManager.getInstance().getDocument(testFile) document?.setText(testFileContent) testFile } } companion object { private val log = logger() } } fun createConfigForJava(virtualFile: VirtualFile, project: Project): RunConfiguration? { val gradleLibraryData = ProjectDataManager.getInstance().getExternalProjectData( project, GradleConstants.SYSTEM_ID, project.basePath!! ) if (gradleLibraryData == null) { return createConfigForMaven(virtualFile, project) } return createConfigForGradle(virtualFile, project) } fun createConfigForGradle(virtualFile: VirtualFile, project: Project): GradleRunConfiguration? { val name = virtualFile.name val canonicalName = runReadAction { val psiFile: PsiJavaFile = PsiManager.getInstance(project).findFile(virtualFile) as? PsiJavaFile ?: return@runReadAction null // skip for non-test files (psiFile.packageName + "." + virtualFile.nameWithoutExtension).removePrefix(".") } ?: return null val runManager = RunManager.getInstance(project) var moduleName = "" val moduleForFile = runReadAction { ProjectFileIndex.getInstance(project).getModuleForFile(virtualFile) } // a moduleForFile.name will be like .., so we need to remove the last part and first part if (moduleForFile != null) { val moduleNameSplit = moduleForFile.name.split(".").drop(1).dropLast(1).joinToString(":") if (moduleNameSplit.isNotEmpty()) { moduleName = "$moduleNameSplit:" } } // todo: add maven ?? val configuration = runManager.createConfiguration(name, GradleExternalTaskConfigurationType::class.java) val runConfiguration = configuration.configuration as GradleRunConfiguration runConfiguration.isDebugServerProcess = false runConfiguration.settings.externalProjectPath = project.guessProjectDir()?.path // todo: add module for test runConfiguration.rawCommandLine = moduleName + "test --tests \"${canonicalName}\"" runManager.addConfiguration(configuration) runManager.selectedConfiguration = configuration return runConfiguration } fun createConfigForMaven(virtualFile: VirtualFile, project: Project): MavenRunConfiguration? { val projectsManager = MavenProjectsManager.getInstance(project); val mavenProject: MavenProject = projectsManager.findProject(virtualFile) ?: return null val module = runReadAction { projectsManager.findModule(mavenProject) } ?: return null var trulyMavenProject = projectsManager.projects.firstOrNull { it.mavenId.artifactId == module.name } if (trulyMavenProject == null) { trulyMavenProject = projectsManager.projects.first() ?: return null } val pomFile = trulyMavenProject.file.name val parameters = MavenRunnerParameters( true, trulyMavenProject.directory, pomFile, listOf("test"), projectsManager.explicitProfiles.enabledProfiles, arrayListOf() ) // $MODULE_WORKING_DIR$ // // -ea Method: com.example.demo.MathHelperTest should_ReturnSum_When_GivenTwoPositiveNumbers // /Users/phodal/Library/Java/JavaVirtualMachines/corretto-18.0.2/Contents/Home/bin/java // -ea -Didea.test.cyclic.buffer.size=1048576 // -javaagent:ideaIU-2024.1/lib/idea_rt.jar=54637:1/bin -Dfile.encoding=UTF-8 // -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8 val runnerAndConfigurationSettings = MavenRunConfigurationType.createRunnerAndConfigurationSettings(null, null, parameters, project) val runManager = RunManager.getInstance(project) val configuration = runnerAndConfigurationSettings.configuration runManager.addConfiguration(runnerAndConfigurationSettings) runManager.selectedConfiguration = runnerAndConfigurationSettings return configuration as MavenRunConfiguration } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/codeedit/JavaCodeModifier.kt ================================================ package com.phodal.shirelang.java.codeedit import com.intellij.lang.Language import com.intellij.lang.java.JavaLanguage import com.intellij.openapi.application.ReadAction import com.intellij.openapi.application.runReadAction import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.* import com.intellij.psi.codeStyle.CodeStyleManager import com.phodal.shirecore.provider.codeedit.CodeModifier open class JavaCodeModifier : CodeModifier { private val log = logger() override fun isApplicable(language: Language) = language is JavaLanguage override fun smartInsert(sourceFile: VirtualFile, project: Project, code: String): PsiElement? { val isTestFile = sourceFile.name.endsWith("Test.java") if (!isTestFile) { return insertTestCode(sourceFile, project, code) } return insertMethod(sourceFile, project, code) } private fun lookupFile(project: Project, sourceFile: VirtualFile) = PsiManager.getInstance(project).findFile(sourceFile) as PsiJavaFile /** * This function is used to insert test code into a specified source file in a Kotlin project. * It takes the source file, project, and the test code as parameters. * * The function first trims the test code by removing leading and trailing whitespaces, as well as any surrounding triple backticks and "java" prefix. * If the trimmed code does not contain the "@Test" annotation, a warning is logged and the method code is inserted into the source file. * * It then checks if the trimmed code is a full class code (starts with "import" or "package" and contains "class "). * If the source file already contains classes, the function inserts the test code into an existing class. * * If the trimmed code is a full class code, the function inserts a new class into the source file. * * If none of the above conditions are met, the function inserts the test code as a method into the source file. * * @param sourceFile The VirtualFile representing the source file where the test code will be inserted. * @param project The Project to which the source file belongs. * @param code The test code to be inserted into the source file. * @return Boolean value indicating whether the test code was successfully inserted. */ override fun insertTestCode(sourceFile: VirtualFile, project: Project, code: String): PsiElement? { val trimCode = code.trim().removeSurrounding("```").removePrefix("java").trim() val isFullTestCode = (trimCode.startsWith("import") || trimCode.startsWith("package")) && trimCode.contains("class ") val existTestFileClasses = runReadAction { lookupFile(project, sourceFile).classes } val alreadyExtTestFile = existTestFileClasses.isNotEmpty() return when { alreadyExtTestFile -> return insertToExistClass(existTestFileClasses, project, trimCode) isFullTestCode -> return insertClass(sourceFile, project, trimCode) trimCode.contains("@Test") -> insertMethod(sourceFile, project, trimCode) else -> { log.warn("methodCode does not contain @Test annotation: $trimCode") insertMethod(sourceFile, project, trimCode) } } } private fun insertToExistClass( testFileClasses: Array, project: Project, trimCode: String, ): PsiElement? { // todo: check to naming testFile, but since Java only has One Class under file val lastClass = testFileClasses.last() val classEndOffset = runReadAction { lastClass.textRange.endOffset } val psiFile = try { PsiFileFactory.getInstance(project) .createFileFromText("Test.java", JavaLanguage.INSTANCE, trimCode) } catch (e: Throwable) { log.warn("Failed to create file from text: $trimCode", e) null } val newCode = psiFile?.text ?: trimCode try { val newClassMethods = runReadAction { psiFile?.children?.firstOrNull { it is PsiClass }?.children?.filterIsInstance() } WriteCommandAction.runWriteCommandAction(project) { newClassMethods?.forEach { lastClass.add(it) } } return lastClass } catch (e: Throwable) { WriteCommandAction.runWriteCommandAction(project) { val document = PsiDocumentManager.getInstance(project).getDocument(lastClass.containingFile) document?.insertString(classEndOffset - 1, "\n $newCode") } return lastClass } } override fun insertMethod(sourceFile: VirtualFile, project: Project, code: String): PsiElement? { val rootElement = runReadAction { val psiJavaFile = lookupFile(project, sourceFile) val psiClass = psiJavaFile.classes.firstOrNull() if (psiClass == null) { log.error("Failed to find PsiClass in the source file: $psiJavaFile, code: $code") return@runReadAction null } return@runReadAction psiClass } ?: return null val newTestMethod = ReadAction.compute { val psiElementFactory = PsiElementFactory.getInstance(project) try { val methodCode = psiElementFactory.createMethodFromText(code, rootElement) if (rootElement.findMethodsByName(methodCode.name, false).isNotEmpty()) { log.error("Method already exists in the class: ${methodCode.name}") } methodCode } catch (e: Throwable) { log.error("Failed to create method from text: $code", e) return@compute null } } WriteCommandAction.runWriteCommandAction(project) { try { rootElement.add(newTestMethod) } catch (e: Throwable) { val classEndOffset = rootElement.textRange.endOffset val document = PsiDocumentManager.getInstance(project).getDocument(rootElement.containingFile) document?.insertString(classEndOffset - 1, "\n ") document?.insertString(classEndOffset - 1 + "\n ".length, newTestMethod.text) } } project.guessProjectDir()?.refresh(true, true) return newTestMethod } override fun insertClass(sourceFile: VirtualFile, project: Project, code: String): PsiElement? { return WriteCommandAction.runWriteCommandAction(project) { val psiFile = lookupFile(project, sourceFile) val document = psiFile.viewProvider.document!! document.insertString(document.textLength, code) psiFile.classes.firstOrNull() } } } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/codemodel/JavaClassStructureProvider.kt ================================================ package com.phodal.shirelang.java.codemodel import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.runReadAction import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.util.ProgressIndicatorBase import com.intellij.psi.* import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.SearchScope import com.intellij.psi.search.searches.MethodReferencesSearch import com.intellij.psi.search.searches.ReferencesSearch import com.phodal.shirecore.provider.codemodel.ClassStructureProvider import com.phodal.shirecore.provider.codemodel.model.ClassStructure class JavaClassStructureProvider : ClassStructureProvider { override fun build(psiElement: PsiElement, gatherUsages: Boolean): ClassStructure? { if (psiElement !is PsiClass) return null val supers = runReadAction { psiElement.extendsList?.referenceElements?.mapNotNull { it.text } } return runReadAction { val fields = psiElement.fields.toList() val methods = psiElement.methods.toList() val usages = if (gatherUsages) findUsages(psiElement as PsiNameIdentifierOwner) else emptyList() val annotations: List = psiElement.annotations.mapNotNull { it.text } ClassStructure( psiElement, psiElement.text, psiElement.name, displayName = psiElement.qualifiedName, methods, fields, supers, annotations, usages ) } } companion object { /** * This method is used to find usages of a given PsiNameIdentifierOwner in the project. * * @param nameIdentifierOwner the PsiNameIdentifierOwner for which usages need to be found * @return a list of PsiReference objects representing the usages of the given PsiNameIdentifierOwner */ fun findUsages(nameIdentifierOwner: PsiNameIdentifierOwner): List { val project = nameIdentifierOwner.project val searchScope = GlobalSearchScope.allScope(project) var results = emptyList() ProgressManager.getInstance().runProcess(Runnable { results = when (nameIdentifierOwner) { is PsiMethod -> { MethodReferencesSearch.search(nameIdentifierOwner, searchScope, true) } else -> { ReferencesSearch.search((nameIdentifierOwner as PsiElement), searchScope, true) } }.findAll().map { it as PsiReference } }, ProgressIndicatorBase()) return results } } } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/codemodel/JavaFileStructureProvider.kt ================================================ package com.phodal.shirelang.java.codemodel import com.intellij.psi.* import com.intellij.psi.util.PsiTreeUtil.getChildrenOfTypeAsList import com.phodal.shirecore.provider.codemodel.FileStructureProvider import com.phodal.shirecore.provider.codemodel.model.FileStructure import com.phodal.shirecore.relativePath class JavaFileStructureProvider : FileStructureProvider { override fun build(psiFile: PsiFile): FileStructure { val packageStatement = getChildrenOfTypeAsList(psiFile, PsiPackageStatement::class.java).firstOrNull() val importLists = getChildrenOfTypeAsList(psiFile, PsiImportList::class.java) val classDeclarations = getChildrenOfTypeAsList(psiFile, PsiClass::class.java) val imports = mutableListOf() for (it in importLists) imports.addAll(it.allImportStatements) val packageString = packageStatement?.text val path = psiFile.virtualFile.relativePath(psiFile.project) return FileStructure(psiFile, psiFile.name, path, packageString, imports, classDeclarations, emptyList()) } } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/codemodel/JavaMethodStructureProvider.kt ================================================ package com.phodal.shirelang.java.codemodel import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.runReadAction import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement import com.intellij.psi.PsiMethod import com.intellij.psi.PsiNameIdentifierOwner import com.phodal.shirecore.provider.codemodel.MethodStructureProvider import com.phodal.shirecore.provider.codemodel.model.MethodStructure import com.phodal.shirelang.java.util.JavaTypeResolver import java.util.concurrent.Future class JavaMethodStructureProvider : MethodStructureProvider { override fun build(psiElement: PsiElement, includeClassContext: Boolean, gatherUsages: Boolean): MethodStructure? { if (psiElement !is PsiMethod) { return null } val parameterList = runReadAction { psiElement.parameters.mapNotNull { it.name } } val variableContextList = parameterList.map { it } val usagesList = if (gatherUsages) { JavaClassStructureProvider.findUsages(psiElement as PsiNameIdentifierOwner) } else { emptyList() } val ios: List = try { val executeOnPooledThread: Future> = ApplicationManager.getApplication().executeOnPooledThread> { return@executeOnPooledThread JavaTypeResolver.resolveByMethod(psiElement).values.map { it } } executeOnPooledThread.get() } catch (e: Exception) { emptyList() } return ApplicationManager.getApplication().executeOnPooledThread { runReadAction { MethodStructure( psiElement, text = psiElement.text, name = psiElement.name, signature = getSignatureString(psiElement), enclosingClass = psiElement.containingClass, language = psiElement.language.displayName, returnType = processReturnTypeText(psiElement.returnType?.presentableText), variableContextList, includeClassContext, usagesList, ios ) } }.get() } private fun processReturnTypeText(returnType: String?): String? { return if (returnType == "void") null else returnType } companion object { fun getSignatureString(method: PsiMethod): String { val bodyStart = runReadAction { method.body?.startOffsetInParent ?: method.textLength } val text = runReadAction { method.text } val substring = text.substring(0, bodyStart) val trimmed = substring.replace('\n', ' ').trim() return trimmed } } } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/codemodel/JavaVariableStructureProvider.kt ================================================ package com.phodal.shirelang.java.codemodel import com.intellij.openapi.application.runReadAction import com.intellij.psi.PsiElement import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.psi.PsiVariable import com.phodal.shirecore.provider.codemodel.VariableStructureProvider import com.phodal.shirecore.provider.codemodel.model.VariableStructure import com.phodal.shirelang.java.util.getContainingClass import com.phodal.shirelang.java.util.getContainingMethod class JavaVariableStructureProvider : VariableStructureProvider { override fun build( psiElement: PsiElement, withMethodContext: Boolean, withClassContext: Boolean, gatherUsages: Boolean, ): VariableStructure? { if (psiElement !is PsiVariable) return null val containingMethod = runReadAction {psiElement.getContainingMethod() } val containingClass = runReadAction { psiElement.getContainingClass()} val references = if (gatherUsages) JavaClassStructureProvider.findUsages(psiElement as PsiNameIdentifierOwner) else emptyList() return runReadAction { VariableStructure( psiElement, psiElement.text ?: "", psiElement.name, containingMethod, containingClass, references, withMethodContext, withClassContext ) } } } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/complexity/JavaComplexityProvider.kt ================================================ package com.phodal.shirelang.java.complexity import com.intellij.psi.PsiElement import com.phodal.shirecore.ast.ComplexitySink import com.phodal.shirecore.ast.ComplexityVisitor import com.phodal.shirecore.provider.complexity.ComplexityProvider class JavaComplexityProvider : ComplexityProvider { override fun process(element: PsiElement): Int { val sink = ComplexitySink() val visitor = visitor(sink) element.accept(visitor) return sink.getComplexity() } override fun visitor(sink: ComplexitySink): ComplexityVisitor { return JavaLanguageVisitor(sink) } } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/complexity/JavaLanguageVisitor.kt ================================================ /** * The MIT License (MIT) *

* https://github.com/nikolaikopernik/code-complexity-plugin *

*/ package com.phodal.shirelang.java.complexity import com.intellij.psi.JavaTokenType import com.intellij.psi.PsiBreakStatement import com.intellij.psi.PsiCatchSection import com.intellij.psi.PsiConditionalExpression import com.intellij.psi.PsiContinueStatement import com.intellij.psi.PsiDoWhileStatement import com.intellij.psi.PsiElement import com.intellij.psi.PsiExpression import com.intellij.psi.PsiForStatement import com.intellij.psi.PsiForeachStatement import com.intellij.psi.PsiIfStatement import com.intellij.psi.PsiJavaToken import com.intellij.psi.PsiKeyword import com.intellij.psi.PsiLambdaExpression import com.intellij.psi.PsiMethod import com.intellij.psi.PsiMethodCallExpression import com.intellij.psi.PsiParenthesizedExpression import com.intellij.psi.PsiPolyadicExpression import com.intellij.psi.PsiPrefixExpression import com.intellij.psi.PsiSwitchStatement import com.intellij.psi.PsiWhileStatement import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.tree.IElementType import com.phodal.shirecore.ast.ComplexitySink import com.phodal.shirecore.ast.ComplexityVisitor import com.phodal.shirecore.ast.PointType import com.phodal.shirecore.ast.PointType.* class JavaLanguageVisitor(private val sink: ComplexitySink) : ComplexityVisitor() { override fun processElement(element: PsiElement) { when (element) { is PsiWhileStatement -> sink.increaseComplexityAndNesting(LOOP_WHILE) is PsiDoWhileStatement -> sink.increaseComplexityAndNesting(LOOP_WHILE) is PsiIfStatement -> element.processIfExpression() is PsiKeyword -> { if (element.text == PsiKeyword.ELSE && element.parent is PsiIfStatement) { sink.increaseComplexity(ELSE) } } is PsiConditionalExpression -> { sink.increaseComplexityAndNesting(IF) element.calculateBinaryComplexity() } is PsiSwitchStatement -> sink.increaseComplexityAndNesting(SWITCH) is PsiForStatement -> sink.increaseComplexityAndNesting(LOOP_FOR) is PsiForeachStatement -> sink.increaseComplexityAndNesting(LOOP_FOR) is PsiCatchSection -> sink.increaseComplexityAndNesting(CATCH) is PsiBreakStatement -> if (element.labelIdentifier != null) sink.increaseComplexity(BREAK) is PsiContinueStatement -> if (element.labelIdentifier != null) sink.increaseComplexity(CONTINUE) is PsiLambdaExpression -> sink.increaseNesting() is PsiPolyadicExpression -> { // this method will go over all the nested elements as well // we don't want that so we accept only the top-level expressions // and the entire expression will be processed recursively in [calculateBinaryComplexity] if (element.parent !is PsiExpression) { element.calculateBinaryComplexity() } } is PsiMethodCallExpression -> if (element.isRecursion()) sink.increaseComplexity(RECURSION) } } private fun PsiExpression.calculateBinaryComplexity() { var prevOperand: IElementType? = null this.children.forEach { element -> when (element) { is PsiJavaToken -> if (element.tokenType in listOf(JavaTokenType.ANDAND, JavaTokenType.OROR)) { if (prevOperand == null || element.tokenType != prevOperand) { sink.increaseComplexity(element.tokenType.toPointType()) } prevOperand = element.tokenType } is PsiParenthesizedExpression -> { element.calculateBinaryComplexity() prevOperand = null } is PsiPrefixExpression -> { element.calculateBinaryComplexity() prevOperand = null } is PsiPolyadicExpression -> element.calculateBinaryComplexity() } } } override fun postProcess(element: PsiElement) { if (element is PsiWhileStatement || element is PsiDoWhileStatement || element is PsiConditionalExpression || element is PsiForStatement || element is PsiForeachStatement || element is PsiCatchSection || element is PsiSwitchStatement || element is PsiLambdaExpression ) { sink.decreaseNesting() } else if (element is PsiIfStatement && !element.isElseIf()) { sink.decreaseNesting() } } override fun shouldVisitElement(element: PsiElement): Boolean = true private fun PsiIfStatement.processIfExpression() { // if exists `else` that is not a plain IF -> ignoring if (this.isElseIf()) { return } sink.increaseComplexityAndNesting(IF) } } /** * Checking if recursion is used. * Same problems as in [KtLanguageVisitor] */ private fun PsiMethodCallExpression.isRecursion(): Boolean { val parentMethod: PsiMethod = this.findCurrentMethod() ?: return false if (this.methodExpression.text != parentMethod.nameIdentifier?.text) return false if (this.argumentList.expressionCount != parentMethod.parameterList.parametersCount) return false return true } private fun PsiElement.findCurrentMethod(): PsiMethod? { var element: PsiElement? = this while (element != null && element !is PsiMethod) element = element.parent return element?.let { it as PsiMethod } } private fun PsiIfStatement.isElseIf(): Boolean = this.prevNotWhitespace().isElse() private fun PsiElement?.isElse(): Boolean = this?.let { it is PsiKeyword && it.text == PsiKeyword.ELSE } ?: false private fun PsiIfStatement.prevNotWhitespace(): PsiElement? { var prev: PsiElement = this while (prev.prevSibling != null) { prev = prev.prevSibling if (prev !is PsiWhiteSpace) { return prev } } return null } private fun IElementType.toPointType(): PointType = when (this) { JavaTokenType.OROR -> LOGICAL_OR JavaTokenType.ANDAND -> LOGICAL_AND else -> UNKNOWN } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/impl/JavaElementStrategyBuilder.kt ================================================ package com.phodal.shirelang.java.impl import com.intellij.openapi.project.Project import com.intellij.psi.* import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.util.PsiTreeUtil import com.phodal.shirecore.provider.codemodel.model.ClassStructure import com.phodal.shirecore.provider.psi.PsiElementStrategyBuilder import com.phodal.shirelang.java.codemodel.JavaClassStructureProvider class JavaElementStrategyBuilder : PsiElementStrategyBuilder { override fun lookupElement(project: Project, canonicalName: String): ClassStructure? { val psiClass: PsiClass = JavaPsiFacade.getInstance(project) .findClass(canonicalName, GlobalSearchScope.projectScope(project)) ?: return null return JavaClassStructureProvider().build(psiClass, false) } override fun relativeElement(project: Project, givenElement: PsiElement, type: PsiComment): PsiElement? { return PsiTreeUtil.getParentOfType(givenElement, type::class.java) } override fun findNearestTarget(psiElement: PsiElement): PsiNameIdentifierOwner? { if (psiElement is PsiMethod || psiElement is PsiClass) return psiElement as PsiNameIdentifierOwner val closestIdentifierOwner = PsiTreeUtil.getParentOfType(psiElement, PsiNameIdentifierOwner::class.java) if (closestIdentifierOwner !is PsiMethod) { return PsiTreeUtil.getParentOfType(psiElement, PsiMethod::class.java) ?: closestIdentifierOwner } return closestIdentifierOwner } } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/impl/JavaPsiElementDataBuilder.kt ================================================ package com.phodal.shirelang.java.impl import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.openapi.util.NlsSafe import com.intellij.psi.* import com.intellij.psi.impl.source.PsiClassReferenceType import com.intellij.psi.search.GlobalSearchScope import com.phodal.shirecore.provider.codemodel.ClassStructureProvider import com.phodal.shirecore.provider.codemodel.model.ClassStructure import com.phodal.shirecore.provider.psi.PsiElementDataBuilder import com.phodal.shirelang.java.util.JavaContextCollection open class JavaPsiElementDataBuilder : PsiElementDataBuilder { /** * Returns the base route of a given Kotlin language method. * * This method takes a PsiElement as input and checks if it is an instance of PsiMethod. If it is not, an empty string is returned. * If the input element is a PsiMethod, the method checks if its containing class has the annotation "@RequestMapping" from the Spring Framework. * If the annotation is found, the method retrieves the value attribute of the annotation and returns it as a string. * If the value attribute is not a PsiLiteralExpression, an empty string is returned. * * @param element the PsiElement representing the Kotlin language method * @return the base route of the method as a string, or an empty string if the method does not have a base route or if the input element is not a PsiMethod */ override fun baseRoute(element: PsiElement): String { if (element !is PsiMethod) return "" val containingClass = element.containingClass ?: return "" val requestMappingAnnotation = containingClass.annotations.firstOrNull { it.qualifiedName?.endsWith("RequestMapping") == true } ?: return "" val value = requestMappingAnnotation.findAttributeValue("value") as? PsiLiteralExpression return value?.value as? String ?: "" } override fun inboundData(element: PsiElement): Map { if (element !is PsiMethod) return emptyMap() val result = mutableMapOf() val parameters = element.parameterList.parameters for (parameter in parameters) { result += handleFromType(parameter) } return result } private fun handleFromType(parameter: PsiParameter): Map<@NlsSafe String, String> { when (val type = parameter.type) { is PsiClassType -> processingClassType(type) } return emptyMap() } private fun processing(returnType: PsiType): Map<@NlsSafe String, String> { when { returnType is PsiClassType -> { return processingClassType(returnType) } } return mapOf() } private fun processingClassType(type: PsiClassType): Map<@NlsSafe String, String> { val result = mutableMapOf() when (type) { is PsiClassReferenceType -> { type.reference.typeParameters.forEach { result += processing(it) } } } type.resolve()?.let { val qualifiedName = it.qualifiedName!! JavaContextCollection.dataStructure(it)?.let { simpleClassStructure -> result += mapOf(qualifiedName to simpleClassStructure.toString()) } } return result } override fun outboundData(element: PsiElement): Map { if (element !is PsiMethod) return emptyMap() val result = mutableMapOf() val returnType = element.returnType ?: return emptyMap() result += processing(returnType) return result } override fun lookupElement(project: Project, canonicalName: String): ClassStructure? { val psiFacade = JavaPsiFacade.getInstance(project) val psiClass: PsiClass = psiFacade.findClass(canonicalName, GlobalSearchScope.projectScope(project)) ?: return null return ClassStructureProvider.from(psiClass, false) } override fun parseComment(project: Project, code: String): String? { val psiElementFactory = JavaPsiFacade.getInstance(project).elementFactory try { val docComment = psiElementFactory.createDocCommentFromText(code) return docComment.text } catch (e: Exception) { logger().warn("Failed to parse comment: $code", e) } return code } } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/impl/JavaRefactoringTool.kt ================================================ package com.phodal.shirelang.java.impl import com.intellij.codeInsight.daemon.impl.quickfix.RenameElementFix import com.intellij.codeInsight.daemon.impl.quickfix.SafeDeleteFix import com.intellij.codeInspection.MoveToPackageFix import com.intellij.ide.highlighter.JavaFileType import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.ProjectManager import com.intellij.psi.* import com.intellij.psi.search.FileTypeIndex import com.intellij.psi.search.ProjectScope import com.phodal.shirecore.provider.shire.RefactoringTool import com.phodal.shirecore.variable.toolchain.refactoring.RefactorInstElement class JavaRefactoringTool : RefactoringTool { private val project = ProjectManager.getInstance().openProjects.firstOrNull() override fun lookupFile(path: String): PsiFile? { if (project == null) return null val elementInfo = getElementInfo(path, null) ?: return null val searchScope = ProjectScope.getProjectScope(project) val javaFiles: List = FileTypeIndex.getFiles(JavaFileType.INSTANCE, searchScope) .mapNotNull { PsiManager.getInstance(project).findFile(it) as? PsiJavaFile } val className = elementInfo.className val packageName = elementInfo.pkgName val sourceFile = javaFiles.firstOrNull { it.packageName == packageName && it.name == "$className.java" } ?: return null return sourceFile } override fun rename(sourceName: String, targetName: String, psiFile: PsiFile?): Boolean { if (project == null) return false val elementInfo = getElementInfo(sourceName, psiFile) ?: return false val element: PsiNamedElement = if (psiFile != null) { if (psiFile is PsiJavaFile) { val methodName = elementInfo.methodName val className = elementInfo.className val psiMethod: PsiMethod? = psiFile.classes.firstOrNull { it.name == className } ?.methods?.firstOrNull { it.name == methodName } psiMethod ?: psiFile } else { psiFile } } else { searchPsiElementByName(elementInfo, sourceName) ?: return false } try { RenameElementFix(element, targetName) .invoke(project, element.containingFile, element, element) performRefactoringRename(project, element, targetName) } catch (e: Exception) { return false } return true } /** * Deletes the given PsiElement in a safe manner, ensuring that no syntax errors or unexpected behavior occur as a result. * The method performs checks before deletion to confirm that it is safe to remove the element from the code structure. * * @param element The PsiElement to be deleted. This should be a valid element within the PSI tree structure. * @return true if the element was successfully deleted without any issues, false otherwise. This indicates whether * the deletion was performed and considered safe. */ override fun safeDelete(element: PsiElement): Boolean { val delete = SafeDeleteFix(element) try { delete.invoke(element.project, element.containingFile, element, element) } catch (e: Exception) { return false } return true } /** * In Java the canonicalName is the fully qualified name of the target package. * In Kotlin the canonicalName is the fully qualified name of the target package or class. */ override fun move(element: PsiElement, canonicalName: String): Boolean { val file = element.containingFile val fix = MoveToPackageFix(file, canonicalName) try { fix.invoke(file.project, file, element, element) } catch (e: Exception) { return false } return true } private fun searchPsiElementByName(refactorInstElement: RefactorInstElement, sourceName: String): PsiNamedElement? = runReadAction { when { refactorInstElement.isMethod -> { val className = refactorInstElement.className val javaFile = this.lookupFile(sourceName) as? PsiJavaFile ?: return@runReadAction null val psiMethod: PsiMethod = javaFile.classes.firstOrNull { it.name == className } ?.methods?.firstOrNull { it.name == refactorInstElement.methodName } ?: return@runReadAction null psiMethod } refactorInstElement.isClass -> { val javaFile = this.lookupFile(sourceName) as? PsiJavaFile ?: return@runReadAction null javaFile.classes.firstOrNull { it.name == refactorInstElement.className } } else -> { val javaFile = this.lookupFile(sourceName) as? PsiJavaFile ?: return@runReadAction null javaFile } } } /** * input will be canonicalName#methodName or just methodName */ private fun getElementInfo(input: String, psiFile: PsiFile?): RefactorInstElement? { if (!input.contains("#") && psiFile != null) { val javaFile = psiFile as? PsiJavaFile ?: return null val className = javaFile.classes.firstOrNull()?.name ?: return null val canonicalName = javaFile.packageName + "." + className return RefactorInstElement( true, isMethod = true, methodName = input, canonicalName = canonicalName, className = className, pkgName = javaFile.packageName, ) } val isMethod = input.contains("#") val methodName = input.substringAfter("#") val canonicalName = input.substringBefore("#") val maybeClassName = canonicalName.substringAfterLast(".") // the clasName should be Uppercase, or it will be the package var isClass = false var pkgName = canonicalName.substringBeforeLast(".") if (maybeClassName[0].isLowerCase()) { pkgName = "$pkgName.$maybeClassName" } else { isClass = true } return RefactorInstElement(isClass, isMethod, methodName, canonicalName, maybeClassName, pkgName) } } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/impl/JavaShireQLInterpreter.kt ================================================ package com.phodal.shirelang.java.impl import com.intellij.lang.Language import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.searches.AnnotatedElementsSearch import com.intellij.psi.search.searches.ClassInheritorsSearch import com.phodal.shirecore.provider.variable.ShireQLInterpreter import com.phodal.shirecore.function.shireql.JvmShireQLFuncType class JavaShireQLInterpreter : ShireQLInterpreter { override fun supportsMethod(language: Language, methodName: String): List { if (language.id != "JAVA") return emptyList() return JvmShireQLFuncType.entries.map { it.methodName } } /** * clazz.getName() or clazz.extensions */ override fun resolveCall(element: PsiElement, methodName: String, arguments: List): Any { // is of method if (methodName.endsWith("Of")) { return this.resolveOfTypedCall(element.project, methodName, arguments) } return when (element) { is PsiClass -> { when (methodName) { JvmShireQLFuncType.GET_NAME.methodName -> element.name!! JvmShireQLFuncType.NAME.methodName -> element.name!! JvmShireQLFuncType.EXTENDS.methodName -> element .extendsList?.referencedTypes?.map { it.resolve() } ?: emptyList() JvmShireQLFuncType.IMPLEMENTS.methodName -> element .implementsList?.referencedTypes?.map { it.resolve() } ?: emptyList() JvmShireQLFuncType.METHOD_CODE_BY_NAME.methodName -> element .methods .filter { it.name == arguments.first() } JvmShireQLFuncType.FIELD_CODE_BY_NAME.methodName -> element .fields .filter { it.name == arguments.first() } else -> "" } } else -> "" } } override fun resolveOfTypedCall(project: Project, methodName: String, arguments: List): Any { // get first argument for infer type val firstArgument = arguments.firstOrNull().toString() if (firstArgument.isBlank()) { logger().warn("Cannot find first argument") return "" } return when (methodName) { JvmShireQLFuncType.SUBCLASSES_OF.methodName -> { val facade = JavaPsiFacade.getInstance(project) val psiClass = facade.findClass(firstArgument, GlobalSearchScope.projectScope(project)) if (psiClass == null) { logger().warn("Cannot find class: $firstArgument") return "" } val map: List = ClassInheritorsSearch.search(psiClass, GlobalSearchScope.projectScope(project), true).map { it } map } JvmShireQLFuncType.ANNOTATED_OF.methodName -> { val facade = JavaPsiFacade.getInstance(project) val annotationClass = facade.findClass(firstArgument, GlobalSearchScope.allScope(project)) if (annotationClass == null) { logger().warn("Cannot find annotation class: $firstArgument") return "" } val classes = AnnotatedElementsSearch .searchPsiClasses(annotationClass, GlobalSearchScope.projectScope(project)) .findAll() classes.toList() } JvmShireQLFuncType.SUPERCLASS_OF.methodName -> { val psiClass = searchClass(project, firstArgument) ?: return "" psiClass.superClass ?: "" } JvmShireQLFuncType.IMPLEMENTS_OF.methodName -> { val psiClass = searchClass(project, firstArgument) ?: return emptyList() psiClass.implementsList?.referencedTypes ?: emptyList() } else -> { logger().error("Cannot find method: $methodName") } } } private fun searchClass(project: Project, className: String): PsiClass? { val scope = GlobalSearchScope.allScope(project) val psiFacade = JavaPsiFacade.getInstance(project) return psiFacade.findClass(className, scope) } } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/impl/JavaSymbolProvider.kt ================================================ package com.phodal.shirelang.java.impl import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.completion.CompletionUtil import com.intellij.codeInsight.completion.CompletionUtilCore import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.ide.highlighter.JavaFileType import com.intellij.lang.java.JavaLanguage import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.intellij.psi.* import com.intellij.psi.impl.file.impl.JavaFileManagerImpl import com.intellij.psi.search.FileTypeIndex import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.ProjectScope import com.intellij.psi.search.PsiShortNamesCache import com.intellij.psi.util.PsiTreeUtil import com.intellij.util.SmartList import com.phodal.shirecore.provider.shire.ShireSymbolProvider class JavaSymbolProvider : ShireSymbolProvider { override val language: String = JavaLanguage.INSTANCE.displayName override fun lookupSymbol( project: Project, parameters: CompletionParameters, result: CompletionResultSet, ): List { val lookupElements: MutableList = SmartList() val searchScope = ProjectScope.getProjectScope(project) val javaFiles = FileTypeIndex.getFiles(JavaFileType.INSTANCE, searchScope) if (javaFiles.isEmpty()) return lookupElements val prefixMatcher = CompletionUtil.findReferenceOrAlphanumericPrefix(parameters) result.withPrefixMatcher(prefixMatcher) val text = parameters.position.text.removePrefix(CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED) val packageStatements = javaFiles.mapNotNull { val psi = PsiManager.getInstance(project).findFile(it) ?: return@mapNotNull null PsiTreeUtil.getChildrenOfTypeAsList(psi, PsiPackageStatement::class.java).firstOrNull() } packageStatements.forEach { if (it.packageName.startsWith(text)) { val element = LookupElementBuilder.create(it.packageName).withIcon(JavaFileType.INSTANCE.icon) lookupElements.add(element) } } return lookupElements } override fun lookupElementByName(project: Project, name: String): List? { val searchScope = ProjectScope.getProjectScope(project) val virtualFiles = FileTypeIndex.getFiles(JavaFileType.INSTANCE, searchScope) val psiManager = PsiManager.getInstance(project) return when (name) { "PsiFile" -> virtualFiles.mapNotNull(psiManager::findFile).toList() "PsiPackage" -> virtualFiles.mapNotNull { psiManager.findFile(it) } .flatMap { PsiTreeUtil.getChildrenOfTypeAsList(it, PsiPackageStatement::class.java) } .toList() "PsiClass" -> virtualFiles.mapNotNull { psiManager.findFile(it) as PsiJavaFile } .flatMap { it.classes.toList() } .toList() "PsiMethod" -> virtualFiles.mapNotNull { psiManager.findFile(it) as PsiJavaFile } .flatMap { it.classes.toList() } .flatMap { it.methods.toList() } "PsiField" -> virtualFiles.mapNotNull { psiManager.findFile(it) as PsiJavaFile } .flatMap { it.classes.toList() } .flatMap { it.fields.toList() } else -> { null } } } override fun resolveSymbol(project: Project, symbol: String): List { val scope = GlobalSearchScope.allScope(project) if (symbol.isEmpty()) return emptyList() // className only, like `String` not Dot if (symbol.contains(".").not()) { val psiClasses = PsiShortNamesCache.getInstance(project).getClassesByName(symbol, scope) if (psiClasses.isNotEmpty()) { return psiClasses.toList() } } // for package name only, like `cc.unitmesh` JavaFileManagerImpl(project).findPackage(symbol)?.let { pkg -> return pkg.classes.toList() } // for single class, with function name, like `cc.unitmesh.idea.provider.JavaCustomShireSymbolProvider` val clazz = JavaFileManagerImpl(project).findClass(symbol, scope) if (clazz != null) { return clazz.methods.toList() } // for lookup for method val method = symbol.split("#") if (method.size == 2) { val clazzName = method[0] val methodName = method[1] return lookupWithMethodName(project, clazzName, scope, methodName) } // may by not our format, like .. split last val lastDotIndex = symbol.lastIndexOf(".") if (lastDotIndex != -1) { val clazzName = symbol.substring(0, lastDotIndex) val methodName = symbol.substring(lastDotIndex + 1) return lookupWithMethodName(project, clazzName, scope, methodName) } return emptyList() } private fun lookupWithMethodName( project: Project, clazzName: String, scope: GlobalSearchScope, methodName: String, ): List { val psiClass = JavaFileManagerImpl(project).findClass(clazzName, scope) ?: return emptyList() val psiMethod = ApplicationManager.getApplication().executeOnPooledThread { runReadAction { psiClass.findMethodsByName(methodName, true).firstOrNull() } }.get() ?: return emptyList() return listOf(psiMethod) } } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/impl/JvmBuildSystemProvider.kt ================================================ package com.phodal.shirelang.java.impl import com.intellij.openapi.externalSystem.service.project.ProjectDataManager import com.intellij.openapi.project.Project import com.phodal.shirecore.variable.toolchain.buildsystem.BuildSystemContext import com.phodal.shirecore.provider.context.BuildSystemProvider import com.phodal.shirelang.java.toolchain.* import org.jetbrains.plugins.gradle.util.GradleConstants open class JvmBuildSystemProvider : BuildSystemProvider() { override fun collect(project: Project): BuildSystemContext { val projectDataManager = ProjectDataManager.getInstance() val javaVersion = JvmLanguageDetector.detectLanguageLevel(project, null) val gradleInfo = projectDataManager.getExternalProjectsData(project, GradleConstants.SYSTEM_ID) when { gradleInfo.isNotEmpty() -> { val buildTool = GradleBuildTool() return BuildSystemContext( buildToolName = buildTool.toolName(), buildToolVersion = "", languageName = "Java", languageVersion = "$javaVersion", taskString = buildTool.collectTasks(project).joinToString(" ") { it.text }, libraries = buildTool.prepareLibraryData(project)?.map { it.prettyString() } ?: emptyList() ) } else -> { val buildTool = MavenBuildTool() val buildToolName = buildTool.toolName() val libraryData = buildTool.prepareLibraryData(project) val collectTasks = buildTool.collectTasks(project) val taskString = collectTasks.joinToString(" ") { it.text } return BuildSystemContext( buildToolName = buildToolName, buildToolVersion = "", languageName = "Java", languageVersion = "$javaVersion", taskString = taskString, libraries = libraryData.map { it.prettyString() } ) } } } } val JAVA_TASK_COMPLETION_COMPARATOR = Comparator { o1, o2 -> when { o1.startsWith("--") && o2.startsWith("--") -> o1.compareTo(o2) o1.startsWith("-") && o2.startsWith("--") -> -1 o1.startsWith("--") && o2.startsWith("-") -> 1 o1.startsWith(":") && o2.startsWith(":") -> o1.compareTo(o2) o1.startsWith(":") && o2.startsWith("-") -> -1 o1.startsWith("-") && o2.startsWith(":") -> 1 o2.startsWith("-") -> -1 o2.startsWith(":") -> -1 o1.startsWith("-") -> 1 o1.startsWith(":") -> 1 else -> o1.compareTo(o2) } } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/provider/JavaRelatedClassesProvider.kt ================================================ package com.phodal.shirelang.java.provider import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.runReadAction import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.psi.* import com.intellij.psi.util.* import com.intellij.testIntegration.TestFinderHelper import com.phodal.shirecore.provider.psi.RelatedClassesProvider import com.phodal.shirelang.java.util.JavaContextCollection.isJavaBuiltin import com.phodal.shirelang.java.util.JavaContextCollection.isPopularFramework class JavaRelatedClassesProvider : RelatedClassesProvider { override fun lookup(element: PsiElement): List { return when (element) { is PsiMethod -> findRelatedClasses(element) .flatMap { findSuperClasses(it) } .map { cleanUp(it) } .toList() is PsiClass -> findRelatedClasses(element) else -> emptyList() } } override fun lookup(element: PsiFile): List { return when (element) { is PsiJavaFile -> findRelatedClasses(element.classes.first()) + lookupTestFile(element.classes.first()) else -> emptyList() } } private fun lookupTestFile(psiElement: PsiElement): List { if (!psiElement.isValid) return emptyList() return ApplicationManager.getApplication().executeOnPooledThread> { runReadAction { val isTest = TestFinderHelper.isTest(psiElement) if (isTest) return@runReadAction emptyList() TestFinderHelper.findTestsForClass(psiElement) }?.toList() ?: emptyList() }.get() ?: emptyList() } private fun findRelatedClasses(clazz: PsiClass): List { if (!clazz.isValid) return emptyList() val qualifiedName = clazz.qualifiedName return ApplicationManager.getApplication().executeOnPooledThread?> { runReadAction { val methods = clazz.allMethods.flatMap { findRelatedClasses(it) } val fieldsTypes: List = clazz.fields.mapNotNull { when (it.type) { is PsiClassType -> { val resolve = (it.type as PsiClassType).resolve() ?: return@mapNotNull null if (resolve.qualifiedName == qualifiedName) return@mapNotNull null if (isJavaBuiltin(resolve.qualifiedName) == true || isPopularFramework(resolve.qualifiedName) == true) { return@mapNotNull null } resolve } else -> null } } return@runReadAction (fieldsTypes + methods).distinct() } }?.get() ?: emptyList() } /** * Finds related classes to the given PsiMethod by analyzing its parameters, return type, and generic types. * * @param method the PsiMethod for which related classes need to be found * @return a list of PsiClass instances that are related to the given PsiMethod, filtered to include only classes that are part of the project content */ private fun findRelatedClasses(method: PsiMethod): List = runReadAction { if (!method.isValid) return@runReadAction emptyList() val parameters = method.parameterList.parameters val parameterTypes = parameters.map { it.type } val genericTypes = parameters.flatMap { (it.type as? PsiClassType)?.parameters?.toList() ?: emptyList() } val mentionedTypes = parameterTypes + genericTypes val filterIsInstance = mentionedTypes.filterIsInstance() .distinct() return@runReadAction ApplicationManager.getApplication().executeOnPooledThread> { return@executeOnPooledThread filterIsInstance .mapNotNull { runReadAction { it.resolve() } } .filter { isProjectContent(it) } .toList() }.get() } /** * Cleans up a given PsiClass by removing unnecessary elements such as method bodies, method comments, and any other removable members. * * @param psiClass the PsiClass to be cleaned up * @return a new PsiClass with the unnecessary elements removed */ private fun cleanUp(psiClass: PsiClass): PsiClass { val psiElement = psiClass.copy() as PsiClass psiElement.containingFile.setName(psiClass.containingFile.name) val members = PsiTreeUtil.findChildrenOfType(psiElement, PsiMember::class.java) members.filterIsInstance().forEach { it.body?.delete() it.docComment?.delete() } members.filter { canBeRemoved(it) }.forEach { it.delete() } psiElement.docComment?.delete() return psiElement } private fun findSuperClasses(psiClass: PsiClass): List { val superClass = psiClass.superClass ?: return emptyList() if (isProjectContent(superClass)) { return listOf(psiClass.superClass!!, psiClass) } if (isProjectContent(psiClass)) { return listOf(psiClass) } return emptyList() } private fun canBeRemoved(member: PsiMember): Boolean { if (member.modifierList?.hasModifierProperty("public") == true) return false return member.annotations.isEmpty() } private fun isProjectContent(element: PsiElement): Boolean { val virtualFile = PsiUtil.getVirtualFile(element) ?: return false return ApplicationManager.getApplication().executeOnPooledThread { runReadAction { ProjectFileIndex.getInstance(element.project).isInSourceContent(virtualFile) } }.get() } } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/toolchain/GradleBuildTool.kt ================================================ package com.phodal.shirelang.java.toolchain import com.intellij.execution.RunManager import com.intellij.execution.configurations.LocatableConfigurationBase import com.intellij.openapi.externalSystem.model.project.LibraryData import com.intellij.openapi.externalSystem.service.project.ProjectDataManager import com.intellij.openapi.externalSystem.service.ui.completion.TextCompletionInfo import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.util.NlsSafe import com.intellij.openapi.vfs.VirtualFile import com.phodal.shirecore.provider.context.BuildTool import com.phodal.shirecore.provider.context.CommonLibraryData import com.phodal.shirelang.java.impl.JAVA_TASK_COMPLETION_COMPARATOR import org.jetbrains.plugins.gradle.service.execution.GradleExternalTaskConfigurationType import org.jetbrains.plugins.gradle.service.execution.GradleRunConfiguration import org.jetbrains.plugins.gradle.service.project.GradleTasksIndices import org.jetbrains.plugins.gradle.util.GradleConstants import org.jetbrains.plugins.gradle.util.GradleTaskData class GradleBuildTool: BuildTool { override fun toolName(): String = "Gradle" override fun prepareLibraryData(project: Project): List? { val basePath = project.basePath ?: return null val projectData = ProjectDataManager.getInstance().getExternalProjectData( project, GradleConstants.SYSTEM_ID, basePath ) val libraryDataList: List? = projectData?.externalProjectStructure?.children?.filter { it.data is LibraryData }?.map { it.data as LibraryData } return libraryDataList?.map { CommonLibraryData(it.groupId, it.artifactId, it.version) } } override fun collectTasks(project: Project): List { val indices = GradleTasksIndices.getInstance(project) val tasks = indices.findTasks(project.guessProjectDir()!!.path) .filterNot { it.isInherited } .groupBy { it.name } .map { TextCompletionInfo(it.key, it.value.first().description) } .sortedWith( Comparator.comparing( { it.text }, JAVA_TASK_COMPLETION_COMPARATOR ) ) return tasks } override fun configureRun( project: Project, taskName: String, virtualFile: VirtualFile?, ): LocatableConfigurationBase<*> { val runManager = RunManager.getInstance(project) val configuration = runManager.createConfiguration( taskName, GradleExternalTaskConfigurationType::class.java ) val runConfiguration = configuration.configuration as GradleRunConfiguration runConfiguration.isDebugServerProcess = false runConfiguration.settings.externalProjectPath = project.guessProjectDir()?.path runConfiguration.rawCommandLine = taskName runManager.addConfiguration(configuration) runManager.selectedConfiguration = configuration return runConfiguration } companion object { fun collectGradleTasksData(project: Project): List { val indices = GradleTasksIndices.getInstance(project) val tasks = indices.findTasks(project.guessProjectDir()!!.path) return tasks } } } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/toolchain/JvmLanguageDetector.kt ================================================ package com.phodal.shirelang.java.toolchain import com.intellij.openapi.module.LanguageLevelUtil import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.module.ModuleUtilCore import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.JavaSdkType import com.intellij.openapi.roots.ModuleRootManager import com.intellij.openapi.roots.ProjectRootManager import com.intellij.pom.java.LanguageLevel import com.intellij.psi.PsiFile import com.intellij.psi.util.PsiUtil object JvmLanguageDetector { fun detectLanguageLevel(project: Project, sourceFile: PsiFile?): LanguageLevel? { val projectSdk = ProjectRootManager.getInstance(project).projectSdk if (projectSdk != null) { if (projectSdk.sdkType !is JavaSdkType) return null return PsiUtil.getLanguageLevel(project) } val moduleForFile = ModuleUtilCore.findModuleForFile(sourceFile) ?: ModuleManager.getInstance(project).modules.firstOrNull() ?: return null if (ModuleRootManager.getInstance(moduleForFile).sdk !is JavaSdkType) return null return LanguageLevelUtil.getEffectiveLanguageLevel(moduleForFile) } } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/toolchain/JvmRunProjectService.kt ================================================ package com.phodal.shirelang.java.toolchain import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.completion.PrioritizedLookupElement import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.JavaSdk import com.intellij.openapi.roots.ProjectRootManager import com.intellij.util.SmartList import com.phodal.shirecore.provider.shire.ProjectRunService import com.phodal.shirecore.runner.ConfigurationRunner import icons.GradleIcons class JvmRunProjectService : ProjectRunService, ConfigurationRunner { override fun isAvailable(project: Project): Boolean { return ProjectRootManager.getInstance(project).projectSdk is JavaSdk } override fun run(project: Project, taskName: String) { val runConfiguration = GradleBuildTool().configureRun(project, taskName, null) executeRunConfigurations(project, runConfiguration) } override fun lookupAvailableTask( project: Project, parameters: CompletionParameters, result: CompletionResultSet, ): List { val lookupElements: MutableList = SmartList() GradleBuildTool.collectGradleTasksData(project) .filter { !it.isTest && !it.isJvmTest } .forEach { val element = LookupElementBuilder.create(it.getFqnTaskName()) .withTypeText(it.description) .withIcon(GradleIcons.Gradle) lookupElements.add(PrioritizedLookupElement.withPriority(element, 99.0)) } return lookupElements } override fun tasks(project: Project): List { return GradleBuildTool.collectGradleTasksData(project).map { it.getFqnTaskName() } } } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/toolchain/MavenBuildTool.kt ================================================ package com.phodal.shirelang.java.toolchain import com.intellij.execution.RunManager import com.intellij.execution.configurations.LocatableConfigurationBase import com.intellij.openapi.application.runReadAction import com.intellij.openapi.externalSystem.service.ui.completion.TextCompletionInfo import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.phodal.shirecore.provider.context.BuildTool import com.phodal.shirecore.provider.context.CommonLibraryData import com.phodal.shirelang.java.impl.JAVA_TASK_COMPLETION_COMPARATOR import org.jetbrains.idea.maven.execution.MavenRunConfiguration import org.jetbrains.idea.maven.execution.MavenRunConfigurationType import org.jetbrains.idea.maven.execution.MavenRunnerParameters import org.jetbrains.idea.maven.project.MavenProject import org.jetbrains.idea.maven.project.MavenProjectsManager class MavenBuildTool() : BuildTool { override fun toolName(): String = "Maven" override fun prepareLibraryData(project: Project): List { val projectDependencies: List = MavenProjectsManager.getInstance(project).projects.flatMap { it.dependencies } return projectDependencies.map { CommonLibraryData(it.groupId, it.artifactId, it.version) } } override fun collectTasks(project: Project): List { val projectsManager = MavenProjectsManager.getInstance(project) val mavenProjects: List = projectsManager.projects val tasks = mavenProjects.flatMap { it.plugins }.flatMap { it.executions } .map { TextCompletionInfo(it.executionId, it.phase) } .sortedWith(Comparator.comparing({ it.text }, JAVA_TASK_COMPLETION_COMPARATOR)) return tasks } override fun configureRun( project: Project, taskName: String, virtualFile: VirtualFile?, ): LocatableConfigurationBase<*>? { if (virtualFile == null) return null val projectsManager = MavenProjectsManager.getInstance(project); val mavenProject: MavenProject = projectsManager.findProject(virtualFile) ?: return null val module = runReadAction { projectsManager.findModule(mavenProject) } ?: return null var trulyMavenProject = projectsManager.projects.firstOrNull { it.mavenId.artifactId == module.name } if (trulyMavenProject == null) { trulyMavenProject = projectsManager.projects.first() ?: return null } val pomFile = trulyMavenProject.file.name val parameters = MavenRunnerParameters( true, trulyMavenProject.directory, pomFile, listOf("test"), projectsManager.explicitProfiles.enabledProfiles, arrayListOf() ) // $MODULE_WORKING_DIR$ // // -ea Method: com.example.demo.MathHelperTest should_ReturnSum_When_GivenTwoPositiveNumbers // /Users/phodal/Library/Java/JavaVirtualMachines/corretto-18.0.2/Contents/Home/bin/java // -ea -Didea.test.cyclic.buffer.size=1048576 // -javaagent:ideaIU-2024.1/lib/idea_rt.jar=54637:1/bin -Dfile.encoding=UTF-8 // -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8 val runnerAndConfigurationSettings = MavenRunConfigurationType.createRunnerAndConfigurationSettings(null, null, parameters, project) val runManager = RunManager.getInstance(project) val configuration = runnerAndConfigurationSettings.configuration runManager.addConfiguration(runnerAndConfigurationSettings) runManager.selectedConfiguration = runnerAndConfigurationSettings return configuration as MavenRunConfiguration } } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/util/JavaContextCollection.kt ================================================ package com.phodal.shirelang.java.util import com.intellij.openapi.diagnostic.logger import com.intellij.psi.* import com.intellij.psi.impl.source.PsiClassReferenceType object JavaContextCollection { private val logger = logger() /** * This method takes a PsiClass object as input and builds a tree of the class and its fields, including the fields of the fields, and so on. The resulting tree is represented as a HashMap where the keys are the PsiClass objects and the values are ArrayLists of PsiField objects. * * @param clazz the PsiClass object for which the tree needs to be built * @return a HashMap where the keys are the PsiClass objects, and the values are ArrayLists of PsiField objects * * For example, if a BlogPost class includes a Comment class, and the Comment class includes a User class, then the resulting tree will be: * * ``` * parent: BlogPost Psi * child: id * child: Comment * child: User * child: name *``` */ fun dataStructure(clazz: PsiClass): SimpleClassStructure? { return simpleStructure(clazz) } private val psiStructureCache = mutableMapOf() /** * Creates a simple class structure for the given PsiClass and search scope. * * @param clazz the PsiClass for which the simple class structure needs to be created. * @return a SimpleClassStructure object representing the simple class structure of the given PsiClass. * The object contains the name of the class, the name of the fields, their types, and whether they are built-in or not. * If the field type is a primitive type or a boxed type, it is marked as built-in. * If the field type is a custom class, the method recursively creates a SimpleClassStructure object for that class. * If the field type cannot be resolved, it is skipped. */ private fun simpleStructure(clazz: PsiClass): SimpleClassStructure? { // skip for generic if (clazz.name?.uppercase() == clazz.name && clazz.name?.length == 1) return null val qualifiedName = clazz.qualifiedName if ((qualifiedName != null) && psiStructureCache.containsKey(clazz)) { return psiStructureCache[clazz]!! } if (isJavaBuiltin(qualifiedName) == true || isPopularFramework(qualifiedName)) return null val classStructures = clazz.fields.mapNotNull { field -> // if current field same to parent class, skip it if (field.type == clazz) return@mapNotNull null if (field.type is PsiTypeParameter) return@mapNotNull null val simpleClassStructure = when { // like: int, long, boolean, etc. field.type is PsiPrimitiveType -> { SimpleClassStructure(field.name, field.type.presentableText, emptyList(), builtIn = true) } // like: String, List, etc. isPsiBoxedType(field.type) -> { SimpleClassStructure(field.name, field.type.presentableText, emptyList(), builtIn = true) } field.type is PsiTypeParameter -> { null } field.type is PsiClassType -> { // skip for some frameworks like, org.springframework, etc. val resolve = (field.type as PsiClassType).resolve() ?: return@mapNotNull null if (resolve.qualifiedName == qualifiedName) return@mapNotNull null if (isJavaBuiltin(resolve.qualifiedName) == true || isPopularFramework(resolve.qualifiedName) == true) { return@mapNotNull null } val classStructure = simpleStructure(resolve) ?: return@mapNotNull null classStructure.fieldName = field.name classStructure.builtIn = false classStructure } else -> { logger.warn("Unknown supported type: ${field.type}") return@mapNotNull null } } simpleClassStructure } val simpleClassStructure = SimpleClassStructure(clazz.name ?: "", clazz.name ?: "", classStructures) psiStructureCache[clazz] = simpleClassStructure return simpleClassStructure } private val popularFrameworks = listOf( "org.springframework", "org.apache", "org.hibernate", "org.slf4j", "org.junit", "org.mockito" ) fun isPopularFramework(qualifiedName: String?): Boolean { return popularFrameworks.any { qualifiedName?.startsWith(it) == true } } /** * Checks if the given PsiType is a boxed type. * * A boxed type refers to a type that is represented by a PsiClassReferenceType and its resolve() method returns null. * This typically occurs when the type is a generic type parameter or a type that cannot be resolved in the current context. * * @param type the PsiType to be checked * @return true if the given type is a boxed type, false otherwise */ private fun isPsiBoxedType(type: PsiType): Boolean { if (type !is PsiClassReferenceType) return false val resolve = try { type.resolve() ?: return true } catch (e: Exception) { return false } return isJavaBuiltin(resolve.qualifiedName) == true } fun isJavaBuiltin(qualifiedName: String?) = qualifiedName?.startsWith("java.") } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/util/JavaTestHelper.kt ================================================ package com.phodal.shirelang.java.util import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.util.ProgressIndicatorBase import com.intellij.openapi.project.Project import com.intellij.psi.* import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.ProjectScope import com.intellij.psi.search.PsiShortNamesCache import com.intellij.psi.search.searches.ClassInheritorsSearch import com.intellij.psi.search.searches.MethodReferencesSearch import com.intellij.psi.util.* import com.phodal.shirecore.search.algorithm.TfIdf import com.phodal.shirecore.search.tokenizer.CodeNamingTokenizer object JavaTestHelper { fun extractMethodCalls(project: Project, psiElement: PsiElement): String { val searchScope = GlobalSearchScope.allScope(project) return when (psiElement) { is PsiFile -> { PsiTreeUtil.findChildrenOfAnyType(psiElement, PsiClass::class.java) .joinToString("\n") { extractMethodCalls(project, it) } } is PsiClass -> { PsiTreeUtil.findChildrenOfAnyType(psiElement, PsiMethod::class.java) .joinToString("\n") { extractMethodCalls(project, it) } } is PsiMethod -> { PsiTreeUtil.findChildrenOfAnyType(psiElement.body, PsiMethodCallExpression::class.java) .filter { isMethodCallFromInheritedClass(it, searchScope) } .joinToString("\n") { it.text } } else -> "" } } private fun isMethodCallFromInheritedClass( callExpression: PsiMethodCallExpression, searchScope: GlobalSearchScope, ): Boolean { val resolvedMethod = callExpression.resolveMethod() ?: return false val containingClass = resolvedMethod.containingClass ?: return false return ClassInheritorsSearch.search(containingClass, searchScope, true).findAll().isNotEmpty() } fun searchSimilarTestCases(psiElement: PsiElement, minScore: Double = 1.0): List { val project = psiElement.project val psiMethod = psiElement as? PsiMethod ?: return emptyList() val methodName = psiMethod.name // 使用缓存机制获取所有测试方法 val allTestMethods = getAllTestMethods(project) // 使用 TfIdf 进行相似度计算 val tfIdf = TfIdf>() tfIdf.setTokenizer(CodeNamingTokenizer()) allTestMethods.forEach { tfIdf.addDocument(it.name, it) } return tfIdf.tfidfs(methodName) .mapIndexedNotNull { index, measure -> if (measure > minScore) allTestMethods[index] else null } } /** * Retrieves all test methods from the given project. * * @param project the project from which to retrieve the test methods * @return a list of PsiMethod objects representing all test methods found in the project */ private fun getAllTestMethods(project: Project): List { val cachedValue: CachedValue> = CachedValuesManager.getManager(project).createCachedValue { val testMethods = mutableListOf() val scope = GlobalSearchScope.projectScope(project) PsiShortNamesCache.getInstance(project).allClassNames .filter { it.contains("Test") } .forEach { className -> PsiShortNamesCache.getInstance(project).getClassesByName(className, scope) .filter { it.containingFile.name.endsWith("Test.java") } .forEach { psiClass -> testMethods.addAll(psiClass.methods) } } CachedValueProvider.Result.create(testMethods, PsiModificationTracker.MODIFICATION_COUNT) } return cachedValue.value } /** * Finds all the methods called by the given method. * * @param method the method for which callees need to be found * @return a list of PsiMethod objects representing the methods called by the given method */ fun findCallees(project: Project, method: PsiMethod): List { val calledMethods = mutableSetOf() method.accept(object : JavaRecursiveElementVisitor() { override fun visitMethodCallExpression(expression: PsiMethodCallExpression) { super.visitMethodCallExpression(expression) calledMethods.add(expression.resolveMethod() ?: return) } }) return calledMethods .mapNotNull { val containingClass = it.containingClass ?: return@mapNotNull null if (!ProjectScope.getProjectScope(project).contains(containingClass.containingFile.virtualFile)) { return@mapNotNull null } "ClassName: ${containingClass.qualifiedName}\nMethodText:\n${it.text}" } } /** * Finds all the callers of a given method. * * @param method the method for which callers need to be found * @return a list of PsiMethod objects representing the callers of the given method */ fun findCallers(project: Project, method: PsiMethod): List { val callers: MutableList = ArrayList() ProgressManager.getInstance().runProcess(Runnable { val references = MethodReferencesSearch.search(method, method.useScope, true).findAll() for (reference in references) { PsiTreeUtil.getParentOfType(reference.element, PsiMethod::class.java)?.let { callers.add(it) } } }, ProgressIndicatorBase()) val psiMethods = callers.distinct() return psiMethods .mapNotNull { val containingClass = it.containingClass ?: return@mapNotNull null "ClassName: ${containingClass.qualifiedName}\nMethodText:\n${it.text}" } } } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/util/JavaTypeResolver.kt ================================================ package com.phodal.shirelang.java.util import com.intellij.openapi.application.runReadAction import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.psi.* import com.intellij.psi.impl.source.PsiClassReferenceType import com.intellij.psi.util.PsiUtil object JavaTypeResolver { fun resolveByType(outputType: PsiType?): Map { val resolvedClasses = mutableMapOf() if (outputType is PsiClassReferenceType) { resolvedClasses.putAll(resolveTypeReferences(outputType)) } return resolvedClasses.filter { isProjectContent(it.value) }.toMap() } private fun resolveTypeReferences(outputType: PsiClassReferenceType): MutableMap { val resolvedClasses = mutableMapOf() fun resolveRecursively(type: PsiClassReferenceType) { val resolvedClass = type.resolve() if (resolvedClass != null) { resolvedClasses[type.canonicalText] = resolvedClass } type.parameters.filterIsInstance().forEach { childType -> resolveRecursively(childType) } } resolveRecursively(outputType) return resolvedClasses } fun resolveByField(element: PsiElement): Map { val psiFile = element.containingFile as PsiJavaFile val resolvedClasses = mutableMapOf() psiFile.classes.forEach { psiClass -> psiClass.fields.forEach { field -> resolvedClasses.putAll(resolveByType(field.type)) } } return resolvedClasses.filter { isProjectContent(it.value) }.toMap() } fun resolveByClass(element: PsiElement): Map { val resolvedClasses = mutableMapOf() if (element !is PsiClass) { return emptyMap() } return runReadAction { element.fields.forEach { field -> resolvedClasses.putAll(resolveByType(field.type)) } element.methods.forEach { method -> resolvedClasses.putAll(resolveByMethod(method)) } resolvedClasses.filter { isProjectContent(it.value) }.toMap() } } /** * The resolved classes include all the classes in the method signature. For example, if the method signature is * Int, will return Int, but if the method signature is List, will return List and Int. * So, remember to filter out the classes that are not needed. */ fun resolveByMethod(element: PsiElement): Map { val resolvedClasses = mutableMapOf() if (element !is PsiMethod) { return emptyMap() } return runReadAction { element.parameterList.parameters .filter { it.type is PsiClassReferenceType } .map { parameter -> val type = parameter.type as PsiClassReferenceType val resolve: PsiClass = type.resolve() ?: return@map null val typeParametersTypeList: List = getTypeParametersType(type) val relatedClass = mutableListOf(parameter.type) relatedClass.addAll(typeParametersTypeList) relatedClass .filter { isProjectContent((it as PsiClassReferenceType).resolve() ?: return@filter false) } .forEach { resolvedClasses.putAll(resolveByType(it)) } // class kotlin.Unit cannot be cast to class java.lang.Void if (resolve is PsiClass) { resolvedClasses[parameter.name] = resolve } resolvedClasses } val outputType = element.returnTypeElement?.type resolvedClasses.putAll(resolveByType(outputType)) resolvedClasses.filter { isProjectContent(it.value) }.toMap() } } private fun getTypeParametersType( psiType: PsiClassReferenceType, ): List { val result = psiType.resolveGenerics() val psiClass = result.element ?: return emptyList(); return runReadAction { val substitutor = result.substitutor psiClass.typeParameters.mapNotNull { substitutor.substitute(it) } } } } fun isProjectContent(element: PsiElement): Boolean { val virtualFile = PsiUtil.getVirtualFile(element) val project = runReadAction { element.project } return virtualFile == null || ProjectFileIndex.getInstance(project).isInContent(virtualFile) } fun PsiElement.getContainingClass(): PsiClass? { var context: PsiElement? = this.context while (context != null) { if (context is PsiClass) return context if (context is PsiMember) return context.containingClass context = context.context } return null } fun PsiElement.getContainingMethod(): PsiMethod? { var context: PsiElement? = this.context while (context != null) { if (context is PsiMethod) return context context = context.context } return null } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/util/SimpleClassStructure.kt ================================================ package com.phodal.shirelang.java.util data class SimpleClassStructure( var fieldName: String, var fieldType: String, val children: List, var builtIn: Boolean = false ) { private val childrenUml: MutableMap = mutableMapOf() /** * Returns a PlantUML string representation of the class structure. * * This method generates a PlantUML string representation of the class structure based on the current object and its child objects. The resulting string is built using the buildPuml() method and the childPuml map. The resulting string will be a tree-like structure that shows the relationships between the classes. * * @return the PlantUML string representation of the class structure * * For example, if a BlogPost class includes a Comment class, and the Comment class includes a User class, then the resulting tree will be: * * ```puml * class BlogPost { * id: long * comment: Comment *} * * class Comment { * user: User * } * * class User { * name: String * } *``` */ override fun toString(): String { val diagramBuilder = StringBuilder() diagramBuilder.append(classStructureToUml(this)) buildChildUmlHierarchy(children) childrenUml.forEach { diagramBuilder.append("\n") diagramBuilder.append(it.value) } return diagramBuilder.toString() } /** * Returns a PlantUML string representation of the class structure * for example: * ``` * class BlogPost { * long id; * Comment comment; * } * class Comment { * User user; * } * class User { * String name; * } *``` * * will be represented as: * * ```puml * class BlogPost { * long id; * Comment comment; *} *``` */ private fun classStructureToUml(simpleClassStructure: SimpleClassStructure): String { val children = simpleClassStructure.children.joinToString("\n") { " ${it.fieldName}: ${it.fieldType}" } return "class ${simpleClassStructure.fieldType} {\n" + children + "\n}\n" } private fun buildChildUmlHierarchy(data: List) { data.filter { !it.builtIn }.forEach { childrenUml[it.fieldType] = classStructureToUml(it) buildChildUmlHierarchy(it.children) } } } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/variable/JavaLanguageToolchainProvider.kt ================================================ package com.phodal.shirelang.java.variable import com.intellij.lang.java.JavaLanguage import com.intellij.openapi.module.Module import com.intellij.openapi.module.ModuleUtilCore import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.JavaSdkType import com.intellij.openapi.roots.ModuleRootManager import com.intellij.openapi.roots.ProjectRootManager import com.intellij.psi.PsiJavaFile import com.phodal.shirecore.provider.context.LanguageToolchainProvider import com.phodal.shirecore.provider.context.ToolchainContextItem import com.phodal.shirecore.provider.context.ToolchainPrepareContext import com.phodal.shirelang.java.toolchain.JvmLanguageDetector class JavaLanguageToolchainProvider : LanguageToolchainProvider { override fun isApplicable(project: Project, context: ToolchainPrepareContext): Boolean { val sourceFile = context.sourceFile ?: return false if (sourceFile.language != JavaLanguage.INSTANCE) return false val projectSdk = ProjectRootManager.getInstance(project).projectSdk if (projectSdk?.sdkType !is JavaSdkType) return false val module: Module = try { ModuleUtilCore.findModuleForFile(sourceFile) } catch (e: Exception) { return false } ?: return false return ModuleRootManager.getInstance(module).sdk?.sdkType is JavaSdkType } override suspend fun collect( project: Project, context: ToolchainPrepareContext, ): List { return collectJavaVersion(context, project)?.let { listOf(it) } ?: emptyList() } private fun collectJavaVersion( context: ToolchainPrepareContext, project: Project, ): ToolchainContextItem? { val psiFile = context.sourceFile as? PsiJavaFile ?: return null val languageLevel = JvmLanguageDetector.detectLanguageLevel(project, psiFile) ?: return null val prompt = "You are working on a project that uses Java SDK version $languageLevel." return ToolchainContextItem(JavaLanguageToolchainProvider::class, prompt) } } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/variable/JavaPsiContextVariableProvider.kt ================================================ package com.phodal.shirelang.java.variable import com.intellij.lang.java.JavaLanguage import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement import com.intellij.psi.PsiJavaFile import com.intellij.psi.PsiMethod import com.intellij.testIntegration.TestFinderHelper import com.phodal.shirecore.provider.variable.PsiContextVariableProvider import com.phodal.shirecore.psi.CodeSmellCollector import com.phodal.shirecore.provider.variable.model.PsiContextVariable import com.phodal.shirecore.provider.variable.model.PsiContextVariable.* import com.phodal.shirecore.search.similar.SimilarChunksSearch import com.phodal.shirelang.java.codemodel.JavaClassStructureProvider import com.phodal.shirelang.java.util.JavaTestHelper import com.phodal.shirelang.java.util.getContainingClass import com.phodal.shirelang.java.provider.JavaRelatedClassesProvider class JavaPsiContextVariableProvider : PsiContextVariableProvider { override fun resolve(variable: PsiContextVariable, project: Project, editor: Editor, psiElement: PsiElement?): Any { if (psiElement?.language != JavaLanguage.INSTANCE) return "" val clazz: PsiClass? = psiElement as? PsiClass ?: psiElement.getContainingClass() val sourceFile: PsiJavaFile = psiElement.containingFile as PsiJavaFile return when (variable) { IMPORTS -> sourceFile.importList?.text ?: "" CURRENT_CLASS_NAME -> clazz?.name ?: "" CURRENT_CLASS_CODE -> sourceFile.text CURRENT_METHOD_NAME -> (psiElement as? PsiMethod)?.name ?: "" CURRENT_METHOD_CODE -> (psiElement as? PsiMethod)?.text ?: "" RELATED_CLASSES -> JavaRelatedClassesProvider().lookup(psiElement.parent).joinToString("\n") { it.text } SIMILAR_TEST_CASE -> JavaTestHelper.searchSimilarTestCases(psiElement).joinToString("\n") { it.text } IS_NEED_CREATE_FILE -> TestFinderHelper.findClassesForTest(psiElement).isEmpty() TARGET_TEST_FILE_NAME -> sourceFile.name.replace(".java", "") + "Test.java" UNDER_TEST_METHOD_CODE -> JavaTestHelper.extractMethodCalls(project, psiElement) CODE_SMELL -> CodeSmellCollector.collectElementProblemAsSting(psiElement, project, editor) METHOD_CALLER -> { if (psiElement !is PsiMethod) return "" return JavaTestHelper.findCallers(project, psiElement).joinToString("\n\n") } CALLED_METHOD -> { if (psiElement !is PsiMethod) return "" return JavaTestHelper.findCallees(project, psiElement).joinToString("\n\n") } SIMILAR_CODE -> return SimilarChunksSearch.createQuery(psiElement) ?: "" STRUCTURE -> clazz?.let { JavaClassStructureProvider().build(it, true)?.format() ?: "" } ?: "" FRAMEWORK_CONTEXT -> return collectFrameworkContext(psiElement, project) CHANGE_COUNT -> calculateChangeCount(psiElement) LINE_COUNT -> calculateLineCount(psiElement) COMPLEXITY_COUNT -> calculateComplexityCount(psiElement) } } } ================================================ FILE: languages/shire-java/src/main/kotlin/com/phodal/shirelang/java/variable/JavaVariableProvider.kt ================================================ package com.phodal.shirelang.java.variable import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.phodal.shirecore.provider.variable.ToolchainVariableProvider import com.phodal.shirecore.provider.variable.model.ToolchainVariable import com.phodal.shirecore.provider.variable.model.toolchain.BuildToolchainVariable import com.phodal.shirelang.java.impl.JvmBuildSystemProvider import com.phodal.shirelang.java.toolchain.GradleBuildTool class JavaVariableProvider : ToolchainVariableProvider { override fun isResolvable(variable: ToolchainVariable, psiElement: PsiElement?, project: Project): Boolean { return variable is BuildToolchainVariable && GradleBuildTool().prepareLibraryData(project)?.isNotEmpty() == true } override fun resolve(variable: ToolchainVariable, project: Project, editor: Editor, psiElement: PsiElement?): Any { return when (variable) { BuildToolchainVariable.ProjectDependencies -> JvmBuildSystemProvider().collect(project) else -> "" } ?: "" } } ================================================ FILE: languages/shire-java/src/main/resources/com.phodal.shirelang.java.xml ================================================ ================================================ FILE: languages/shire-java/src/test/kotlin/com/phodal/shirelang/java/complexity/JavaComplexityProviderTest.kt ================================================ package com.phodal.shirelang.java.complexity import com.intellij.psi.PsiJavaFile import com.intellij.testFramework.fixtures.BasePlatformTestCase import org.intellij.lang.annotations.Language class JavaComplexityProviderTest: BasePlatformTestCase() { fun testShouldCalculateComplexitySize2() { @Language("Java") val code = """ public class TestClass { @Complexity(4) public void parenthesisInCenterSplitTheGroup() { if ( // +1 if a || b || // +1 OR !(c || d) // +1 OR separate || e || f) { // +1 new OR return; } } } """.trimIndent() val psiFile = myFixture.addFileToProject("TestClass.java", code) as PsiJavaFile val psiClass = psiFile.classes[0] val complexityProvider = JavaComplexityProvider() val result = complexityProvider.process(psiClass) assertEquals(4, result) } } ================================================ FILE: languages/shire-java/src/test/kotlin/com/phodal/shirelang/java/impl/JavaBuildSystemProviderTest.kt ================================================ package com.phodal.shirelang.java.impl import org.junit.Assert.assertEquals import org.junit.Test class JavaTaskCompletionComparatorTest { @Test fun should_compare_when_both_start_with_double_dash() { // Given val o1 = "--task1" val o2 = "--task2" // When val result = JAVA_TASK_COMPLETION_COMPARATOR.compare(o1, o2) // Then assertEquals(o1.compareTo(o2), result) } @Test fun should_compare_when_first_starts_with_dash_and_second_with_double_dash() { // Given val o1 = "-task1" val o2 = "--task2" // When val result = JAVA_TASK_COMPLETION_COMPARATOR.compare(o1, o2) // Then assertEquals(-1, result) } @Test fun should_compare_when_first_starts_with_double_dash_and_second_with_dash() { // Given val o1 = "--task1" val o2 = "-task2" // When val result = JAVA_TASK_COMPLETION_COMPARATOR.compare(o1, o2) // Then assertEquals(1, result) } @Test fun should_compare_when_both_start_with_colon() { // Given val o1 = ":task1" val o2 = ":task2" // When val result = JAVA_TASK_COMPLETION_COMPARATOR.compare(o1, o2) // Then assertEquals(o1.compareTo(o2), result) } @Test fun should_compare_when_first_starts_with_colon_and_second_with_dash() { // Given val o1 = ":task1" val o2 = "-task2" // When val result = JAVA_TASK_COMPLETION_COMPARATOR.compare(o1, o2) // Then assertEquals(-1, result) } @Test fun should_compare_when_first_starts_with_dash_and_second_with_colon() { // Given val o1 = "-task1" val o2 = ":task2" // When val result = JAVA_TASK_COMPLETION_COMPARATOR.compare(o1, o2) // Then assertEquals(1, result) } @Test fun should_compare_when_second_starts_with_dash() { // Given val o1 = "task1" val o2 = "-task2" // When val result = JAVA_TASK_COMPLETION_COMPARATOR.compare(o1, o2) // Then assertEquals(-1, result) } @Test fun should_compare_when_second_starts_with_colon() { // Given val o1 = "task1" val o2 = ":task2" // When val result = JAVA_TASK_COMPLETION_COMPARATOR.compare(o1, o2) // Then assertEquals(-1, result) } @Test fun should_compare_when_first_starts_with_dash() { // Given val o1 = "-task1" val o2 = "task2" // When val result = JAVA_TASK_COMPLETION_COMPARATOR.compare(o1, o2) // Then assertEquals(1, result) } @Test fun should_compare_when_first_starts_with_colon() { // Given val o1 = ":task1" val o2 = "task2" // When val result = JAVA_TASK_COMPLETION_COMPARATOR.compare(o1, o2) // Then assertEquals(1, result) } @Test fun should_compare_when_neither_starts_with_dash_or_colon() { // Given val o1 = "task1" val o2 = "task2" // When val result = JAVA_TASK_COMPLETION_COMPARATOR.compare(o1, o2) // Then assertEquals(o1.compareTo(o2), result) } } ================================================ FILE: languages/shire-java/src/test/kotlin/com/phodal/shirelang/java/impl/JavaPsiQLInterpreterTest.kt ================================================ package com.phodal.shirelang.java.impl import com.intellij.psi.PsiClass import com.intellij.psi.PsiJavaFile import com.intellij.testFramework.fixtures.BasePlatformTestCase class JavaPsiQLInterpreterTest: BasePlatformTestCase() { fun testShouldSuccessGetClassName() { val runnableCode = """ public class Runnable { public String getName() { return "Runnable"; } } """.trimIndent() myFixture.addFileToProject("Runnable.java", runnableCode) val shireObjectCode = """ public class ShireObject { public String getName() { return "ShireObject"; } } """.trimIndent() myFixture.addFileToProject("ShireObject.java", shireObjectCode) val javaClassCode = """ public class TestClass extends ShireObject implements Runnable { public String getName() { return "TestClass"; } } """.trimIndent() val psiFile = myFixture.addFileToProject("TestClass.java", javaClassCode) as PsiJavaFile val psiClass = psiFile.classes[0] val interpreter = JavaShireQLInterpreter() val result = interpreter.resolveCall(psiClass, "getName", emptyList()) val extendsClasses = interpreter.resolveCall(psiClass, "extends", emptyList()) // implements val implementsClass = interpreter.resolveCall(psiClass, "implements", emptyList()) assertEquals("TestClass", result) assertEquals("ShireObject", (extendsClasses as List).first().name) assertEquals("Runnable", (implementsClass as List).first().name) } fun testShouldResolveParentOf() { val javaClassCode = """ public class TestClass extends ShireObject implements Runnable { public String getName() { return "TestClass"; } } """.trimIndent() val shireObjectCode = """ public class ShireObject { public String getName() { return "ShireObject"; } } """.trimIndent() val psiFile = myFixture.addFileToProject("TestClass.java", javaClassCode) as PsiJavaFile myFixture.addFileToProject("ShireObject.java", shireObjectCode) val psiClass = psiFile.classes[0] val interpreter = JavaShireQLInterpreter() val result = interpreter.resolveCall(psiClass, "superclassOf", listOf("TestClass")) // extendsOf val extendsClasses = interpreter.resolveOfTypedCall(project, "subclassesOf", listOf("ShireObject")) assertEquals("ShireObject", (result as PsiClass).name) assertEquals("TestClass", (extendsClasses as List).first().name) } } ================================================ FILE: languages/shire-java/src/test/kotlin/com/phodal/shirelang/java/toolchain/SpringLayerCharacteristicTest.kt ================================================ package com.phodal.shirelang.java.toolchain import com.phodal.shirelang.java.archmeta.SpringLayerCharacteristic import org.junit.Test class SpringLayerCharacteristicTest { @Test fun should_return_true_when_spring_controller() { val code = """ package cc.unitmesh.devti.flow; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RestController; @Controller public class UserController { } """.trimIndent() val result = SpringLayerCharacteristic.check(code, "controller") assert(result) } @Test fun should_return_true_when_is_a_mvc_service() { val serviceCode = """ @Service public class HelloWorldService { } """.trimIndent() val result = SpringLayerCharacteristic.check(serviceCode, "service") assert(result) } @Test fun should_return_true_when_given_a_dto_code() { val dtoCode = """ @Data public class UserDto { } """.trimIndent() val result = SpringLayerCharacteristic.check(dtoCode, "dto") assert(result) } @Test fun should_return_true_when_given_a_repository_code() { val repositoryCode = """ @Repository public class UserRepository { } """.trimIndent() val result = SpringLayerCharacteristic.check(repositoryCode, "repository") assert(result) } } ================================================ FILE: languages/shire-java/src/test/kotlin/com/phodal/shirelang/java/variable/JavaTestHelperTest.kt ================================================ package com.phodal.shirelang.java.variable import com.intellij.psi.PsiJavaFile import com.intellij.psi.PsiMethod import com.intellij.testFramework.fixtures.BasePlatformTestCase import com.phodal.shirelang.java.util.JavaTestHelper import junit.framework.TestCase class JavaTestHelperTest : BasePlatformTestCase() { private val code: String = """ import org.junit.Test; public class MathHelperTest { @Test public void testAdditionWithPositiveNumbers() { } @Test public void testAdditionWithNegativeNumbers() { } @Test public void testSubtractionWithPositiveNumbers() { } @Test public void testSubtractionWithNegativeNumbers() { } @Test public void testMultiplicationWithPositiveNumbers() { } @Test public void testMultiplicationWithNegativeNumbers() { } @Test public void testDivisionWithPositiveNumbers() { } @Test public void testDivisionWithNegativeNumbers() { } } """.trimIndent() fun testShouldReturnCorrectJavaMethodName() { val code2 = """ class MathHelper { public int AdditionWith(int a, int b) { return a + b; } } """.trimIndent() myFixture.addFileToProject("MathHelperTest.java", code) val psiFile2 = myFixture.addFileToProject("MathHelper.java", code2) as PsiJavaFile val addMethod = psiFile2.classes.first().methods.first() as PsiMethod val testCases = JavaTestHelper.searchSimilarTestCases(addMethod) TestCase.assertEquals(2, testCases.size) } } ================================================ FILE: languages/shire-javascript/src/main/kotlin/com/phodal/shirelang/javascript/JSTypeResolver.kt ================================================ package com.phodal.shirelang.javascript import com.intellij.lang.javascript.psi.JSFunction import com.intellij.lang.javascript.psi.ecma6.TypeScriptInterface import com.intellij.lang.javascript.psi.ecma6.TypeScriptSingleType import com.intellij.lang.javascript.psi.ecmal4.JSClass import com.intellij.lang.javascript.psi.util.JSStubBasedPsiTreeUtil import com.intellij.openapi.application.ReadAction import com.intellij.psi.PsiElement object JSTypeResolver { fun resolveByElement(element: PsiElement): List = ReadAction.compute, Throwable> { val elements = mutableListOf() when (element) { is JSClass -> { element.functions.map { elements += resolveByFunction(it).values } } is JSFunction -> { elements += resolveByFunction(element).values } else -> {} } return@compute elements } private fun resolveByFunction(jsFunction: JSFunction): Map { val result = mutableMapOf() jsFunction.parameterList?.parameters?.map { it.typeElement?.let { typeElement -> result += resolveByType(typeElement, it.typeElement!!.text) } } result += jsFunction.returnTypeElement?.let { resolveByType(it, jsFunction.returnType!!.resolvedTypeText) } ?: emptyMap() return result } private fun resolveByType( returnType: PsiElement?, typeName: String, ): MutableMap { val result = mutableMapOf() when (returnType) { is TypeScriptSingleType -> { when (val referenceLocally = JSStubBasedPsiTreeUtil.resolveLocally(typeName, returnType)) { is TypeScriptInterface -> { result += mapOf(typeName to referenceLocally) } } } } return result } } ================================================ FILE: languages/shire-javascript/src/main/kotlin/com/phodal/shirelang/javascript/codeedit/JSAutoTestingService.kt ================================================ package com.phodal.shirelang.javascript.codeedit import com.intellij.execution.configurations.RunProfile import com.intellij.lang.javascript.buildTools.npm.rc.NpmRunConfiguration import com.intellij.lang.javascript.psi.JSFile import com.intellij.lang.javascript.psi.ecmal4.JSImportStatement import com.intellij.openapi.application.ReadAction import com.intellij.openapi.application.runReadAction import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.intellij.psi.util.PsiTreeUtil import com.phodal.shirecore.provider.TestingService import com.phodal.shirecore.provider.codemodel.model.ClassStructure import com.phodal.shirecore.variable.toolchain.unittest.AutoTestingPromptContext import com.phodal.shirelang.javascript.JSTypeResolver import com.phodal.shirelang.javascript.codemodel.JavaScriptClassStructureProvider import com.phodal.shirelang.javascript.codemodel.JavaScriptMethodStructureProvider import com.phodal.shirelang.javascript.util.JSPsiUtil import com.phodal.shirelang.javascript.util.LanguageApplicableUtil import kotlin.io.path.Path class JSAutoTestingService : TestingService() { private val log = logger() override fun runConfigurationClass(project: Project): Class = NpmRunConfiguration::class.java override fun isApplicable(element: PsiElement): Boolean { val sourceFile: PsiFile = element.containingFile ?: return false return LanguageApplicableUtil.isWebChatCreationContextSupported(sourceFile) } override fun isApplicable(project: Project, file: VirtualFile): Boolean { val psiFile = PsiManager.getInstance(project).findFile(file) as? JSFile ?: return false return LanguageApplicableUtil.isWebChatCreationContextSupported(psiFile) } override fun findOrCreateTestFile( sourceFile: PsiFile, project: Project, psiElement: PsiElement, ): AutoTestingPromptContext? { val language = sourceFile.language val testFilePath = JSPsiUtil.getTestFilePath(psiElement)?.toString() if (testFilePath == null) { log.warn("Failed to find test file path for: $psiElement") return null } val elementToTest = runReadAction { JSPsiUtil.getElementToTest(psiElement) } if (elementToTest == null) { log.warn("Failed to find element to test for: ${psiElement}, check your function is exported.") return null } val elementName = JSPsiUtil.elementName(elementToTest) if (elementName == null) { log.warn("Failed to find element name for: $psiElement") return null } var testFile = LocalFileSystem.getInstance().findFileByPath(testFilePath) if (testFile != null) { return AutoTestingPromptContext(false, testFile, emptyList(), null, language, null) } WriteCommandAction.writeCommandAction(sourceFile.project).withName("Generate Unit Tests") .compute { val parentDir = VfsUtil.createDirectoryIfMissing(Path(testFilePath).parent.toString()) testFile = parentDir?.createChildData(this, Path(testFilePath).fileName.toString()) } val underTestObj = ReadAction.compute { val underTestObj = JavaScriptClassStructureProvider() .build(elementToTest, false)?.format() if (underTestObj == null) { val funcObj = JavaScriptMethodStructureProvider() .build(elementToTest, false, false)?.format() return@compute funcObj ?: "" } else { return@compute underTestObj } } val imports: List = (sourceFile as? JSFile)?.let { PsiTreeUtil.findChildrenOfType(it, JSImportStatement::class.java) }?.map { it.text } ?: emptyList() return AutoTestingPromptContext(true, testFile!!, emptyList(), elementName, language, underTestObj, imports) } override fun lookupRelevantClass(project: Project, element: PsiElement): List { return JSTypeResolver.resolveByElement(element).mapNotNull { JavaScriptClassStructureProvider().build(it, false) } } } ================================================ FILE: languages/shire-javascript/src/main/kotlin/com/phodal/shirelang/javascript/codeedit/JSFileRunService.kt ================================================ package com.phodal.shirelang.javascript.codeedit import com.intellij.execution.RunManager import com.intellij.execution.configurations.RunConfiguration import com.intellij.execution.configurations.RunProfile import com.intellij.lang.javascript.psi.JSFile import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiManager import com.jetbrains.nodejs.run.NodeJsRunConfiguration import com.jetbrains.nodejs.run.NodeJsRunConfigurationType import com.phodal.shirecore.provider.shire.FileRunService class JSFileRunService : FileRunService { override fun isApplicable(project: Project, file: VirtualFile): Boolean { return PsiManager.getInstance(project).findFile(file) is JSFile && file.name.endsWith(".js") } override fun runConfigurationClass(project: Project): Class? { return NodeJsRunConfiguration::class.java } override fun createConfiguration(project: Project, virtualFile: VirtualFile): RunConfiguration? { val configurationSetting = runReadAction { val runManager = RunManager.getInstance(project) val configurationType = NodeJsRunConfigurationType.getInstance() val configuration = runManager.createConfiguration("Node.js", configurationType.configurationFactories[0]) runManager.addConfiguration(configuration) configuration } val runConfiguration = configurationSetting.configuration as NodeJsRunConfiguration runConfiguration.name = virtualFile.nameWithoutExtension runConfiguration.mainScriptFilePath = virtualFile.path runConfiguration.workingDirectory = virtualFile.parent.path runConfiguration.nodeOptions = "--harmony" return runConfiguration } } ================================================ FILE: languages/shire-javascript/src/main/kotlin/com/phodal/shirelang/javascript/codeedit/JavaScriptTestCodeModifier.kt ================================================ package com.phodal.shirelang.javascript.codeedit import com.intellij.lang.Language import com.intellij.lang.javascript.psi.JSFile import com.intellij.lang.javascript.psi.impl.JSPsiElementFactory import com.intellij.openapi.application.runReadAction import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement import com.intellij.psi.PsiFileFactory import com.intellij.psi.PsiManager import com.phodal.shirecore.provider.codeedit.CodeModifier import com.phodal.shirelang.javascript.util.LanguageApplicableUtil open class JavaScriptTestCodeModifier : CodeModifier { override fun isApplicable(language: Language): Boolean { return LanguageApplicableUtil.isJavaScriptApplicable(language) } override fun smartInsert( sourceFile: VirtualFile, project: Project, code: String, ): PsiElement? { if (sourceFile !is JSFile) { return insertClass(sourceFile, project, code) } return insertMethod(sourceFile, project, code) } override fun insertTestCode(sourceFile: VirtualFile, project: Project, code: String): PsiElement? { if (sourceFile !is JSFile) return insertClass(sourceFile, project, code) return insertMethod(sourceFile, project, code) } override fun insertMethod(sourceFile: VirtualFile, project: Project, code: String): PsiElement? { // todo: spike for insert different method type, like named function, arrow function, etc. val jsFile = PsiManager.getInstance(project).findFile(sourceFile) as JSFile val psiElement = jsFile.lastChild val element = PsiFileFactory.getInstance(project).createFileFromText(jsFile.language, "") val codeElement = JSPsiElementFactory.createJSStatement(code, element) return runReadAction { psiElement?.parent?.addAfter(codeElement, psiElement) } } override fun insertClass(sourceFile: VirtualFile, project: Project, code: String): PsiElement? { return WriteCommandAction.runWriteCommandAction(project) { val psiFile = PsiManager.getInstance(project).findFile(sourceFile) as JSFile val document = psiFile.viewProvider.document!! document.insertString(document.textLength, code) psiFile.lastChild } } } ================================================ FILE: languages/shire-javascript/src/main/kotlin/com/phodal/shirelang/javascript/codeedit/JestCodeModifier.kt ================================================ package com.phodal.shirelang.javascript.codeedit import com.intellij.lang.Language import com.phodal.shirelang.javascript.util.LanguageApplicableUtil class JestCodeModifier : JavaScriptTestCodeModifier() { override fun isApplicable(language: Language): Boolean { return LanguageApplicableUtil.isJavaScriptApplicable(language) } } ================================================ FILE: languages/shire-javascript/src/main/kotlin/com/phodal/shirelang/javascript/codemodel/JavaScriptClassStructureProvider.kt ================================================ package com.phodal.shirelang.javascript.codemodel import com.intellij.lang.javascript.psi.ecmal4.JSClass import com.intellij.openapi.application.runReadAction import com.intellij.psi.* import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.searches.ReferencesSearch import com.phodal.shirecore.provider.codemodel.ClassStructureProvider import com.phodal.shirecore.provider.codemodel.model.ClassStructure class JavaScriptClassStructureProvider : ClassStructureProvider { override fun build(psiElement: PsiElement, gatherUsages: Boolean): ClassStructure? { when (psiElement) { is JSClass -> { val methods: List = psiElement.functions.toList() val fields: List = psiElement.fields.toList() val usages = if (gatherUsages) findUsages(psiElement as PsiNameIdentifierOwner) else emptyList() val supers = psiElement.supers val superClasses = supers.filterIsInstance().mapNotNull { it.name } val annotations: List = mutableListOf() return ClassStructure( psiElement, psiElement.text, psiElement.name, displayName = runReadAction { psiElement.qualifiedName }, methods, fields, superClasses, annotations, usages ) } else -> return null } } companion object { fun findUsages(psiElement: PsiElement): List { val globalSearchScope = GlobalSearchScope.allScope(psiElement.project) return ReferencesSearch.search(psiElement, globalSearchScope, true) .findAll() .toList() } } } ================================================ FILE: languages/shire-javascript/src/main/kotlin/com/phodal/shirelang/javascript/codemodel/JavaScriptFileStructureProvider.kt ================================================ package com.phodal.shirelang.javascript.codemodel import com.intellij.lang.ecmascript6.psi.impl.ES6ImportPsiUtil import com.intellij.lang.javascript.psi.JSFunction import com.intellij.lang.javascript.psi.ecmal4.JSClass import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.util.PsiTreeUtil import com.phodal.shirecore.provider.codemodel.FileStructureProvider import com.phodal.shirecore.provider.codemodel.model.FileStructure import com.phodal.shirecore.relativePath class JavaScriptFileStructureProvider : FileStructureProvider { override fun build(psiFile: PsiFile): FileStructure? { val file = if (psiFile.virtualFile != null) psiFile.virtualFile!!.relativePath(psiFile.project) else "" val importDeclarations = ES6ImportPsiUtil.getImportDeclarations((psiFile as PsiElement)) val classes = PsiTreeUtil.getChildrenOfTypeAsList(psiFile as PsiElement, JSClass::class.java) val functions = PsiTreeUtil.getChildrenOfTypeAsList(psiFile as PsiElement, JSFunction::class.java) return FileStructure( psiFile, psiFile.name, file, null, importDeclarations, classes, functions ) } } ================================================ FILE: languages/shire-javascript/src/main/kotlin/com/phodal/shirelang/javascript/codemodel/JavaScriptMethodStructureProvider.kt ================================================ package com.phodal.shirelang.javascript.codemodel import com.intellij.lang.javascript.presentable.JSFormatUtil import com.intellij.lang.javascript.psi.JSFunction import com.intellij.lang.javascript.psi.JSType import com.intellij.lang.javascript.psi.util.JSUtils import com.intellij.psi.PsiElement import com.intellij.psi.PsiNameIdentifierOwner import com.phodal.shirecore.provider.codemodel.MethodStructureProvider import com.phodal.shirecore.provider.codemodel.model.MethodStructure class JavaScriptMethodStructureProvider : MethodStructureProvider { override fun build(psiElement: PsiElement, includeClassContext: Boolean, gatherUsages: Boolean): MethodStructure? { if (psiElement !is JSFunction) return null val functionSignature = JSFormatUtil.buildFunctionSignaturePresentation(psiElement) val containingClass: PsiElement? = JSUtils.getMemberContainingClass(psiElement) val languageDisplayName = psiElement.language.displayName val returnType = psiElement.returnType val returnTypeText = returnType?.substitute()?.getTypeText(JSType.TypeTextFormat.CODE) val parameterNames = psiElement.parameters.mapNotNull { it.name } val usages = if (gatherUsages) JavaScriptClassStructureProvider.findUsages(psiElement as PsiNameIdentifierOwner) else emptyList() return MethodStructure( psiElement, psiElement.text, psiElement.name!!, psiElement.name + functionSignature, containingClass, languageDisplayName, returnTypeText, parameterNames, includeClassContext, usages ) } } ================================================ FILE: languages/shire-javascript/src/main/kotlin/com/phodal/shirelang/javascript/codemodel/JavaScriptVariableStructureProvider.kt ================================================ package com.phodal.shirelang.javascript.codemodel import com.intellij.lang.javascript.psi.JSFieldVariable import com.intellij.lang.javascript.psi.JSFunction import com.intellij.lang.javascript.psi.util.JSUtils import com.intellij.psi.PsiElement import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.psi.PsiReference import com.intellij.psi.util.PsiTreeUtil import com.phodal.shirecore.provider.codemodel.VariableStructureProvider import com.phodal.shirecore.provider.codemodel.model.VariableStructure class JavaScriptVariableStructureProvider : VariableStructureProvider { override fun build( psiElement: PsiElement, withMethodContext: Boolean, withClassContext: Boolean, gatherUsages: Boolean ): VariableStructure? { if (psiElement !is JSFieldVariable) { return null } val parentOfType: PsiElement? = PsiTreeUtil.getParentOfType(psiElement, JSFunction::class.java, true) val memberContainingClass: PsiElement = JSUtils.getMemberContainingClass(psiElement) val psiReferences: List = if (gatherUsages) { JavaScriptClassStructureProvider.findUsages(psiElement as PsiNameIdentifierOwner) } else { emptyList() } return VariableStructure( psiElement, psiElement.text, psiElement.name!!, parentOfType, memberContainingClass, psiReferences, withMethodContext, withClassContext ) } } ================================================ FILE: languages/shire-javascript/src/main/kotlin/com/phodal/shirelang/javascript/framework/ReactPage.kt ================================================ package com.phodal.shirelang.javascript.framework import com.intellij.lang.javascript.JavaScriptFileType import com.intellij.lang.javascript.dialects.ECMA6LanguageDialect import com.intellij.lang.javascript.dialects.TypeScriptJSXLanguageDialect import com.intellij.lang.javascript.psi.JSFile import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.psi.PsiManager import com.intellij.psi.search.FileTypeIndex import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.ProjectScope import com.phodal.shirecore.variable.frontend.Component import com.phodal.shirecore.variable.frontend.ComponentProvider import kotlinx.serialization.json.Json enum class RouterFile(val filename: String) { UMI(".umirc.ts"), NEXT("next.config.js"), VITE("vite.config.js"), } class ReactPage(private val project: Project): ComponentProvider { private val logger = logger() private val routes: MutableMap = mutableMapOf() private val pages: MutableList = mutableListOf() private val components: MutableList = mutableListOf() // config files private val configs: MutableList = mutableListOf() init { val searchScope: GlobalSearchScope = ProjectScope.getContentScope(project) val psiManager = PsiManager.getInstance(project) val virtualFiles = FileTypeIndex.getFiles(JavaScriptFileType.INSTANCE, searchScope) // FileTypeIndex.getFiles(TypeScriptJSXFileType.INSTANCE, searchScope) // FileTypeIndex.getFiles(JSXHarmonyFileType.INSTANCE, searchScope) val root = project.guessProjectDir()!! virtualFiles.forEach { file -> val path = file.canonicalFile?.path ?: return@forEach val jsFile = (psiManager.findFile(file) ?: return@forEach) as? JSFile ?: return@forEach if (jsFile.isTestFile) return@forEach when { path.contains("pages") -> buildComponent(jsFile)?.let { pages += it } path.contains("components") -> buildComponent(jsFile)?.let { components += it } else -> { if (root.findChild(file.name) != null) { RouterFile.entries.filter { it.filename == file.name }.map { routes += it to jsFile } configs.add(jsFile) } } } } } override fun getPages(): List = pages override fun getComponents(): List = components private fun buildComponent(jsFile: JSFile): List? { return when (jsFile.language) { is TypeScriptJSXLanguageDialect, is ECMA6LanguageDialect, -> { // val Components = ReactPsiUtil.tsxComponentToComponent(jsFile) // if (Components.isEmpty()) { // logger.warn("no component found in ${jsFile.name}") // } // Components null } else -> { logger.warn("unknown language: ${jsFile.language}") null } } } override fun getRoutes(): Map { return this.routes.map { when (it.key) { RouterFile.UMI -> emptyMap() RouterFile.NEXT -> { pages.associate { page -> val route = page.name.replace(Regex("([A-Z])"), "-$1").lowercase() route to route } } RouterFile.VITE -> emptyMap() } }.reduce { acc, map -> acc + map } } /** * Retrieves a list of design system components from the ds.json file located in the prompts/context directory. * The method first attempts to locate the ds.json file by traversing the project directory structure. * If the file is found, it reads its content and decodes it as a JSON string into a list of [Component] objects. * In case of any exception during the reading or decoding process, an empty list is returned. * * @return a list of design system components parsed from the ds.json file, or an empty list if the file is not found * or an error occurs during parsing. */ fun getDesignSystemComponents(): List { val rootConfig = project.guessProjectDir() ?.findChild("prompts") ?.findChild("context") ?.findChild("ds.json") ?: return emptyList() val json = rootConfig.inputStream.reader().readText() return try { val result: List = Json.decodeFromString(json) result } catch (e: Exception) { emptyList() } } fun filterComponents(components: List): List { val comps = this.pages + this.components return components.mapNotNull { component -> comps.find { it.name == component } } } } ================================================ FILE: languages/shire-javascript/src/main/kotlin/com/phodal/shirelang/javascript/framework/ReactPsiUtil.kt ================================================ package com.phodal.shirelang.javascript.framework import com.intellij.lang.ecmascript6.psi.ES6ExportDeclaration import com.intellij.lang.ecmascript6.psi.ES6ExportDefaultAssignment import com.intellij.lang.javascript.presentable.JSFormatUtil import com.intellij.lang.javascript.psi.JSFile import com.intellij.lang.javascript.psi.JSFunctionExpression import com.intellij.lang.javascript.psi.JSReferenceExpression import com.intellij.lang.javascript.psi.JSVariable import com.intellij.lang.javascript.psi.ecma6.TypeScriptClass import com.intellij.lang.javascript.psi.ecma6.TypeScriptFunction import com.intellij.lang.javascript.psi.ecma6.TypeScriptSingleType import com.intellij.lang.javascript.psi.ecma6.TypeScriptVariable import com.intellij.lang.javascript.psi.resolve.JSResolveResult import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.psi.util.PsiTreeUtil import com.phodal.shirecore.variable.frontend.Component object ReactPsiUtil { private fun getExportElements(file: JSFile): List { val exportDeclarations = PsiTreeUtil.getChildrenOfTypeAsList(file, ES6ExportDeclaration::class.java) val map = exportDeclarations.map { exportDeclaration -> exportDeclaration.exportSpecifiers .asSequence() .mapNotNull { it.resolve()?.originalElement ?: it.alias?.findAliasedElement() } .filterIsInstance() .toList() }.flatten() val defaultAssignments = PsiTreeUtil.getChildrenOfTypeAsList(file, ES6ExportDefaultAssignment::class.java) val defaultAssignment = defaultAssignments.mapNotNull { val jsReferenceExpression = it.expression as? JSReferenceExpression ?: return@mapNotNull null val resolveReference = JSResolveResult.resolveReference(jsReferenceExpression) resolveReference.firstOrNull() as? PsiNameIdentifierOwner } return map + defaultAssignment } // fun tsxComponentToComponent(jsFile: JSFile): List = getExportElements(jsFile).map { psiElement -> // val name = psiElement.name ?: return@map null // // val projectPath = jsFile.project.basePath ?: "" // val path = jsFile.virtualFile.path.removePrefix(projectPath) // .replace("\\", "/") // .removePrefix("/") // // return@map when (psiElement) { // is TypeScriptFunction -> Component(name = name, path) // is TypeScriptClass -> Component(name = name, path) // is TypeScriptVariable, is JSVariable -> { // val funcExpr = PsiTreeUtil.findChildrenOfType(psiElement, JSFunctionExpression::class.java) // .firstOrNull() ?: return@map null // // val signature = JSFormatUtil.buildFunctionSignaturePresentation(funcExpr) // val props: List = funcExpr.parameterList?.parameters?.mapNotNull { parameter -> // val typeElement = parameter.typeElement ?: return@mapNotNull null // when (typeElement) { // is TypeScriptSingleType -> { // val resolve = typeElement.referenceExpression?.resolve() // resolve?.text // } // // else -> null // } // } ?: emptyList() // // Component(name = name, path, props = props, signature = signature) // } // // else -> null // } // }.filterNotNull() } ================================================ FILE: languages/shire-javascript/src/main/kotlin/com/phodal/shirelang/javascript/impl/JavaScriptBuildSystemProvider.kt ================================================ package com.phodal.shirelang.javascript.impl import com.intellij.lang.javascript.buildTools.npm.NpmScriptsUtil import com.intellij.lang.javascript.buildTools.npm.PackageJsonUtil import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.phodal.shirecore.provider.context.BuildSystemProvider import com.phodal.shirecore.variable.toolchain.buildsystem.BuildSystemContext import com.phodal.shirelang.javascript.util.JsDependenciesSnapshot import com.phodal.shirelang.javascript.variable.JsWebFrameworks open class JavaScriptBuildSystemProvider : BuildSystemProvider() { override fun collect(project: Project): BuildSystemContext? { val snapshot = JsDependenciesSnapshot.create(project, null) if (snapshot.packageJsonFiles.isEmpty()) { return null } var language = "JavaScript" var languageVersion = "ES5" val buildTool = "NPM" val packageJson = snapshot.packages["typescript"] val tsVersion = packageJson?.parseVersion() if (tsVersion != null) { language = "TypeScript" languageVersion = tsVersion.rawVersion } JsWebFrameworks.entries.forEach { framework -> if (snapshot.packages[framework.packageName] != null) { language += " with ${framework.presentation}" } } var taskString = "" runReadAction { val root = PackageJsonUtil.findChildPackageJsonFile(project.guessProjectDir()) ?: return@runReadAction NpmScriptsUtil.listTasks(project, root).scripts.forEach { task -> taskString += task.name + " " } } return BuildSystemContext( buildToolName = buildTool, buildToolVersion = "", languageName = language, languageVersion = languageVersion, taskString = taskString, libraries = snapshot.mostPopularFrameworks() ) } } ================================================ FILE: languages/shire-javascript/src/main/kotlin/com/phodal/shirelang/javascript/impl/TypeScriptRefactoringTool.kt ================================================ package com.phodal.shirelang.javascript.impl import com.intellij.codeInsight.daemon.impl.quickfix.RenameElementFix import com.intellij.codeInsight.daemon.impl.quickfix.SafeDeleteFix import com.intellij.codeInspection.MoveToPackageFix import com.intellij.lang.javascript.JavaScriptFileType import com.intellij.lang.javascript.psi.JSFile import com.intellij.lang.javascript.psi.JSFunction import com.intellij.lang.javascript.psi.ecmal4.JSClass import com.intellij.openapi.project.ProjectManager import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.intellij.psi.PsiNamedElement import com.intellij.psi.search.FileTypeIndex import com.intellij.psi.search.ProjectScope import com.intellij.psi.util.PsiTreeUtil import com.phodal.shirecore.provider.shire.RefactoringTool import com.phodal.shirecore.variable.toolchain.refactoring.RefactorInstElement class TypeScriptRefactoringTool : RefactoringTool { private val identifierPattern = Regex("^[a-zA-Z_][a-zA-Z0-9_]*$") val project = ProjectManager.getInstance().openProjects.firstOrNull() override fun lookupFile(path: String): PsiFile? { if (project == null) return null val searchScope = ProjectScope.getProjectScope(project) val jsFiles = FileTypeIndex.getFiles(JavaScriptFileType.INSTANCE, searchScope) .mapNotNull { PsiManager.getInstance(project).findFile(it) as? JSFile } val tsFiles = FileTypeIndex.getFiles(JavaScriptFileType.INSTANCE, searchScope) .mapNotNull { PsiManager.getInstance(project).findFile(it) as? JSFile } val files = jsFiles + tsFiles val sourceFile = files.firstOrNull { it.virtualFile.path == path } ?: return null return sourceFile } /** * Deletes the given PsiElement in a safe manner, ensuring that no syntax errors or unexpected behavior occur as a result. * The method performs checks before deletion to confirm that it is safe to remove the element from the code structure. * * @param element The PsiElement to be deleted. This should be a valid element within the PSI tree structure. * @return true if the element was successfully deleted without any issues, false otherwise. This indicates whether * the deletion was performed and considered safe. */ override fun safeDelete(element: PsiElement): Boolean { val delete = SafeDeleteFix(element) try { delete.invoke(element.project, element.containingFile, element, element) } catch (e: Exception) { return false } return true } /** * In Java the canonicalName is the fully qualified name of the target package. * In Kotlin the canonicalName is the fully qualified name of the target package or class. */ override fun move(element: PsiElement, canonicalName: String): Boolean { val file = element.containingFile val fix = MoveToPackageFix(file, canonicalName) try { fix.invoke(file.project, file, element, element) } catch (e: Exception) { return false } return true } private fun findNamedElement(psiFile: PsiFile?, elementInfo: RefactorInstElement): PsiNamedElement? { return when (psiFile) { is JSFile -> findElementInJSFile(psiFile, elementInfo) else -> null } } private fun findElementInJSFile(jsFile: JSFile, elementInfo: RefactorInstElement): PsiNamedElement? { val classes = PsiTreeUtil.getChildrenOfTypeAsList(jsFile, JSClass::class.java) val functions = PsiTreeUtil.getChildrenOfTypeAsList(jsFile, JSFunction::class.java) return when { elementInfo.isClass -> findClassByName(classes, elementInfo.className) elementInfo.isMethod -> findMethodByName(classes, functions, elementInfo.methodName) else -> null } } private fun findClassByName(classes: List, className: String): PsiNamedElement? { return classes.firstOrNull { it.name == className } } private fun findMethodByName( classes: List, functions: List, methodName: String ): PsiNamedElement? { return classes.firstNotNullOfOrNull { it.findFunctionByName(methodName) } ?: functions.firstOrNull { it.name == methodName } } override fun rename(sourceName: String, targetName: String, psiFile: PsiFile?): Boolean { if (project == null) return false // if targetElement is not a valid function name, return false if (!identifierPattern.matches(targetName)) { return false } val elementInfo = getElementInfo(sourceName, psiFile) ?: return false val element = findNamedElement(psiFile, elementInfo) ?: return false try { var target = targetName if (element is JSFile) { target += element.name.substringAfterLast(".") } RenameElementFix(element, target) .invoke(project, element.containingFile, element, element) performRefactoringRename(project, element, targetName) } catch (e: Exception) { return false } return false } private fun getElementInfo(input: String, psiFile: PsiFile?): RefactorInstElement? { if (!input.contains("#") && psiFile != null) { val jsFile = psiFile as? JSFile ?: return null // check input name is uppercase val isClass = input[0].isUpperCase() val isMethod = input[0].isLowerCase() return RefactorInstElement(isClass, isMethod, input, input, input, jsFile.name) } val isMethod = input.contains("#") val methodName = input.substringAfter("#") val canonicalName = input.substringBefore("#") val maybeClassName = canonicalName.substringAfterLast(".") // the clasName should be Uppercase or it will be the package var isClass = false var pkgName = canonicalName.substringBeforeLast(".") if (maybeClassName[0].isLowerCase()) { pkgName = "$pkgName.$maybeClassName" } else { isClass = true } return RefactorInstElement(isClass, isMethod, methodName, canonicalName, maybeClassName, pkgName) } } ================================================ FILE: languages/shire-javascript/src/main/kotlin/com/phodal/shirelang/javascript/provider/JavaScriptRelatedClassesProvider.kt ================================================ package com.phodal.shirelang.javascript.provider import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.phodal.shirecore.provider.psi.RelatedClassesProvider import com.phodal.shirelang.javascript.JSTypeResolver class JavaScriptRelatedClassesProvider : RelatedClassesProvider { override fun lookup(element: PsiElement): List { return JSTypeResolver.resolveByElement(element) } override fun lookup(element: PsiFile): List { return JSTypeResolver.resolveByElement(element) } } ================================================ FILE: languages/shire-javascript/src/main/kotlin/com/phodal/shirelang/javascript/util/JSPsiUtil.kt ================================================ package com.phodal.shirelang.javascript.util import com.intellij.lang.ecmascript6.psi.ES6ExportDeclaration import com.intellij.lang.ecmascript6.psi.ES6ExportDefaultAssignment import com.intellij.lang.javascript.frameworks.commonjs.CommonJSUtil import com.intellij.lang.javascript.psi.* import com.intellij.lang.javascript.psi.ecma6.TypeScriptGenericOrMappedTypeParameter import com.intellij.lang.javascript.psi.ecmal4.JSAttributeList import com.intellij.lang.javascript.psi.ecmal4.JSAttributeListOwner import com.intellij.lang.javascript.psi.ecmal4.JSClass import com.intellij.lang.javascript.psi.ecmal4.JSQualifiedNamedElement import com.intellij.lang.javascript.psi.resolve.JSResolveResult import com.intellij.lang.javascript.psi.stubs.JSImplicitElement import com.intellij.lang.javascript.psi.util.JSDestructuringUtil import com.intellij.lang.javascript.psi.util.JSStubBasedPsiTreeUtil import com.intellij.lang.javascript.psi.util.JSUtils import com.intellij.openapi.application.ReadAction import com.intellij.openapi.application.runReadAction import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.psi.* import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.parents import com.phodal.shirecore.project.isInProject import java.io.File import java.nio.file.Path object JSPsiUtil { fun resolveReference(node: JSReferenceExpression, scope: PsiElement): PsiElement? { val resolveReference = JSResolveResult.resolveReference(node) var resolved = resolveReference.firstOrNull() as? JSImplicitElement if (resolved != null) { resolved = resolved.parent as? JSImplicitElement } if (resolved is JSFunction && resolved.isConstructor) { resolved = JSUtils.getMemberContainingClass(resolved) as? JSImplicitElement } if (resolved == null || skipDeclaration(resolved)) { return null } val virtualFile = resolved.containingFile?.virtualFile if (virtualFile == null || !node.project.isInProject(virtualFile) || ProjectFileIndex.getInstance(node.project).isInLibrary(virtualFile) ) { return JSStubBasedPsiTreeUtil.resolveReferenceLocally(node as PsiPolyVariantReference, node.referenceName) } val jSImplicitElement = resolved return if (jSImplicitElement.textLength == 0 || !PsiTreeUtil.isAncestor(scope, jSImplicitElement, true)) { jSImplicitElement } else { null } } private fun skipDeclaration(element: PsiElement): Boolean { return when (element) { is JSParameter, is TypeScriptGenericOrMappedTypeParameter -> true is JSField -> { element.initializerOrStub !is JSFunctionExpression } is JSVariable -> { var initializer = JSDestructuringUtil.getNearestDestructuringInitializer(element) if (initializer == null) { initializer = element.initializerOrStub ?: return true } !(initializer is JSCallExpression || initializer is JSFunctionExpression || initializer is JSObjectLiteralExpression ) } else -> false } } fun isExportedFileFunction(element: PsiElement): Boolean { when (val parent = element.parent) { is JSFile, is JSEmbeddedContent -> { return when (element) { is JSVarStatement -> { val variables = element.variables val variable = variables.firstOrNull() ?: return false variable.initializerOrStub is JSFunction && exported(variable) } is JSFunction -> exported(element) else -> false } } is JSVariable -> { val varStatement = parent.parent as? JSVarStatement ?: return false return varStatement.parent is JSFile && exported(parent) } else -> { return parent is ES6ExportDefaultAssignment } } } fun isExportedClass(elementForTests: PsiElement?): Boolean { return elementForTests is JSClass && elementForTests.isExported } fun isExportedClassPublicMethod(psiElement: PsiElement): Boolean { val jsClass = PsiTreeUtil.getParentOfType(psiElement, JSClass::class.java, true) ?: return false if (!exported(jsClass as PsiElement)) return false val parentElement = psiElement.parents(true).firstOrNull() ?: return false if (isPrivateMember(parentElement)) return false return when (parentElement) { is JSFunction -> !parentElement.isConstructor is JSVarStatement -> { val variables = parentElement.variables val jSVariable = variables.firstOrNull() (jSVariable?.initializerOrStub as? JSFunction) != null } else -> false } } private fun exported(element: PsiElement): Boolean { if (element !is JSElementBase) return false if (element.isExported || element.isExportedWithDefault) { return true } if (element is JSPsiElementBase && CommonJSUtil.isExportedWithModuleExports(element)) { return true } val containingFile = element.containingFile ?: return false val exportDeclarations = PsiTreeUtil.getChildrenOfTypeAsList(containingFile, ES6ExportDeclaration::class.java) return exportDeclarations.any { exportDeclaration -> exportDeclaration.exportSpecifiers .asSequence() .any { it.alias?.findAliasedElement() == element } } } fun elementName(psiElement: PsiElement): String? { if (psiElement !is JSVarStatement) { if (psiElement !is JSNamedElement) return null return psiElement.name } val jSVariable = psiElement.variables.firstOrNull() ?: return null return jSVariable.name } /** * Determines whether the given [element] is a private member. * * @param element the PSI element to check * @return `true` if the element is a private member, `false` otherwise */ private fun isPrivateMember(element: PsiElement): Boolean { if (element is JSQualifiedNamedElement && element.isPrivateName) { return true } if (element !is JSAttributeListOwner) return false val attributeList = element.attributeList return attributeList?.accessType == JSAttributeList.AccessType.PRIVATE } /** * In JavaScript/TypeScript a testable element is a function, a class or a variable. * * Function: * ```javascript * function testableFunction() {} * export testableFunction * ``` * * Class: * ```javascript * export class TestableClass {} * ``` * * Variable: * ```javascript * var functionA = function() {} * export functionA * ``` */ fun getElementToTest(psiElement: PsiElement): PsiElement? { if (psiElement is JSFile) return psiElement if (psiElement is JSClass) return psiElement val jsFunc = PsiTreeUtil.getParentOfType(psiElement, JSFunction::class.java, false) val jsVarStatement = PsiTreeUtil.getParentOfType(psiElement, JSVarStatement::class.java, false) val jsClazz = PsiTreeUtil.getParentOfType(psiElement, JSClass::class.java, false) val elementForTests: PsiElement? = when { jsFunc != null -> jsFunc jsVarStatement != null -> jsVarStatement jsClazz != null -> jsClazz else -> null } if (elementForTests == null) return null return when { isExportedClassPublicMethod(elementForTests) -> elementForTests isExportedFileFunction(elementForTests) -> elementForTests isExportedClass(elementForTests) -> elementForTests else -> { null } } } fun getTestFilePath(element: PsiElement): Path? { val testDirectory = suggestTestDirectory(element) if (testDirectory == null) { logger().warn("Failed to find test directory for: $element") return null } val containingFile: PsiFile = runReadAction { element.containingFile } ?: return null val extension = containingFile.virtualFile?.extension ?: return null val elementName = elementName(element) ?: return null val testFile: Path = generateUniqueTestFile(elementName, containingFile, testDirectory, extension).toPath() return testFile } /** * Todo: since in JavaScript has different test framework, we need to find the test directory by the framework. */ fun suggestTestDirectory(element: PsiElement): PsiDirectory? = ReadAction.compute { val project: Project = element.project val elementDirectory = element.containingFile val parentDir = elementDirectory?.virtualFile?.parent ?: return@compute null val psiManager = PsiManager.getInstance(project) val findDirectory = psiManager.findDirectory(parentDir) if (findDirectory != null) { return@compute findDirectory } val createChildDirectory = parentDir.createChildDirectory(this, "test") return@compute psiManager.findDirectory(createChildDirectory) } fun generateUniqueTestFile( elementName: String?, containingFile: PsiFile, testDirectory: PsiDirectory, extension: String, ): File { val testPath = testDirectory.virtualFile.path val prefix = elementName ?: containingFile.name.substringBefore('.', "") val nameCandidate = "$prefix.test.$extension" var testFile = File(testPath, nameCandidate) var i = 1 while (testFile.exists()) { val nameCandidateWithIndex = "$prefix${i}.test.$extension" i++ testFile = File(testPath, nameCandidateWithIndex) } return testFile } } ================================================ FILE: languages/shire-javascript/src/main/kotlin/com/phodal/shirelang/javascript/util/JsDependeciesSnapshot.kt ================================================ package com.phodal.shirelang.javascript.util import com.intellij.javascript.nodejs.PackageJsonData import com.intellij.javascript.nodejs.packageJson.PackageJsonFileManager import com.intellij.lang.javascript.buildTools.npm.PackageJsonUtil import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiFile import com.phodal.shirelang.javascript.variable.MOST_POPULAR_PACKAGES /** * Represents a snapshot of JavaScript dependencies in a Kotlin language project. * * This class provides information about the JavaScript dependencies in the project, including the package.json files, * whether the package.json files have been resolved, the tsconfig.json files, and the packages defined in the package.json files. * * @property packageJsonFiles The set of package.json files in the project. * @property resolvedPackageJson A flag indicating whether the package.json files have been resolved. * @property tsConfigs The set of tsconfig.json files in the project. * @property packages The map of package names to their corresponding PackageJsonDependencyEntry objects. */ class JsDependenciesSnapshot( val packageJsonFiles: Set, private val resolvedPackageJson: Boolean, private val tsConfigs: Set, val packages: Map ) { fun mostPopularFrameworks(): List { val dependencies = this.packages .asSequence() .filter { entry -> MOST_POPULAR_PACKAGES.contains(entry.key) && !entry.key.startsWith("@type") } .map { entry -> val dependency = entry.key val version = entry.value.parseVersion() if (version != null) "$dependency: $version" else dependency } .toList() return dependencies } fun language(): String { var language = "JavaScript" var languageVersion = "ES5" val packageJson = this.packages["typescript"] val tsVersion = packageJson?.parseVersion() if (tsVersion != null) { language = "TypeScript" languageVersion = tsVersion.rawVersion } return "$language: $languageVersion" } companion object { fun create(project: Project, psiFile: PsiFile?): JsDependenciesSnapshot { var packageJsonFiles = emptySet() var resolvedPackageJson = false val virtualFile = psiFile?.virtualFile if (virtualFile != null) { val packageJson = PackageJsonUtil.findUpPackageJson(virtualFile) if (packageJson != null) { packageJsonFiles = setOf(packageJson) resolvedPackageJson = true } } if (packageJsonFiles.isEmpty()) { packageJsonFiles = PackageJsonFileManager.getInstance(project).validPackageJsonFiles } val tsConfigs = findTsConfigs(project, packageJsonFiles) val packages = enumerateAllPackages(packageJsonFiles) return JsDependenciesSnapshot(packageJsonFiles, resolvedPackageJson, tsConfigs, packages) } private fun enumerateAllPackages(set: Set): Map { return set.asSequence() .map { PackageJsonData.getOrCreate(it) } .flatMap { it.allDependencyEntries.entries } .associateBy({ it.key }, { it.value }) } private fun findTsConfigs(project: Project, set: Set): Set { val mapNotNull = set.asSequence().mapNotNull { it.parent?.findChild("tsconfig.json") } val rootConfig = project.guessProjectDir()?.findChild("tsconfig.json") return mapNotNull.plus(rootConfig).filterNotNull().toSet() } } } ================================================ FILE: languages/shire-javascript/src/main/kotlin/com/phodal/shirelang/javascript/util/LanguageApplicationUtil.kt ================================================ package com.phodal.shirelang.javascript.util import com.intellij.lang.Language import com.intellij.lang.html.HTMLLanguage import com.intellij.lang.javascript.DialectDetector import com.intellij.lang.javascript.JavascriptLanguage import com.intellij.lang.javascript.buildTools.npm.PackageJsonUtil import com.intellij.psi.PsiFile import com.phodal.shirecore.provider.context.ToolchainPrepareContext object LanguageApplicableUtil { fun isJavaScriptApplicable(language: Language) = language.isKindOf(JavascriptLanguage.INSTANCE) || language.isKindOf(HTMLLanguage.INSTANCE) fun isPreferTypeScript(context: ToolchainPrepareContext): Boolean { val sourceFile = context.sourceFile ?: return false return DialectDetector.isTypeScript(sourceFile) } fun isWebChatCreationContextSupported(psiFile: PsiFile?): Boolean { return isWebLLMContext(psiFile) } fun isWebLLMContext(psiFile: PsiFile?): Boolean { if (psiFile == null) return false if (PackageJsonUtil.isPackageJsonFile(psiFile)) return true return isJavaScriptApplicable(psiFile.language) } } ================================================ FILE: languages/shire-javascript/src/main/kotlin/com/phodal/shirelang/javascript/variable/JSPsiContextVariableProvider.kt ================================================ package com.phodal.shirelang.javascript.variable import com.intellij.lang.ecmascript6.psi.ES6ImportDeclaration import com.intellij.lang.ecmascript6.psi.ES6ImportSpecifier import com.intellij.lang.ecmascript6.psi.ES6ImportSpecifierAlias import com.intellij.lang.javascript.JavascriptLanguage import com.intellij.lang.javascript.psi.JSFile import com.intellij.lang.javascript.psi.JSFunction import com.intellij.lang.javascript.psi.ecmal4.JSClass import com.intellij.lang.javascript.psi.ecmal4.JSImportStatement import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.util.PsiTreeUtil import com.phodal.shirecore.provider.variable.PsiContextVariableProvider import com.phodal.shirecore.psi.CodeSmellCollector import com.phodal.shirecore.provider.variable.model.PsiContextVariable import com.phodal.shirecore.provider.variable.model.PsiContextVariable.* import com.phodal.shirecore.search.similar.SimilarChunksSearch import com.phodal.shirelang.javascript.JSTypeResolver import com.phodal.shirelang.javascript.codemodel.JavaScriptClassStructureProvider import com.phodal.shirelang.javascript.codemodel.JavaScriptMethodStructureProvider import com.phodal.shirelang.javascript.util.JSPsiUtil class JSPsiContextVariableProvider : PsiContextVariableProvider { override fun resolve(variable: PsiContextVariable, project: Project, editor: Editor, psiElement: PsiElement?): Any { if (psiElement == null) return "" if(!psiElement.language.isKindOf(JavascriptLanguage.INSTANCE)) return "" val underTestElement = JSPsiUtil.getElementToTest(psiElement) ?: return "" val sourceFile = underTestElement.containingFile as? JSFile ?: return "" return when (variable) { CURRENT_CLASS_NAME -> { when (underTestElement) { is JSClass -> underTestElement.name ?: "" else -> "" } } CURRENT_CLASS_CODE -> { val underTestObj = JavaScriptClassStructureProvider() .build(underTestElement, false)?.format() if (underTestObj == null) { val funcObj = JavaScriptMethodStructureProvider() .build(underTestElement, false, false)?.format() funcObj ?: "" } else { underTestObj } } CURRENT_METHOD_NAME -> { when (underTestElement) { is JSFunction -> underTestElement.name ?: "" else -> "" } } CURRENT_METHOD_CODE -> { when (underTestElement) { is JSFunction -> underTestElement.text ?: "" else -> "" } } RELATED_CLASSES -> JSTypeResolver.resolveByElement(underTestElement) SIMILAR_TEST_CASE -> "" IMPORTS -> { return PsiTreeUtil.findChildrenOfAnyType(sourceFile, JSImportStatement::class.java, ES6ImportDeclaration::class.java, ES6ImportSpecifier::class.java, ES6ImportSpecifierAlias::class.java ) .map { it.text } } IS_NEED_CREATE_FILE -> TODO() TARGET_TEST_FILE_NAME -> JSPsiUtil.getTestFilePath(psiElement) ?: "" UNDER_TEST_METHOD_CODE -> { when (underTestElement) { is JSFunction -> underTestElement.text ?: "" else -> "" } } FRAMEWORK_CONTEXT -> { collectFrameworkContext(underTestElement, project) } CODE_SMELL -> CodeSmellCollector.collectElementProblemAsSting(psiElement, project, editor) METHOD_CALLER -> TODO() CALLED_METHOD -> TODO() SIMILAR_CODE -> return SimilarChunksSearch.createQuery(psiElement) ?: "" STRUCTURE -> { when (underTestElement) { is JSClass -> JavaScriptClassStructureProvider().build(underTestElement, true)?.toString() ?: "" is JSFunction -> JavaScriptMethodStructureProvider().build(underTestElement, false, false) else -> null } ?: "" } CHANGE_COUNT -> calculateChangeCount(psiElement) LINE_COUNT -> calculateLineCount(psiElement) COMPLEXITY_COUNT -> calculateComplexityCount(psiElement) } } } ================================================ FILE: languages/shire-javascript/src/main/kotlin/com/phodal/shirelang/javascript/variable/JavaScriptFrameworks.kt ================================================ package com.phodal.shirelang.javascript.variable interface Framework { val presentation: String val packageName: String } enum class JsWebFrameworks(override val presentation: String, override val packageName: String) : Framework { React("React", "react"), Vue("Vue", "vue"), Angular("Angular", "@angular/core"), AngularJS("AngularJS", "angular"), Svelte("Svelte", "svelte"), Astro("Astro", "astro"), Lit("Lit", "lit"), Solid("Solid", "solid-js"), Preact("Preact", "preact"), Next("Next", "next"), Nuxt("Nuxt", "nuxt"), } enum class JsTestFrameworks(override val presentation: String, override val packageName: String) : Framework { Jest("Jest", "jest"), Mocha("Mocha", "mocha"), Jasmine("Jasmine", "jasmine"), Karma("Karma", "karma"), Ava("Ava", "ava"), Tape("Tape", "tape"), Qunit("Qunit", "qunit"), Tap("Tap", "tap"), Cypress("Cypress", "cypress"), Protractor("Protractor", "protractor"), Nightwatch("Nightwatch", "nightwatch"), Vitest("Vitest", "vitest") } private const val TYPESCRIPT_PACKAGE = "typescript" val MOST_POPULAR_PACKAGES = setOf( "lodash", "request", "commander", "react", "express", "async", "moment", "prop-types", "react-dom", "bluebird", "underscore", "vue", "axios", "tslib", "glob", "yargs", "colors", "webpack", "uuid", "classnames", "minimist", "body-parser", "rxjs", "babel-runtime", "jquery", "babel-core", "core-js", "babel-loader", "cheerio", "rimraf", "eslint", "dotenv", TYPESCRIPT_PACKAGE, "@types/node", "@angular/core", "@angular/common", "redux", "gulp", "node-fetch", "@angular/platform-browser", "@babel/runtime", "handlebars", "@angular/compiler", "aws-sdk", "@angular/forms", "webpack-dev-server", "@angular/platform-browser-dynamic", "mocha", "socket.io", "ws", "node-sass", "@angular/router", "ramda", "react-redux", "@babel/core", "@angular/http", "ejs", "coffee-script", "mongodb", "chai", "mongoose", "xml2js", "bootstrap", "jest", "redis", "vue-router", "optimist", "promise", "@angular/animations", "postcss", "morgan", "less", "immutable" ) ================================================ FILE: languages/shire-javascript/src/main/kotlin/com/phodal/shirelang/javascript/variable/JavaScriptLanguageToolchainProvider.kt ================================================ package com.phodal.shirelang.javascript.variable import com.intellij.javascript.nodejs.PackageJsonDependency import com.intellij.openapi.project.Project import com.phodal.shirecore.provider.context.LanguageToolchainProvider import com.phodal.shirecore.provider.context.ToolchainContextItem import com.phodal.shirecore.provider.context.ToolchainPrepareContext import com.phodal.shirelang.javascript.util.JsDependenciesSnapshot import com.phodal.shirelang.javascript.util.LanguageApplicableUtil class JavaScriptLanguageToolchainProvider : LanguageToolchainProvider { override fun isApplicable(project: Project, context: ToolchainPrepareContext): Boolean { return LanguageApplicableUtil.isWebChatCreationContextSupported(context.sourceFile) } override suspend fun collect(project: Project, context: ToolchainPrepareContext): List { val results = mutableListOf() val snapshot = JsDependenciesSnapshot.create(project, context.sourceFile) val preferType = if (LanguageApplicableUtil.isPreferTypeScript(context)) "TypeScript" else "JavaScript" results.add( ToolchainContextItem( JavaScriptLanguageToolchainProvider::class, "You are working on a project that uses $preferType language" ) ) if (preferType == "TypeScript") { getTypeScriptLanguageContext(snapshot)?.let { results.add(it) } } getMostPopularPackagesContext(snapshot)?.let { results.add(it) } getJsWebFrameworksContext(snapshot)?.let { results.add(it) } getTestFrameworksContext(snapshot)?.let { results.add(it) } return results } private fun getTypeScriptLanguageContext(snapshot: JsDependenciesSnapshot): ToolchainContextItem? { val packageJson = snapshot.packages["typescript"] ?: return null val version = packageJson.parseVersion() return ToolchainContextItem( JavaScriptLanguageToolchainProvider::class, "The project uses TypeScript language" + (version?.let { ", version: $version" } ?: "") ) } private fun getMostPopularPackagesContext(snapshot: JsDependenciesSnapshot): ToolchainContextItem? { val dependencies = snapshot.mostPopularFrameworks() return dependencies.takeIf { it.isNotEmpty() }?.let { ToolchainContextItem( JavaScriptLanguageToolchainProvider::class, "The project uses the following JavaScript packages: ${it.joinToString(", ")}" ) } } private fun getJsWebFrameworksContext(snapshot: JsDependenciesSnapshot): ToolchainContextItem? { val frameworks = collectFrameworks(snapshot, JsWebFrameworks.entries) return frameworks.takeIf { it.isNotEmpty() }?.let { ToolchainContextItem( JavaScriptLanguageToolchainProvider::class, "The project uses the following JavaScript component frameworks: $it" ) } } private fun getTestFrameworksContext(snapshot: JsDependenciesSnapshot): ToolchainContextItem? { val frameworks = collectFrameworks(snapshot, JsTestFrameworks.entries) return frameworks.takeIf { it.isNotEmpty() }?.let { ToolchainContextItem( JavaScriptLanguageToolchainProvider::class, "The project uses $it to test." ) } } private fun collectFrameworks( snapshot: JsDependenciesSnapshot, frameworks: List, ): Map { return snapshot.packages.filter { (_, entry) -> entry.dependencyType == PackageJsonDependency.dependencies || entry.dependencyType == PackageJsonDependency.devDependencies }.mapNotNull { (name, _) -> frameworks.find { name.startsWith(it.packageName) || name == it.packageName }?.packageName }.associateWith { true } } } ================================================ FILE: languages/shire-javascript/src/main/resources/com.phodal.shirelang.javascript.xml ================================================ ================================================ FILE: languages/shire-json/src/main/kotlin/com/phodal/shire/json/PsiJsonUtil.kt ================================================ package com.phodal.shire.json import com.intellij.json.JsonUtil import com.intellij.json.psi.* fun JsonProperty.valueAsString(obj: JsonObject): String? { val value = JsonUtil.getPropertyValueOfType(obj, name, JsonLiteral::class.java) return when (value) { is JsonStringLiteral -> value.value is JsonBooleanLiteral -> value.value.toString() else -> value?.text } } fun JsonObject.findString(name: String): String? { val property = findProperty(name) ?: return null return property.valueAsString(this) } fun JsonObject.findNumber(name: String): Number? { val property = findProperty(name) ?: return null return JsonUtil.getPropertyValueOfType(this, name, JsonNumberLiteral::class.java)?.value } ================================================ FILE: languages/shire-json/src/main/kotlin/com/phodal/shire/json/ShireEnvReader.kt ================================================ package com.phodal.shire.json import com.intellij.json.psi.JsonFile import com.intellij.json.psi.JsonObject import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.intellij.psi.search.GlobalSearchScope import com.intellij.util.indexing.FileBasedIndex object ShireEnvReader { const val DEFAULT_ENV_NAME = "development" /** * This function attempts to retrieve a JSON file associated with a given environment name within the specified scope and project. * * @param envName The name of the environment for which to find the associated JSON file. * @param scope The GlobalSearchScope to limit the search for the JSON file. * @param project The Project within which to search for the JSON file. * * @return A JsonFile object if a file with the environment name is found, or null if no such file exists within the given scope and project. */ private fun getEnvJsonFile( envName: String, scope: GlobalSearchScope, project: Project, ): JsonFile? { return DumbService.getInstance(project).runReadActionInSmartMode { FileBasedIndex.getInstance().getContainingFiles(ShireEnvironmentIndex.id(), envName, scope) .firstOrNull() ?.let { (PsiManager.getInstance(project).findFile(it) as? JsonFile) } } } fun getEnvObject( envName: String, scope: GlobalSearchScope, project: Project, ): JsonObject? { val psiFile = getEnvJsonFile(envName, scope, project) val envObject = getEnvObject(envName, psiFile) return envObject } /** * Read Shire env file object */ fun getEnvObject(envName: String, psiFile: PsiFile?): JsonObject? { val rootObject = (psiFile as? JsonFile)?.topLevelValue as? JsonObject ?: return null return rootObject.propertyList.firstOrNull { it.name == envName }?.value as? JsonObject } fun fetchEnvironmentVariables(envName: String, scope: GlobalSearchScope): List> { return FileBasedIndex.getInstance().getValues( ShireEnvironmentIndex.id(), envName, scope ) } fun getAllEnvironments(project: Project, scope: GlobalSearchScope): Collection { try { return DumbService.getInstance(project).runReadActionInSmartMode> { val index = FileBasedIndex.getInstance() index.getAllKeys(ShireEnvironmentIndex.id(), project).stream() .filter { it != ShireEnvironmentIndex.MODEL_LIST && index.getContainingFiles( ShireEnvironmentIndex.id(), it, scope ).isNotEmpty() } .toList() } } catch (e: Exception) { return emptyList() } } } ================================================ FILE: languages/shire-json/src/main/kotlin/com/phodal/shire/json/ShireEnvVariableFiller.kt ================================================ package com.phodal.shire.json import com.intellij.json.JsonUtil import com.intellij.json.psi.* import com.intellij.openapi.util.TextRange object ShireEnvVariableFiller { private fun getVariableValue(jsonObject: JsonObject, name: String, processVars: Map): String? { val value = JsonUtil.getPropertyValueOfType(jsonObject, name, JsonLiteral::class.java) val jsonResult = getValueAsString(value) if (jsonResult != null) { return jsonResult } return processVars[name] } private fun getValueAsString(value: JsonLiteral?): String? { return when (value) { is JsonStringLiteral -> value.value is JsonBooleanLiteral -> value.value.toString() else -> value?.text } } fun fillVariables( messageBody: String, variables: List>, obj: JsonObject?, processVars: Map ): String { if (obj == null) return messageBody if (variables.isEmpty()) return messageBody val envRanges = collectVariablesRangesInMessageBody(messageBody) val result = StringBuilder(messageBody.length) var lastVariableRangeEndOffset = 0 for (variableRange in envRanges) { result.append(messageBody as CharSequence, lastVariableRangeEndOffset, variableRange.startOffset) val variableValue = getVariableValue(obj, getVariableKey(variableRange, messageBody), processVars) result.append(variableValue) lastVariableRangeEndOffset = variableRange.endOffset } result.append(messageBody as CharSequence, lastVariableRangeEndOffset, messageBody.length) val sb = result.toString() return sb } private fun getVariableKey(variableRange: TextRange, messageBody: String) = variableRange.substring(messageBody).removePrefix("\${").removeSuffix("}") private fun collectVariablesRangesInMessageBody(body: String): List { val ranges = mutableListOf() var startIndex = 0 while (startIndex < body.length) { val openBraceIndex = body.indexOf("\${", startIndex) val closeBraceIndex = body.indexOf("}", openBraceIndex) if (openBraceIndex == -1 || closeBraceIndex == -1) { break } val range = TextRange(openBraceIndex, closeBraceIndex + 1) val contentInsideBraces = body.substring(openBraceIndex + 2, closeBraceIndex) if (contentInsideBraces.isNotBlank()) { ranges.add(range) } startIndex = closeBraceIndex + 1 } return ranges } } ================================================ FILE: languages/shire-json/src/main/kotlin/com/phodal/shire/json/ShireEnvironmentIndex.kt ================================================ package com.phodal.shire.json import com.intellij.json.psi.* import com.intellij.openapi.util.text.StringUtil import com.intellij.util.indexing.* import com.intellij.util.io.DataExternalizer import com.intellij.util.io.EnumeratorStringDescriptor import com.intellij.util.io.KeyDescriptor class ShireEnvironmentIndex : FileBasedIndexExtension>() { companion object { val SHIRE_ENV_ID: ID> = ID.create("shire.environment") const val MODEL_TITLE = "title" const val MODEL_LIST = "models" fun id(): ID> { return SHIRE_ENV_ID } } override fun getValueExternalizer(): DataExternalizer> = ShireStringsExternalizer() override fun getVersion(): Int = 2 override fun getInputFilter(): FileBasedIndex.InputFilter = ShireEnvironmentInputFilter() override fun dependsOnFileContent(): Boolean = true override fun getName(): ID> = SHIRE_ENV_ID override fun getKeyDescriptor(): KeyDescriptor = EnumeratorStringDescriptor.INSTANCE override fun getIndexer(): DataIndexer, FileContent> { return DataIndexer { inputData: FileContent -> val file = inputData.psiFile require(file is JsonFile) { AssertionError() } val variablesFromFile = getVariablesFromFile(file) variablesFromFile } } private fun getVariablesFromFile(file: JsonFile): Map> { val result: MutableMap> = HashMap() when (val topLevelValue = file.topLevelValue) { is JsonObject -> { for (property in topLevelValue.propertyList) { when (val value = property.value) { is JsonObject -> { result[property.name] = readEnvVariables(value, file.name) } is JsonArray -> { // the prop key should be "models" if (property.name != MODEL_LIST) { continue } // the child elements of the array are objects, which should have prop call "name" val envVariables = value.children .filterIsInstance() .mapNotNull { obj -> val name = obj.findProperty(MODEL_TITLE)?.value?.text name?.let { StringUtil.unquoteString(it) } } .toSet() result[property.name] = envVariables } } } } } return result } private fun readEnvVariables(obj: JsonObject, fileName: String): Set { val properties = obj.propertyList return if (properties.isEmpty()) { emptySet() } else { val set = properties.stream() .map { property -> StringUtil.nullize(property.name) } .toList() .mapNotNull { it } .toSet() set } } } ================================================ FILE: languages/shire-json/src/main/kotlin/com/phodal/shire/json/ShireEnvironmentInputFilter.kt ================================================ package com.phodal.shire.json import com.intellij.json.JsonFileType import com.intellij.openapi.fileTypes.FileType import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.indexing.DefaultFileTypeSpecificInputFilter class ShireEnvironmentInputFilter : DefaultFileTypeSpecificInputFilter(*arrayOf(JsonFileType.INSTANCE)) { override fun acceptInput(file: VirtualFile): Boolean { return super.acceptInput(file) && isShireEnvFile(file) } private fun isShireEnvFile(file: VirtualFile?): Boolean { return file?.name?.endsWith(".shireEnv.json") ?: false } } ================================================ FILE: languages/shire-json/src/main/kotlin/com/phodal/shire/json/ShireStringsExternalizer.kt ================================================ package com.phodal.shire.json import com.intellij.util.io.DataExternalizer import java.io.DataInput import java.io.DataOutput class ShireStringsExternalizer : DataExternalizer> { override fun save(out: DataOutput, value: Set) { out.writeInt(value.size) for (s in value) { out.writeUTF(s) } } override fun read(input: DataInput): Set { val size = input.readInt() val result: MutableSet = HashSet(size) for (i in 0 until size) { result.add(input.readUTF()) } return result } } ================================================ FILE: languages/shire-json/src/main/kotlin/com/phodal/shire/json/llm/LlmEnv.kt ================================================ package com.phodal.shire.json.llm import com.intellij.json.psi.JsonArray import com.intellij.json.psi.JsonFile import com.intellij.json.psi.JsonObject import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.intellij.psi.PsiManager import com.intellij.psi.search.GlobalSearchScope import com.intellij.util.indexing.FileBasedIndex import com.phodal.shire.json.ShireEnvironmentIndex import com.phodal.shire.json.valueAsString class LlmEnv { companion object { private fun configFromFile(modelName: String, psiFile: JsonFile?): JsonObject? { val rootObject = psiFile?.topLevelValue as? JsonObject ?: return null val envObject = rootObject.propertyList.firstOrNull { it.name == ShireEnvironmentIndex.MODEL_LIST }?.value as? JsonArray return envObject?.children?.firstOrNull { it is JsonObject && it.findProperty(ShireEnvironmentIndex.MODEL_TITLE)?.valueAsString(it) == modelName } as? JsonObject } fun configFromFile(modelName: String, scope: GlobalSearchScope, project: Project): JsonObject? { val jsonFile = runReadAction { FileBasedIndex.getInstance().getContainingFiles(ShireEnvironmentIndex.id(), ShireEnvironmentIndex.MODEL_LIST, scope) .firstOrNull() ?.let { (PsiManager.getInstance(project).findFile(it) as? JsonFile) } } return configFromFile(modelName, jsonFile) } } } ================================================ FILE: languages/shire-json/src/main/resources/com.phodal.shire.json.xml ================================================ ================================================ FILE: languages/shire-json/src/test/kotlin/com/phodal/shire/json/ShireEnvVariableFillerTest.kt ================================================ package com.phodal.shire.json import com.intellij.json.psi.JsonObject import org.assertj.core.api.Assertions.assertThat import org.junit.Test class ShireEnvVariableFillerTest { @Test fun should_return_original_message_body_when_json_object_is_null() { // Given val messageBody = "Hello, \${name}!" val variables = listOf(setOf("name")) val jsonObject: JsonObject? = null val processVars = mapOf("name" to "John") // When val result = ShireEnvVariableFiller.fillVariables(messageBody, variables, jsonObject, processVars) // Then assertThat(result).isEqualTo(messageBody) } } ================================================ FILE: languages/shire-kotlin/src/main/kotlin/com/phodal/shirelang/kotlin/KotlinContextCollector.kt ================================================ package com.phodal.shirelang.kotlin import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.psi.PsiElement import com.intellij.psi.PsiElementFactory import com.intellij.psi.PsiReference import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.kotlin.analysis.decompiler.psi.file.KtDecompiledFile import org.jetbrains.kotlin.idea.KotlinLanguage import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.psi.* object KotlinContextCollector { /** * Resolves the given reference to a PSI element. * * @param reference the reference to resolve * @return the resolved PSI element, or null if the reference cannot be resolved */ fun resolveReference(reference: PsiReference?): PsiElement? { val resolvedElement: PsiElement? = reference?.resolve() if (resolvedElement is KtPrimaryConstructor) { return PsiTreeUtil.getParentOfType(resolvedElement, KtClass::class.java, false) } if (resolvedElement == null) return null val virtualFile = resolvedElement.containingFile.virtualFile if (virtualFile != null && !ProjectFileIndex.getInstance(resolvedElement.project).isInLibrary(virtualFile)) { if (resolvedElement is KtClass || resolvedElement is KtFunction) { return resolvedElement } } return null } /** * Finds and returns a list of used variables within the given scope. * * @param scope the scope within which to search for used variables * @return a list of KtValVarKeywordOwner objects representing the used variables found within the scope */ fun findUsedVariables(scope: PsiElement): List { val referenceExpressions = PsiTreeUtil.findChildrenOfType(scope, KtReferenceExpression::class.java) val resolvedReferences = referenceExpressions.mapNotNull { it.mainReference.resolve() as? KtValVarKeywordOwner } return resolvedReferences.toList() } /** * Returns the text representation of the return type of a Kotlin function. * * @param function The Kotlin function for which the return type text is to be retrieved. * * @return The text representation of the return type of the function, preceded by a colon (":"). * If the function does not have a return type, an empty string is returned. * * When given a class, this method does not return the code representation of the class. * Instead, it returns the comment representation of the class, enclosed in /** ... */. * * Note: This documentation does not include @author and @version tags. */ fun returnTypeText(function: KtFunction?): String { val typeReference = function?.typeReference val typeText = typeReference?.getTypeText() ?: return "" return ": $typeText" } /** * Replaces all occurrences of a reference expression with a new name in the given element. * * @param element the element in which to replace the reference expressions * @param oldName the old name of the reference expression to be replaced * @param newName the new name to replace the reference expression with * @return a new PsiElement with the replaced reference expressions, or the original element if newName is null */ private fun replaceReferenceExpressions(element: PsiElement, oldName: String, newName: String?): PsiElement { if (newName == null) return element val copiedElement = element.copy() val factory = PsiElementFactory.getInstance(element.project) PsiTreeUtil.findChildrenOfAnyType(copiedElement, false, KtReferenceExpression::class.java) .filterIsInstance() .filter { it.text == oldName } .forEach { reference -> reference.replace(factory.createExpressionFromText(newName, reference.context)) } return copiedElement } /** * Generates a summary for the given Kotlin language PsiElement. * * @param psiElement The PsiElement to generate the summary for. * @return The generated summary as a String, or null if the PsiElement is not applicable. * * If the PsiElement is a KtFile, it creates a copy of it and removes the function implementations. * Then it summarizes each class or object in the file. * * If the PsiElement is a KtClassOrObject, it creates a copy of it and summarizes the class or object. * * The generated summary is returned as a String. * If the PsiElement is not applicable, null is returned. */ fun generateSummary(psiElement: PsiElement): String? { if (psiElement.language != KotlinLanguage.INSTANCE || psiElement is KtDecompiledFile) { return null } return when (psiElement) { is KtFile -> { val copy = psiElement.copy() as KtFile val project = copy.project val functions = PsiTreeUtil.getChildrenOfType(copy, KtFunction::class.java) ?: emptyArray() functions.forEach { function -> removeFunctionImplementation(project, function) } val classesOrObjects = PsiTreeUtil.getChildrenOfType(copy, KtClassOrObject::class.java) ?: emptyArray() classesOrObjects.forEach { classOrObject -> summarizeClassOrObject(project, classOrObject) } copy.text } is KtClassOrObject -> { val copy = psiElement.copy() as KtClassOrObject val project = copy.project summarizeClassOrObject(project, copy) copy.text } else -> null } } private fun summarizeClassOrObject(project: Project, item: KtClassOrObject) { for (declaration in item.declarations) { when (declaration) { is KtFunction -> { removeFunctionImplementation(project, declaration) } is KtClassOrObject -> { summarizeClassOrObject(project, declaration) } } } } private const val placeholderMessage = "/* implementation omitted for shortness */" /** * Removes the implementation of a function. * * @param project the project context * @param function the function to remove the implementation from * * This method removes the implementation of the given function by deleting the child range of the body expression. * If the length of the placeholder message is greater than or equal to the length of the body expression, no changes are made. * After removing the implementation, a placeholder is added as the first child of the body expression. */ private fun removeFunctionImplementation(project: Project, function: KtFunction) { val bodyExpression = function.bodyExpression ?: return if (placeholderMessage.length >= bodyExpression.textLength) return bodyExpression.deleteChildRange(bodyExpression.firstChild, bodyExpression.lastChild) bodyExpression.addAfter(makePlaceholder(project), bodyExpression.firstChild) } private fun makePlaceholder(project: Project): PsiElement { return KtPsiFactory(project, false).createComment(placeholderMessage) } } fun KtNamedDeclaration.getReturnTypeReferences(): List { return when (this) { is KtCallableDeclaration -> listOfNotNull(typeReference) is KtClassOrObject -> superTypeListEntries.mapNotNull { it.typeReference } is KtScript -> emptyList() else -> throw AssertionError("Unexpected declaration kind: $text") } } fun KtTypeReference?.getTypeText(): String? { if (this == null) return null val typeElement = this.typeElement if (typeElement is KtUserType) { val typeElementReference = typeElement.referenceExpression?.mainReference?.resolve() if (typeElementReference is KtClass) { return typeElementReference.name } } return this.text } ================================================ FILE: languages/shire-kotlin/src/main/kotlin/com/phodal/shirelang/kotlin/KotlinPsiUtil.kt ================================================ package com.phodal.shirelang.kotlin import com.intellij.psi.PsiElement import com.intellij.psi.PsiMethod import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.psi.PsiReference import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.SearchScope import com.intellij.psi.search.searches.MethodReferencesSearch import com.intellij.psi.search.searches.ReferencesSearch import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFunction import org.jetbrains.kotlin.psi.KtNamedFunction object KotlinPsiUtil { fun getFunctions(kotlinClass: KtClassOrObject): List { return kotlinClass.getDeclarations().filterIsInstance() } fun getClasses(ktFile: KtFile): List { return ktFile.declarations.filterIsInstance() } fun signatureString(signatureString: KtNamedFunction): String { val bodyBlockExpression = signatureString.bodyBlockExpression val startOffsetInParent = if (bodyBlockExpression != null) { bodyBlockExpression.startOffsetInParent } else { val bodyExpression = signatureString.bodyExpression bodyExpression?.startOffsetInParent ?: signatureString.textLength } val text = signatureString.text val substring = text.substring(0, startOffsetInParent) return substring.replace('\n', ' ').trim() } fun findUsages(nameIdentifierOwner: PsiNameIdentifierOwner): List { val project = nameIdentifierOwner.project val searchScope = GlobalSearchScope.allScope(project) as SearchScope return when (nameIdentifierOwner) { is PsiMethod -> { MethodReferencesSearch.search(nameIdentifierOwner, searchScope, true) } else -> { ReferencesSearch.search((nameIdentifierOwner as PsiElement), searchScope, true) } }.findAll().map { it as PsiReference } } } ================================================ FILE: languages/shire-kotlin/src/main/kotlin/com/phodal/shirelang/kotlin/KotlinTypeResolver.kt ================================================ package com.phodal.shirelang.kotlin import com.intellij.psi.PsiElement import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getValueParameters object KotlinTypeResolver { fun resolveByElement(element: PsiElement): MutableMap { if (element !is KtNamedDeclaration) return mutableMapOf() val resolvedClasses: MutableMap = resolveByMethod(element) when (element) { is KtClassOrObject -> { KotlinPsiUtil.getFunctions(element).forEach { resolvedClasses.putAll(resolveByMethod(it)) } resolvedClasses.putAll(resolveByFields(element)) } is KtFile -> { KotlinPsiUtil.getClasses(element).forEach { resolvedClasses.putAll(resolveByFields(it)) } } } return resolvedClasses } fun resolveByFields(element: KtClassOrObject): Map { val resolvedClasses = mutableMapOf() element.primaryConstructorParameters.forEach { val typeReference = it.typeReference val elements = resolveType(typeReference) elements.forEach { element -> if (element is KtClass) { resolvedClasses[element.name!!] = element } } } return resolvedClasses } fun resolveByMethod(element: PsiElement): MutableMap { val resolvedClasses = mutableMapOf() when (element) { is KtNamedDeclaration -> { element.getValueParameters().map { val typeReference = it.typeReference resolveType(typeReference) }.forEach { if (it is KtClass) { resolvedClasses[it.name!!] = it } } // with Generic returnType, like: ResponseEntity> element.getReturnTypeReferences().forEach { returnType -> resolveType(returnType).filterIsInstance().forEach { resolvedClasses[it.name!!] = it } } } } return resolvedClasses } private fun resolveType(typeReference: KtTypeReference?): List { if (typeReference == null) return emptyList() val result = mutableListOf() when (val ktTypeElement = typeReference.typeElement) { is KtUserType -> { ktTypeElement.typeArguments.forEach { result += resolveType(it.typeReference) } val typeElementReference = ktTypeElement.referenceExpression?.mainReference?.resolve() if (typeElementReference is KtClass) { result += typeElementReference } } } return result } } ================================================ FILE: languages/shire-kotlin/src/main/kotlin/com/phodal/shirelang/kotlin/codemodel/KotlinClassStructureProvider.kt ================================================ package com.phodal.shirelang.kotlin.codemodel import com.intellij.psi.PsiElement import com.intellij.psi.PsiNameIdentifierOwner import com.phodal.shirecore.provider.codemodel.ClassStructureProvider import com.phodal.shirecore.provider.codemodel.model.ClassStructure import com.phodal.shirelang.kotlin.KotlinPsiUtil import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtParameter class KotlinClassStructureProvider : ClassStructureProvider { private fun getPrimaryConstructorFields(kotlinClass: KtClassOrObject): List { return kotlinClass.getPrimaryConstructorParameters().filter { it.hasValOrVar() } } override fun build(psiElement: PsiElement, gatherUsages: Boolean): ClassStructure? { if (psiElement !is KtClassOrObject) return null val text = psiElement.text val name = psiElement.name val functions = KotlinPsiUtil.getFunctions(psiElement) val allFields = getPrimaryConstructorFields(psiElement) val usages = if (gatherUsages) KotlinPsiUtil.findUsages(psiElement as PsiNameIdentifierOwner) else emptyList() val annotations: List = psiElement.annotationEntries.mapNotNull { it.text } val displayName = psiElement.fqName?.asString() ?: psiElement.name ?: "" return ClassStructure( psiElement, text, name, displayName, functions, allFields, null, annotations = annotations, usages ) } } ================================================ FILE: languages/shire-kotlin/src/main/kotlin/com/phodal/shirelang/kotlin/codemodel/KotlinFileStructureProvider.kt ================================================ package com.phodal.shirelang.kotlin.codemodel import com.intellij.psi.PsiFile import com.intellij.psi.util.PsiTreeUtil import com.phodal.shirecore.provider.codemodel.FileStructureProvider import com.phodal.shirecore.provider.codemodel.model.FileStructure import com.phodal.shirecore.relativePath import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtImportList import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtPackageDirective class KotlinFileStructureProvider : FileStructureProvider { override fun build(psiFile: PsiFile): FileStructure? { val name = psiFile.name val path = if (psiFile.virtualFile != null) psiFile.virtualFile!!.relativePath(psiFile.project) else "" val packageDirective = PsiTreeUtil.getChildrenOfTypeAsList(psiFile, KtPackageDirective::class.java).firstOrNull() val packageName = packageDirective?.text ?: "" val importList = PsiTreeUtil.getChildrenOfTypeAsList(psiFile, KtImportList::class.java) val imports = importList.flatMap { it.imports } val classOrObjects = PsiTreeUtil.getChildrenOfTypeAsList(psiFile, KtClassOrObject::class.java) val namedFunctions = PsiTreeUtil.getChildrenOfTypeAsList(psiFile, KtNamedFunction::class.java) return FileStructure(psiFile, name, path, packageName, imports, classOrObjects, namedFunctions) } } ================================================ FILE: languages/shire-kotlin/src/main/kotlin/com/phodal/shirelang/kotlin/codemodel/KotlinMethodStructureProvider.kt ================================================ package com.phodal.shirelang.kotlin.codemodel import com.intellij.psi.PsiElement import com.intellij.psi.PsiNameIdentifierOwner import com.phodal.shirecore.provider.codemodel.MethodStructureProvider import com.phodal.shirecore.provider.codemodel.model.MethodStructure import com.phodal.shirelang.kotlin.KotlinPsiUtil import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getReturnTypeReference import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.psiUtil.containingClass class KotlinMethodStructureProvider : MethodStructureProvider { override fun build(psiElement: PsiElement, includeClassContext: Boolean, gatherUsages: Boolean): MethodStructure? { if (psiElement !is KtNamedFunction) return null val returnType = psiElement.getReturnTypeReference()?.text val containingClass = psiElement.containingClass() val signatureString = KotlinPsiUtil.signatureString(psiElement) val displayName = psiElement.language.displayName val valueParameters = psiElement.valueParameters.mapNotNull { it.name } val usages = if (gatherUsages) KotlinPsiUtil.findUsages(psiElement as PsiNameIdentifierOwner) else emptyList() return MethodStructure( psiElement, psiElement.text, psiElement.name, signatureString, containingClass, displayName, returnType, valueParameters, includeClassContext, usages ) } } ================================================ FILE: languages/shire-kotlin/src/main/kotlin/com/phodal/shirelang/kotlin/codemodel/KotlinVariableStructureProvider.kt ================================================ package com.phodal.shirelang.kotlin.codemodel import com.intellij.psi.PsiElement import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.psi.util.PsiTreeUtil import com.phodal.shirecore.provider.codemodel.VariableStructureProvider import com.phodal.shirecore.provider.codemodel.model.VariableStructure import com.phodal.shirelang.kotlin.KotlinPsiUtil import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtParameter import org.jetbrains.kotlin.psi.KtVariableDeclaration import org.jetbrains.kotlin.psi.psiUtil.containingClass class KotlinVariableStructureProvider : VariableStructureProvider { override fun build( psiElement: PsiElement, withMethodContext: Boolean, withClassContext: Boolean, gatherUsages: Boolean ): VariableStructure? { when (psiElement) { is KtVariableDeclaration -> { val text = psiElement.text val name = psiElement.name val parentOfType = PsiTreeUtil.getParentOfType(psiElement, KtNamedFunction::class.java, true) val containingClass = psiElement.containingClass() val psiNameIdentifierOwner = psiElement as? PsiNameIdentifierOwner val usages = if (gatherUsages && psiNameIdentifierOwner != null) { KotlinPsiUtil.findUsages(psiNameIdentifierOwner) } else { emptyList() } return VariableStructure(psiElement, text, name, parentOfType, containingClass, usages, withMethodContext, withClassContext) } is KtParameter -> { val text = psiElement.text val name = psiElement.name val parentOfType = PsiTreeUtil.getParentOfType(psiElement, KtNamedFunction::class.java, true) val containingClass = psiElement.containingClass() val psiNameIdentifierOwner = psiElement as? PsiNameIdentifierOwner val usages = if (gatherUsages && psiNameIdentifierOwner != null) { KotlinPsiUtil.findUsages(psiNameIdentifierOwner) } else { emptyList() } return VariableStructure(psiElement, text, name, parentOfType, containingClass, usages, withMethodContext, withClassContext) } else -> { return null } } } } ================================================ FILE: languages/shire-kotlin/src/main/kotlin/com/phodal/shirelang/kotlin/complexity/KotlinComplexityProvider.kt ================================================ package com.phodal.shirelang.kotlin.complexity import com.intellij.psi.PsiElement import com.phodal.shirecore.ast.ComplexitySink import com.phodal.shirecore.ast.ComplexityVisitor import com.phodal.shirecore.provider.complexity.ComplexityProvider class KotlinComplexityProvider : ComplexityProvider { override fun process(element: PsiElement): Int { val sink = ComplexitySink() val visitor = visitor(sink) element.accept(visitor) return sink.getComplexity() } override fun visitor(sink: ComplexitySink): ComplexityVisitor { return KotlinLanguageVisitor(sink) } } ================================================ FILE: languages/shire-kotlin/src/main/kotlin/com/phodal/shirelang/kotlin/complexity/KotlinLanguageVisitor.kt ================================================ /** * The MIT License (MIT) *

* https://github.com/nikolaikopernik/code-complexity-plugin *

*/ package com.phodal.shirelang.kotlin.complexity import com.intellij.psi.NavigatablePsiElement import com.intellij.psi.PsiElement import com.intellij.psi.tree.TokenSet import com.phodal.shirecore.ast.ComplexitySink import com.phodal.shirecore.ast.ComplexityVisitor import com.phodal.shirecore.ast.PointType import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.lexer.KtToken import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.parents import org.jetbrains.kotlin.resolve.inline.InlineUtil import org.jetbrains.kotlin.types.expressions.OperatorConventions import org.jetbrains.kotlin.util.OperatorNameConventions class KotlinLanguageVisitor(private val sink: ComplexitySink) : ComplexityVisitor() { override fun processElement(element: PsiElement) { when (element) { is KtWhileExpression -> sink.increaseComplexityAndNesting(PointType.LOOP_WHILE) is KtDoWhileExpression -> sink.increaseComplexityAndNesting(PointType.LOOP_WHILE) is KtWhenExpression -> sink.increaseComplexityAndNesting(PointType.SWITCH) is KtIfExpression -> processIfExpression(element) // `else if` is KtContainerNodeForControlStructureBody -> { if ((element.expression is KtIfExpression) && (element.firstChild is KtIfExpression)) { sink.decreaseNesting() } } is KtForExpression -> sink.increaseComplexityAndNesting(PointType.LOOP_FOR) is KtCatchClause -> sink.increaseComplexityAndNesting(PointType.CATCH) is KtBreakExpression -> if (element.labelQualifier != null) sink.increaseComplexity(PointType.BREAK) is KtContinueExpression -> if (element.labelQualifier != null) sink.increaseComplexity(PointType.CONTINUE) is KtLambdaExpression -> sink.increaseNesting() is KtBinaryExpression -> { if (element.parent is KtStatementExpression || element.parent !is KtExpression) { element.calculateLogicalComplexity() } } is KtNameReferenceExpression -> if (isRecursiveCall(element)) sink.increaseComplexity(PointType.RECURSION) } } override fun postProcess(element: PsiElement) { if (element is KtWhileExpression || element is KtWhenExpression || element is KtDoWhileExpression || element is KtForExpression || element is KtCatchClause || element is KtLambdaExpression || element is KtIfExpression && element.`else` !is KtIfExpression ) { sink.decreaseNesting() } } override fun shouldVisitElement(element: PsiElement): Boolean = true private fun processIfExpression(element: KtIfExpression) { // if exists `else` that is not `else if` val ktExpression = element.`else` if (ktExpression != null && ktExpression !is KtIfExpression) { sink.increaseComplexity(PointType.ELSE) } val parent = element.parent if (parent is KtContainerNodeForControlStructureBody && parent.expression is KtIfExpression ) { sink.increaseNesting() sink.increaseComplexity(PointType.IF) } else { sink.increaseComplexityAndNesting(PointType.IF) } } private fun KtExpression.calculateLogicalComplexity(prevToken: KtToken? = null): KtToken? { var prevOperand = prevToken this.children.forEach { element -> when (element) { is KtOperationReferenceExpression -> if (element.operationSignTokenType != null && element.operationSignTokenType in (getLogicalOperationsTokens())) { if (prevOperand == null || element.operationSignTokenType != prevOperand) { sink.increaseComplexity(element.operationSignTokenType!!.toPointType()) } prevOperand = element.operationSignTokenType } is KtBinaryExpression -> prevOperand = element.calculateLogicalComplexity(prevOperand) is KtPrefixExpression -> prevOperand = element.calculateLogicalComplexity(prevOperand) is KtParenthesizedExpression -> { element.calculateLogicalComplexity() prevOperand = null } } } return prevOperand } private fun getLogicalOperationsTokens(): TokenSet { return TokenSet.create( KtTokens.ANDAND, KtTokens.OROR ) } private fun getNegationOperationToken(): KtToken { return KtTokens.EXCL } private fun getTempNegOperationToken(): KtToken { return KtTokens.QUEST } /** * Checking if recursion is used. * Have to do it fast and dirty as it should be fast to avoid exception in IDEA. * Basically we check: * - if the found reference expression name matches the direct parent method name. This code won't detect recursion * if more than one method involved (method A calling B and then B calling A) * - if the number of arguments matches the number of parameters * * Possible improvements: * - check parameter types as well */ private fun isRecursiveCall(element: KtNameReferenceExpression): Boolean { val argumentList = element.nextSibling if (argumentList is KtValueArgumentList) { val parentMethod: KtNamedFunction = element.findCurrentMethod() ?: return false if (element.getReferencedName() != parentMethod.nameIdentifier?.text) return false if (argumentList.arguments.size != parentMethod.valueParameterList?.parameters?.size) return false return true } return false } private fun getEnclosingFunction( element: NavigatablePsiElement, stopOnNonInlinedLambdas: Boolean, ): KtNamedFunction? { for (parent in element.parents) { when (parent) { is KtFunctionLiteral -> if (stopOnNonInlinedLambdas && !InlineUtil.isInlinedArgument( parent, parent.analyze(), false ) ) return null is KtNamedFunction -> { when (parent.parent) { is KtBlockExpression, is KtClassBody, is KtFile, is KtScript -> return parent else -> if (stopOnNonInlinedLambdas && !InlineUtil.isInlinedArgument( parent, parent.analyze(), false ) ) return null } } is KtClassOrObject -> return null } } return null } private fun getCallNameFromPsi(element: KtElement): Name? { when (element) { is KtSimpleNameExpression -> when (val elementParent = element.getParent()) { is KtCallExpression -> return Name.identifier(element.getText()) is KtOperationExpression -> { val operationReference = elementParent.operationReference if (element == operationReference) { val node = operationReference.getReferencedNameElementType() return if (node is KtToken) { val conventionName = if (elementParent is KtPrefixExpression) OperatorConventions.getNameForOperationSymbol(node, true, false) else OperatorConventions.getNameForOperationSymbol(node) conventionName ?: Name.identifier(element.getText()) } else { Name.identifier(element.getText()) } } } } is KtArrayAccessExpression -> return OperatorNameConventions.GET is KtThisExpression -> if (element.getParent() is KtCallExpression) return OperatorNameConventions.INVOKE } return null } } private fun KtToken.toPointType(): PointType = when (this) { KtTokens.ANDAND -> PointType.LOGICAL_AND KtTokens.OROR -> PointType.LOGICAL_OR else -> PointType.UNKNOWN } private fun PsiElement.findCurrentMethod(): KtNamedFunction? { var element: PsiElement? = this while (element != null && element !is KtNamedFunction) element = element.parent return element?.let { it as KtNamedFunction } } ================================================ FILE: languages/shire-kotlin/src/main/kotlin/com/phodal/shirelang/kotlin/impl/KotlinRefactoringTool.kt ================================================ package com.phodal.shirelang.kotlin.impl import com.intellij.codeInsight.daemon.impl.quickfix.RenameElementFix import com.intellij.codeInsight.daemon.impl.quickfix.SafeDeleteFix import com.intellij.codeInspection.MoveToPackageFix import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.ProjectManager import com.intellij.psi.* import com.intellij.psi.search.FileTypeIndex import com.intellij.psi.search.ProjectScope import com.phodal.shirecore.provider.shire.RefactoringTool import com.phodal.shirecore.variable.toolchain.refactoring.RefactorInstElement import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtNamedFunction class KotlinRefactoringTool : RefactoringTool { private val project = ProjectManager.getInstance().openProjects.firstOrNull() override fun lookupFile(path: String): PsiFile? { if (project == null) return null val elementInfo = getElementInfo(path, null) ?: return null val searchScope = ProjectScope.getProjectScope(project) val ktFiles = FileTypeIndex.getFiles(KotlinFileType.INSTANCE, searchScope) .mapNotNull { PsiManager.getInstance(project).findFile(it) as? KtFile } val className = elementInfo.className val packageName = elementInfo.pkgName val sourceFile = ktFiles.firstOrNull { it.packageFqName.asString() == packageName && it.name == "$className.kt" } ?: return null return sourceFile } override fun rename(sourceName: String, targetName: String, psiFile: PsiFile?): Boolean { if (project == null) return false val retrieveElementInfo = getElementInfo(sourceName, psiFile) val elementInfo = if (retrieveElementInfo != null) { retrieveElementInfo } else { return false } val element: PsiNamedElement = if (psiFile != null) { if (psiFile is KtFile) { val methodName = elementInfo.methodName val className = elementInfo.className val pkgName = elementInfo.pkgName if (elementInfo.isMethod) { val findClassAndMethod: PsiMethod? = psiFile.classes .firstOrNull { it.name == className } ?.findMethodsByName(methodName, false)?.firstOrNull() // lookup by function only findClassAndMethod ?: (psiFile.declarations .filterIsInstance() .firstOrNull { it.name == methodName } ?: return false) } else { psiFile.declarations .filterIsInstance() .firstOrNull { it.name == className && it.packageFqName.asString() == pkgName } ?: return false } } else { return false } } else { return false } val rename = RenameElementFix(element, targetName) try { rename.invoke(project, psiFile, element, element) } catch (e: Exception) { logger().error("Error in renaming", e) return false } return true } private fun getElementInfo(input: String, psiFile: PsiFile?): RefactorInstElement? { if (!input.contains("#") && psiFile != null) { val kotlinFile = psiFile as? KtFile ?: return null val className = kotlinFile.classes.firstOrNull()?.name ?: return null val canonicalName = kotlinFile.packageFqName.asString() + "." + className return RefactorInstElement(true, true, input, canonicalName, className, kotlinFile.packageFqName.asString()) } val isMethod = input.contains("#") val methodName = input.substringAfter("#") val canonicalName = input.substringBefore("#") val maybeClassName = canonicalName.substringAfterLast(".") // the clasName should be Uppercase or it will be the package var isClass = false var pkgName = canonicalName.substringBeforeLast(".") if (maybeClassName[0].isLowerCase()) { pkgName = "$pkgName.$maybeClassName" } else { isClass = true } return RefactorInstElement(isClass, isMethod, methodName, canonicalName, maybeClassName, pkgName) } override fun safeDelete(element: PsiElement): Boolean { val delete = SafeDeleteFix(element) try { delete.invoke(element.project, element.containingFile, element, element) } catch (e: Exception) { return false } return true } /** * In Kotlin the canonicalName is the fully qualified name of the target package or class. */ override fun move(element: PsiElement, canonicalName: String): Boolean { val file = element.containingFile val fix = MoveToPackageFix(file, canonicalName) try { fix.invoke(file.project, file, element, element) } catch (e: Exception) { return false } return true } } ================================================ FILE: languages/shire-kotlin/src/main/kotlin/com/phodal/shirelang/kotlin/provider/KotlinAutoTestService.kt ================================================ package com.phodal.shirelang.kotlin.provider import com.intellij.execution.configurations.RunProfile import com.intellij.openapi.application.ReadAction import com.intellij.openapi.application.runReadAction import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.intellij.psi.util.PsiTreeUtil import com.phodal.shirecore.provider.TestingService import com.phodal.shirecore.provider.codemodel.ClassStructureProvider import com.phodal.shirecore.provider.codemodel.FileStructureProvider import com.phodal.shirecore.provider.codemodel.model.ClassStructure import com.phodal.shirecore.variable.toolchain.unittest.AutoTestingPromptContext import com.phodal.shirelang.kotlin.KotlinPsiUtil import com.phodal.shirelang.kotlin.KotlinTypeResolver import com.phodal.shirelang.kotlin.getReturnTypeReferences import org.jetbrains.kotlin.idea.KotlinLanguage import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getValueParameters import org.jetbrains.plugins.gradle.service.execution.GradleRunConfiguration import java.io.File class KotlinAutoTestService : TestingService() { override fun isApplicable(element: PsiElement): Boolean = element.language is KotlinLanguage override fun runConfigurationClass(project: Project): Class = GradleRunConfiguration::class.java override fun isApplicable(project: Project, file: VirtualFile): Boolean { return (file.extension == "kt" || file.extension == "kts") && PsiManager.getInstance(project) .findFile(file) is KotlinLanguage } override fun findOrCreateTestFile( sourceFile: PsiFile, project: Project, psiElement: PsiElement, ): AutoTestingPromptContext? { val sourceFilePath = sourceFile.virtualFile val parentDir = sourceFilePath.parent val className = sourceFile.name.replace(".kt", "") + "Test" val parentDirPath = ReadAction.compute { parentDir?.path } ?: return null val relatedModels = lookupRelevantClass(project, psiElement).distinctBy { it.name } if (!(parentDirPath.contains("/main/java") || parentDirPath.contains("/main/kotlin"))) { log.error("SourceFile is not under the main/kotlin or main/java directory: $parentDirPath") return null } var isNewFile = false val testDirPath = parentDir.path .replace("/main/kotlin/", "/test/kotlin/") .replace("/main/java/", "/test/java/") var testDir = LocalFileSystem.getInstance().findFileByPath(testDirPath) if (testDir == null || !testDir.isDirectory) { isNewFile = true // Create the test directory if it doesn't exist val testDirFile = File(testDirPath) if (!testDirFile.exists()) { testDirFile.mkdirs() LocalFileSystem.getInstance().refreshAndFindFileByPath(testDirPath)?.let { refreshedDir -> testDir = refreshedDir } } } val testDirCreated: VirtualFile? = VirtualFileManager.getInstance().refreshAndFindFileByUrl("file://$testDirPath") if (testDirCreated == null) { log.error("Failed to create test directory: $testDirPath") return null } // Test directory already exists, find the corresponding test file val testFilePath = testDirPath + "/" + sourceFile.name.replace(".kt", "Test.kt") val testFile = LocalFileSystem.getInstance().findFileByPath(testFilePath) project.guessProjectDir()?.refresh(true, true) val currentClass: String = ReadAction.compute { val classContext = when (psiElement) { is KtFile -> FileStructureProvider.from(psiElement) is KtClassOrObject -> ClassStructureProvider.from(psiElement) is KtNamedFunction -> { PsiTreeUtil.getParentOfType(psiElement, KtClassOrObject::class.java)?.let { ClassStructureProvider.from(it) } } else -> null } return@compute classContext?.format() ?: "" } val imports: List = runReadAction { (sourceFile as KtFile).importList?.imports?.map { it.text } ?: emptyList() } val relatedCode = relatedModels.map { it.format() } return if (testFile != null) { AutoTestingPromptContext(isNewFile, testFile, relatedCode, className, sourceFile.language, currentClass, imports) } else { val targetFile = createTestFile(sourceFile, testDir!!, project) AutoTestingPromptContext(isNewFile = true, targetFile, relatedCode, "", sourceFile.language, currentClass, imports) } } override fun lookupRelevantClass(project: Project, element: PsiElement): List { return ReadAction.compute, Throwable> { val elements = mutableListOf() val projectPath = project.guessProjectDir()?.path val resolvedClasses = KotlinTypeResolver.resolveByElement(element) // find the class in the same project resolvedClasses.forEach { (_, psiClass) -> val classPath = psiClass?.containingFile?.virtualFile?.path if (classPath?.contains(projectPath!!) == true) { elements += ClassStructureProvider.from(psiClass) ?: return@forEach } } elements } } private fun createTestFile( sourceFile: PsiFile, testDir: VirtualFile, project: Project ): VirtualFile { val sourceFileName = sourceFile.name val testFileName = sourceFileName.replace(".kt", "Test.kt") val testFileContent = "" return WriteCommandAction.runWriteCommandAction(project) { val testFile = testDir.createChildData(this, testFileName) val document = FileDocumentManager.getInstance().getDocument(testFile) document?.setText(testFileContent) testFile } } } ================================================ FILE: languages/shire-kotlin/src/main/kotlin/com/phodal/shirelang/kotlin/provider/KotlinPsiElementDataBuilder.kt ================================================ package com.phodal.shirelang.kotlin.provider import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.psi.PsiElement import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiUtil import com.phodal.shirecore.provider.psi.PsiElementDataBuilder import com.phodal.shirelang.kotlin.codemodel.KotlinClassStructureProvider import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getReturnTypeReference import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getContentRange class KotlinPsiElementDataBuilder : PsiElementDataBuilder { override fun baseRoute(element: PsiElement): String { if (element !is KtNamedFunction) return "" val clazz = PsiTreeUtil.getParentOfType(element, PsiNameIdentifierOwner::class.java) if (clazz !is KtClass) return "" clazz.annotationEntries.forEach { when { it.shortName?.asString() == "RequestMapping" -> { return when (val value = it.valueArguments.firstOrNull()?.getArgumentExpression()) { is KtStringTemplateExpression -> value.literalContents() ?: value.text is KtSimpleNameExpression -> value.getReferencedName() else -> "" } } } } return "" } override fun inboundData(element: PsiElement): Map { if (element !is KtNamedFunction) return emptyMap() return handleParameters(element.valueParameters) } private fun handleParameters(ktParameters: MutableList): MutableMap { val result = mutableMapOf() ktParameters.map { parameter -> result += handleFromType(parameter) } return result } private fun handleFromType(parameter: KtParameter): Map { val map = when (val type = parameter.typeReference?.typeElement) { is KtClass -> processingClassType(type) else -> emptyMap() } return map } private fun processingClassType(type: KtClass): Map { if (!isProjectContent(type)) return emptyMap() val result = mutableMapOf() val fqn = type.fqName?.asString() ?: return result KotlinClassStructureProvider().build(type, false)?.format()?.let { result += mapOf(fqn to it) } return result } override fun outboundData(element: PsiElement): Map { if (element is KtClass) { return processingMethodsOutbound(element) } if (element !is KtNamedFunction) return emptyMap() val returnType = element.getReturnTypeReference() ?: return emptyMap() return processing(returnType, element) } override fun parseComment(project: Project, code: String): String? { return null } /** * Processes the outbound methods of a Kotlin class and returns a map of method names and their return types. * * @param element The Kotlin class element to process. * @return A map of method names and their return types. */ private fun processingMethodsOutbound(element: KtClass): Map { val result = mutableMapOf() val methods = element.declarations.filterIsInstance() for (method in methods) { val parameters = handleParameters(method.valueParameters) result += parameters val returnType = method.getReturnTypeReference() ?: continue result += processing(returnType, element) } return result } private fun processing(returnType: KtTypeReference, element: PsiElement): Map { val result = mutableMapOf() when (val typeElement = returnType.typeElement) { is KtUserType -> { val referenceExpression = typeElement.referenceExpression?.resolveMainReference() if (referenceExpression is KtClass) { result += processingClassType(referenceExpression) } typeElement.typeArgumentsAsTypes.forEach { result += processing(it, element) } } } return result } } internal fun KtStringTemplateExpression.literalContents(): String? { val escaper = createLiteralTextEscaper() val ssb = StringBuilder() return when (escaper.decode(getContentRange(), ssb)) { true -> ssb.toString() false -> null } } fun KtReferenceExpression.resolveMainReference(): PsiElement? = try { mainReference.resolve() } catch (e: Exception) { null } fun isProjectContent(element: PsiElement): Boolean { val virtualFile = PsiUtil.getVirtualFile(element) val project = runReadAction { element.project } return virtualFile == null || ProjectFileIndex.getInstance(project).isInContent(virtualFile) } ================================================ FILE: languages/shire-kotlin/src/main/kotlin/com/phodal/shirelang/kotlin/provider/KotlinRelatedClassesProvider.kt ================================================ package com.phodal.shirelang.kotlin.provider import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.phodal.shirecore.provider.psi.RelatedClassesProvider import com.phodal.shirelang.kotlin.KotlinTypeResolver class KotlinRelatedClassesProvider : RelatedClassesProvider { override fun lookup(element: PsiElement): List { return KotlinTypeResolver.resolveByElement(element).values.filterNotNull().toList() } override fun lookup(element: PsiFile): List { return KotlinTypeResolver.resolveByElement(element).values.filterNotNull().toList() } } ================================================ FILE: languages/shire-kotlin/src/main/kotlin/com/phodal/shirelang/kotlin/variable/KotlinLanguageToolchainProvider.kt ================================================ package com.phodal.shirelang.kotlin.variable import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.phodal.shirecore.provider.context.LanguageToolchainProvider import com.phodal.shirecore.provider.context.ToolchainContextItem import com.phodal.shirecore.provider.context.ToolchainPrepareContext import org.jetbrains.kotlin.idea.KotlinLanguage import org.jetbrains.kotlin.idea.base.projectStructure.languageVersionSettings class KotlinLanguageToolchainProvider : LanguageToolchainProvider { override fun isApplicable(project: Project, context: ToolchainPrepareContext): Boolean { return context.sourceFile?.language is KotlinLanguage } override suspend fun collect(project: Project, context: ToolchainPrepareContext): List { val languageVersionSettings = runReadAction { project.languageVersionSettings } val languageVersion = languageVersionSettings.languageVersion.versionString return listOf( ToolchainContextItem( KotlinLanguageToolchainProvider::class, "You are working on a project that uses Kotlin API version: $languageVersion" ) ) } } ================================================ FILE: languages/shire-kotlin/src/main/kotlin/com/phodal/shirelang/kotlin/variable/KotlinPsiContextVariableProvider.kt ================================================ package com.phodal.shirelang.kotlin.variable import org.jetbrains.kotlin.idea.KotlinLanguage import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.util.PsiTreeUtil import com.intellij.testIntegration.TestFinderHelper import com.phodal.shirecore.provider.variable.PsiContextVariableProvider import com.phodal.shirecore.provider.variable.model.PsiContextVariable import com.phodal.shirecore.psi.CodeSmellCollector import com.phodal.shirelang.kotlin.codemodel.KotlinClassStructureProvider import com.phodal.shirelang.kotlin.provider.KotlinRelatedClassesProvider import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtImportList import org.jetbrains.kotlin.psi.KtNamedFunction class KotlinPsiContextVariableProvider : PsiContextVariableProvider { override fun resolve(variable: PsiContextVariable, project: Project, editor: Editor, psiElement: PsiElement?): Any { if (psiElement?.language != KotlinLanguage.INSTANCE) return "" val psiFile = psiElement.containingFile val importList = PsiTreeUtil.getChildrenOfTypeAsList(psiFile, KtImportList::class.java) return when (variable) { PsiContextVariable.CURRENT_CLASS_NAME -> { val clazz: KtClassOrObject = PsiTreeUtil.getParentOfType(psiElement, KtClassOrObject::class.java) ?: return "" clazz.name ?: "" } PsiContextVariable.CURRENT_CLASS_CODE -> { val clazz: KtClassOrObject = PsiTreeUtil.getParentOfType(psiElement, KtClassOrObject::class.java) ?: return "" clazz.text } PsiContextVariable.CURRENT_METHOD_NAME -> { when (psiElement) { is KtClassOrObject -> psiElement.name ?: "" is KtNamedFunction -> psiElement.name ?: "" else -> psiElement.text } } PsiContextVariable.CURRENT_METHOD_CODE -> { when (psiElement) { is KtClassOrObject -> psiElement.text ?: "" is KtNamedFunction -> psiElement.bodyExpression?.text ?: "" else -> psiElement.text } } PsiContextVariable.RELATED_CLASSES -> { KotlinRelatedClassesProvider().lookup(psiElement.parent).joinToString("\n") { it.text } } PsiContextVariable.SIMILAR_TEST_CASE -> { "" } PsiContextVariable.IMPORTS -> { importList.joinToString("\n") { it.text } } PsiContextVariable.IS_NEED_CREATE_FILE -> TestFinderHelper.findClassesForTest(psiElement).isEmpty() PsiContextVariable.TARGET_TEST_FILE_NAME -> psiFile.name.replace(".kt", "") + "Test.kt" PsiContextVariable.UNDER_TEST_METHOD_CODE -> { "" } PsiContextVariable.FRAMEWORK_CONTEXT -> collectFrameworkContext(psiElement, project) PsiContextVariable.CODE_SMELL -> CodeSmellCollector.collectElementProblemAsSting(psiElement, project, editor) PsiContextVariable.METHOD_CALLER -> TODO() PsiContextVariable.CALLED_METHOD -> TODO() PsiContextVariable.SIMILAR_CODE -> TODO() PsiContextVariable.STRUCTURE -> when (psiElement) { is KtClassOrObject -> KotlinClassStructureProvider().build(psiElement, true)?.toString() ?: "" else -> "" } PsiContextVariable.CHANGE_COUNT -> calculateChangeCount(psiElement) PsiContextVariable.LINE_COUNT -> calculateLineCount(psiElement) PsiContextVariable.COMPLEXITY_COUNT -> calculateComplexityCount(psiElement) } } } ================================================ FILE: languages/shire-kotlin/src/main/resources/com.phodal.shirelang.kotlin.xml ================================================ ================================================ FILE: languages/shire-markdown/src/main/kotlin/com/phodal/shirelang/markdown/MarkdownNode.kt ================================================ package com.phodal.shirelang.markdown import org.intellij.markdown.IElementType import org.intellij.markdown.MarkdownElementTypes import org.intellij.markdown.MarkdownTokenTypes import org.intellij.markdown.ast.ASTNode import org.intellij.markdown.flavours.gfm.GFMElementTypes import org.intellij.markdown.flavours.gfm.GFMTokenTypes class MarkdownNode(val node: ASTNode, val parent: MarkdownNode?, val rootText: String) { val children: List = node.children.map { MarkdownNode(it, this, rootText) } val endOffset: Int get() = node.endOffset val startOffset: Int get() = node.startOffset val type: IElementType get() = node.type val text: String get() = rootText.substring(startOffset, endOffset) fun child(type: IElementType): MarkdownNode? = children.firstOrNull { it.type == type } } private fun MarkdownNode.capture(): MutableList { val result = mutableListOf() visit { node, processChildren -> fun wrapChildren(tag: String, newline: Boolean = false) { processChildren() } val nodeType = node.type val nodeText = node.text println("nodeType: $nodeType, nodeText: $nodeText") when (nodeType) { MarkdownElementTypes.UNORDERED_LIST -> wrapChildren("ul", newline = true) MarkdownElementTypes.ORDERED_LIST -> wrapChildren("ol", newline = true) MarkdownElementTypes.LIST_ITEM -> wrapChildren("li") MarkdownElementTypes.EMPH -> wrapChildren("em") MarkdownElementTypes.STRONG -> wrapChildren("strong") GFMElementTypes.STRIKETHROUGH -> wrapChildren("del") MarkdownElementTypes.ATX_1 -> wrapChildren("h1") MarkdownElementTypes.ATX_2 -> wrapChildren("h2") MarkdownElementTypes.ATX_3 -> wrapChildren("h3") MarkdownElementTypes.ATX_4 -> wrapChildren("h4") MarkdownElementTypes.ATX_5 -> wrapChildren("h5") MarkdownElementTypes.ATX_6 -> wrapChildren("h6") MarkdownElementTypes.BLOCK_QUOTE -> wrapChildren("blockquote") MarkdownElementTypes.PARAGRAPH -> wrapChildren("p", newline = true) MarkdownElementTypes.CODE_SPAN, MarkdownElementTypes.CODE_BLOCK, MarkdownElementTypes.CODE_FENCE, MarkdownTokenTypes.FENCE_LANG, MarkdownTokenTypes.CODE_LINE, MarkdownTokenTypes.CODE_FENCE_CONTENT, -> { // skip } MarkdownElementTypes.SHORT_REFERENCE_LINK, MarkdownElementTypes.FULL_REFERENCE_LINK, -> { val linkLabelNode = node.child(MarkdownElementTypes.LINK_LABEL) val linkLabelContent = linkLabelNode?.children ?.dropWhile { it.type == MarkdownTokenTypes.LBRACKET } ?.dropLastWhile { it.type == MarkdownTokenTypes.RBRACKET } if (linkLabelContent != null) { val label = linkLabelContent.joinToString(separator = "") { it.text } val linkText = node.child(MarkdownElementTypes.LINK_TEXT)?.text ?: label result.add(linkText) } } MarkdownElementTypes.INLINE_LINK -> { val destination = node.child(MarkdownElementTypes.LINK_DESTINATION)?.text result.add(destination ?: "") } MarkdownTokenTypes.TEXT, MarkdownTokenTypes.WHITE_SPACE, MarkdownTokenTypes.COLON, MarkdownTokenTypes.SINGLE_QUOTE, MarkdownTokenTypes.DOUBLE_QUOTE, MarkdownTokenTypes.LPAREN, MarkdownTokenTypes.RPAREN, MarkdownTokenTypes.LBRACKET, MarkdownTokenTypes.RBRACKET, MarkdownTokenTypes.EXCLAMATION_MARK, GFMTokenTypes.CHECK_BOX, GFMTokenTypes.GFM_AUTOLINK, -> { result.add(nodeText) } MarkdownTokenTypes.EOL -> {} MarkdownTokenTypes.GT -> {} MarkdownTokenTypes.LT -> {} MarkdownElementTypes.LINK_TEXT -> {} MarkdownTokenTypes.EMPH -> {} GFMTokenTypes.TILDE -> {} GFMElementTypes.TABLE -> {} // ignore image MarkdownElementTypes.IMAGE -> {} else -> { // println("unknown type: $nodeType") processChildren() } } } return result } private fun MarkdownNode.visit(action: (MarkdownNode, () -> Unit) -> Unit) { action(this) { for (child in children) { child.visit(action) } } } ================================================ FILE: languages/shire-markdown/src/main/kotlin/com/phodal/shirelang/markdown/MarkdownPsiCapture.kt ================================================ package com.phodal.shirelang.markdown import com.phodal.shirecore.provider.psi.PsiCapture import org.intellij.markdown.IElementType import org.intellij.markdown.MarkdownElementTypes import org.intellij.markdown.ast.ASTNode import org.intellij.markdown.ast.accept import org.intellij.markdown.ast.visitors.RecursiveVisitor import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor import org.intellij.markdown.flavours.gfm.GFMTokenTypes import org.intellij.markdown.parser.MarkdownParser class MarkdownPsiCapture: PsiCapture { private val embeddedHtmlType = IElementType("ROOT") /** * Capture markdown text with ast node Node */ override fun capture(fileContent: String, type: String): List { val flavour = GFMFlavourDescriptor() val parsedTree: ASTNode = MarkdownParser(flavour).parse(embeddedHtmlType, fileContent) val types: List = when (type) { /** * [GFMTokenTypes.GFM_AUTOLINK] , [MarkdownElementTypes.INLINE_LINK] */ "link" -> listOf("GFM_AUTOLINK") else -> listOf() } // Traverse the AST to find and process nodes of the specified type val result = mutableListOf() parsedTree.accept(object : RecursiveVisitor() { override fun visitNode(node: ASTNode) { when { // ignore image node.type == MarkdownElementTypes.IMAGE -> { return } types.contains(node.type.name) -> { result.add(fileContent.substring(node.startOffset, node.endOffset)) } node.type.name.lowercase() == type -> { result.add(fileContent.substring(node.startOffset, node.endOffset)) } } super.visitNode(node) } }) return result.map { it.trim() } } } ================================================ FILE: languages/shire-markdown/src/main/resources/com.phodal.shirelang.markdown.xml ================================================ ================================================ FILE: languages/shire-markdown/src/test/kotlin/com/phodal/shirelang/markdown/MarkdownPsiCaptureTest.kt ================================================ import com.phodal.shirelang.markdown.MarkdownPsiCapture import junit.framework.TestCase.assertEquals import org.intellij.lang.annotations.Language import org.junit.Test class MarkdownPsiCaptureTest { @Test fun should_return_url_when_gfm_auto_link() { @Language("Markdown") val markdownText = """ normal text link: https://shire.phodal.com """.trimIndent() val markdownPsiCapture = MarkdownPsiCapture() // when val result = markdownPsiCapture.capture(markdownText, "link") // then assertEquals("https://shire.phodal.com", result.first()) } @Test fun should_return_url_when_markdown_label_link() { @Language("Markdown") val markdownText = """ normal text link: [Shire](https://shire.phodal.com) link 2: https://aise.phodal.com """.trimIndent() val markdownPsiCapture = MarkdownPsiCapture() // when val result = markdownPsiCapture.capture(markdownText, "link") // then assertEquals("https://shire.phodal.com", result.first()) assertEquals("https://aise.phodal.com", result[1]) } @Test fun should_ignore_image_url() { @Language("Markdown") val markdownText = """ hello sample ![Shire](https://shire.phodal.com/images/pluginIcon.svg) """.trimIndent() val markdownPsiCapture = MarkdownPsiCapture() // when val result = markdownPsiCapture.capture(markdownText, "link") // then assertEquals(0, result.size) } @Test fun shouldParseLinkInList() { @Language("Markdown") val markdownText = """ 1. [aichat](https://github.com/sigoden/aichat) - all-in-one AI powered CLI chat and copilot. 2. [aider](https://github.com/paul-gauthier/aider) - AI pair programming in your terminal 3. [elia](https://github.com/darrenburns/elia) - A TUI ChatGPT client built with Textual 4. [gpterminator](https://github.com/AineeJames/ChatGPTerminator) - A TUI for OpenAI's ChatGPT 5. [gtt](https://github.com/eeeXun/gtt) - A TUI for Google Translate, ChatGPT, DeepL and other AI services. 6. [nvitop](https://github.com/XuehaiPan/nvitop) - An interactive NVIDIA-GPU process viewer and beyond. 7. [nvtop](https://github.com/Syllo/nvtop) - NVIDIA GPUs htop like monitoring tool 8. [ollama](https://github.com/ollama/ollama) - get up and running with large language models locally. 9. [oterm](https://github.com/ggozad/oterm) - A text-based terminal client for ollama. 10. [tgpt](https://github.com/aandrew-me/tgpt) - AI Chatbots in the terminal without needing API keys. 11. [yai](https://github.com/ekkinox/yai) - Your AI powered terminal assistant """.trimMargin() val markdownPsiCapture = MarkdownPsiCapture() val result = markdownPsiCapture.capture(markdownText, "link") assertEquals(11, result.size) } } ================================================ FILE: languages/shire-proto/src/main/kotlin/com/phodal/shirelang/proto/codemodel/ProtoClassStructureProvider.kt ================================================ package com.phodal.shirelang.proto.codemodel import com.intellij.protobuf.lang.psi.PbDefinition import com.intellij.protobuf.lang.psi.PbMessageDefinition import com.intellij.protobuf.lang.psi.PbServiceDefinition import com.intellij.protobuf.lang.psi.util.PbPsiUtil import com.intellij.psi.PsiElement import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.psi.PsiReference import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.searches.ReferencesSearch import com.phodal.shirecore.provider.codemodel.ClassStructureProvider import com.phodal.shirecore.provider.codemodel.model.ClassStructure class ProtoClassStructureProvider : ClassStructureProvider { override fun build(psiElement: PsiElement, gatherUsages: Boolean): ClassStructure? { if (psiElement !is PbDefinition) return null return when (psiElement) { is PbMessageDefinition -> { val text = psiElement.text val name = psiElement.name val usages = if (gatherUsages) findUsages(psiElement as PsiNameIdentifierOwner) else emptyList() val fields = mutableListOf() psiElement.body?.simpleFieldList?.let { fields += it } psiElement.body?.mapFieldList?.let { fields += it } psiElement.body?.oneofDefinitionList?.let { fields += it } ClassStructure( psiElement, text, name, name, emptyList(), fields, null, emptyList(), usages ) } is PbServiceDefinition -> { val text = psiElement.text val name = psiElement.name val methods = psiElement.body?.serviceMethodList ?: emptyList() val usages = if (gatherUsages) findUsages(psiElement as PsiNameIdentifierOwner) else emptyList() ClassStructure( psiElement, text, name, name, methods, emptyList(), null, emptyList(), usages ) } else -> { return null } } } companion object { fun findUsages(psiElement: PsiElement): List { val globalSearchScope = GlobalSearchScope.allScope(psiElement.project) return ReferencesSearch.search(psiElement, globalSearchScope, true) .findAll() .toList() } } } ================================================ FILE: languages/shire-proto/src/main/kotlin/com/phodal/shirelang/proto/codemodel/ProtoFileStructureProvider.kt ================================================ package com.phodal.shirelang.proto.codemodel import com.intellij.protobuf.lang.psi.PbImportStatement import com.intellij.protobuf.lang.psi.PbMessageDefinition import com.intellij.protobuf.lang.psi.PbPackageStatement import com.intellij.protobuf.lang.psi.PbServiceDefinition import com.intellij.psi.PsiFile import com.intellij.psi.util.PsiTreeUtil import com.phodal.shirecore.provider.codemodel.FileStructureProvider import com.phodal.shirecore.provider.codemodel.model.FileStructure import com.phodal.shirecore.relativePath class ProtoFileStructureProvider : FileStructureProvider { override fun build(psiFile: PsiFile): FileStructure? { val name = psiFile.name val path = if (psiFile.virtualFile != null) psiFile.virtualFile!!.relativePath(psiFile.project) else "" val packageName = PsiTreeUtil.getChildrenOfTypeAsList(psiFile, PbPackageStatement::class.java).firstOrNull()?.text ?: "" val imports = PsiTreeUtil.getChildrenOfTypeAsList(psiFile, PbImportStatement::class.java) val messages: List = PsiTreeUtil.getChildrenOfTypeAsList(psiFile, PbMessageDefinition::class.java) val services = PsiTreeUtil.getChildrenOfTypeAsList(psiFile, PbServiceDefinition::class.java) val enum = PsiTreeUtil.getChildrenOfTypeAsList(psiFile, PbMessageDefinition::class.java) val classes = messages + services + enum return FileStructure(psiFile, name, path, packageName, imports, classes, emptyList()) } } ================================================ FILE: languages/shire-proto/src/main/kotlin/com/phodal/shirelang/proto/provider/ShireProtoPsiVariableProvider.kt ================================================ package com.phodal.shirelang.proto.provider import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.protobuf.lang.PbLanguage import com.intellij.protobuf.lang.psi.* import com.intellij.psi.PsiElement import com.phodal.shirecore.provider.variable.PsiContextVariableProvider import com.phodal.shirecore.psi.CodeSmellCollector import com.phodal.shirecore.provider.variable.model.PsiContextVariable import com.phodal.shirelang.proto.codemodel.ProtoClassStructureProvider import com.phodal.shirelang.proto.codemodel.ProtoFileStructureProvider class ShireProtoPsiVariableProvider : PsiContextVariableProvider { override fun resolve(variable: PsiContextVariable, project: Project, editor: Editor, psiElement: PsiElement?): Any { if (psiElement?.language !is PbLanguage) return "" val containingFile: PbFile = psiElement.containingFile as? PbFile ?: return "" return when (variable) { PsiContextVariable.CURRENT_CLASS_NAME -> { return when (psiElement) { is PbFile -> psiElement.name ?: "" is PbMessageDefinition -> psiElement.name ?: "" is PbEnumDefinition -> psiElement.name ?: "" is PbServiceDefinition -> psiElement.name ?: "" else -> "" } } PsiContextVariable.CURRENT_CLASS_CODE -> return psiElement.text PsiContextVariable.CURRENT_METHOD_NAME -> { return when (psiElement) { is PbServiceMethod -> psiElement.nameIdentifier ?: "" else -> "" } } PsiContextVariable.CURRENT_METHOD_CODE -> return psiElement.text PsiContextVariable.RELATED_CLASSES -> { if (psiElement !is PbServiceDefinition) return "" ShireProtoUtils.lookupDependenceMessages(psiElement, project).map { it.text ?: "" } } PsiContextVariable.IMPORTS -> { return containingFile.importStatements.joinToString("\n") { it.text } } PsiContextVariable.FRAMEWORK_CONTEXT -> return collectFrameworkContext(psiElement, project) PsiContextVariable.CODE_SMELL -> return CodeSmellCollector.collectElementProblemAsSting( psiElement, project, editor ) PsiContextVariable.METHOD_CALLER -> ShireProtoUtils.lookupUsage(psiElement, project).joinToString("\n") PsiContextVariable.CALLED_METHOD -> { if (psiElement !is PbServiceDefinition) return "" ShireProtoUtils.lookupDependenceMessages(psiElement, project).map { it.text ?: "" } } PsiContextVariable.STRUCTURE -> when (psiElement) { is PbFile -> ProtoFileStructureProvider().build(psiElement)?.toString() ?: "" is PbMessageDefinition, is PbServiceDefinition, -> ProtoClassStructureProvider().build(psiElement, true)?.toString() ?: "" else -> null } ?: "" PsiContextVariable.SIMILAR_CODE -> "" PsiContextVariable.SIMILAR_TEST_CASE -> "" PsiContextVariable.IS_NEED_CREATE_FILE -> "" PsiContextVariable.TARGET_TEST_FILE_NAME -> "" PsiContextVariable.UNDER_TEST_METHOD_CODE -> "" PsiContextVariable.CHANGE_COUNT -> calculateChangeCount(psiElement) PsiContextVariable.LINE_COUNT -> calculateLineCount(psiElement) PsiContextVariable.COMPLEXITY_COUNT -> calculateComplexityCount(psiElement) } } } ================================================ FILE: languages/shire-proto/src/main/kotlin/com/phodal/shirelang/proto/provider/ShireProtoUtils.kt ================================================ package com.phodal.shirelang.proto.provider import com.intellij.openapi.project.Project import com.intellij.protobuf.ide.gutter.findImplementations import com.intellij.protobuf.lang.findusages.PbFindUsagesProvider import com.intellij.protobuf.lang.psi.PbMessageDefinition import com.intellij.protobuf.lang.psi.PbMessageTypeName import com.intellij.protobuf.lang.psi.PbNamedElement import com.intellij.protobuf.lang.psi.PbServiceDefinition import com.intellij.protobuf.lang.resolve.ProtoSymbolPathReference import com.intellij.protobuf.lang.stub.index.QualifiedNameIndex import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.stubs.StubIndex import com.phodal.shirecore.provider.codemodel.ClassStructureProvider import com.phodal.shirecore.provider.codemodel.FileStructureProvider import com.phodal.shirecore.provider.codemodel.MethodStructureProvider object ShireProtoUtils { fun lookupDependenceMessages(psiElement: PbServiceDefinition, project: Project): List { val methods = psiElement.body?.serviceMethodList ?: return listOf() val type = methods.map { method -> method.serviceMethodTypeList.mapNotNull { type -> val messageTypeName: PbMessageTypeName = type.messageTypeName val protoSymbolPathReference = messageTypeName.symbolPath.reference as ProtoSymbolPathReference protoSymbolPathReference.multiResolve(true).mapNotNull { it.element as? PbMessageDefinition } }.flatten() }.flatten() val messages = type.distinct() val secondLevels = messages.mapNotNull { it.body?.simpleFieldList?.mapNotNull { field -> field.typeName.symbolPath.reference?.resolve() as? PbMessageDefinition } }.flatten().distinct() return messages + secondLevels } /** * Maybe can use for GoTo Languages? */ private fun getItemsByName( project: Project, name: String, ): List { val projectScope = GlobalSearchScope.projectScope(project) val results: MutableCollection = StubIndex.getElements( QualifiedNameIndex.KEY, name, project, projectScope, PbNamedElement::class.java ) return results.toList() } fun lookupUsage(psiElement: PsiElement, project: Project): List { if (!PbFindUsagesProvider().canFindUsagesFor(psiElement)) { return listOf() } return when (psiElement) { is PbMessageDefinition -> { return findImplClassCode(psiElement) } is PbServiceDefinition -> { findImplClassCode(psiElement) } else -> listOf() } } private fun findImplClassCode(psiElement: PsiElement): List { return findImplementations(psiElement).map { formatElement(it) ?: it.text }.toList() } private fun formatElement(psiElement: PsiElement): String? { return when { psiElement is PsiFile -> { FileStructureProvider.from(psiElement)?.format() } else -> ClassStructureProvider.from(psiElement, false)?.format() ?: MethodStructureProvider.from(psiElement, false)?.format() } } } ================================================ FILE: languages/shire-proto/src/main/kotlin/com/phodal/shirelang/proto/variable/ProtobufToolchainProvider.kt ================================================ package com.phodal.shirelang.proto.variable import com.intellij.openapi.project.Project import com.intellij.protobuf.lang.PbLanguage import com.intellij.protobuf.lang.psi.PbFile import com.phodal.shirecore.provider.context.LanguageToolchainProvider import com.phodal.shirecore.provider.context.ToolchainContextItem import com.phodal.shirecore.provider.context.ToolchainPrepareContext class ProtobufToolchainProvider : LanguageToolchainProvider { override fun isApplicable(project: Project, context: ToolchainPrepareContext): Boolean { return context.sourceFile?.language is PbLanguage } override suspend fun collect(project: Project, context: ToolchainPrepareContext): List { val file = context.sourceFile as? PbFile ?: return emptyList() val protoVersion = file.syntaxLevel return listOf( ToolchainContextItem( ProtobufToolchainProvider::class, "- Protobuf version: $protoVersion" ) ) } } ================================================ FILE: languages/shire-proto/src/main/resources/com.phodal.shirelang.proto.xml ================================================ ================================================ FILE: languages/shire-python/src/main/kotlin/com/phodal/shirelang/python/provider/ShirePythonAutoTesting.kt ================================================ package com.phodal.shirelang.python.provider import com.intellij.execution.RunManager import com.intellij.execution.actions.ConfigurationContext import com.intellij.execution.actions.RunConfigurationProducer import com.intellij.execution.configurations.RunConfiguration import com.intellij.execution.configurations.RunProfile import com.intellij.openapi.application.WriteAction import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.jetbrains.python.PythonLanguage import com.jetbrains.python.psi.PyFile import com.jetbrains.python.run.PythonRunConfiguration import com.jetbrains.python.run.PythonRunConfigurationProducer import com.phodal.shirecore.provider.codemodel.model.ClassStructure import com.phodal.shirecore.provider.TestingService import com.phodal.shirecore.variable.toolchain.unittest.AutoTestingPromptContext import com.phodal.shirelang.python.util.PyTestUtil class ShirePythonAutoTesting : TestingService() { override fun isApplicable(element: PsiElement): Boolean = element.language.displayName == "Python" override fun isApplicable(project: Project, file: VirtualFile): Boolean = file.extension == "py" override fun runConfigurationClass(project: Project): Class = PythonRunConfiguration::class.java override fun createConfiguration(project: Project, virtualFile: VirtualFile): RunConfiguration? { val psiFile: PyFile = PsiManager.getInstance(project).findFile(virtualFile) as? PyFile ?: return null val runManager = RunManager.getInstance(project) val context = ConfigurationContext(psiFile) val configProducer = RunConfigurationProducer.getInstance( PythonRunConfigurationProducer::class.java ) var settings = configProducer.findExistingConfiguration(context) if (settings == null) { val fromContext = configProducer.createConfigurationFromContext(context) ?: return null settings = fromContext.configurationSettings runManager.setTemporaryConfiguration(settings) } val configuration = settings.configuration as PythonRunConfiguration return configuration } override fun findOrCreateTestFile(sourceFile: PsiFile, project: Project, psiElement: PsiElement): AutoTestingPromptContext? { val testFileName = PyTestUtil.getTestNameExample(sourceFile.virtualFile) val testDir = PyTestUtil.getTestsDirectory(sourceFile.virtualFile, project) val testFile = WriteAction.computeAndWait { testDir.findOrCreateChildData(this, PyTestUtil.toTestFileName(testFileName, sourceFile.name)) } return AutoTestingPromptContext(true, testFile, listOf(), "", PythonLanguage.INSTANCE) } override fun lookupRelevantClass(project: Project, element: PsiElement): List { return listOf() } } ================================================ FILE: languages/shire-python/src/main/kotlin/com/phodal/shirelang/python/provider/ShirePythonPsiVariableProvider.kt ================================================ package com.phodal.shirelang.python.provider import com.intellij.openapi.application.WriteAction import com.intellij.openapi.editor.Editor import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.findFile import com.intellij.psi.PsiElement import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.searches.ReferencesSearch import com.jetbrains.python.PythonLanguage import com.jetbrains.python.psi.PyClass import com.jetbrains.python.psi.PyFile import com.jetbrains.python.psi.PyFunction import com.phodal.shirecore.provider.variable.PsiContextVariableProvider import com.phodal.shirecore.psi.CodeSmellCollector import com.phodal.shirecore.provider.variable.model.PsiContextVariable import com.phodal.shirelang.python.util.PyTestUtil import com.phodal.shirelang.python.util.PythonPsiUtil class ShirePythonPsiVariableProvider : PsiContextVariableProvider { override fun resolve( variable: PsiContextVariable, project: Project, editor: Editor, psiElement: PsiElement?, ): Any { if (psiElement?.language !is PythonLanguage) return "" val underTestElement = PyTestUtil.getElementForTests(project, editor) val sourceFile = underTestElement?.containingFile as? PyFile ?: return "" return when (variable) { PsiContextVariable.CURRENT_CLASS_NAME -> { when (underTestElement) { is PyClass -> underTestElement.name ?: "" is PyFunction -> underTestElement.name ?: "" else -> "" } } PsiContextVariable.CURRENT_CLASS_CODE -> { when (underTestElement) { is PyClass -> underTestElement.text is PyFunction -> underTestElement.text else -> "" } } PsiContextVariable.CURRENT_METHOD_NAME -> { when (underTestElement) { is PyFunction -> underTestElement.name ?: "" else -> "" } } PsiContextVariable.CURRENT_METHOD_CODE -> { when (underTestElement) { is PyFunction -> underTestElement.text else -> "" } } PsiContextVariable.RELATED_CLASSES -> { when (underTestElement) { is PyFunction -> { PythonPsiUtil.findRelatedTypes(underTestElement).mapNotNull { it?.name ?: "" } } else -> listOf() }.joinToString("\n") } PsiContextVariable.SIMILAR_TEST_CASE -> TODO() PsiContextVariable.IMPORTS -> PythonPsiUtil.getImportsInFile(sourceFile) PsiContextVariable.IS_NEED_CREATE_FILE -> { val testFileName = PyTestUtil.getTestNameExample(sourceFile.virtualFile) val testDir = PyTestUtil.getTestsDirectory(sourceFile.virtualFile, project) val testFile = WriteAction.computeAndWait { testDir.findFile(PyTestUtil.toTestFileName(testFileName, sourceFile.name)) } testFile != null } PsiContextVariable.TARGET_TEST_FILE_NAME -> { PyTestUtil.getTestNameExample(sourceFile.virtualFile) } PsiContextVariable.UNDER_TEST_METHOD_CODE -> TODO() PsiContextVariable.FRAMEWORK_CONTEXT -> return collectFrameworkContext(psiElement, project) PsiContextVariable.CODE_SMELL -> CodeSmellCollector.collectElementProblemAsSting( underTestElement, project, editor ) PsiContextVariable.METHOD_CALLER -> { val psiReferences = ReferencesSearch.search(underTestElement, GlobalSearchScope.projectScope(project)) ProgressManager.checkCanceled() psiReferences.mapNotNull { it.element?.text }.toList() } PsiContextVariable.CALLED_METHOD -> { PythonPsiUtil.collectAndResolveReferences(underTestElement) } PsiContextVariable.SIMILAR_CODE -> TODO() PsiContextVariable.STRUCTURE -> TODO() PsiContextVariable.CHANGE_COUNT -> calculateChangeCount(psiElement) PsiContextVariable.LINE_COUNT -> calculateLineCount(psiElement) PsiContextVariable.COMPLEXITY_COUNT -> calculateComplexityCount(psiElement) } } } ================================================ FILE: languages/shire-python/src/main/kotlin/com/phodal/shirelang/python/provider/ShirePythonRunService.kt ================================================ package com.phodal.shirelang.python.provider import com.intellij.execution.RunManager import com.intellij.execution.actions.ConfigurationContext import com.intellij.execution.actions.RunConfigurationProducer import com.intellij.execution.configurations.RunConfiguration import com.intellij.execution.configurations.RunProfile import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiManager import com.jetbrains.python.psi.PyFile import com.jetbrains.python.run.PythonRunConfiguration import com.jetbrains.python.run.PythonRunConfigurationProducer import com.phodal.shirecore.provider.shire.FileRunService class ShirePythonRunService : FileRunService { override fun isApplicable(project: Project, file: VirtualFile): Boolean { return file.extension == "py" } override fun runConfigurationClass(project: Project): Class = PythonRunConfiguration::class.java override fun createConfiguration(project: Project, virtualFile: VirtualFile): RunConfiguration? { val psiFile: PyFile = runReadAction { PsiManager.getInstance(project).findFile(virtualFile) as? PyFile } ?: return null val runManager = RunManager.getInstance(project) val context = runReadAction { ConfigurationContext(psiFile) } val configProducer = RunConfigurationProducer.getInstance( PythonRunConfigurationProducer::class.java ) var settings = configProducer.findExistingConfiguration(context) if (settings == null) { val fromContext = configProducer.createConfigurationFromContext(context) ?: throw IllegalStateException("Failed to create configuration from context") settings = fromContext.configurationSettings runManager.setTemporaryConfiguration(settings) } return settings.configuration as PythonRunConfiguration } } ================================================ FILE: languages/shire-python/src/main/kotlin/com/phodal/shirelang/python/util/PyTestUtil.kt ================================================ package com.phodal.shirelang.python.util import com.intellij.lang.injection.InjectedLanguageManager import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiUtilBase import com.jetbrains.python.psi.PyClass import com.jetbrains.python.psi.PyFunction object PyTestUtil { fun getElementForTests(project: Project, editor: Editor): PsiElement? { val element = PsiUtilBase.getElementAtCaret(editor) ?: return null val containingFile: PsiFile = element.containingFile ?: return null if (InjectedLanguageManager.getInstance(project).isInjectedFragment(containingFile)) { return containingFile } return PsiTreeUtil.getParentOfType(element, PyFunction::class.java, false) ?: PsiTreeUtil.getParentOfType(element, PyClass::class.java, false) ?: containingFile } fun getTestNameExample(file: VirtualFile): String { val children = file.children for (child in children) { val fileName = (child ?: continue).name if (fileName.endsWith(".py") && !fileName.startsWith("_")) { return fileName } } return "test_example.py" } fun getTestsDirectory(file: VirtualFile, project: Project): VirtualFile { val baseDirectory: VirtualFile? = ProjectFileIndex.getInstance(project).getContentRootForFile(file) if (baseDirectory == null) { val parent = file.parent return parent } val testDir = VfsUtil.createDirectoryIfMissing("tests") ?: baseDirectory return testDir } fun toTestFileName(testFileName: String, exampleName: String): String { if (exampleName.startsWith("test_")) return "test_$testFileName.py" return "${testFileName}_test.py" } } ================================================ FILE: languages/shire-python/src/main/kotlin/com/phodal/shirelang/python/util/PythonPsiUtil.kt ================================================ package com.phodal.shirelang.python.util import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.util.PsiTreeUtil import com.intellij.util.concurrency.annotations.RequiresReadLock import com.jetbrains.python.psi.* import com.jetbrains.python.psi.types.PyType import com.jetbrains.python.psi.types.TypeEvalContext object PythonPsiUtil { fun getImportsInFile(file: PsiFile): String { if (file !is PyFile) return "" val fromImports = file.fromImports .map { it.text }.distinct() .joinToString("\n") val imports = file.importTargets .asSequence() .map { it.parent.text } .distinct() .joinToString("\n") return (imports + "\n" + fromImports).trimIndent() } fun getClassWithoutMethod(clazz: PyClass, function: PyFunction): PyClass { val classCopy = clazz.copy() as PyClass val methods = classCopy.methods val methodsToDelete = methods.filter { it.name == function.name } methodsToDelete.forEach { it.delete() } return classCopy } private fun getFunctionSignature(function: PyFunction, inplace: Boolean): PyFunction { val functionCopy = if (inplace) { function } else { function.copy() as PyFunction } functionCopy.statementList.statements.forEach { it.delete() } return functionCopy } fun clearClass(classCopy: PyClass) { classCopy.instanceAttributes.forEach { it.findAssignedValue()?.replace(makeEllipsisExpression(classCopy.project)) } classCopy.classAttributes.forEach { it.findAssignedValue()?.replace(makeEllipsisExpression(classCopy.project)) } classCopy.methods.forEach { method -> method.statementList.statements.forEach { statement -> statement.delete() } } classCopy.nestedClasses.forEach { nestedClass -> clearClass(nestedClass) } } private fun makeEllipsisExpression(project: Project): PyExpression { return PyElementGenerator.getInstance(project).createEllipsis() } @RequiresReadLock fun findRelatedTypes(function: PyFunction): List { val context = TypeEvalContext.codeCompletion(function.project, function.containingFile) val resultType = function.getReturnStatementType(context) val parameters = (function as? PyCallable)?.parameterList?.parameters?.toList() ?: emptyList() val parameterTypes = parameters .filterIsInstance() .map { context.getType(it) } .toMutableList() return parameterTypes + resultType } fun collectAndResolveReferences(psiElement: PsiElement): String { val list = mutableListOf() psiElement.accept(object : PyRecursiveElementVisitor() { override fun visitPyCallExpression(expression: PyCallExpression) { super.visitPyCallExpression(expression) val callee = expression.callee val resolved = callee?.reference?.resolve() addResolvedElement(expression.text, resolved) } override fun visitPyReferenceExpression(expression: PyReferenceExpression) { super.visitPyReferenceExpression(expression) val resolved = expression.reference.multiResolve(false).firstOrNull()?.element addResolvedElement(expression.text, resolved) } private fun addResolvedElement(declarationName: String, element: PsiElement?) { if (element == null) return if (ProjectFileIndex.getInstance(element.project).isInLibrary(element.containingFile.virtualFile)) return val resolvedElement = if (PyUtil.isInitOrNewMethod(element)) { PsiTreeUtil.getParentOfType(element, PyClass::class.java, false) } else { element } when (resolvedElement) { is PyFunction -> addFunction(declarationName, resolvedElement) is PyClass -> addClass(declarationName, resolvedElement) } } private fun addClass(declarationName: String, clazz: PyClass) { list.add(clazz.text) } private fun addFunction(declarationName: String, function: PyFunction) { list.add(function.text) } }) return list.joinToString("\n") } } ================================================ FILE: languages/shire-python/src/main/resources/com.phodal.shirelang.python.xml ================================================ ================================================ FILE: languages/shire-python/src/test/kotlin/com/phodal/shirelang/python/util/PyTestUtilTest.kt ================================================ package com.phodal.shirelang.python.util import com.intellij.testFramework.fixtures.BasePlatformTestCase import org.intellij.lang.annotations.Language class PyTestUtilTest : BasePlatformTestCase() { fun testShouldReturnCorrectPythonTestName() { @Language("Python") val code = """ def hello(): pass """.trimIndent() val psiFile = myFixture.addFileToProject("Hello.py", code) val testName = PyTestUtil.getTestNameExample(psiFile.virtualFile) assertEquals("test_example.py", testName) } } ================================================ FILE: languages/shire-python/src/test/kotlin/com/phodal/shirelang/python/util/PythonPsiUtilTest.kt ================================================ package com.phodal.shirelang.python.util import com.intellij.testFramework.fixtures.BasePlatformTestCase import org.intellij.lang.annotations.Language class PythonPsiUtilTest : BasePlatformTestCase() { fun testShouldGetClassWithoutMethod() { @Language("Python") val code = """ class MathHelper: def addition_with_positive_numbers(self): pass def addition_with_negative_numbers(self): pass """.trimIndent() val file = myFixture.addFileToProject("Hello.py", code) // val psiFile = PsiManager.getInstance(project).findFile(file.virtualFile) as PyFile // // val firstClass = psiFile.children[0] as PyClass // val firstMethod = firstClass.children[0] as PyFunction // // val testName = PythonPsiUtil.getClassWithoutMethod(firstClass, firstMethod) // assertEquals("test_example.py", testName) } } ================================================ FILE: languages/shire-python/src/test/resources/META-INF/plugin.xml ================================================ com.phodal.shire com.intellij.modules.python ================================================ FILE: qodana.yml ================================================ # Qodana configuration: # https://www.jetbrains.com/help/qodana/qodana-yaml.html version: 1.0 linter: jetbrains/qodana-jvm-community:latest projectJDK: "17" profile: name: qodana.recommended exclude: - name: All paths: - .qodana ================================================ FILE: settings.gradle.kts ================================================ rootProject.name = "intellij-shire" enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") include( "core", "shirelang", ) include( "languages:shire-java", "languages:shire-javascript", "languages:shire-python", "languages:shire-kotlin", "languages:shire-go", "languages:shire-markdown", "languages:shire-json", "languages:shire-proto", "toolsets:git", "toolsets:httpclient", "toolsets:terminal", "toolsets:sonarqube", "toolsets:plantuml", "toolsets:database", "toolsets:mock", "toolsets:openrewrite", "toolsets:mermaid", "toolsets:docker", // "toolsets:uitest", ) ================================================ FILE: shirelang/.gitignore ================================================ src/gen ================================================ FILE: shirelang/README.md ================================================ # The Shire Language and Compiler ## Http Request ```http request ### GET request to example server GET https://examples.http-client.intellij.net/get$END$ ?generated-in=IntelliJ IDEA ``` ## OpenWrite Docs: [https://docs.openrewrite.org/](https://docs.openrewrite.org/) [Open Rewrite](https://github.com/openrewrite/rewrite) Fast, repeatable refactoring for developers ```yml --- type: specs.openrewrite.org/v1beta/recipe name: com.yourorg.FindAndReplaceJDK17 displayName: Find and replace JDK 17 example recipeList: - org.openrewrite.text.FindAndReplace: find: eclipse-temurin:17-jdk-jammy replace: eclipse-temurin:21.0.2_13-jdk-jammy filePattern: 'Dockerfile' ``` ================================================ FILE: shirelang/editor/ShireVariablePanel.kt ================================================ ================================================ FILE: shirelang/src/main/grammar/ShireLexer.flex ================================================ package com.phodal.shirelang.lexer; import com.intellij.lexer.FlexLexer; import com.intellij.psi.tree.IElementType; import static com.phodal.shirelang.psi.ShireTypes.*; import com.intellij.psi.TokenType; import java.util.regex.Matcher; import java.util.regex.Pattern; %% %{ public _ShireLexer() { this((java.io.Reader)null); } %} %class ShireLexer %class _ShireLexer %implements FlexLexer %unicode %function advance %type IElementType %eof{ return; %eof} %s YYUSED %s AGENT_BLOCK %s VARIABLE_BLOCK %s COMMAND_BLOCK %s SINGLE_COMMNET_BLOCK %s COMMAND_VALUE_BLOCK %s EXPR_BLOCK %s CODE_BLOCK %s CONTENT_COMMENT_BLOCK %s LINE_BLOCK %s FRONT_MATTER_BLOCK %s FRONT_MATTER_VALUE_BLOCK %s FRONT_MATTER_VAL_OBJECT %s PATTERN_ACTION_BLOCK %s CONDITION_EXPR_BLOCK %s FUNCTION_DECL_BLOCK %s EXT_FUNCTION_BLOCK %s LANG_ID SPACE = [ \t\n\x0B\f\r]+ IDENTIFIER = [a-zA-Z0-9][_\-a-zA-Z0-9]* FRONTMATTER_KEY = [a-zA-Z0-9][_\-a-zA-Z0-9]* DATE = [0-9]{4}-[0-9]{2}-[0-9]{2} STRING = [a-zA-Z0-9][_\-a-zA-Z0-9]* // in Intellij Platform, the language id enable whitespace LANGUAGE_IDENTIFIER = [a-zA-Z][_\-a-zA-Z0-9 .]* EOL=\R INDENT = [ \t][ \t]+ WHITE_SPACE = [ \t]+ COMMENTS = "//"[^\r\n]* CONTENT_COMMENTS = \[ ([^\]]+)? \] [^\t\r\n]* BLOCK_COMMENT = [/][*][^*]*[*]+([^/*][^*]*[*]+)*[/] EscapedChar = "\\" [^\n] RegexWord = [^\r\n\\\"' \t$`()] | {EscapedChar} REGEX = \/{RegexWord}+\/ PATTERN_EXPR = \/{RegexWord}+\/ LPAREN = \( RPAREN = \) PIPE = \| NUMBER = [0-9]+ BOOLEAN = true|false|TRUE|FALSE|"true"|"false" TEXT_SEGMENT = [^$/@#\n]+ DOUBLE_QUOTED_STRING = \"([^\\\"\r\n]|\\[^\r\n])*\"? SINGLE_QUOTED_STRING = '([^\\'\r\n]|\\[^\r\n])*'? QUOTE_STRING = {DOUBLE_QUOTED_STRING}|{SINGLE_QUOTED_STRING} // READ LINE FORMAT: L2C2-L0C100 or L1-L1 LINE_INFO = L[0-9]+(C[0-9]+)?(-L[0-9]+(C[0-9]+)?)? COMMAND_PROP = [^\ \t\r\n]* CODE_CONTENT = [^\n]+ NEWLINE = \n | \r | \r\n COLON =: SHARP =# LBRACKET =\[ RBRACKET =\] COMMA =, ACCESS =:: PROCESS =-> DEFAULT =default CASE =case ARROW ==> WHEN =when FUNCTIONS =functions CONDITION =condition IF =if ELSE =else ELSEIF =elseif ENDIF =endif END =end AND =and ON_STREAMING =onStreaming BEFORE_STREAMING =beforeStreaming ON_STREAMING_END =onStreamingEnd AFTER_STREAMING =afterStreaming %{ private boolean isCodeStart = false; private boolean isInsideShireTemplate = false; private boolean isInsideFunctionBlock = false; private boolean isInsideFrontMatter = false; private boolean hasFrontMatter = false; private boolean patternActionBraceStart = false; private int patternActionBraceLevel = 0; %} %{ private IElementType codeContent() { yybegin(YYINITIAL); // handle for end which is \n``` String text = yytext().toString().trim(); if ((text.equals("\n```") || text.equals("```")) && isCodeStart == true ) { isCodeStart = false; return CODE_BLOCK_END; } // new line if (text.equals("\n")) { return NEWLINE; } if (isCodeStart == false) { return TEXT_SEGMENT; } return CODE_CONTENT; } private IElementType content() { String text = yytext().toString().trim(); if (isCodeStart == true && text.equals("```")) { return codeContent(); } if (isCodeStart == false && text.startsWith("```")) { isCodeStart = true; yypushback(yylength() - 3); yybegin(LANG_ID); return CODE_BLOCK_START; } if (isCodeStart) { return CODE_CONTENT; } else { if (text.startsWith("[")) { yybegin(CONTENT_COMMENT_BLOCK); return comment(); } yypushback(yylength()); yybegin(YYUSED); return TEXT_SEGMENT; } } private IElementType comment() { String text = yytext().toString(); if (text.contains("[") && text.contains("]")) { return CONTENT_COMMENTS; } else { return TEXT_SEGMENT; } } private IElementType command_value() { String text = yytext().toString().trim(); String [] split = text.split("#"); if (split.length == 1) { return COMMAND_PROP; } // split by # if it is a line info String last = split[split.length - 1]; Pattern compile = Pattern.compile("L\\d+(C\\d+)?(-L\\d+(C\\d+)?)?"); Matcher matcher = compile.matcher(last); if (matcher.matches()) { // before # is command prop, after # is line info int number = last.length() + "#".length(); if (number > 0) { yypushback(number); yybegin(LINE_BLOCK); return COMMAND_PROP; } else { return COMMAND_PROP; } } return COMMAND_PROP; } /** @param offset offset from currently matched token start (could be negative) */ private char getCharAtOffset(final int offset) { final int loc = getTokenStart() + offset; return 0 <= loc && loc < zzBuffer.length() ? zzBuffer.charAt(loc) : (char) -1; } private boolean isAfterEol() { final char prev = getCharAtOffset(-1); return prev == (char)-1 || prev == '\n'; } %} %% { "---" { if (isCodeStart) { return CODE_CONTENT; } else { isInsideFrontMatter = true; yybegin(FRONT_MATTER_BLOCK); return FRONTMATTER_START; } } {CODE_CONTENT} { return content(); } {NEWLINE} { return NEWLINE; } {BLOCK_COMMENT} { return BLOCK_COMMENT; } {COMMENTS} { return COMMENTS; } } { {WHEN} { return WHEN; } {ON_STREAMING} { return ON_STREAMING; } {BEFORE_STREAMING} { return BEFORE_STREAMING; } {ON_STREAMING_END} { return ON_STREAMING_END; } {AFTER_STREAMING} { return AFTER_STREAMING; } {FUNCTIONS} { return FUNCTIONS; } {IDENTIFIER} { return IDENTIFIER; } {PATTERN_EXPR} { return PATTERN_EXPR; } ":" { yybegin(FRONT_MATTER_VALUE_BLOCK); return COLON; } "{" { patternActionBraceLevel++; yybegin(FUNCTION_DECL_BLOCK); return OPEN_BRACE; } {INDENT} { yybegin(FRONT_MATTER_VAL_OBJECT); return INDENT; } {NEWLINE} { return NEWLINE; } "---" { isInsideFrontMatter = false; hasFrontMatter = true; yybegin(YYINITIAL); return FRONTMATTER_END; } [^] { yypushback(yylength()); yybegin(YYINITIAL); } } { {COMMENTS} { return COMMENTS; } {NEWLINE} { return NEWLINE; } {QUOTE_STRING} { return QUOTE_STRING; } ":" { yybegin(FRONT_MATTER_VALUE_BLOCK); return COLON; } [^] { yypushback(yylength()); yybegin(FRONT_MATTER_BLOCK); } } { {NUMBER} { return NUMBER; } {DATE} { return DATE; } {BOOLEAN} { return BOOLEAN; } {IDENTIFIER} { return IDENTIFIER; } {QUOTE_STRING} { return QUOTE_STRING; } {PATTERN_EXPR} { yybegin(PATTERN_ACTION_BLOCK); return PATTERN_EXPR; } {ACCESS} { return ACCESS; } {PROCESS} { return PROCESS; } "[" { return LBRACKET; } "]" { return RBRACKET; } "," { return COMMA; } "!" { return NOT; } "&&" { return ANDAND; } "||" { return OROR; } "." { return DOT; } "==" { return EQEQ; } "!=" { return NEQ; } "<" { return LT; } "<=" { return LTE; } ">" { return GT; } ">=" { return GTE; } "$" { return VARIABLE_START; } "(" { return LPAREN; } ")" { return RPAREN; } {COMMENTS} { return COMMENTS; } {WHITE_SPACE} { return TokenType.WHITE_SPACE; } [^] { yypushback(yylength()); yybegin(FRONT_MATTER_BLOCK); } } { "{" { patternActionBraceStart = true; patternActionBraceLevel++; return OPEN_BRACE; } "}" { patternActionBraceLevel--; return CLOSE_BRACE; } "|" { return PIPE; } "," { return COMMA; } "(" { return LPAREN; } ")" { return RPAREN; } {INDENT} { if (patternActionBraceStart && patternActionBraceLevel == 0) { patternActionBraceStart = false; yybegin(FRONT_MATTER_VAL_OBJECT); return INDENT; } else { return TokenType.WHITE_SPACE; } } {WHITE_SPACE} { return TokenType.WHITE_SPACE; } {NEWLINE} { return NEWLINE; } // keywords "case" { return CASE; } "default" { return DEFAULT; } {IDENTIFIER} { if (isInsideFunctionBlock) { yypushback(yylength()); yybegin(EXPR_BLOCK); } else { switch (yytext().toString().trim()) { case "when": return WHEN; case "onStreaming": return ON_STREAMING; case "beforeStreaming": return BEFORE_STREAMING; case "onStreamingEnd": return ON_STREAMING_END; case "afterStreaming": return AFTER_STREAMING; default: return IDENTIFIER; } } } {QUOTE_STRING} { return QUOTE_STRING; } {PATTERN_EXPR} { return PATTERN_EXPR; } "$" { return VARIABLE_START; } "=>" { return ARROW; } [^] { patternActionBraceStart = false; yypushback(yylength()); yybegin(FRONT_MATTER_VALUE_BLOCK); } } { {CONTENT_COMMENTS} { return comment(); } [^] { yypushback(yylength()); yybegin(YYINITIAL); return TEXT_SEGMENT; } } { "from" { return FROM; } "where" { return WHERE; } "select" { return SELECT; } "condition" { return CONDITION; } "case" { return CASE; } "default" { return DEFAULT; } "{" { patternActionBraceLevel++; isInsideFunctionBlock = true; yybegin(EXPR_BLOCK); return OPEN_BRACE; } "}" { patternActionBraceLevel--; if (patternActionBraceLevel == 0) { isInsideFunctionBlock = false; } return CLOSE_BRACE; } {NUMBER} { return NUMBER; } {IDENTIFIER} { if (isInsideFunctionBlock) { yypushback(yylength()); yybegin(EXPR_BLOCK); } else { switch (yytext().toString().trim()) { case "when": return WHEN; case "onStreaming": return ON_STREAMING; case "beforeStreaming": return BEFORE_STREAMING; case "onStreamingEnd": return ON_STREAMING_END; case "afterStreaming": return AFTER_STREAMING; default: return IDENTIFIER; } } } {DATE} { return DATE; } {BOOLEAN} { return BOOLEAN; } {QUOTE_STRING} { return QUOTE_STRING; } {PATTERN_EXPR} { yybegin(PATTERN_ACTION_BLOCK); return PATTERN_EXPR; } "[" { return LBRACKET; } "]" { return RBRACKET; } "," { return COMMA; } "!" { return NOT; } "&&" { return ANDAND; } "||" { return OROR; } "." { return DOT; } "==" { return EQEQ; } "!=" { return NEQ; } "<" { return LT; } "<=" { return LTE; } ">" { return GT; } ">=" { return GTE; } "$" { return VARIABLE_START; } "(" { return LPAREN; } ")" { return RPAREN; } "|" { return PIPE; } {COMMENTS} { return COMMENTS; } {BLOCK_COMMENT} { return BLOCK_COMMENT; } {COMMA} { return COMMA; } {NEWLINE} { return NEWLINE; } {WHITE_SPACE} { if (isInsideFunctionBlock && patternActionBraceLevel == 0) { isInsideFunctionBlock = false; System.out.println("PatternActionBraceLevel: " + patternActionBraceLevel); yybegin(FRONT_MATTER_VAL_OBJECT); return INDENT; } else { return TokenType.WHITE_SPACE; } } [^] { isInsideFunctionBlock = false; yypushback(yylength()); yybegin(FRONT_MATTER_BLOCK); } } { "@" { yybegin(AGENT_BLOCK); return AGENT_START; } "//" {TEXT_SEGMENT} { yybegin(SINGLE_COMMNET_BLOCK); return COMMENTS; } "/" { yybegin(COMMAND_BLOCK); return COMMAND_START; } "$" { yybegin(VARIABLE_BLOCK); return VARIABLE_START; } "```" {IDENTIFIER}? { yybegin(LANG_ID); if (isCodeStart == true) { isCodeStart = false; return CODE_BLOCK_END; } else { isCodeStart = true; }; yypushback(yylength()); } {NEWLINE} { return NEWLINE; } {TEXT_SEGMENT} { return TEXT_SEGMENT; } {SHARP} { yybegin(EXPR_BLOCK); return SHARP; } [^] { return TokenType.BAD_CHARACTER; } } { {IDENTIFIER} { return IDENTIFIER; } {COLON} { yybegin(COMMAND_VALUE_BLOCK); return COLON; } [^] { yypushback(1); yybegin(YYINITIAL); } } { {NEWLINE} { return NEWLINE; } [^] { yypushback(yylength()); if (isInsideFrontMatter) { yybegin(FRONT_MATTER_BLOCK); } else { yybegin(YYINITIAL); } } } { {COMMAND_PROP} { return command_value(); } [^] { yypushback(yylength()); yybegin(YYINITIAL); } } { {SHARP} { return SHARP; } {LINE_INFO} { return LINE_INFO; } [^] { yypushback(yylength()); yybegin(COMMAND_VALUE_BLOCK); } } { {IDENTIFIER} { yybegin(YYINITIAL); return IDENTIFIER; } {QUOTE_STRING} { yybegin(YYINITIAL); return QUOTE_STRING; } [^] { yypushback(yylength()); yybegin(YYINITIAL); } } { {IDENTIFIER} { return IDENTIFIER; } "{" { return OPEN_BRACE; } "}" { return CLOSE_BRACE; } "." { return DOT; } "(" { return LPAREN; } ")" { return RPAREN; } {COMMENTS} { return COMMENTS; } [^] { yypushback(yylength()); yybegin(YYINITIAL); } } { {IF} { return IF; } {ELSE} { return ELSE; } {ELSEIF} { return ELSEIF; } {ENDIF} { return ENDIF; } {END} { return END; } {AND} { return AND; } "(" { return LPAREN; } ")" { return RPAREN; } "<" { return LT; } "[" { return LBRACKET; } "]" { return RBRACKET; } "," { return COMMA; } "!" { return NOT; } "&&" { return ANDAND; } "||" { return OROR; } "." { return DOT; } "==" { return EQEQ; } "!=" { return NEQ; } "<" { return LT; } "<=" { return LTE; } ">" { return GT; } ">=" { return GTE; } "$" { return VARIABLE_START; } "{" { return OPEN_BRACE; } "}" { return CLOSE_BRACE; } {COMMA} { return COMMA; } {NUMBER} { return NUMBER; } {IDENTIFIER} { return IDENTIFIER; } {QUOTE_STRING} { return QUOTE_STRING; } {WHITE_SPACE} { return TokenType.WHITE_SPACE; } // FOR Markdown Header "#" { yybegin(YYUSED); return SHARP; } [^] { yypushback(yylength()); if (isInsideShireTemplate) { yybegin(CODE_BLOCK); } else if (isInsideFunctionBlock) { yybegin(FUNCTION_DECL_BLOCK);} else { yybegin(YYINITIAL); } } } { {CODE_CONTENT} { if(isCodeStart) { return codeContent(); } else { yybegin(YYINITIAL); yypushback(yylength()); } } {NEWLINE} { return NEWLINE; } <> { isCodeStart = false; isInsideShireTemplate = false; yybegin(YYINITIAL); yypushback(yylength()); } } { "```" { return CODE_BLOCK_START; } {LANGUAGE_IDENTIFIER} { return LANGUAGE_IDENTIFIER; } "$" { isInsideShireTemplate = true; yybegin(EXPR_BLOCK); return VARIABLE_START; } [^] { yypushback(yylength()); yybegin(CODE_BLOCK); } } ================================================ FILE: shirelang/src/main/grammar/ShireParser.bnf ================================================ { parserClass="com.phodal.shirelang.parser.ShireParser" extends="com.intellij.extapi.psi.ASTWrapperPsiElement" psiClassPrefix="Shire" psiImplClassSuffix="Impl" psiPackage="com.phodal.shirelang.psi" psiImplPackage="com.phodal.shirelang.psi.impl" elementTypeHolderClass="com.phodal.shirelang.psi.ShireTypes" elementTypeClass="com.phodal.shirelang.psi.ShireElementType" tokenTypeClass="com.phodal.shirelang.lexer.ShireTokenType" extends(".*Expr") = expr tokens=[ COMMENTS = 'regexp://[^\r\n]*' BLOCK_COMMENT = 'regexp:/[*][^*]*[*]+([^/*][^*]*[*]+)*/' CONTENT_COMMENTS = 'regexp:X?\[([^\]]+)?\][^\t\r\n]*' CODE_BLOCK_START = "regexp:X?```[a-zA-Z]*" CODE_BLOCK_END = "regexp:X?```" SINGLE_QUOTED_STRING = "regexp:X?'(''|[^'])*'" DOUBLE_QUOTED_STRING = "regexp:X?\"(\"\"|[^\"])*\"" QUOTE_STRING = "regexp:X?'(''|[^'])*' | X?\"(\"\"|[^\"])*\"" CODE_BLOCK = "CODE_BLOCK" CODE_CONTENT = "CODE_CONTENT" IDENTIFIER = 'regexp:[_a-zA-Z0-9]\w*' COLON = "regexp:X?:" COMMAND_PROP = "regexp:X?[^\\ \\t\\r\\n]*" SHARP = "#" LINE_INFO = "regexp:X?L[0-9]+(C[0-9]+)?(-L[0-9]+(C[0-9]+)?)?" FRONTMATTER_START = "FRONTMATTER_START" FRONTMATTER_END = "FRONTMATTER_END" IDENTIFIER = 'regexp:[_a-zA-Z]\w*' LBRACKET = "[" RBRACKET = "]" INDENT = "INDENT" ARROW = "regexp:X?=>" OPEN_BRACE = "{" CLOSE_BRACE = "}" LPAREN = "(" RPAREN = ")" NEWLINE = "regexp:\n" CASE = "case" DEFAULT = "default" IF = 'if' ELSE = 'else' ELSEIF = 'elseif' END = 'end' ENDIF = 'endif' FROM = 'from' WHERE = 'where' SELECT = 'select' CONDITION = 'condition' WHEN = "when" ON_STREAMING = "onStreaming" BEFORE_STREAMING = "beforeStreaming" ON_STREAMING_END = "onStreamingEnd" AFTER_STREAMING = "afterStreaming" FUNCTIONS = "functions" // operators DASH = "-" EQEQ = '==' NEQ = '!=' LT = '<' GT = '>' LTE = '<=' GTE = '>=' ANDAND = '&&' AND = 'and' OROR = '||' DOT = '.' NOT = '!' COMMA = ',' PIPE = '|' ACCESS = '::' PROCESS = "regexp:X?->" ] } ShireFile ::= frontMatterHeader? (used | code | velocityExpr | markdownHeader | TEXT_SEGMENT | NEWLINE | CONTENT_COMMENTS)* frontMatterHeader ::= FRONTMATTER_START NEWLINE frontMatterEntries FRONTMATTER_END frontMatterEntries ::= frontMatterEntry* frontMatterEntry ::= // life_cycle lifecycleId COLON (functionStatement | conditionExpr) COMMENTS? NEWLINE? // normal declaration | frontMatterKey COLON (foreignFunction | frontMatterValue | patternAction | functionStatement) COMMENTS? NEWLINE? // handle for comments in code | COMMENTS NEWLINE lifecycleId ::= "when" | "onStreaming" | "beforeStreaming" | "onStreamingEnd" | "afterStreaming" frontMatterKey ::= frontMatterId | QUOTE_STRING | pattern | FUNCTIONS frontMatterValue ::= (NEWLINE objectKeyValue) | frontMatterArray | IDENTIFIER | QUOTE_STRING | NUMBER | DATE | BOOLEAN frontMatterArray ::= "[" (frontMatterValue (COMMA frontMatterValue)*) "]" frontMatterId ::= IDENTIFIER objectKeyValue ::= (INDENT keyValue)* keyValue ::= frontMatterEntry /// changed pipelineArgs to types foreignFunction ::= foreignPath (ACCESS foreignFuncName)? "(" (foreignType (COMMA foreignType)*)? ")" (PROCESS foreignOutput)? foreignOutput ::= outputVar (COMMA outputVar)* outputVar ::= IDENTIFIER foreignPath ::= QUOTE_STRING foreignType ::= IDENTIFIER foreignFuncName ::= IDENTIFIER patternAction ::= pattern actionBlock actionBlock ::= blockStart (actionBody) blockEnd actionBody ::= (actionExpr PIPE)* actionExpr actionExpr ::= funcCall | caseBody funcCall ::= funcName ("(" pipelineArgs? ")")? funcName ::= IDENTIFIER pipelineArgs ::= (pipelineArg (COMMA pipelineArg)*)? pipelineArg ::= NUMBER | IDENTIFIER | QUOTE_STRING | variableStart variableId caseBody ::= conditionFlag? CASE NEWLINE* ('condition' | QUOTE_STRING) blockStart casePatternAction* blockEnd casePatternAction ::= caseCondition blockStart actionBody blockEnd caseCondition ::= DEFAULT | pattern | QUOTE_STRING conditionFlag ::= 'condition' blockStart conditionStatement* blockEnd conditionStatement ::= caseCondition blockStart conditionExpr blockEnd NEWLINE? pattern ::= PATTERN_EXPR conditionExpr ::= expr expr ::= logicalOrExpr | logicalAndExpr | eqComparisonExpr | ineqComparisonExpr | callExpr | qualRefExpr | simpleRefExpr | literalExpr | parenExpr | variableExpr // See also: // b/37137454: Modify databinding expression grammar to allow calls to unqualified methods // https://github.com/JetBrains/Grammar-Kit/blob/master/HOWTO.md#24-compact-expression-parsing-with-priorities // https://github.com/JetBrains/Grammar-Kit/blob/master/testData/generator/ExprParser.bnf fake refExpr ::= expr? '.' IDENTIFIER simpleRefExpr ::= IDENTIFIER {extends=refExpr elementType=refExpr} qualRefExpr ::= expr '.' IDENTIFIER {extends=refExpr elementType=refExpr} logicalOrExpr ::= expr '||' expr logicalAndExpr ::= expr ('&&' | 'and') expr eqComparisonExpr ::= expr eqComparisonOp expr ineqComparisonExpr ::= expr ineqComparisonOp expr callExpr ::= refExpr '(' expressionList? ')' expressionList ::= expr (',' expr)* /// variable block variableExpr ::= "{" expr "}" literalExpr ::= literal parenExpr ::= '(' expr ')' // when private eqComparisonOp ::= '==' | 'and' | 'AND' ineqComparisonOp ::= '<=' | '>=' | '<' | '>' | '!=' private literal ::= NUMBER | TRUE | FALSE | QUOTE_STRING | IDENTIFIER | "$" IDENTIFIER used ::= ( agentStart agentId | commandStart commandId (COLON COMMAND_PROP (SHARP LINE_INFO)?)? | variableStart (variableId | varAccess) | '#' variableStart expr ) agentStart ::= '@' commandStart ::= '/' variableStart ::= '$' agentId ::= IDENTIFIER | QUOTE_STRING commandId ::= IDENTIFIER variableId ::= IDENTIFIER languageId ::= LANGUAGE_IDENTIFIER // just make template pass success not fail varAccess ::= OPEN_BRACE variableId (DOT variableId)* CLOSE_BRACE code ::= CODE_BLOCK_START (languageId | variableStart expr)? NEWLINE? code_contents? CODE_BLOCK_END? code_contents ::= (NEWLINE | CODE_CONTENT)* velocityExpr ::= ifExpr NEWLINE* velocityBlock ::= (used | code | velocityExpr | markdownHeader | TEXT_SEGMENT | NEWLINE | CONTENT_COMMENTS)* ifExpr ::= ifClause elseifClause* elseClause? '#' 'end' ifClause ::= '#' 'if' '(' expr ')' velocityBlock elseifClause ::= '#' 'elseif' '(' expr ')' velocityBlock elseClause ::= '#' 'else' velocityBlock // for example, the commit id will be #$storyId markdownHeader ::= SHARP SHARP* TEXT_SEGMENT functionStatement ::= blockStart functionBody? blockEnd functionBody ::= queryStatement | actionBody | conditionExpr queryStatement ::= from_clause where_clause select_clause from_clause ::= FROM blockStart psi_element_decl blockEnd psi_element_decl ::= psi_var_decl ("," psi_var_decl)* (NEWLINE)* where_clause ::= WHERE blockStart expr blockEnd select_clause ::= SELECT blockStart expr (',' expr)* blockEnd private blockStart ::= NEWLINE* "{" NEWLINE* private blockEnd ::= NEWLINE* "}" NEWLINE* psi_var_decl ::= psi_type IDENTIFIER psi_type ::= IDENTIFIER ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/ShireActionStartupActivity.kt ================================================ package com.phodal.shirelang import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.Constraints import com.intellij.openapi.actionSystem.DefaultActionGroup import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.smartReadAction import com.intellij.openapi.project.Project import com.intellij.openapi.startup.ProjectActivity import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.wm.ex.ToolWindowManagerListener import com.intellij.psi.PsiManager import com.intellij.psi.search.FileTypeIndex import com.intellij.psi.search.ProjectScope import com.phodal.shirecore.config.InteractionType import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirecore.provider.ide.InlineChatProvider import com.phodal.shirelang.actions.GlobalShireFileChangesProvider import com.phodal.shirelang.actions.ShireFileChangesProvider import com.phodal.shirelang.actions.base.DynamicShireActionService import com.phodal.shirelang.actions.copyPaste.PasteManagerService import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole import com.phodal.shirelang.thirdparty.ShireSonarLintToolWindowListener import com.phodal.shirelang.psi.ShireFile class ShireActionStartupActivity : ProjectActivity { override suspend fun execute(project: Project) { bindingShireActions(project) } private suspend fun bindingShireActions(project: Project) { GlobalShireFileChangesProvider.getInstance().startup(::attachCopyPasteAction) val changesProvider = ShireFileChangesProvider.getInstance(project) smartReadAction(project) { changesProvider.startup { shireConfig, shireFile -> attachCopyPasteAction(shireConfig, shireFile) attachInlineChat(project) } obtainShireFiles(project).forEach { changesProvider.onUpdated(it) } attachTerminalAction() attachDatabaseAction() attachVcsLogAction() // attache extension actions, like SonarLint attachExtensionActions(project) } } private fun attachCopyPasteAction(shireConfig: HobbitHole, shireFile: ShireFile) { if (shireConfig.interaction == InteractionType.OnPaste) { PasteManagerService.getInstance() .registerPasteProcessor(shireConfig, shireFile) } } /** * We make terminal plugin optional, so can't add to `TerminalToolwindowActionGroup` the plugin.xml. * So we add it manually here, if terminal plugin is not enabled, this action will not be shown. */ private fun attachTerminalAction() { val actionManager = ActionManager.getInstance() val toolsMenu = actionManager.getAction("TerminalToolwindowActionGroup") as? DefaultActionGroup ?: return val action = actionManager.getAction("ShireTerminalAction") if (!toolsMenu.containsAction(action)) { toolsMenu.add(action) } } private fun attachDatabaseAction() { val actionManager = ActionManager.getInstance() val toolsMenu = actionManager.getAction("DatabaseViewPopupMenu") as? DefaultActionGroup ?: return val action = actionManager.getAction("ShireDatabaseAction") if (!toolsMenu.containsAction(action)) { toolsMenu.add(action, Constraints.LAST) } } private fun attachVcsLogAction() { val actionManager = ActionManager.getInstance() val toolsMenu = actionManager.getAction("Vcs.Log.ContextMenu") as? DefaultActionGroup ?: return val action = actionManager.getAction("ShireVcsLogAction") if (!toolsMenu.containsAction(action)) { toolsMenu.add(action, Constraints.FIRST) } } private fun attachInlineChat(project: Project) { if (DynamicShireActionService.getInstance(project).getActions(ShireActionLocation.INLINE_CHAT).isNotEmpty()) { InlineChatProvider.provide()?.addListener(project) }else{ InlineChatProvider.provide()?.removeListener(project) } } private fun attachExtensionActions(project: Project) { project.messageBus.connect().subscribe(ToolWindowManagerListener.TOPIC, ShireSonarLintToolWindowListener()); } companion object { private fun obtainShireFiles(project: Project): List { ApplicationManager.getApplication().assertReadAccessAllowed() val projectShire = obtainProjectShires(project).map { PsiManager.getInstance(project).findFile(it) as ShireFile } return projectShire } private fun obtainProjectShires(project: Project): List { val scope = ProjectScope.getContentScope(project) val projectShire = FileTypeIndex.getFiles(ShireFileType.INSTANCE, scope).mapNotNull { it } return projectShire } fun findShireFile(project: Project, filename: String): ShireFile? { return DynamicShireActionService.getInstance(project).getAllActions().map { it.shireFile }.firstOrNull { it.name == filename } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/ShireAstFactory.kt ================================================ package com.phodal.shirelang import com.intellij.lang.ASTFactory class ShireAstFactory : ASTFactory() ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/ShireBundle.kt ================================================ package com.phodal.shirelang import com.intellij.DynamicBundle import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.PropertyKey @NonNls private const val ShireBUNDLE: String = "messages.ShireBundle" object ShireBundle : DynamicBundle(ShireBUNDLE) { @Suppress("SpreadOperator") @JvmStatic fun message(@PropertyKey(resourceBundle = ShireBUNDLE) key: String, vararg params: Any) = getMessage(key, *params) @Suppress("SpreadOperator", "unused") @JvmStatic fun messagePointer(@PropertyKey(resourceBundle = ShireBUNDLE) key: String, vararg params: Any) = getLazyMessage(key, *params) } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/ShireFileType.kt ================================================ package com.phodal.shirelang import com.intellij.openapi.fileTypes.LanguageFileType import com.intellij.openapi.vfs.VirtualFile import javax.swing.Icon class ShireFileType private constructor() : LanguageFileType(ShireLanguage.INSTANCE) { companion object { @JvmStatic val INSTANCE = ShireFileType() } override fun getName(): String = "ShireFile" override fun getIcon(): Icon = ShireIcons.DEFAULT override fun getDefaultExtension(): String = "shire" override fun getCharset(file: VirtualFile, content: ByteArray): String = "UTF-8" override fun getDescription(): String = "Shire file" } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/ShireIcons.kt ================================================ package com.phodal.shirelang import com.intellij.openapi.util.IconLoader import javax.swing.Icon object ShireIcons { @JvmField val DEFAULT: Icon = IconLoader.getIcon("/icons/shire.svg", ShireIcons::class.java) @JvmField val COMMAND: Icon = IconLoader.getIcon("/icons/shire.svg", ShireIcons::class.java) @JvmField val Terminal: Icon = IconLoader.getIcon("/icons/terminal.svg", ShireIcons::class.java) @JvmField val Idea: Icon = IconLoader.getIcon("/icons/idea.svg", ShireIcons::class.java) @JvmField val PsiExpr: Icon = IconLoader.getIcon("/icons/shire-psi-expr.svg", ShireIcons::class.java) @JvmField val Variable: Icon = IconLoader.getIcon("/icons/shire-variable.svg", ShireIcons::class.java) @JvmField val Pipeline: Icon = IconLoader.getIcon("/icons/shire-pipeline.svg", ShireIcons::class.java) @JvmField val Case: Icon = IconLoader.getIcon("/icons/shire-case.svg", ShireIcons::class.java) } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/ShireInCommentInjector.kt ================================================ package com.phodal.shirelang import com.intellij.lang.injection.MultiHostInjector import com.intellij.lang.injection.MultiHostRegistrar import com.intellij.psi.PsiComment import com.intellij.psi.PsiDocCommentBase import com.intellij.psi.PsiElement import com.intellij.psi.PsiLanguageInjectionHost import org.jetbrains.annotations.NotNull import java.util.regex.Matcher import java.util.regex.Pattern /** * Comment injector for Shire language */ class ShireInCommentInjector : MultiHostInjector { private val SHIRE_CODE_PATTERN: Pattern = Pattern.compile("```shire\\s*(.*?)\\s*```", Pattern.DOTALL) override fun getLanguagesToInject(@NotNull registrar: MultiHostRegistrar, @NotNull host: PsiElement) { if (host !is PsiDocCommentBase) { return } val commentText = host.getText() val matcher: Matcher = SHIRE_CODE_PATTERN.matcher(commentText) if (host as? PsiLanguageInjectionHost == null) { return } /// refs to : https://github.com/intellij-rust/intellij-rust/blob/c6657c02bb62075bf7b7ceb84d000f93dda34dc1/src/main/kotlin/org/rust/ide/injected/RsDoctestLanguageInjector.kt // while (matcher.find()) { // val start: Int = matcher.start(1) // val end: Int = matcher.end(1) // // registrar.startInjecting(ShireLanguage.INSTANCE) // .addPlace(null, null, host as PsiLanguageInjectionHost, TextRange.create(start, end)) // .doneInjecting() // } } @NotNull override fun elementsToInjectIn(): List?> { return listOf(PsiComment::class.java) } companion object { } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/ShireLanguage.kt ================================================ package com.phodal.shirelang import com.intellij.lang.Language class ShireLanguage : Language("Shire", "text/shire", "text/x-shire", "application/x-shire") { companion object { @JvmStatic val INSTANCE = ShireLanguage() } override fun isCaseSensitive() = true override fun getDisplayName() = "Shire" } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/ShireLanguageInjector.kt ================================================ // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.phodal.shirelang import com.intellij.lang.Language import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.util.TextRange import com.intellij.psi.InjectedLanguagePlaces import com.intellij.psi.LanguageInjector import com.intellij.psi.PsiLanguageInjectionHost import com.intellij.psi.util.elementType import com.phodal.shirecore.utils.markdown.CodeFenceLanguage import com.phodal.shirelang.parser.CodeBlockElement import com.phodal.shirelang.parser.PatternElement import com.phodal.shirelang.psi.ShireTypes import com.phodal.shirelang.psi.impl.ShireFuncCallImpl class ShireLanguageInjector : LanguageInjector { override fun getLanguagesToInject(host: PsiLanguageInjectionHost, registrar: InjectedLanguagePlaces) { injectRegexLanguage(host, registrar) injectCodeBlockLanguage(host, registrar) injectRegexFunction(host, registrar) } private fun injectRegexFunction(host: PsiLanguageInjectionHost, registrar: InjectedLanguagePlaces) { if (host !is ShireFuncCallImpl || !host.isValidHost()) return val args = host.pipelineArgs?.children ?: return val language = CodeFenceLanguage.findLanguage("RegExp") val funcLength = host.funcName.text.length args.firstOrNull()?.let { element -> when (element.node.firstChildNode.elementType) { ShireTypes.QUOTE_STRING -> { val startOffset = element.startOffsetInParent + funcLength + 2 val endOffset = element.startOffsetInParent + funcLength + element.textLength registrar.addPlace(language, TextRange(startOffset, endOffset), null, null) } } } } private fun injectRegexLanguage(host: PsiLanguageInjectionHost, registrar: InjectedLanguagePlaces) { if (host !is PatternElement || !host.isValidHost()) return val text = host.text val language = CodeFenceLanguage.findLanguage("RegExp") val range = TextRange(0, text.length) registrar.addPlace(language, range, null, null) } private fun injectCodeBlockLanguage( host: PsiLanguageInjectionHost, registrar: InjectedLanguagePlaces, ) { if (host !is CodeBlockElement || !host.isValidHost()) return val hasCodeContents = host.children.any { it.elementType == ShireTypes.CODE_CONTENTS } if (!hasCodeContents) return val text: String = if (host.isShireTemplateCodeBlock()) { ShireLanguage.INSTANCE.id } else { host.getLanguageId()?.text } ?: return val contentList = CodeBlockElement.obtainFenceContent(host) ?: return if (contentList.isEmpty()) return val language = CodeFenceLanguage.findLanguage(text) injectAsOnePlace(host, language, registrar) } private fun injectAsOnePlace(host: CodeBlockElement, language: Language, registrar: InjectedLanguagePlaces) { val contentList = CodeBlockElement.obtainFenceContent(host) ?: return val first = contentList.first() val last = contentList.last() try { val textRange = TextRange.create(first.startOffsetInParent, last.startOffsetInParent + last.textLength) registrar.addPlace(language, textRange, null, null) } catch (e: Exception) { logger().warn("Failed to inject language", e) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/ShireTypedHandler.kt ================================================ // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.phodal.shirelang import com.intellij.codeInsight.AutoPopupController import com.intellij.codeInsight.CodeInsightSettings import com.intellij.codeInsight.editorActions.TypedHandlerDelegate import com.intellij.openapi.editor.Editor import com.intellij.openapi.fileTypes.FileType import com.intellij.openapi.project.Project import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiFile import com.intellij.psi.impl.source.tree.LeafPsiElement import com.intellij.psi.util.elementType import com.phodal.shirelang.psi.ShireFile import com.phodal.shirelang.psi.ShireTypes import org.jetbrains.kotlin.utils.addToStdlib.UnsafeCastFunction import org.jetbrains.kotlin.utils.addToStdlib.safeAs class ShireTypedHandler : TypedHandlerDelegate() { override fun checkAutoPopup(charTyped: Char, project: Project, editor: Editor, file: PsiFile): Result { if (file !is ShireFile) return Result.CONTINUE return when (charTyped) { '`' -> { val offset = editor.caretModel.primaryCaret.offset if (offset == 0) return Result.CONTINUE val element = file.findElementAt(offset - 1) if (element?.elementType == ShireTypes.CODE_CONTENT || element?.elementType == ShireTypes.CODE_BLOCK_END) { return Result.CONTINUE } PsiDocumentManager.getInstance(project).commitDocument(editor.document) AutoPopupController.getInstance(project).autoPopupMemberLookup(editor, null) return Result.STOP } '@', '/', '$', ':' -> { PsiDocumentManager.getInstance(project).commitDocument(editor.document) AutoPopupController.getInstance(project).autoPopupMemberLookup(editor, null) Result.STOP } else -> { Result.CONTINUE } } } override fun beforeCharTyped(c: Char, project: Project, editor: Editor, file: PsiFile, fileType: FileType): Result { if (file.fileType != ShireFileType.INSTANCE) return Result.CONTINUE return Result.CONTINUE } @OptIn(UnsafeCastFunction::class) override fun charTyped(c: Char, project: Project, editor: Editor, file: PsiFile): Result { if (file.fileType != ShireFileType.INSTANCE) return Result.CONTINUE when { c == '{' && CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET -> { PsiDocumentManager.getInstance(project).commitDocument(editor.document) val offset = editor.caretModel.offset val previousElement = file.findElementAt(offset - 1) if (previousElement is LeafPsiElement) { val identifier = file.findElementAt(offset) ?.safeAs() ?.takeIf { it.elementType == ShireTypes.IDENTIFIER || it.elementType == ShireTypes.QUOTE_STRING } ?: kotlin.run { editor.document.insertString(offset, "}") return Result.STOP } // todo: add more logic here } } } return Result.CONTINUE } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/GlobalShireFileChangesProvider.kt ================================================ package com.phodal.shirelang.actions import com.intellij.concurrency.ConcurrentCollectionFactory import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.PathManager import com.intellij.openapi.application.ReadAction import com.intellij.openapi.components.Service import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.ProjectManager import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.LocalFileSystem.WatchRequest import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFileEvent import com.intellij.openapi.vfs.VirtualFileListener import com.intellij.openapi.vfs.newvfs.RefreshQueue import com.intellij.openapi.vfs.newvfs.impl.VfsData import com.intellij.psi.PsiManager import com.phodal.shirecore.workerThread import com.phodal.shirelang.actions.base.GlobalShireActionService import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole import com.phodal.shirelang.psi.ShireFile import kotlinx.coroutines.CoroutineScope import java.nio.file.Paths import java.util.* import kotlin.io.path.pathString /** * Load all shire files in the specified path and its sub paths,and watch their changes. * * The root path may be outside the project path,It is necessary to load the shire files * and directories into vfs to ensure that [allChildrenLoaded][VfsData.DirectoryData.myAllChildrenLoaded] is true, * otherwise related events cannot be generated in processEvents of [RefreshQueue]. * * @author lk */ @Service(Service.Level.APP) class GlobalShireFileChangesProvider { // The root path can be configured in the future private val homeShirePath: String? = runCatching { Paths.get(System.getProperty("user.home"), ".shire").pathString }.getOrNull() private val watchRoots = ConcurrentCollectionFactory.createConcurrentMap() private val localFileSystem: LocalFileSystem = LocalFileSystem.getInstance() private val listener: VirtualFileListener = object : VirtualFileListener { override fun fileCreated(event: VirtualFileEvent) { if (event.file.isDirectory) { loadShireAction(PathManager.getAbsolutePath(event.file.path)) } } override fun beforeFileDeletion(event: VirtualFileEvent) { if (event.file.isDirectory) { removeShireAction(PathManager.getAbsolutePath(event.file.path)) } } } @Volatile var shireFileModifier: ShireFileModifier? = null private fun addRootToWatch(path: String) { if (watchRoots.isEmpty()) localFileSystem.addVirtualFileListener(listener) watchRoots.computeIfAbsent(path) { localFileSystem.addRootToWatch(it, false) } } private fun removeRootToWatch(path: String) { // Do not delete the root path if (homeShirePath == path) return watchRoots.remove(path) if (watchRoots.isEmpty()) localFileSystem.removeVirtualFileListener(listener) } private fun loadShireAction(path: String) { if (homeShirePath == null || !path.startsWith(homeShirePath)) return refreshShireAction(path, ::addRootToWatch) } private fun removeShireAction(path: String) { val removedRoots = mutableSetOf() refreshShireAction(path, removedRoots::add) removedRoots.forEach(::removeRootToWatch) } private fun refreshShireAction(path: String, handle: (String) -> Unit) { val queue = LinkedList() val file = localFileSystem.findFileByPath(path) ?: return queue.push(file) while (!queue.isEmpty()) { val virtualFile = queue.pop() if (virtualFile.isDirectory) { handle(PathManager.getAbsolutePath(virtualFile.path)) virtualFile.children.forEach { queue.push(it) } } else { ShireUpdater.publisher.onUpdated(virtualFile) } } } fun startup(afterUpdater: (HobbitHole, ShireFile) -> Unit) { if (homeShirePath == null) { logger.warn("Unable to access the root directory of the global shire file configuration") return } var initial = false (shireFileModifier ?: synchronized(this) { shireFileModifier ?: ShireFileModifier(ShireFileModificationContext( GlobalShireActionService.getInstance(), afterUpdater, CoroutineScope(workerThread) ) { ReadAction.compute { it.run { ProjectManager.getInstance().openProjects.firstNotNullOfOrNull { PsiManager.getInstance(it).findFile(this) as? ShireFile } } } }).also { initial = true shireFileModifier = it } }).startup { watchRoots.keys.contains(PathManager.getAbsolutePath(it.parent.path)) } if (initial) loadShireAction(homeShirePath) } companion object { private val logger = logger() fun getInstance(): GlobalShireFileChangesProvider = ApplicationManager.getApplication().getService(GlobalShireFileChangesProvider::class.java) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/ShireFileChangesProvider.kt ================================================ package com.phodal.shirelang.actions import com.intellij.openapi.Disposable import com.intellij.openapi.application.ReadAction import com.intellij.openapi.components.Service import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.event.DocumentEvent import com.intellij.openapi.editor.event.DocumentListener import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.fileEditor.FileDocumentManagerListener import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.vfs.AsyncFileListener import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.newvfs.events.VFileCopyEvent import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent import com.intellij.openapi.vfs.newvfs.events.VFileEvent import com.intellij.psi.PsiManager import com.intellij.testFramework.LightVirtualFile import com.phodal.shirecore.ShireCoroutineScope import com.phodal.shirelang.ShireFileType import com.phodal.shirelang.actions.base.DynamicShireActionService import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole import com.phodal.shirelang.psi.ShireFile /** * It is used after the project is started * and can use [shireFileModifier] to handle shire file change events. * * @author lk */ @Service(Service.Level.PROJECT) class ShireFileChangesProvider(val project: Project) : Disposable { @Volatile var shireFileModifier: ShireFileModifier? = null fun startup(afterUpdater: (HobbitHole, ShireFile) -> Unit) { (shireFileModifier ?: synchronized(this) { shireFileModifier ?: ShireFileModifier( ShireFileModificationContext( DynamicShireActionService.getInstance(project), afterUpdater, ShireCoroutineScope.scope(project) ) { ReadAction.compute { PsiManager.getInstance(project).findFile(it) as? ShireFile } }).also { shireFileModifier = it } }).startup { ReadAction.compute { ProjectFileIndex.getInstance(project).isInProject(it) } } } fun onUpdated(file: ShireFile) { ShireUpdater.publisher.onUpdated(file.virtualFile) } companion object { fun getInstance(project: Project): ShireFileChangesProvider { return project.getService(ShireFileChangesProvider::class.java) } } override fun dispose() { shireFileModifier?.dispose() } } internal class ShireFileModificationListener : FileDocumentManagerListener, DocumentListener, ShireFileListener { fun onUpdated(document: Document) { FileDocumentManager.getInstance().getFile(document).let { onUpdated(it) } } override fun documentChanged(event: DocumentEvent) { onUpdated(event.document) } override fun bulkUpdateFinished(document: Document) { onUpdated(document) } override fun unsavedDocumentDropped(document: Document) { onUpdated(document) } } internal class AsyncShireFileListener : AsyncFileListener, ShireFileListener { override fun prepareChange(events: MutableList): AsyncFileListener.ChangeApplier { val beforeChangedEvents = mutableListOf() val afterChangedEvents = mutableListOf() for (event in events) { when (event) { is VFileDeleteEvent -> { beforeChangedEvents.add(event) } else -> { afterChangedEvents.add(event) // Maybe the file type has been changed } } } return object : AsyncFileListener.ChangeApplier { override fun beforeVfsChange() { beforeChangedEvents.forEach { onUpdated(it.file) } } override fun afterVfsChange() { afterChangedEvents.forEach { when (it) { is VFileCopyEvent -> { onUpdated(it.findCreatedFile()) } else -> { onUpdated(it.file) } } } } } } } /** * Only handle events related to shire file */ interface ShireFileListener { fun onUpdated(file: VirtualFile?) { try { if (file == null || !file.isValid || file.isDirectory) return if (file.fileType !is ShireFileType) return if (file is LightVirtualFile) return ShireUpdater.publisher.onUpdated(file) } catch (e: Exception) { e.printStackTrace() } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/ShireFileModifier.kt ================================================ package com.phodal.shirelang.actions import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.runReadAction import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.messages.MessageBusConnection import com.intellij.util.messages.Topic import com.phodal.shirelang.actions.base.DynamicActionService import com.phodal.shirelang.actions.base.DynamicShireActionConfig import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole import com.phodal.shirelang.compiler.parser.HobbitHoleParser import com.phodal.shirelang.psi.ShireFile import kotlinx.coroutines.* import java.util.concurrent.TimeUnit /** * This class is provided to ShireFileChangesProvider to dynamically adjust * the shire action config when the content of the shire file changes. * * It supports delayed processing([delayTime]) to avoid duplicate updates as much as possible. * * @author lk */ class ShireFileModifier(val context: ShireFileModificationContext) { private val dynamicActionService: DynamicActionService private val scope: CoroutineScope init { dynamicActionService = context.dynamicActionService scope = context.scope } private val queue: MutableSet = mutableSetOf() private val waitingUpdateQueue: ArrayDeque = ArrayDeque() private val delayTime: Long = TimeUnit.SECONDS.toMillis(3) @OptIn(ExperimentalCoroutinesApi::class) private val dispatcher = Dispatchers.IO.limitedParallelism(1) @Volatile var connect: MessageBusConnection? = null private fun modify(afterUpdater: ((HobbitHole, ShireFile) -> Unit)?) { scope.launch(dispatcher) { delay(delayTime) synchronized(queue) { waitingUpdateQueue.addAll(queue) queue.clear() } runBlocking { runReadAction { waitingUpdateQueue.forEach { file -> if (!file.isValid) { dynamicActionService.removeAction(file) logger.debug("Shire file[${file.name}] is deleted") file.virtualFile.takeIf { it.isValid }?.run { context.convertor.invoke(this)?.let { println("reload.") loadShireAction(it, afterUpdater) } } return@forEach } if (!file.isPhysical) return@forEach loadShireAction(file, afterUpdater) } } waitingUpdateQueue.clear() } } } private fun loadShireAction(file: ShireFile, afterUpdater: ((HobbitHole, ShireFile) -> Unit)?) { try { HobbitHoleParser.parse(file).let { dynamicActionService.putAction(file, DynamicShireActionConfig(it?.name ?: file.name, it, file)) if (it != null) afterUpdater?.invoke(it, file) logger.debug("Shire file[${file.virtualFile.path}] is loaded") } } catch (e: Exception) { logger.error("An error occurred while parsing shire file: ${file.virtualFile.path}", e) } } fun startup(predicate: (VirtualFile) -> Boolean) { connect ?: synchronized(this) { connect ?: ShireUpdater.register { it.takeIf(predicate)?.let(context.convertor)?.let { add(it) } }.also { connect = it } } } private fun add(file: ShireFile) { synchronized(queue) { queue.add(file) } modify(context.afterUpdater) } fun dispose() { connect?.dispose() } companion object { private val logger = logger() } } fun interface ShireUpdater { fun onUpdated(file: VirtualFile) companion object { @Topic.ProjectLevel val TOPIC: Topic = Topic.create("shire file updated", ShireUpdater::class.java) val publisher: ShireUpdater get() = ApplicationManager.getApplication().messageBus.syncPublisher(TOPIC) fun register(subscriber: ShireUpdater): MessageBusConnection { val connection = ApplicationManager.getApplication().messageBus.connect() connection.subscribe(TOPIC, subscriber) return connection } } } data class ShireFileModificationContext( val dynamicActionService: DynamicActionService, val afterUpdater: ((HobbitHole, ShireFile) -> Unit)?, val scope: CoroutineScope, val convertor: (VirtualFile) -> ShireFile? ) ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/ShireRunFileAction.kt ================================================ package com.phodal.shirelang.actions import com.intellij.execution.ExecutionManager import com.intellij.execution.RunManager import com.intellij.execution.RunnerAndConfigurationSettings import com.intellij.execution.actions.ConfigurationContext import com.intellij.execution.actions.RunConfigurationProducer import com.intellij.execution.executors.DefaultRunExecutor import com.intellij.execution.process.ProcessEvent import com.intellij.execution.runners.ExecutionEnvironmentBuilder import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirelang.actions.base.DynamicShireActionConfig import com.phodal.shirelang.psi.ShireFile import com.phodal.shirelang.run.* import org.jetbrains.annotations.NonNls import java.util.concurrent.CompletableFuture class ShireRunFileAction : DumbAwareAction() { override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT override fun update(e: AnActionEvent) { val file = e.getData(CommonDataKeys.PSI_FILE) ?: return e.presentation.isEnabledAndVisible = file is ShireFile if (e.presentation.text.isNullOrBlank()) { e.presentation.text = "Run Shire file: ${file.name}" } } override fun actionPerformed(e: AnActionEvent) { val file = e.getData(CommonDataKeys.PSI_FILE) as? ShireFile ?: return val project = file.project val config = DynamicShireActionConfig.from(file) val existingConfiguration = createRunConfig(e) executeFile(project, config, existingConfiguration) } companion object { const val ID: @NonNls String = "runShireFileAction" fun createRunConfig(e: AnActionEvent): RunnerAndConfigurationSettings? { val context = ConfigurationContext.getFromContext(e.dataContext, e.place) return RunConfigurationProducer.getInstance(ShireRunConfigurationProducer::class.java) .findExistingConfiguration(context) } /** * Executes a Shire file within the specified project context. * * ```kotlin * val project = ... // IntelliJ IDEA project * val config = ... // DynamicShireActionConfig object * val runSettings = ... // Optional RunnerAndConfigurationSettings * val variables = mapOf("key1" to "value1", "key2" to "value2") * * executeFile(project, config, runSettings, variables) * ``` * * @param project The IntelliJ IDEA project in which the Shire file is to be executed. * @param config The configuration object containing details about the Shire file to be executed. * @param runSettings Optional runner and configuration settings to use for execution. If null, a new configuration will be created. * @param variables A map of variables to be passed to the Shire file during execution. Defaults to an empty map. * * @throws Exception If there is an error creating the run configuration or execution environment. */ fun executeFile( project: Project, config: DynamicShireActionConfig, runSettings: RunnerAndConfigurationSettings?, variables: Map = mapOf(), ) { val settings = try { runSettings ?: RunManager.getInstance(project) .createConfiguration(config.name, ShireConfigurationType::class.java) } catch (e: Exception) { logger().error("Failed to create configuration", e) return } val runConfiguration = settings.configuration as ShireConfiguration runConfiguration.setScriptPath(config.shireFile.virtualFile.path) if (variables.isNotEmpty()) { runConfiguration.setVariables(variables) PostProcessorContext.updateRunConfigVariables(variables) } val executorInstance = DefaultRunExecutor.getRunExecutorInstance() val executionEnvironment = ExecutionEnvironmentBuilder .createOrNull(executorInstance, runConfiguration) ?.build() if (executionEnvironment == null) { logger().error("Failed to create execution environment") return } ExecutionManager.getInstance(project).restartRunProfile(executionEnvironment) } fun suspendExecuteFile( project: Project, file: ShireFile, variableNames: Array = arrayOf(), variableTable: MutableMap = mutableMapOf(), ): String? { val variables: MutableMap = mutableMapOf() for (i in variableNames.indices) { val varName = variableNames[i] val varValue = variableTable[varName].toString() variables[varName] = varValue } val config = DynamicShireActionConfig.from(file) val settings = try { RunManager.getInstance(project) .createConfiguration(config.name, ShireConfigurationType::class.java) } catch (e: Exception) { logger().error("Failed to create configuration", e) return null } val runConfiguration = settings.configuration as ShireConfiguration runConfiguration.setScriptPath(config.shireFile.virtualFile.path) if (variables.isNotEmpty()) { runConfiguration.setVariables(variables) PostProcessorContext.updateRunConfigVariables(variables) } val executorInstance = DefaultRunExecutor.getRunExecutorInstance() val executionEnvironment = ExecutionEnvironmentBuilder .createOrNull(executorInstance, runConfiguration) ?.build() if (executionEnvironment == null) { logger().error("Failed to create execution environment") return null } val future = CompletableFuture() val hintDisposable = Disposer.newDisposable() val connection = ApplicationManager.getApplication().messageBus.connect(hintDisposable) connection.subscribe(ShireRunListener.TOPIC, object : ShireRunListener { override fun runFinish( allOutput: String, llmOutput: String, event: ProcessEvent, scriptPath: String, consoleView: ShireConsoleView?, ) { future.complete(llmOutput) connection.disconnect() Disposer.dispose(hintDisposable) } }) ExecutionManager.getInstance(project).restartRunProfile( project, executorInstance, executionEnvironment.executionTarget, settings, null ) return future.get() } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/base/DynamicShireActionConfig.kt ================================================ package com.phodal.shirelang.actions.base import com.intellij.openapi.editor.Editor import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole import com.phodal.shirelang.compiler.parser.HobbitHoleParser import com.phodal.shirelang.psi.ShireFile data class DynamicShireActionConfig( val name: String, val hole: HobbitHole? = null, val shireFile: ShireFile, val editor: Editor? = null, ) { companion object { fun from(file: ShireFile): DynamicShireActionConfig { val hole = HobbitHoleParser.parse(file) return DynamicShireActionConfig(file.name, hole, file) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/base/DynamicShireActionService.kt ================================================ package com.phodal.shirelang.actions.base import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.KeyboardShortcut import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.Service import com.intellij.openapi.keymap.KeymapManager import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirelang.psi.ShireFile import java.util.* @Service(Service.Level.PROJECT) class DynamicShireActionService: DynamicActionService { private val dynamicActionService = GlobalShireActionService.getInstance() private val actionCache = WeakHashMap() override fun putAction(key: ShireFile, action: DynamicShireActionConfig) { actionCache[key] = action } override fun removeAction(key: ShireFile) = actionCache.keys.removeIf { it == key } override fun getAllActions(): List { return (actionCache.values.toList() + dynamicActionService.getAllActions()) .distinctBy { it.shireFile.virtualFile } } fun getActions(location: ShireActionLocation): List { return getAllActions().filter { it.hole?.actionLocation == location && it.hole.enabled } } /** * Sets a keymap shortcut for a specified action ID. * * This method takes in the action ID of the desired action and a keyboard string representing the shortcut keys to be set. * It retrieves the action manager and keymap manager instances, then adds the specified keyboard shortcut to the active keymap. * * @param action The ID of the action for which the shortcut is being set. * @param keyboardShortcut A string representing the keyboard shortcut keys (e.g. "ctrl shift A"). */ fun bindShortcutToAction(action: AnAction, keyboardShortcut: KeyboardShortcut) { val actionId = ActionManager.getInstance().getId(action) ?: return val activeKeymap = KeymapManager.getInstance().activeKeymap activeKeymap.removeAllActionShortcuts(actionId) activeKeymap.addShortcut(actionId, keyboardShortcut) } companion object { fun getInstance(project: Project): DynamicShireActionService = project.getService(DynamicShireActionService::class.java) } } @Service(Service.Level.APP) class GlobalShireActionService: DynamicActionService { private val globalActionCache = WeakHashMap() override fun putAction(key: ShireFile, action: DynamicShireActionConfig) { globalActionCache[key.virtualFile] = action } override fun removeAction(key: ShireFile) = globalActionCache.keys.removeIf{ key.virtualFile == it } override fun getAllActions(): List = globalActionCache.values.toList() companion object { fun getInstance(): GlobalShireActionService = ApplicationManager.getApplication().getService(GlobalShireActionService::class.java) } } interface DynamicActionService { fun putAction(key: ShireFile, action: DynamicShireActionConfig) fun removeAction(key: ShireFile): Boolean fun getAllActions(): List } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/base/validator/WhenConditionValidator.kt ================================================ package com.phodal.shirelang.actions.base.validator import com.intellij.psi.PsiFile import com.phodal.shirecore.provider.variable.model.ConditionPsiVariable import com.phodal.shirelang.compiler.ast.FrontMatterType import com.phodal.shirelang.compiler.ast.Statement object WhenConditionValidator { private fun buildPsiVariable(file: PsiFile): Map { return ConditionPsiVariable.values().associate { when (it) { ConditionPsiVariable.FILE_PATH -> it.variableName to file.virtualFile.path ConditionPsiVariable.FILE_NAME -> it.variableName to file.name ConditionPsiVariable.FILE_EXTENSION -> it.variableName to (file.virtualFile.extension ?: "") ConditionPsiVariable.FILE_CONTENT -> it.variableName to file.text } } } fun isAvailable(conditions: FrontMatterType.EXPRESSION, file: PsiFile): Boolean { return (conditions.value as? Statement)?.evaluate(buildPsiVariable(file)) == true } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/console/ShireConsoleAction.kt ================================================ package com.phodal.shirelang.actions.console import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.project.Project import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirecore.variable.template.VariableActionEventDataHolder import com.phodal.shirelang.actions.ShireRunFileAction import com.phodal.shirelang.actions.base.DynamicShireActionService class ShireConsoleAction : AnAction() { override fun getActionUpdateThread() = ActionUpdateThread.EDT private fun shireActionConfigs(project: Project) = DynamicShireActionService.getInstance(project).getActions(ShireActionLocation.CONSOLE_MENU) override fun update(e: AnActionEvent) { val project = e.project ?: return val isOnlyOneConfig = shireActionConfigs(project).size == 1 val hobbitHole = shireActionConfigs(project).firstOrNull()?.hole e.presentation.isVisible = isOnlyOneConfig e.presentation.isEnabled = hobbitHole != null && hobbitHole.enabled if (hobbitHole != null) { e.presentation.text = hobbitHole.name ?: "" } } override fun actionPerformed(e: AnActionEvent) { val project = e.project ?: return VariableActionEventDataHolder.putData(VariableActionEventDataHolder(e.dataContext)) val config = shireActionConfigs(project).firstOrNull() ?: return ShireRunFileAction.executeFile(project, config, null) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/context/ShireContextMenuAction.kt ================================================ package com.phodal.shirelang.actions.context import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.DumbAwareAction import com.phodal.shirelang.ShireIcons import com.phodal.shirelang.actions.ShireRunFileAction import com.phodal.shirelang.actions.base.DynamicShireActionConfig import com.phodal.shirelang.actions.base.validator.WhenConditionValidator class ShireContextMenuAction(private val config: DynamicShireActionConfig) : DumbAwareAction(config.name, config.hole?.description, ShireIcons.DEFAULT) { init { templatePresentation.text = config.name.ifBlank { "Unknown" } } override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT override fun update(e: AnActionEvent) { //2024-07-13 10:32:57,277 [51307999] SEVERE - #c.i.o.a.i.Utils - Empty menu item text for ShireContextMenuAction@EditorPopup (com.phodal.shirelang.actions.context.ShireContextMenuAction). The default action text must be specified in plugin.xml or its class constructor [Plugin: com.phodal.shire] // com.intellij.diagnostic.PluginException: Empty menu item text for ShireContextMenuAction@EditorPopup (com.phodal.shirelang.actions.context.ShireContextMenuAction). The default action text must be specified in plugin.xml or its class constructor [Plugin: com.phodal.shire] try { val conditions = config.hole?.when_ ?: return val psiFile = e.getData(CommonDataKeys.PSI_FILE) ?: return WhenConditionValidator.isAvailable(conditions, psiFile).let { e.presentation.isEnabled = it e.presentation.isVisible = it e.presentation.text = config.hole.name } } catch (e: Exception) { logger().error("Error in ShireContextMenuAction", e) } } override fun actionPerformed(e: AnActionEvent) { val project = e.project ?: return ShireRunFileAction.executeFile( project, config, ShireRunFileAction.createRunConfig(e) ) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/context/ShireContextMenuActionGroup.kt ================================================ package com.phodal.shirelang.actions.context import com.intellij.openapi.actionSystem.* import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirelang.actions.base.DynamicShireActionService import com.phodal.shirelang.actions.base.validator.WhenConditionValidator class ShireContextMenuActionGroup : ActionGroup() { override fun getActionUpdateThread(): ActionUpdateThread { return ActionUpdateThread.BGT } override fun update(e: AnActionEvent) { val project = e.project ?: return e.presentation.isPopupGroup = DynamicShireActionService.getInstance(project).getAllActions().size > 1 } override fun getChildren(e: AnActionEvent?): Array { val project = e?.project ?: return emptyArray() val actionService = DynamicShireActionService.getInstance(project) return actionService.getActions(ShireActionLocation.CONTEXT_MENU).mapNotNull { actionConfig -> if (actionConfig.hole == null) return@mapNotNull null if (!actionConfig.hole.enabled) return@mapNotNull null actionConfig.hole.when_?.let { val psiFile = e.getData(CommonDataKeys.PSI_FILE) ?: return@mapNotNull null if (!WhenConditionValidator.isAvailable(it, psiFile)) return@mapNotNull null } val menuAction = ShireContextMenuAction(actionConfig) if (actionConfig.hole.shortcut != null) { actionService.bindShortcutToAction(menuAction, actionConfig.hole.shortcut) } menuAction }.distinctBy { it.templatePresentation.text }.toTypedArray() } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/copyPaste/ShireCopyPastePreProcessor.kt ================================================ package com.phodal.shirelang.actions.copyPaste import com.intellij.codeInsight.editorActions.CopyPastePreProcessor import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.Service import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.RawText import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile import com.phodal.shirecore.ShireCoroutineScope import com.phodal.shirecore.llm.LlmProvider import com.phodal.shirecore.utils.markdown.CodeFence import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirelang.compiler.template.ShireVariableTemplateCompiler import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole import com.phodal.shirelang.psi.ShireFile import com.phodal.shirelang.run.runner.ShireRunner import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.cancellable import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import java.util.concurrent.CompletableFuture @Service(Service.Level.APP) class PasteManagerService { private val pasteProcessorMap = mutableMapOf() fun registerPasteProcessor(key: HobbitHole, file: ShireFile) { pasteProcessorMap[key] = file } fun firstProcessor(): HobbitHole? { return pasteProcessorMap.keys.firstOrNull() } fun executeProcessor( project: Project, hobbitHole: HobbitHole, text: String, file: PsiFile, editor: Editor ): String { val future = CompletableFuture() val shireFile = pasteProcessorMap[hobbitHole] ?: return text val compileResult = ShireRunner.preAnalysisAndLocalExecute(shireFile, project) val variableTable = compileResult.variableTable val templateCompiler = ShireVariableTemplateCompiler(project, hobbitHole, variableTable, compileResult.shireOutput, editor) templateCompiler.putCustomVariable("text", text) val promptText = runBlocking { templateCompiler.compile().trim() } PostProcessorContext.getData()?.lastTaskOutput?.let { templateCompiler.putCustomVariable("output", it) } val flow: Flow? = LlmProvider.provider(project)?.stream(promptText, "", false) ShireCoroutineScope.scope(project).launch { val suggestion = StringBuilder() flow?.cancellable()?.collect { char -> suggestion.append(char) } val code = CodeFence.parse(suggestion.toString()) future.complete(code.text) logger().info("paste code: $code") } return future.get() } companion object { fun getInstance(): PasteManagerService = ApplicationManager.getApplication().getService(PasteManagerService::class.java) } } class ShireCopyPastePreProcessor : CopyPastePreProcessor { override fun preprocessOnCopy(file: PsiFile, startOffsets: IntArray, endOffsets: IntArray, text: String): String? { return text } override fun preprocessOnPaste( project: Project, file: PsiFile, editor: Editor, text: String, rawText: RawText?, ): String { val instance = PasteManagerService.getInstance() val hobbitHole = instance.firstProcessor() ?: return text if (!hobbitHole.enabled) return text /// only for test java and kotlin val language = file.language.displayName.lowercase() if (!(language == "java" || language == "kotlin")) { return text } /// should be more than 7 lines if (text.lines().size < 5) { return text } return instance.executeProcessor(project, hobbitHole, text, file, editor) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/database/ShireDatabaseAction.kt ================================================ package com.phodal.shirelang.actions.database import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.project.Project import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirecore.variable.template.VariableActionEventDataHolder import com.phodal.shirelang.actions.base.DynamicShireActionService import com.phodal.shirelang.actions.ShireRunFileAction class ShireDatabaseAction : AnAction() { override fun getActionUpdateThread() = ActionUpdateThread.EDT private fun shireActionConfigs(project: Project) = DynamicShireActionService.getInstance(project).getActions(ShireActionLocation.DATABASE_MENU) override fun update(e: AnActionEvent) { val project = e.project ?: return val isOnlyOneConfig = shireActionConfigs(project).size == 1 val hobbitHole = shireActionConfigs(project).firstOrNull()?.hole e.presentation.isVisible = isOnlyOneConfig e.presentation.isEnabled = hobbitHole != null && hobbitHole.enabled if (hobbitHole != null) { e.presentation.text = hobbitHole.name ?: "" } } override fun actionPerformed(e: AnActionEvent) { val project = e.project ?: return VariableActionEventDataHolder.putData(VariableActionEventDataHolder(e.dataContext)) val config = shireActionConfigs(project).firstOrNull() ?: return ShireRunFileAction.executeFile(project, config, null) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/external/ShireSonarLintAction.kt ================================================ package com.phodal.shirelang.actions.external import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.project.Project import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirecore.variable.template.VariableActionEventDataHolder import com.phodal.shirelang.actions.ShireRunFileAction import com.phodal.shirelang.actions.base.DynamicShireActionService class ShireSonarLintAction : AnAction() { override fun getActionUpdateThread() = ActionUpdateThread.EDT private fun shireActionConfigs(project: Project) = DynamicShireActionService.getInstance(project).getActions(ShireActionLocation.EXT_SONARQUBE_MENU) override fun update(e: AnActionEvent) { val project = e.project ?: return val isOnlyOneConfig = shireActionConfigs(project).size == 1 val hobbitHole = shireActionConfigs(project).firstOrNull()?.hole e.presentation.isVisible = isOnlyOneConfig e.presentation.isEnabled = hobbitHole != null && hobbitHole.enabled if (hobbitHole != null) { e.presentation.text = hobbitHole.name ?: "" } } override fun actionPerformed(e: AnActionEvent) { val project = e.project ?: return VariableActionEventDataHolder.putData(VariableActionEventDataHolder(e.dataContext)) val config = shireActionConfigs(project).firstOrNull() ?: return ShireRunFileAction.executeFile(project, config, null) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/input/ShireInputBoxAction.kt ================================================ package com.phodal.shirelang.actions.input import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.editor.ex.util.EditorUtil import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer import com.intellij.openapi.wm.IdeFocusManager import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirelang.actions.ShireRunFileAction.Companion.executeFile import com.phodal.shirelang.actions.base.DynamicShireActionConfig import com.phodal.shirelang.actions.base.DynamicShireActionService import com.phodal.shirelang.actions.input.inlay.CustomInputBox import com.phodal.shirelang.actions.input.inlay.CustomInputBox.Companion.CUSTOM_INPUT_CANCEL_ACTION import com.phodal.shirelang.actions.input.inlay.CustomInputBox.Companion.CUSTOM_INPUT_SUBMIT_ACTION import com.phodal.shirelang.actions.input.inlay.InlayPanel import java.awt.event.ActionEvent import javax.swing.AbstractAction class ShireInputBoxAction : DumbAwareAction() { override fun getActionUpdateThread() = ActionUpdateThread.EDT private fun shireActionConfigs(project: Project) = DynamicShireActionService.getInstance(project).getActions(ShireActionLocation.INPUT_BOX) override fun update(e: AnActionEvent) { val project = e.project ?: return val hobbitHole = shireActionConfigs(project).firstOrNull()?.hole ?: return e.presentation.isEnabled = hobbitHole.enabled e.presentation.text = hobbitHole?.description ?: "" } override fun actionPerformed(e: AnActionEvent) { val dataContext = e.dataContext val editor = dataContext.getData(CommonDataKeys.EDITOR) ?: return val offset = editor.caretModel.offset val project = dataContext.getData(CommonDataKeys.PROJECT) ?: return val instance = DynamicShireActionService.getInstance(project) val config = shireActionConfigs(project).firstOrNull() ?: return if (config.hole?.shortcut != null) { instance.bindShortcutToAction(this, config.hole.shortcut) } InlayPanel.add(editor as EditorEx, offset, CustomInputBox())?.let { doExecute(it, project, editor, config) } } private fun doExecute( inlay: InlayPanel, project: Project, editor: EditorEx, config: DynamicShireActionConfig, ) { val component = inlay.component component.actionMap.put(CUSTOM_INPUT_SUBMIT_ACTION, object : AbstractAction() { override fun actionPerformed(e: ActionEvent?) { val inputText = component.getText() executeFile(project, config, null, variables = mutableMapOf("input" to inputText)) Disposer.dispose(inlay.inlay!!) } }) component.actionMap.put(CUSTOM_INPUT_CANCEL_ACTION, object : AbstractAction() { override fun actionPerformed(e: ActionEvent?) { Disposer.dispose(inlay.inlay!!) } }) IdeFocusManager.getInstance(project).requestFocus(component, false) EditorUtil.disposeWithEditor(editor, inlay.inlay!!) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/input/inlay/ComponentInlaysContainer.kt ================================================ package com.phodal.shirelang.actions.input.inlay import com.intellij.openapi.Disposable import com.intellij.openapi.application.ReadAction import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Inlay import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Key import java.awt.Rectangle import java.awt.event.ComponentAdapter import java.awt.event.ComponentEvent import javax.swing.JComponent import javax.swing.RepaintManager import javax.swing.SwingUtilities internal class ComponentInlaysContainer(val editor: Editor) : JComponent() { private val inlays: MutableList> = mutableListOf() val editorResizeListener: ComponentAdapter = object : ComponentAdapter() { override fun componentResized(e: ComponentEvent?) { revalidate() repaint() } } fun getInlays(): MutableList> = inlays override fun invalidate() { super.invalidate() RepaintManager.currentManager(this).addInvalidComponent(this) } override fun doLayout() { val inlays: List> = inlays if (inlays.isEmpty()) return val content: JComponent = editor.contentComponent inlays.forEach { it.renderer.inlaySize = it.renderer.component.preferredSize } ReadAction.run { editor.inlayModel.execute(true) { inlays.forEach { if (it.renderer.inlaySize.width != it.widthInPixels || it.renderer.inlaySize.height != it.heightInPixels) { it.update() } } } } if (content.width < content.width) { content.parent.doLayout() } bounds = SwingUtilities.calculateInnerArea(content, null as Rectangle?) inlays.forEach { it.renderer.component.size = it.renderer.inlaySize } } companion object { private val INLAYS_CONTAINER = Key("INLAYS_CONTAINER") fun addInlay(inlay: Inlay) { val editor: Editor = inlay.editor var component = editor.getUserData(INLAYS_CONTAINER) if (component == null) { val newContainer = ComponentInlaysContainer(editor) editor.putUserData(INLAYS_CONTAINER, newContainer) editor.contentComponent.add(newContainer) editor.contentComponent.addComponentListener(newContainer.editorResizeListener) component = newContainer } component.getInlays().add(inlay) component.add(inlay.renderer.component) inlay.whenDisposed { if (!component.getInlays().remove(inlay)) { return@whenDisposed } component.remove(inlay.renderer.component) if (component.getInlays().isEmpty()) { editor.contentComponent.removeComponentListener(component.editorResizeListener) editor.contentComponent.remove(inlay.renderer.component) } } } } } fun Disposable.whenDisposed(listener: () -> Unit) { Disposer.register(this) { listener() } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/input/inlay/CustomInputBox.kt ================================================ package com.phodal.shirelang.actions.input.inlay import com.intellij.ide.KeyboardAwareFocusOwner import com.intellij.ui.scale.JBUIScale import java.awt.Dimension import java.awt.event.KeyEvent import javax.swing.JComponent import javax.swing.JTextField import javax.swing.KeyStroke import kotlin.properties.ReadWriteProperty import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KProperty class CustomInputBox : JTextField(), KeyboardAwareFocusOwner { override fun skipKeyEventDispatcher(event: KeyEvent): Boolean = true init { this.minimumWidth = JBUIScale.scale(480) this.preferredSize = this.minimumSize inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), CUSTOM_INPUT_CANCEL_ACTION) inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), CUSTOM_INPUT_SUBMIT_ACTION) } companion object { const val CUSTOM_INPUT_CANCEL_ACTION = "custom.input.cancel" const val CUSTOM_INPUT_SUBMIT_ACTION = "custom.input.submit" } } var JComponent.minimumWidth: Int by dimensionProperty(JComponent::getMinimumSize, JComponent::setMinimumSize, Dimension::width) private fun dimensionProperty( getSize: Receiver.() -> Dimension, setSize: Receiver.(Dimension) -> Unit, dimensionProperty: KMutableProperty1 ): ReadWriteProperty { return object : ReadWriteProperty { override fun getValue(thisRef: Receiver, property: KProperty<*>): Int { return dimensionProperty.get(getSize(thisRef)) } override fun setValue(thisRef: Receiver, property: KProperty<*>, value: Int) { val size = Dimension(getSize(thisRef)) dimensionProperty.set(size, value) setSize(thisRef, size) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/input/inlay/InlayPanel.kt ================================================ package com.phodal.shirelang.actions.input.inlay import com.intellij.openapi.Disposable import com.intellij.openapi.application.ReadAction import com.intellij.openapi.editor.Inlay import com.intellij.openapi.editor.InlayProperties import com.intellij.openapi.editor.event.VisibleAreaListener import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.rd.paint2DLine import com.intellij.ui.JBColor import com.intellij.ui.paint.LinePainter2D import com.intellij.util.DocumentUtil import com.intellij.util.text.CharArrayUtil import com.intellij.util.ui.JBPoint import com.intellij.util.ui.JBUI import java.awt.* import javax.swing.JComponent import javax.swing.JPanel open class InlayPanel(var component: T) : JPanel() { val panel: JPanel = object : JPanel() { init { setOpaque(false) } override fun paintComponent(g: Graphics) { super.paintComponent(g) val create: Graphics2D? = g.create() as Graphics2D? try { create!!.paint2DLine(JBPoint(0, 0), JBPoint(0, height), LinePainter2D.StrokeType.INSIDE, 3.0, JBColor(Color(0, 100, 89, 100), Color(0, 0, 0, 90)) ) create.dispose() } catch (th: Throwable) { create!!.dispose() throw th } } } var inlay: Inlay<*>? = null private val visibleAreaListener: VisibleAreaListener = VisibleAreaListener { invalidate() } protected open fun setupPane(inlay: Inlay<*>) { this.inlay = inlay add(panel) add(component) setOpaque(true) inlay.editor.scrollingModel.addVisibleAreaListener(visibleAreaListener, (inlay as Disposable)) setLayout(object : LayoutManager { override fun addLayoutComponent(name: String, comp: Component?) {} override fun removeLayoutComponent(comp: Component?) {} override fun preferredLayoutSize(parent: Container?): Dimension { if (!inlay.isValid || parent == null) return Dimension(0, 0) val dimension: Dimension = component!!.preferredSize val xOffsetPosition = dimension.width + getXOffsetPosition(inlay) val insets = component!!.getInsets() return Dimension(xOffsetPosition, dimension.height + insets.height) } override fun minimumLayoutSize(parent: Container?): Dimension { if (!inlay.isValid || parent == null) return Dimension(0, 0) val size: Dimension = component!!.getMinimumSize() val xOffsetPosition = size.width + getXOffsetPosition(inlay) val insets = component!!.getInsets() return Dimension(xOffsetPosition, size.height + insets.height) } override fun layoutContainer(parent: Container?) { if (!inlay.isValid) return val size = parent?.size ?: Dimension(0, 0) val x = getXOffsetPosition(inlay) component!!.setBounds(x, 0, size.width - x, size.height) val scrollPane = (inlay.editor as EditorEx).scrollPane panel.setBounds(scrollPane.viewport.viewRect.x - 1, 0, 5, size.height) } }) } companion object { fun add(editor: EditorEx, offset: Int, component: CustomInputBox): InlayPanel? { val properties = InlayProperties().showAbove(false).showWhenFolded(true); val inlayPanel = InlayPanel(component) val inlayRenderer = InlayRenderer(inlayPanel) val inlayElement = editor.inlayModel.addBlockElement(offset, properties, inlayRenderer) ?: return null ComponentInlaysContainer.addInlay(inlayElement) inlayPanel.setupPane(inlayElement) return inlayPanel } } } val Insets.width: Int get() = left + right val Insets.height: Int get() = top + bottom fun getXOffsetPosition(inlay: Inlay<*>): Int { if (!inlay.isValid) return 0 val editor = inlay.editor val compute = ReadAction.compute { val lineStartOffset = DocumentUtil.getLineStartOffset(inlay.offset, editor.document) val shiftForward = CharArrayUtil.shiftForward(editor.document.immutableCharSequence, lineStartOffset, " \t") editor.offsetToXY(shiftForward).x } return compute } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/input/inlay/InlayRenderer.kt ================================================ package com.phodal.shirelang.actions.input.inlay import com.intellij.openapi.editor.EditorCustomElementRenderer import com.intellij.openapi.editor.Inlay import com.intellij.openapi.editor.markup.TextAttributes import java.awt.* class InlayRenderer(var component: Component) : EditorCustomElementRenderer { var inlaySize: Dimension = Dimension(0, 0) override fun calcWidthInPixels(inlay: Inlay<*>): Int = inlaySize.width override fun calcHeightInPixels(inlay: Inlay<*>): Int = inlaySize.height override fun paint(inlay: Inlay<*>, g: Graphics, targetRegion: Rectangle, textAttributes: TextAttributes) { val bounds = inlay.bounds ?: return val inlayLocation: Point = bounds.location ?: return if (component.location != inlayLocation) { component.location = inlayLocation } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/intention/ShireIntentionAction.kt ================================================ package com.phodal.shirelang.actions.intention import com.intellij.codeInsight.intention.IntentionAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirelang.ShireBundle import com.phodal.shirelang.actions.ShireRunFileAction import com.phodal.shirelang.actions.base.DynamicShireActionService import com.phodal.shirelang.actions.base.validator.WhenConditionValidator import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole import kotlin.collections.firstOrNull class ShireIntentionAction(private val hobbitHole: HobbitHole?, val file: PsiFile, private val event: AnActionEvent?) : IntentionAction { override fun startInWriteAction(): Boolean = true override fun getFamilyName(): String = ShireBundle.message("shire.intention") override fun getText(): String = hobbitHole?.name ?: ShireBundle.message("shire.intention") override fun isAvailable(project: Project, editor: Editor, file: PsiFile): Boolean { val conditions = hobbitHole?.when_ ?: return true return WhenConditionValidator.isAvailable(conditions, file) } override fun invoke(project: Project, editor: Editor?, file: PsiFile?) { val config = DynamicShireActionService.getInstance(project) .getActions(ShireActionLocation.INTENTION_MENU) .firstOrNull { it.hole == hobbitHole } ?: return ShireRunFileAction.executeFile(project, config, null) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/intention/ShireIntentionHelper.kt ================================================ package com.phodal.shirelang.actions.intention import com.intellij.codeInsight.intention.IntentionAction import com.intellij.lang.injection.InjectedLanguageManager import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.openapi.util.Iconable import com.intellij.psi.PsiFile import com.phodal.shirecore.ShireCoreBundle import javax.swing.Icon import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirelang.ShireIcons import com.phodal.shirelang.actions.base.DynamicShireActionService import com.phodal.shirelang.actions.intention.ui.CustomPopupStep class ShireIntentionHelper : IntentionAction, Iconable { override fun startInWriteAction(): Boolean = true override fun getText(): String = ShireCoreBundle.message("intentions.assistant.name") override fun getFamilyName(): String = ShireCoreBundle.message("intentions.assistant.name") override fun getIcon(flags: Int): Icon = ShireIcons.Idea override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?): Boolean { if (file == null) return false val instance = InjectedLanguageManager.getInstance(project) if (instance.getTopLevelFile(file)?.virtualFile == null) return false return getAiAssistantIntentions(file, null).isNotEmpty() } override fun invoke(project: Project, editor: Editor, file: PsiFile) { val intentions = getAiAssistantIntentions(file, null) if (intentions.isEmpty()) return val title = ShireCoreBundle.message("intentions.assistant.popup.title") val popupStep = CustomPopupStep(intentions, project, editor, file, title) try { val popup = JBPopupFactory.getInstance().createListPopup(popupStep) popup.showInBestPositionFor(editor) } catch (e: Exception) { logger().warn("Failed to show popup", e) } } companion object { fun getAiAssistantIntentions(file: PsiFile, event: AnActionEvent?): List { val project = event?.project ?: return emptyList() val shireActionConfigs = DynamicShireActionService.getInstance(project).getActions(ShireActionLocation.INTENTION_MENU) return shireActionConfigs.map { actionConfig -> ShireIntentionAction(actionConfig.hole, file, event) } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/intention/ShireIntentionsActionGroup.kt ================================================ package com.phodal.shirelang.actions.intention import com.intellij.codeInsight.intention.IntentionAction import com.intellij.openapi.actionSystem.ActionGroup import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.DumbAware import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile class ShireIntentionsActionGroup : ActionGroup("Shire Intention", true), DumbAware { override fun getChildren(e: AnActionEvent?): Array { val project: Project = e?.project ?: return emptyArray() val editor: Editor = e.getData(CommonDataKeys.EDITOR) ?: return emptyArray() val file: PsiFile = e.getData(CommonDataKeys.PSI_FILE) ?: return emptyArray() val intentions: List = ShireIntentionHelper.getAiAssistantIntentions(file, e) return intentions.map { action -> DumbAwareAction.create(action.text) { action.invoke(project, editor, file) } }.toTypedArray() } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/intention/ui/CustomPopupStep.kt ================================================ package com.phodal.shirelang.actions.intention.ui import com.intellij.codeInsight.intention.IntentionAction import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.openapi.ui.popup.ListSeparator import com.intellij.openapi.ui.popup.PopupStep import com.intellij.openapi.ui.popup.util.BaseListPopupStep import com.intellij.psi.PsiFile class CustomPopupStep( private val intentionAction: List, private val project: Project, private val editor: Editor, private val psiFile: PsiFile, private val popupTitle: String, ) : BaseListPopupStep(popupTitle, intentionAction) { override fun getTextFor(value: IntentionAction): String = value.text override fun getSeparatorAbove(value: IntentionAction?): ListSeparator? = if (value != null && value == intentionAction) ListSeparator() else null override fun onChosen(selectedValue: IntentionAction?, finalChoice: Boolean): PopupStep<*>? { return doFinalStep { selectedValue?.invoke(project, editor, psiFile) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/template/NewShireFileAction.kt ================================================ package com.phodal.shirelang.actions.template import com.intellij.ide.actions.CreateFileFromTemplateAction import com.intellij.ide.actions.CreateFileFromTemplateDialog import com.intellij.ide.fileTemplates.FileTemplateManager import com.intellij.openapi.project.DumbAware import com.intellij.openapi.project.Project import com.intellij.openapi.ui.NonEmptyInputValidator import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiFile import com.phodal.shirelang.ShireBundle import com.phodal.shirelang.ShireIcons class NewShireFileAction : CreateFileFromTemplateAction( ShireBundle.message("shire.newFile"), "Creates New Shire Action", ShireIcons.DEFAULT ), DumbAware { override fun getDefaultTemplateProperty(): String = "DefaultShireTemplate" override fun getActionName(psi: PsiDirectory?, p1: String, p2: String?): String = ShireBundle.message("shire.newFile") override fun buildDialog(project: Project, psiDir: PsiDirectory, builder: CreateFileFromTemplateDialog.Builder) { builder .setTitle(ShireBundle.message("shire.newFile")) .addKind(ShireBundle.message("shire.file"), ShireIcons.DEFAULT, "Shire Action") .setValidator(NonEmptyInputValidator()) } override fun createFile(name: String?, templateName: String?, dir: PsiDirectory?): PsiFile? { val template = FileTemplateManager.getInstance(dir!!.project).getInternalTemplate(templateName!!) val newName = name!!.lowercase().replace(" ", "_") template.text = template.text.replace("{{name}}", name) return createFileFromTemplate(newName, template, dir) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/terminal/ShireTerminalAction.kt ================================================ package com.phodal.shirelang.actions.terminal import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.AnActionHolder import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.Project import com.intellij.openapi.ui.popup.Balloon import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.openapi.ui.popup.JBPopupListener import com.intellij.openapi.ui.popup.LightweightWindowEvent import com.intellij.openapi.wm.IdeFocusManager import com.intellij.ui.DocumentAdapter import com.intellij.ui.awt.RelativePoint import com.intellij.ui.components.JBLabel import com.intellij.util.ui.JBUI import com.intellij.util.ui.SwingHelper import com.intellij.util.ui.UIUtil import com.phodal.shirecore.ShireCoreBundle import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirecore.provider.action.TerminalLocationExecutor import com.phodal.shirelang.actions.ShireRunFileAction import com.phodal.shirelang.actions.base.DynamicShireActionService import java.awt.Component import java.awt.Font import java.awt.Point import java.awt.event.* import javax.swing.Box import javax.swing.JTextField import javax.swing.event.DocumentEvent class ShireTerminalAction : DumbAwareAction() { override fun getActionUpdateThread() = ActionUpdateThread.EDT private val OUTLINE_PROPERTY = "JComponent.outline" private val ERROR_VALUE = "error" private fun shireActionConfigs(project: Project) = DynamicShireActionService.getInstance(project).getActions(ShireActionLocation.TERMINAL_MENU) override fun update(e: AnActionEvent) { val shireActionConfigs = e.project?.let { shireActionConfigs(it) } val firstHole = shireActionConfigs?.firstOrNull()?.hole e.presentation.isVisible = shireActionConfigs?.size == 1 && firstHole?.enabled == true e.presentation.isEnabled = shireActionConfigs?.size == 1 && firstHole?.enabled == true e.presentation.text = firstHole?.description ?: "AutoDev Placeholder (Bug)" } override fun actionPerformed(e: AnActionEvent) { val project = e.project ?: return val config = shireActionConfigs(project).firstOrNull() ?: return TerminalLocationExecutor.provide(project)?.getComponent(e)?.let { component -> showInputBoxPopup(component, getPreferredPopupPoint(e)) { userInput -> ShireRunFileAction.executeFile(project, config, null, variables = mapOf("input" to userInput) ) } } } private fun getPreferredPopupPoint(e: AnActionEvent): RelativePoint? { if (e.inputEvent is MouseEvent) { val comp = e.inputEvent?.component if (comp is AnActionHolder) { return RelativePoint(comp.parent, Point(comp.x + JBUI.scale(3), comp.y + comp.height + JBUI.scale(3))) } } return null } private fun showInputBoxPopup(component: Component, popupPoint: RelativePoint?, callback: (String) -> Unit) { val textField = JTextField().also { it.text = ShireCoreBundle.message("shell.command.suggestion.action.default.text") it.selectAll() } val label = JBLabel().also { it.font = UIUtil.getLabelFont().deriveFont(Font.BOLD) } val panel = SwingHelper.newLeftAlignedVerticalPanel(label, Box.createVerticalStrut(JBUI.scale(2)), textField) panel.addFocusListener(object : FocusAdapter() { override fun focusGained(e: FocusEvent?) { IdeFocusManager.findInstance().requestFocus(textField, false) } }) val balloon = JBPopupFactory.getInstance().createDialogBalloonBuilder(panel, null) .setShowCallout(true) .setCloseButtonEnabled(false) .setAnimationCycle(0) .setHideOnKeyOutside(true) .setHideOnClickOutside(true) .setRequestFocus(true) .setBlockClicksThroughBalloon(true) .createBalloon() textField.addKeyListener(object : KeyAdapter() { override fun keyPressed(e: KeyEvent?) { if (e != null && e.keyCode == KeyEvent.VK_ENTER) { if (textField.text.isEmpty()) { textField.putClientProperty(OUTLINE_PROPERTY, ERROR_VALUE) textField.repaint() return } callback(textField.text) balloon.hide() } } }) textField.document.addDocumentListener(object : DocumentAdapter() { override fun textChanged(e: DocumentEvent) { val outlineValue = textField.getClientProperty(OUTLINE_PROPERTY) if (outlineValue == ERROR_VALUE) { textField.putClientProperty(OUTLINE_PROPERTY, null) textField.repaint() } } }) balloon.show(popupPoint, Balloon.Position.above) balloon.addListener(object : JBPopupListener { override fun onClosed(event: LightweightWindowEvent) { IdeFocusManager.findInstance().requestFocus(component, false) } }) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/vcs/ShireVcsActionGroup.kt ================================================ package com.phodal.shirelang.actions.vcs import com.intellij.openapi.actionSystem.ActionGroup import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.Project import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirecore.variable.template.VariableActionEventDataHolder import com.phodal.shirelang.ShireIcons import com.phodal.shirelang.actions.ShireRunFileAction import com.phodal.shirelang.actions.base.DynamicShireActionConfig import com.phodal.shirelang.actions.base.DynamicShireActionService class ShireVcsActionGroup : ActionGroup() { override fun getActionUpdateThread() = ActionUpdateThread.BGT override fun update(e: AnActionEvent) { val project = e.project ?: return val isMultipleActions = shireActionConfigs(project).size > 1 e.presentation.isVisible = isMultipleActions e.presentation.isEnabled = shireActionConfigs(project).any { it.hole?.enabled == true } e.presentation.isPopupGroup = true } override fun getChildren(e: AnActionEvent?): Array { val project = e?.project ?: return emptyArray() return shireActionConfigs(project).map(::ShireVcsAction).toTypedArray() } private fun shireActionConfigs(project: Project) = DynamicShireActionService.getInstance(project).getActions(ShireActionLocation.COMMIT_MENU) } class ShireVcsAction(val config: DynamicShireActionConfig) : DumbAwareAction(config.name, config.hole?.description, ShireIcons.DEFAULT) { override fun actionPerformed(e: AnActionEvent) { val project = e.project ?: return VariableActionEventDataHolder.putData(VariableActionEventDataHolder(e.dataContext)) ShireRunFileAction.executeFile(project, config, null) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/vcs/ShireVcsLogAction.kt ================================================ package com.phodal.shirelang.actions.vcs import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.project.Project import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirecore.variable.template.VariableActionEventDataHolder import com.phodal.shirelang.actions.ShireRunFileAction import com.phodal.shirelang.actions.base.DynamicShireActionService class ShireVcsLogAction : AnAction() { override fun getActionUpdateThread() = ActionUpdateThread.EDT private fun shireActionConfigs(project: Project) = DynamicShireActionService.getInstance(project).getActions(ShireActionLocation.VCS_LOG_MENU) override fun update(e: AnActionEvent) { val project = e.project ?: return val isOnlyOneConfig = shireActionConfigs(project).size == 1 val hobbitHole = shireActionConfigs(project).firstOrNull()?.hole e.presentation.isVisible = isOnlyOneConfig e.presentation.isEnabled = hobbitHole != null && hobbitHole.enabled if (hobbitHole != null) { e.presentation.text = hobbitHole.name ?: "" } } override fun actionPerformed(e: AnActionEvent) { val project = e.project ?: return VariableActionEventDataHolder.putData(VariableActionEventDataHolder(e.dataContext)) val config = shireActionConfigs(project).firstOrNull() ?: return ShireRunFileAction.executeFile(project, config, null) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/actions/vcs/ShireVcsSingleAction.kt ================================================ package com.phodal.shirelang.actions.vcs import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.Project import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirecore.variable.template.VariableActionEventDataHolder import com.phodal.shirelang.actions.ShireRunFileAction import com.phodal.shirelang.actions.base.DynamicShireActionService class ShireVcsSingleAction : DumbAwareAction() { override fun getActionUpdateThread() = ActionUpdateThread.EDT private fun shireActionConfigs(project: Project) = DynamicShireActionService.getInstance(project).getActions(ShireActionLocation.COMMIT_MENU) override fun update(e: AnActionEvent) { val project = e.project ?: return val isOnlyOneConfig = shireActionConfigs(project).size == 1 val hobbitHole = shireActionConfigs(project).firstOrNull()?.hole e.presentation.isVisible = isOnlyOneConfig e.presentation.isEnabled = hobbitHole != null && hobbitHole.enabled if (hobbitHole != null) { e.presentation.text = hobbitHole.name ?: "" } } override fun actionPerformed(e: AnActionEvent) { val project = e.project ?: return VariableActionEventDataHolder.putData(VariableActionEventDataHolder(e.dataContext)) val config = shireActionConfigs(project).firstOrNull() ?: return ShireRunFileAction.executeFile(project, config, null) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/comment/ShireCommenter.kt ================================================ package com.phodal.shirelang.comment import com.intellij.application.options.CodeStyle import com.intellij.codeInsight.generation.CommenterDataHolder import com.intellij.codeInsight.generation.SelfManagingCommenter import com.intellij.codeInsight.generation.SelfManagingCommenterUtil import com.intellij.lang.Commenter import com.intellij.openapi.editor.Document import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiFile import com.intellij.util.text.CharArrayUtil import com.phodal.shirelang.ShireLanguage data class CommentHolder(val file: PsiFile) : CommenterDataHolder() { fun useSpaceAfterLineComment(): Boolean = CodeStyle.getLanguageSettings(file, ShireLanguage.INSTANCE).LINE_COMMENT_ADD_SPACE } class ShireCommenter : Commenter, SelfManagingCommenter { override fun getLineCommentPrefix(): String = "//" override fun getBlockCommentPrefix(): String = "/*" override fun getBlockCommentSuffix(): String = "*/" override fun getCommentedBlockCommentPrefix(): String = "*//*" override fun getCommentedBlockCommentSuffix(): String = "*//*" private val LINE_PREFIXES: List = listOf("//") override fun getBlockCommentPrefix( selectionStart: Int, document: Document, data: CommentHolder ): String = blockCommentPrefix override fun getBlockCommentSuffix( selectionEnd: Int, document: Document, data: CommentHolder ): String = blockCommentSuffix override fun getBlockCommentRange( selectionStart: Int, selectionEnd: Int, document: Document, data: CommentHolder ): TextRange? = SelfManagingCommenterUtil.getBlockCommentRange( selectionStart, selectionEnd, document, blockCommentPrefix, blockCommentSuffix ) override fun insertBlockComment( startOffset: Int, endOffset: Int, document: Document, data: CommentHolder? ): TextRange = SelfManagingCommenterUtil.insertBlockComment( startOffset, endOffset, document, blockCommentPrefix, blockCommentSuffix ) override fun uncommentBlockComment( startOffset: Int, endOffset: Int, document: Document, data: CommentHolder? ) = SelfManagingCommenterUtil.uncommentBlockComment( startOffset, endOffset, document, blockCommentPrefix, blockCommentSuffix ) override fun commentLine(line: Int, offset: Int, document: Document, data: CommentHolder) { val addSpace = data.useSpaceAfterLineComment() document.insertString(offset, "//" + if (addSpace) " " else "") } override fun getCommentPrefix(line: Int, document: Document, data: CommentHolder): String = lineCommentPrefix override fun createBlockCommentingState( selectionStart: Int, selectionEnd: Int, document: Document, file: PsiFile ): CommentHolder = CommentHolder(file) override fun isLineCommented(line: Int, offset: Int, document: Document, data: CommentHolder): Boolean { return LINE_PREFIXES.any { CharArrayUtil.regionMatches(document.charsSequence, offset, it) } } override fun uncommentLine(line: Int, offset: Int, document: Document, data: CommentHolder) { val addSpace = data.useSpaceAfterLineComment() document.insertString(offset, "//" + if (addSpace) " " else "") } override fun createLineCommentingState( startLine: Int, endLine: Int, document: Document, file: PsiFile ): CommentHolder = CommentHolder(file) } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/ast/ExpressionBuiltInMethod.kt ================================================ package com.phodal.shirelang.compiler.ast /** * This enum class `ExpressionBuiltInMethod` provides a set of built-in methods for string manipulation in Kotlin. * Each enum constant represents a specific built-in method, and holds information about the method's name, description, * the string to be inserted after the method call, and the position to move the caret to after insertion. * */ enum class ExpressionBuiltInMethod( val methodName: String, val description: String, val postInsertString: String = "()", val moveCaret: Int = 2, ) { LENGTH("length", "The length of the string"), TRIM("trim", "The trimmed string"), CONTAINS("contains", "Check if the string contains a substring", "(\"\")", 2), STARTS_WITH("startsWith", "Check if the string starts with a substring", "(\"\")", 2), ENDS_WITH("endsWith", "Check if the string ends with a substring", "(\"\")", 2), LOWERCASE("lowercase", "The lowercase version of the string"), UPPERCASE("uppercase", "The uppercase version of the string"), IS_EMPTY("isEmpty", "Check if the string is empty"), IS_NOT_EMPTY("isNotEmpty", "Check if the string is not empty"), FIRST("first", "The first character of the string"), LAST("last", "The last character of the string"), MATCHES("matches", "Check if the string matches a regex pattern", "(\"//\")", 3); companion object { fun fromString(methodName: String): ExpressionBuiltInMethod? { return values().find { it.methodName == methodName } } fun completionProvider(): Array { return values() } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/ast/ForeignFunction.kt ================================================ package com.phodal.shirelang.compiler.ast data class ForeignFunction( val funcName: String, val funcPath: String, val accessFuncName: String, val inputTypes: List, val returnVars: Map, ) { companion object { fun from(map: Map): List { return map .filter { (_, value) -> value is FrontMatterType.EXPRESSION && value.value is ForeignFunctionStmt } .map { (key, value) -> val stmt = value.value as ForeignFunctionStmt ForeignFunction( key, stmt.funcPath, stmt.accessFuncName, stmt.inputTypes, stmt.returnVars ) } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/ast/FrontMatterType.kt ================================================ package com.phodal.shirelang.compiler.ast import com.phodal.shirelang.compiler.ast.action.RuleBasedPatternAction /** * The `FrontMatterType` is a sealed class in Kotlin that represents different types of front matter data. * It has several subclasses to represent different types of data. * Each subclass overrides the `display()` function to return a string representation of the data. * * @property value The value of the front matter data. * * @constructor Creates an instance of `FrontMatterType`. * * @see STRING A subclass of `FrontMatterType` that represents a string. * @see NUMBER A subclass of `FrontMatterType` that represents a number. * @see DATE A subclass of `FrontMatterType` that represents a date. * @see BOOLEAN A subclass of `FrontMatterType` that represents a boolean. * @see ARRAY A subclass of `FrontMatterType` that represents a JSON array. * @see OBJECT A subclass of `FrontMatterType` that represents a JSON object. * @see PATTERN A subclass of `FrontMatterType` that represents a pattern action. * @see CASE_MATCH A subclass of `FrontMatterType` that represents a case match. * @see VARIABLE A subclass of `FrontMatterType` that represents a variable. * @see EXPRESSION A subclass of `FrontMatterType` that represents an expression. * @see IDENTIFIER A subclass of `FrontMatterType` that represents an identifier. * @see QUERY_STATEMENT A subclass of `FrontMatterType` that represents a query statement. */ sealed class FrontMatterType(val value: Any) { open fun display(): String = value.toString() open fun toValue(): Any = value class STRING(value: String) : FrontMatterType(value) { override fun display(): String { return "\"$value\"" } } class NUMBER(value: Int) : FrontMatterType(value) { override fun display(): String { return value.toString() } } /** * The `DATE` class is a subclass of `FrontMatterType` that represents a date. */ class DATE(value: String) : FrontMatterType(value) { override fun display(): String { return value.toString() } } class BOOLEAN(value: Boolean) : FrontMatterType(value) { override fun display(): String { return value.toString() } } class ERROR(value: String) : FrontMatterType(value) { override fun display(): String { return value.toString() } } class EMPTY() : FrontMatterType("") { override fun display(): String { return "" } } /** * The `ARRAY` class is a subclass of `FrontMatterType` that represents a JSON array. * * ```shire * --- * variables: ["var1", "var2"] * --- */ class ARRAY(value: List) : FrontMatterType(value) { override fun display(): String { return (value as List).joinToString(", ", "[", "]") { it.display() } } override fun toValue(): List { return (value as List) } } /** * The `OBJECT` class is a subclass of `FrontMatterType` that represents a JSON object. * It takes a `Map` of `String` to `FrontMatterType` as its constructor parameter. * * ```shire * --- * variables: * "var1": "value1" * --- * ``` */ class OBJECT(value: Map) : FrontMatterType(value) { override fun display(): String { return (value as Map).entries.joinToString( ", ", "{", "}" ) { "\"${it.key}\": ${it.value.display()}" } } override fun toValue(): Map { return value as Map } } /** * The pattern action handles for processing * * ```shire * --- * variables: * "var2": /.*.java/ { grep("error.log") | sort | xargs("rm")} * --- * ```` */ class PATTERN(value: RuleBasedPatternAction) : FrontMatterType(value) { override fun display(): String { return (value as RuleBasedPatternAction).pattern + " -> " + (value.processors.joinToString(", ") { it.funcName }) } } /** * The case match for the front matter. * * ```shire * --- * case "$0" { * "error" { grep("ERROR") | sort | xargs("notify_admin") } * "warn" { grep("WARN") | sort | xargs("notify_admin") } * "info" { grep("INFO") | sort | xargs("notify_user") } * default { grep("ERROR") | sort | xargs("notify_admin") } * } * --- */ class CASE_MATCH(value: Map) : FrontMatterType(value) { /** * output sample: * ```shire * case "$0" { * "error" { grep("ERROR") | sort | xargs("notify_admin") } * "warn" { grep("WARN") | sort | xargs("notify_admin") } * "info" { grep("INFO") | sort | xargs("notify_user") } * default { grep("ERROR") | sort | xargs("notify_admin") } * } * ```s */ override fun display(): String { return (value as Map).entries.joinToString( "\n", "case \"\$0\" {\n", "\n}" ) { (key, value: PATTERN) -> val pattern = (value as RuleBasedPatternAction).pattern val processors = value.processors.joinToString(" | ") { it.funcName } " \"$key\" { $processors }" } } } /** * Variable same start with $, other will same to String or IDENTIFIER */ class VARIABLE(value: String) : FrontMatterType(value) { override fun display(): String { return "\$$value" } } /** * The simple expression for the [HobbitHole.WHEN] condition. * * ```shire * --- * when: $selection.length >= 1 && $selection.first() == 'p' * --- * ``` */ class EXPRESSION(value: Statement) : FrontMatterType(value) { override fun display(): String { return (value as Statement).display() } } /** * Identifier for the front matter config expression and template, like [EXPRESSION] or [MethodCall] * * ```shire * --- * when: $selection.length >= 1 && $selection.first() == 'p' * --- * ``` */ class IDENTIFIER(value: String) : FrontMatterType(value) { override fun display(): String { return value.toString() } } /** * The [QUERY_STATEMENT] class is a subclass of [FrontMatterType] that represents a query statement. * for example: * ```shire * --- * variables: * "var1": { * from { * PsiClass clazz * } * where { * clazz.extends("org.springframework.web.bind.annotation.RestController") and clazz.getAnAnnotation() == "org.springframework.web.bind.annotation.RequestMapping" * } * select { * clazz.id, clazz.name, "code" * } * } * --- * ``` */ class QUERY_STATEMENT(value: ShirePsiQueryStatement) : FrontMatterType(value) { override fun display(): String { return value.toString() } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/ast/LineInfo.kt ================================================ package com.phodal.shirelang.compiler.ast data class LineInfo( val startLine: Int, val endLine: Int, val startColumn: Int = 0, val endColumn: Int = 0 ) { fun splitContent(content: String): String { val fileContent = run { val subContent = try { content.split("\n").slice(startLine - 1 until endLine) .joinToString("\n") } catch (e: StringIndexOutOfBoundsException) { content } subContent } return fileContent } companion object { private val regex = Regex("""L(\d+)(?:C(\d+))?(?:-L(\d+)(?:C(\d+))?)?""") /** * Convert a string to a `TextRange`, if possible. The string should be in the format: "filepath#L1-L12", * where "filepath" is the path to the file, "#" is a hash symbol, "L1-L12" is a range of lines from line 1 to line 12. */ fun fromString(input: String): LineInfo? { val matchResult = regex.find(input) ?: return null val startLine = matchResult.groupValues[1].toIntOrNull() ?: 0 val startColumn = matchResult.groupValues[2].toIntOrNull() ?: 0 val endLine = matchResult.groupValues[3].toIntOrNull() ?: startLine // set end line to start line if not found val endColumn = matchResult.groupValues[4].toIntOrNull() ?: 0 return LineInfo(startLine, endLine, startColumn, endColumn) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/ast/ShireExpression.kt ================================================ package com.phodal.shirelang.compiler.ast import com.intellij.openapi.diagnostic.logger import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFunc import java.util.regex.Pattern /** * Represents the base class for all statements. */ abstract class Statement { abstract fun evaluate(variables: Map): Any fun display(): String { return when (this) { is Operator -> this.type.display is StringOperatorStatement -> this.type.display is Comparison -> "${this.left.display()} ${this.operator.display()} ${this.right.display()}" is StringComparison -> "${this.variable} ${this.operator.display()} ${this.value}" is LogicalExpression -> "${this.left.display()} ${this.operator.display} ${this.right.display()}" is NotExpression -> "!${this.operand.display()}" is MethodCall -> { val parameters = this.arguments?.joinToString(", ") { when (it) { is FrontMatterType -> it.display() else -> it.toString() } } val formattedParameters = if (parameters == null) "" else "($parameters)" val dotWithTarget = if (this.methodName is FrontMatterType.EMPTY) { "" } else if (this.methodName is FrontMatterType.IDENTIFIER) { if (this.methodName.value == "") { "" } else { ".${this.methodName.display()}" } } else { ".${this.methodName.display()}" } "${this.objectName.display()}${dotWithTarget}$formattedParameters" } is Value -> this.value.display() is Processor -> this.processors.joinToString(" | ") { it.toString() } else -> throw IllegalArgumentException("Unsupported statement type: $this") } } } /** * This function `evaluate` is used to evaluate the `value` based on its type. * * @param variables A map of variables where the key is a `String` and the value is also a `String`. * * @return Returns the value of the `FrontMatterType` if it is one of the supported types (STRING, NUMBER, DATE, BOOLEAN). * * @throws IllegalArgumentException If the `FrontMatterType` is not one of the supported types, * an `IllegalArgumentException` is thrown with a message indicating the unsupported value type. * * @since 1.0 */ data class Value(val value: FrontMatterType) : Statement() { override fun evaluate(variables: Map): Any { return when (value) { is FrontMatterType.STRING -> value.value is FrontMatterType.NUMBER -> value.value is FrontMatterType.DATE -> value.value is FrontMatterType.BOOLEAN -> value.value else -> throw IllegalArgumentException("Unsupported value type: $value") } } } /** * Enumeration of operator types used in logical and comparison expressions. * * @property display The string representation of the operator. */ sealed class OperatorType(val display: String) { /** Logical OR operator (||). */ object Or : OperatorType("||") /** Logical AND operator (&&). */ object And : OperatorType("&&") /** Logical NOT operator (!). */ object Not : OperatorType("!") /** Equality operator (==). */ object Equal : OperatorType("==") /** Inequality operator (!=). */ object NotEqual : OperatorType("!=") /** Less than operator (<). */ object LessThan : OperatorType("<") /** Greater than operator (>). */ object GreaterThan : OperatorType(">") /** Less than or equal operator (<=). */ object LessEqual : OperatorType("<=") /** Greater than or equal operator (>=). */ object GreaterEqual : OperatorType(">=") companion object { fun fromString(operator: String): OperatorType { return when (operator) { "||" -> Or "&&" -> And "!" -> Not "==" -> Equal "!=" -> NotEqual "<" -> LessThan ">" -> GreaterThan "<=" -> LessEqual ">=" -> GreaterEqual else -> throw IllegalArgumentException("Invalid operator: $operator") } } } } /** * Enumeration of string operator types used in string comparison expressions. * * @property display The string representation of the string operator. */ sealed class StringOperator(val display: String) { /** Contains operator (contains). */ object Contains : StringOperator("contains") /** Starts with operator (startsWith). */ object StartsWith : StringOperator("startsWith") /** Ends with operator (endsWith). */ object EndsWith : StringOperator("endsWith") /** Matches regex operator (matches). */ object Matches : StringOperator("matches") } /** * Represents an operator used in a comparison expression. * * @property type The type of operator. */ data class Operator(val type: OperatorType) : Statement() { override fun evaluate(variables: Map) = type.display } /** * Represents a string operator used in a string comparison expression. * * @property type The type of string operator. */ data class StringOperatorStatement(val type: StringOperator) : Statement() { override fun evaluate(variables: Map) = type.display } /** * Represents a comparison expression, including a variable, an operator, and a value. * * @property left The name of the variable being compared. * @property operator The operator used for comparison. * @property right The value being compared against. */ data class Comparison( val left: FrontMatterType, val operator: Operator, val right: FrontMatterType, ) : Statement() { override fun evaluate(variables: Map): Boolean { val variableValue = when (left.value) { is MethodCall -> left.value.evaluate(variables) is FrontMatterType.STRING -> left.value.value else -> { logger().error("Variable not found: ${left.value}, will use: ${variables[left.value]}") variables[left.value] } } val value = right.value return when (operator.type) { OperatorType.Equal -> variableValue == value OperatorType.NotEqual -> variableValue != value OperatorType.LessThan -> (variableValue as Comparable) < value OperatorType.GreaterThan -> (variableValue as Comparable) > value OperatorType.LessEqual -> (variableValue as Comparable) <= value OperatorType.GreaterEqual -> (variableValue as Comparable) >= value else -> throw IllegalArgumentException("Invalid comparison operator: ${operator.type}") } } } /** * Represents a string comparison expression, including a variable, a string operator, and a value. * * @property variable The name of the variable being compared. * @property operator The string operator used for comparison. * @property value The string value being compared against. */ data class StringComparison( val variable: String, val operator: StringOperatorStatement, val value: String, ) : Statement() { override fun evaluate(variables: Map): Boolean { return when (operator.type) { StringOperator.Contains -> variable.contains(value) StringOperator.StartsWith -> variable.startsWith(value) StringOperator.EndsWith -> variable.endsWith(value) StringOperator.Matches -> variable.matches(Pattern.compile(value).toRegex()) } } } /** * Represents a logical expression, including left and right operands and an operator. * * @property left The left operand of the logical expression. * @property operator The logical operator used in the expression. * @property right The right operand of the logical expression. */ data class LogicalExpression( val left: Statement, val operator: OperatorType, val right: Statement, ) : Statement() { override fun evaluate(variables: Map): Boolean { val leftValue = left.evaluate(variables) as Boolean val rightValue = right.evaluate(variables) as Boolean return when (operator) { OperatorType.And -> leftValue && rightValue OperatorType.Or -> leftValue || rightValue else -> throw IllegalArgumentException("Invalid logical operator: $operator") } } } /** * Represents a negation expression, including an operand. * * @property operand The operand to be negated. */ data class NotExpression(val operand: Statement) : Statement() { override fun evaluate(variables: Map): Boolean { return !(operand.evaluate(variables) as Boolean) } } /** * Represents a method call expression, including the object and method being called. * * @property objectName The name of the object on which the method is called. * @property methodName The name of the method being called. * @property arguments The arguments passed to the method. */ data class MethodCall( val objectName: FrontMatterType, val methodName: FrontMatterType, val arguments: List?, ) : Statement() { override fun evaluate(variables: Map): Any { val value = when (objectName) { is FrontMatterType.STRING -> variables[objectName.value] is FrontMatterType.VARIABLE -> variables[objectName.value] else -> null } ?: throw IllegalArgumentException("Variable not found: ${objectName.value}") val parameters: List? = parameters() return evaluateExpression(methodName, parameters, value) } fun parameters() = this.arguments?.map { when (it) { is FrontMatterType.STRING -> it.display().removeSurrounding("\"") is FrontMatterType.NUMBER -> it.value is FrontMatterType.DATE -> it.value is FrontMatterType -> it.display() else -> it.toString() } } fun evaluateExpression(methodNode: FrontMatterType, parameters: List?, value: String): Comparable<*> { val method = ExpressionBuiltInMethod.fromString(methodNode.value.toString()) return when (method) { ExpressionBuiltInMethod.LENGTH -> value.length ExpressionBuiltInMethod.TRIM -> value.trim() ExpressionBuiltInMethod.CONTAINS -> { if (parameters != null) { value.contains(parameters[0] as String) } else { throw IllegalArgumentException("Missing parameter for method: $methodNode") } } ExpressionBuiltInMethod.STARTS_WITH -> { if (parameters != null) { value.startsWith(parameters[0] as String) } else { throw IllegalArgumentException("Missing parameter for method: $methodNode") } } ExpressionBuiltInMethod.ENDS_WITH -> { if (parameters != null) { value.endsWith(parameters[0] as String) } else { throw IllegalArgumentException("Missing parameter for method: $methodNode") } } ExpressionBuiltInMethod.LOWERCASE -> value.lowercase() ExpressionBuiltInMethod.UPPERCASE -> value.uppercase() ExpressionBuiltInMethod.IS_EMPTY -> value.isEmpty() ExpressionBuiltInMethod.IS_NOT_EMPTY -> value.isNotEmpty() ExpressionBuiltInMethod.FIRST -> value.first().toString() ExpressionBuiltInMethod.LAST -> value.last().toString() ExpressionBuiltInMethod.MATCHES -> { if (parameters != null) { value.matches((parameters[0] as String).toRegex()) } else { throw IllegalArgumentException("Missing parameter for method: $methodNode") } } else -> throw IllegalArgumentException("Unsupported method: $methodNode") } } } data class Processor( val processors: List, ) : Statement() { override fun evaluate(variables: Map): List { return processors } } data class CaseKeyValue( val key: FrontMatterType, val value: FrontMatterType.EXPRESSION, ) : Statement() { override fun evaluate(variables: Map): Any { return key.display() to value } } data class ForeignFunctionStmt( val funcName: String, val funcPath: String, val accessFuncName: String, val inputTypes: List, val returnVars: Map, ) : Statement() { override fun evaluate(variables: Map): ForeignFunction { return ForeignFunction(funcName, funcPath, accessFuncName, inputTypes, returnVars) } } /** * Switch case */ data class ConditionCase( val conditions: List, val cases: List, ) : Statement() { override fun evaluate(variables: Map): Any { val condition = conditions.map { it.display() } val case = cases.map { it.display() } return condition to case } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/ast/ShirePsiQueryStatement.kt ================================================ package com.phodal.shirelang.compiler.ast import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFunc class ShirePsiQueryStatement( val from: List, val where: Statement, val select: List, ) { override fun toString(): String { return """ from { ${from.joinToString(", ")} } where { $where } select ${select.joinToString(", ")}""" .trimIndent() } fun toPatternActionFunc(): List { val selectFunc = PatternActionFunc.From(from) val whereFunc = PatternActionFunc.Where(where) val selectFuncs = PatternActionFunc.Select(select) return listOf(selectFunc, whereFunc, selectFuncs) } } class VariableElement( val variableType: String, val value: String, ) { override fun toString(): String { return "$variableType $value" } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/ast/TaskRoutes.kt ================================================ package com.phodal.shirelang.compiler.ast import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole import com.phodal.shirelang.compiler.execute.FunctionStatementProcessor data class Condition( val conditionKey: String, val valueExpression: FrontMatterType.EXPRESSION, ) sealed class Task(open val expression: FrontMatterType.EXPRESSION?) { class CustomTask(override val expression: FrontMatterType.EXPRESSION?) : Task(expression) class Default(override val expression: FrontMatterType.EXPRESSION?) : Task(expression) } data class Case( val caseKey: String, val valueExpression: Task, ) data class TaskRoutes( val conditions: List, val cases: List, /** * A placeholder for the default task */ val defaultTask: Task? = null, ) { fun execute(myProject: Project, context: PostProcessorContext, hobbitHole: HobbitHole): Any? { val conditionResult = mutableMapOf() /// todo: get processor variable table val variableTable = context.compiledVariables.toMutableMap() variableTable["output"] = context.genText val processor = FunctionStatementProcessor(myProject, hobbitHole) conditions.forEach { val statement = it.valueExpression.value as Statement val result = processor.execute(statement, variableTable) conditionResult[it.conditionKey] = result } val matchedCase = cases.filter { val caseKey = it.caseKey when (val condValue = conditionResult[caseKey]) { is Boolean -> { condValue == true || condValue == "true" } is String -> { condValue.isNotEmpty() } else -> { false } } } var result: Any? = null if (matchedCase.isEmpty()) { val result = ((defaultTask as? Task.Default)?.expression?.value as? Statement)?.let { processor.execute(it, variableTable) } logger().info("no matched case, execute default task: $result") return result } matchedCase.forEach { val statement = (it.valueExpression as Task.CustomTask).expression?.value as Statement result = processor.execute(statement, variableTable) } return result } companion object { fun from(expression: FrontMatterType.ARRAY): TaskRoutes? { val arrays = expression.value as List val taskRoutes = arrays.filterIsInstance() .mapNotNull { caseExpr -> when (val value = caseExpr.value) { is ConditionCase -> { transformConditionCasesToRoutes(value) } else -> { null } } } return taskRoutes.firstOrNull() } /** * Transforms a given [ConditionCase] into a [TaskRoutes] object which contains a structured set of conditions and corresponding tasks. * * @param conditionCase The [ConditionCase] object to transform. This object contains conditions and cases that determine routing logic. * @return A [TaskRoutes] object that encapsulates the transformed conditions and cases, along with a default task if specified. */ private fun transformConditionCasesToRoutes(conditionCase: ConditionCase): TaskRoutes { val conditions: List = conditionCase.conditions.map { val caseKeyValue = it.value as CaseKeyValue Condition(caseKeyValue.key.display(), caseKeyValue.value) } var defaultTask: Task? = null val cases: List = conditionCase.cases.map { val caseKeyValue = it.value as CaseKeyValue val caseKey = caseKeyValue.key.display() if (caseKey == "default") { defaultTask = Task.Default(caseKeyValue.value) } Case(caseKey, Task.CustomTask(caseKeyValue.value)) } return TaskRoutes( conditions = conditions, cases = cases, defaultTask = defaultTask ) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/ast/action/DirectAction.kt ================================================ package com.phodal.shirelang.compiler.ast.action import com.phodal.shirelang.compiler.ast.FrontMatterType import com.phodal.shirelang.compiler.ast.MethodCall import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFunc open class DirectAction(open val processors: List) { companion object { fun from(fmt: FrontMatterType): DirectAction? { return when (fmt) { is FrontMatterType.ARRAY -> { val list = fmt.value as List val actions = list.mapNotNull { when (it) { is FrontMatterType.EXPRESSION -> { val methodCall = it.value as? MethodCall ?: return@mapNotNull null val methodName = methodCall.objectName.display() val methodArgs: List = methodCall.arguments?.map { arg -> arg.toString() } ?: emptyList() PatternActionFunc.from(methodName, methodArgs) } else -> { null } } } return DirectAction(actions) } else -> { null } } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/ast/action/PatternAction.kt ================================================ package com.phodal.shirelang.compiler.ast.action import com.intellij.openapi.diagnostic.logger import com.phodal.shirelang.compiler.ast.FrontMatterType import com.phodal.shirelang.compiler.ast.ShirePsiQueryStatement import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFunc /** * PatternFun is a sealed class in Kotlin representing different pattern processing functions. * It has several subclasses: Prompt, Grep, Sed, Sort, Uniq, Head, Tail, Xargs, and Print, * each representing a specific pattern processing function. * * @property funcName The name of the pattern processing function. */ data class PatternAction( val pattern: String, val patternFuncs: List, val isQueryStatement: Boolean = false ) : DirectAction(patternFuncs) { companion object { /** * Creates a list of PatternFun instances from a FrontMatterType object. * * @param value The FrontMatterType object. * @return A list of corresponding PatternFun instances. */ fun from(value: FrontMatterType): PatternAction? { return when (value) { is FrontMatterType.STRING -> { PatternAction("", listOf(PatternActionFunc.Print(value.value as? String ?: ""))) } is FrontMatterType.PATTERN -> { val action = value.value as? RuleBasedPatternAction ?: return null PatternAction(action.pattern, action.processors) } is FrontMatterType.QUERY_STATEMENT -> { val action = value.value as? ShirePsiQueryStatement ?: return null PatternAction("", action.toPatternActionFunc(), true) } else -> { logger().warn("Unknown pattern processor type: ${value.display()}") null } } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/ast/action/RuleBasedPatternAction.kt ================================================ package com.phodal.shirelang.compiler.ast.action import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFunc class RuleBasedPatternAction(val pattern: String, override val processors: List) : DirectAction(processors) ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/ast/hobbit/HobbitHole.kt ================================================ package com.phodal.shirelang.compiler.ast.hobbit import com.intellij.execution.ui.ConsoleView import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.openapi.actionSystem.KeyboardShortcut import com.intellij.openapi.application.runReadAction import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.phodal.shirecore.config.InteractionType import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirecore.runner.console.isCanceled import com.phodal.shirecore.middleware.post.PostProcessor import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirecore.middleware.post.LifecycleProcessorSignature import com.phodal.shirecore.middleware.select.SelectElementStrategy import com.phodal.shirecore.middleware.select.SelectedEntry import com.phodal.shirecore.workerThread import com.phodal.shirelang.compiler.ast.ForeignFunction import com.phodal.shirelang.compiler.ast.FrontMatterType import com.phodal.shirelang.compiler.ast.MethodCall import com.phodal.shirelang.compiler.ast.TaskRoutes import com.phodal.shirelang.compiler.ast.action.DirectAction import com.phodal.shirelang.compiler.ast.action.PatternAction import com.phodal.shirelang.compiler.execute.FunctionStatementProcessor import com.phodal.shirelang.compiler.ast.hobbit.base.Smials import com.phodal.shirelang.compiler.parser.HobbitHoleParser import com.phodal.shirelang.compiler.ast.patternaction.VariableTransform import com.phodal.shirelang.psi.ShireFile import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch /** * Hobbit Hole 用于定义 IDE 交互逻辑与用户数据的流处理。 * * 示例 * ```shire * --- * name: "Summary" * description: "Generate Summary" * interaction: AppendCursor * actionLocation: ContextMenu * --- * ``` * */ open class HobbitHole( /** * Display name of the Shire command, will show in the IDE's UI base on [HobbitHole.interaction]. * * For example: [ShireActionLocation.CONTEXT_MENU], will show in the context menu. * * ```shire * --- * name: "AutoTest" * --- * ``` */ val name: String, /** * Tooltips for the action, will show in Hover tips on the UI. * * ```shire * --- * description: "Generate Test" * --- * ``` */ val description: String? = null, /** * The output of the action can be in editor with streaming text when use use [InteractionType.AppendCursorStream * * ```shire * --- * interaction: AppendCursor * --- * ``` */ val interaction: InteractionType = InteractionType.RunPanel, /** * The location of the action, should be one of [ShireActionLocation], the default is [ShireActionLocation.RUN_PANEL]. * * ```shire * --- * actionLocation: ContextMenu * --- * ``` */ val actionLocation: ShireActionLocation = ShireActionLocation.RUN_PANEL, /** * The strategy to select the element to apply the action. * If not selected text, will according the element position to select the element block. * For example, if cursor in a function, select the function block. * * ```shire * --- * selectionStrategy: "Block" * --- */ val selectionStrategy: SelectElementStrategy? = null, /** * The list of variables with PatternAction for build the variable. * * ```shire * --- * variables: * "name": "/[a-zA-Z]+/" * "var2": /.*.java/ { grep("error.log") | sort | print } * "testTemplate": /\(.*\).java/ { * case "$1" { * "Controller" { cat(".shire/templates/ControllerTest.java") } * "Service" { cat(".shire/templates/ServiceTest.java") } * default { cat(".shire/templates/DefaultTest.java") } * } * } * * --- */ val variables: MutableMap = mutableMapOf(), /** * This code snippet declares a variable 'when_' of type List and initializes it with an empty list. * 'when_' is a list that stores VariableCondition objects. * * Which is used for: [com.intellij.codeInsight.intention.IntentionAction.isAvailable], [com.intellij.openapi.project.DumbAwareAction.DumbAwareAction.update] to check is show menu. * * ```shire * --- * when: { $filePath.contains("src/main/java") && $fileName.contains(".java") } * --- * ``` */ val when_: FrontMatterType.EXPRESSION? = null, /** * This property represents a list of post-middleware actions to be executed after the streaming process ends. * It allows for the definition of various operations such as logging, metrics collection, code verification, * execution of code, or parsing code, among others. * * ```shire * --- * onStreamingEnd: { parseCode | saveFile("docs/shire/shire-context-variable.md") } * --- * ``` */ val onStreamingEnd: List = emptyList(), /** * This property represents a list of middleware actions to be executed * * ```shire * --- * onStreaming: { logging() | redacting() } * --- */ val onStreaming: List = emptyList(), /** * ```shire * --- * beforeStreaming: { parseCode} * --- * ``` */ val beforeStreaming: DirectAction? = null, /** * The list of actions that this action depends on. * * ```shire * --- * afterStreaming: { * condition { * "variable-success" { $selection.length > 1 } * "jsonpath-success" { jsonpath("/bookstore/book[price>35]") } * } * case condition { * "variable-sucesss" { done } * "jsonpath-success" { TODO } * default { TODO } * } * } * --- * ``` */ val afterStreaming: TaskRoutes? = null, /** * The IDE shortcut for the action, which use the IntelliJ IDEA's shortcut format. * * ```shire * --- * shortcut: "meta pressed V" * --- * ``` */ val shortcut: KeyboardShortcut? = null, /** * the status of the action, default is true. * * ```shire * --- * enabled: false * --- */ val enabled: Boolean = true, /** * the LLM model for action, default is null which will use the default model. * * ```shire * --- * model: "default" * --- * */ val model: String? = null, /** * Custom Functions for the action. */ val foreignFunctions: MutableMap = mutableMapOf(), /** * The rest of the data. */ val userData: Map = mutableMapOf(), ) : Smials { fun pickupElement(project: Project, editor: Editor?): SelectedEntry? { return runReadAction { this.selectionStrategy?.select(project, editor) return@runReadAction selectionStrategy?.getSelectedElement(project, editor) } } fun setupStreamingEndProcessor(project: Project, context: PostProcessorContext) { onStreamingEnd.forEach { funcNode -> PostProcessor.handler(funcNode.funcName)?.setup(context) } } fun executeStreamingEndProcessor( project: Project, console: ConsoleView?, context: PostProcessorContext, compiledVariables: Map, ): String? { console?.print("\n", ConsoleViewContentType.SYSTEM_OUTPUT) onStreamingEnd.forEach { funcNode -> if (console?.isCanceled() == true) return@forEach console?.print("execute streamingEnd: ${funcNode.funcName}\n", ConsoleViewContentType.SYSTEM_OUTPUT) val postProcessor = PostProcessor.handler(funcNode.funcName) if (postProcessor == null) { // TODO: change execute console?.print("Not found function: ${funcNode.funcName}\n", ConsoleViewContentType.SYSTEM_OUTPUT) return@forEach } val args: List = funcNode.args.map { arg -> when (arg) { is String -> { if (arg.startsWith("$")) { if (arg == "\$output" && context.lastTaskOutput != null) { context.lastTaskOutput ?: "\$output" } else { compiledVariables[arg.substring(1)] ?: "" } } else { arg } } else -> arg } } val lastResult = postProcessor.execute(project, context, console, args) context.lastTaskOutput = lastResult as? String } return context.lastTaskOutput } fun executeBeforeStreamingProcessor( myProject: Project, context: PostProcessorContext, console: ConsoleView?, compiledVariables: MutableMap, ): Any? { if (console?.isCanceled() == true) return null if (beforeStreaming == null) return null if (beforeStreaming.processors.isEmpty()) return null CoroutineScope(workerThread).launch { FunctionStatementProcessor(myProject, this@HobbitHole).execute( beforeStreaming.processors, compiledVariables ) } return context.lastTaskOutput } fun executeAfterStreamingProcessor( myProject: Project, console: ConsoleView?, context: PostProcessorContext, ): Any? { if (console?.isCanceled() == true) return null val result = afterStreaming?.execute(myProject, context, this) context.lastTaskOutput = result as? String return result } companion object { const val NAME = "name" const val ACTION_LOCATION = "actionLocation" const val INTERACTION = "interaction" const val STRATEGY_SELECTION = "selectionStrategy" const val ON_STREAMING_END = "onStreamingEnd" const val BEFORE_STREAMING = "beforeStreaming" const val AFTER_STREAMING = "afterStreaming" const val ON_STREAMING = "onStreaming" const val ENABLED = "enabled" const val MODEL = "model" private const val DESCRIPTION = "description" private const val VARIABLES = "variables" private const val FUNCTIONS = "functions" private const val WHEN = "when" private const val SHORTCUT = "shortcut" fun from(file: ShireFile): HobbitHole? { return HobbitHoleParser.parse(file) } fun create(name: String, description: String, interactionType: InteractionType, chatBox: ShireActionLocation): HobbitHole { return HobbitHole(name, description, interactionType, actionLocation = chatBox) } /** * For Code completion , * todo: modify to map with description */ fun keys(): Map { return mapOf( NAME to "The display name of the action", DESCRIPTION to "The tips for the action", WHEN to "The condition to run the action", INTERACTION to "The output of the action can be a file, a string, etc.", ACTION_LOCATION to "The location of the action, can [ShireActionLocation]", SHORTCUT to "The shortcut for the action", STRATEGY_SELECTION to "The strategy to select the element to apply the action", VARIABLES to "The list of variables to apply for the action", FUNCTIONS to "The list of custom functions for the action", ON_STREAMING to "Some actions when receive the streaming text", ON_STREAMING_END to "After Streaming end middleware actions, like Logging, Metrics, CodeVerify, RunCode, ParseCode etc.", BEFORE_STREAMING to "The task/patternAction before streaming", AFTER_STREAMING to "Decision to run the task after streaming, routing to different tasks", ) } fun from(frontMatterMap: MutableMap): HobbitHole { val name = frontMatterMap[NAME]?.value as? String ?: "" val description = frontMatterMap[DESCRIPTION]?.value as? String ?: "" val interaction = frontMatterMap[INTERACTION]?.value as? String ?: "" val actionLocation = frontMatterMap[ACTION_LOCATION]?.value as? String ?: ShireActionLocation.default() val enabled = frontMatterMap[ENABLED]?.value as? Boolean ?: true val model = frontMatterMap[MODEL]?.value as? String val shortcut = (frontMatterMap[SHORTCUT]?.value as? String)?.let { KeyboardShortcut.fromString(it) } val data = mutableMapOf() frontMatterMap.forEach { (key, value) -> if (key !in listOf(NAME, DESCRIPTION, INTERACTION, ACTION_LOCATION)) { data[key] = value } } val selectionStrategy = SelectElementStrategy.fromString( frontMatterMap[STRATEGY_SELECTION]?.value as? String ?: "" ) val endProcessors = frontMatterMap[ON_STREAMING_END]?.let { buildLifecycleProcessors(it) } ?: mutableListOf() val onStreamingProcessors = frontMatterMap[ON_STREAMING]?.let { buildLifecycleProcessors(it) } ?: mutableListOf() val variables = (frontMatterMap[VARIABLES] as? FrontMatterType.OBJECT)?.let { buildVariableTransformations(it.toValue()) } ?: mutableMapOf() val foreignFunctions: MutableMap = (frontMatterMap[FUNCTIONS] as? FrontMatterType.OBJECT)?.let { ForeignFunction.from(it.toValue()) }.orEmpty().associateBy { it.funcName }.toMutableMap() val beforeStreaming: DirectAction? = if (frontMatterMap[BEFORE_STREAMING] != null) { DirectAction.from(frontMatterMap[BEFORE_STREAMING]!!) } else { null } val afterStreaming: TaskRoutes? = (frontMatterMap[AFTER_STREAMING] as? FrontMatterType.ARRAY)?.let { try { TaskRoutes.from(it) } catch (e: Exception) { logger().warn("Error to parse after streaming: $e") null } } val whenCondition = frontMatterMap[WHEN] as? FrontMatterType.EXPRESSION return HobbitHole( name = name, description = description, interaction = InteractionType.from(interaction), actionLocation = ShireActionLocation.from(actionLocation), selectionStrategy = selectionStrategy, variables = variables, foreignFunctions = foreignFunctions, userData = data, when_ = whenCondition, beforeStreaming = beforeStreaming, onStreamingEnd = endProcessors, onStreaming = onStreamingProcessors, afterStreaming = afterStreaming, shortcut = shortcut, enabled = enabled, model = model ) } private fun buildVariableTransformations(variableObject: Map): MutableMap { return variableObject.mapNotNull { (key, value) -> val variable = key.removeSurrounding("\"") PatternAction.from(value)?.let { val pattern = it.pattern.removeSurrounding("/") VariableTransform(variable, pattern, it.patternFuncs, it.isQueryStatement) } }.associateBy { it.variable }.toMutableMap() } private fun buildLifecycleProcessors(item: FrontMatterType): List { val endProcessors: MutableList = mutableListOf() when (item) { is FrontMatterType.ARRAY -> { item.toValue().forEach { matterType -> when (matterType) { is FrontMatterType.EXPRESSION -> { val processorNode = toPostProcessorNode(matterType) endProcessors.add(processorNode) } else -> {} } } } is FrontMatterType.STRING -> { val handleName = item.value as String endProcessors.add(LifecycleProcessorSignature(handleName, emptyList())) } else -> {} } return endProcessors } private fun toPostProcessorNode(expression: FrontMatterType.EXPRESSION): LifecycleProcessorSignature { return when (val child = expression.value) { is MethodCall -> { val handleName = child.objectName.display() val args: List = child.arguments?.map { it.toString() } ?: emptyList() return LifecycleProcessorSignature(handleName, args) } else -> { val handleName = expression.display() LifecycleProcessorSignature(handleName, emptyList()) } } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/ast/hobbit/base/Smials.kt ================================================ package com.phodal.shirelang.compiler.ast.hobbit.base /** * Smials is a type of Hobbit Hole */ interface Smials { } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/ast/patternaction/PatternActionFunc.kt ================================================ package com.phodal.shirelang.compiler.ast.patternaction import com.intellij.openapi.diagnostic.logger import com.phodal.shirelang.compiler.ast.CaseKeyValue import com.phodal.shirelang.compiler.ast.Statement import com.phodal.shirelang.compiler.ast.VariableElement /** * The `PatternActionFunc` is a sealed class in Kotlin that represents a variety of pattern action functions. * Each subclass represents a different function, and each has a unique set of properties relevant to its function. * * @property funcName The name of the function. */ sealed class PatternActionFunc(val type: PatternActionFuncDef) { open val funcName: String = type.funcName /** * Grep subclass for searching with one or more patterns. * * @property patterns The patterns to search for. */ class Grep(vararg val patterns: String) : PatternActionFunc(PatternActionFuncDef.GREP) /** * Find subclass for searching with text * @property text The text to search for. */ class Find(val text: String) : PatternActionFunc(PatternActionFuncDef.FIND) /** * Sed subclass for find and replace operations. * * @property pattern The pattern to search for. * @property replacements The string to replace matches with. * * For example, `sed("foo", "bar")` would replace all instances of "foo" with "bar". */ class Sed(val pattern: String, val replacements: String, val isRegex: Boolean = true) : PatternActionFunc(PatternActionFuncDef.SED) /** * Sort subclass for sorting with one or more arguments. * * @property arguments The arguments to use for sorting. */ class Sort(vararg val arguments: String) : PatternActionFunc(PatternActionFuncDef.SORT) /** * Uniq subclass for removing duplicates based on one or more arguments. * * @property texts The texts to process for uniqueness. */ class Uniq(vararg val texts: String) : PatternActionFunc(PatternActionFuncDef.UNIQ) /** * Head subclass for retrieving the first few lines. * * @property number The number of lines to retrieve from the start. */ class Head(val number: Number) : PatternActionFunc(PatternActionFuncDef.HEAD) /** * Tail subclass for retrieving the last few lines. * * @property number The number of lines to retrieve from the end. */ class Tail(val number: Number) : PatternActionFunc(PatternActionFuncDef.TAIL) /** * Xargs subclass for processing one or more variables. * * @property variables The variables to process. */ class Xargs(vararg val variables: String) : PatternActionFunc(PatternActionFuncDef.XARGS) /** * Print subclass for printing one or more texts. * * @property texts The texts to be printed. */ class Print(vararg val texts: String) : PatternActionFunc(PatternActionFuncDef.PRINT) /** * Cat subclass for concatenating one or more files. * Paths can be absolute or relative to the current working directory. */ class Cat(vararg val paths: String) : PatternActionFunc(PatternActionFuncDef.CAT) /** * Select subclass for selecting one or more elements. */ class From(val variables: List) : PatternActionFunc(PatternActionFuncDef.FROM) /** * Where subclass for filtering elements. */ class Where(val statement: Statement) : PatternActionFunc(PatternActionFuncDef.WHERE) /** * OrderBy subclass for ordering elements. */ class Select(val statements: List) : PatternActionFunc(PatternActionFuncDef.SELECT) /** * Execute a shire script */ class Execute(val filename: String, val variableNames: Array) : PatternActionFunc(PatternActionFuncDef.EXECUTE) /** * Approval Execution */ class ApprovalExecute(val filename: String, val variableNames: Array) : PatternActionFunc(PatternActionFuncDef.APPROVAL_EXECUTE) /** * Use IDE Notify */ class Notify(val message: String) : PatternActionFunc(PatternActionFuncDef.NOTIFY) /** * Case Match */ class CaseMatch(val keyValue: List) : PatternActionFunc(PatternActionFuncDef.CASE_MATCH) /** * Splitting */ class Splitting(val paths: Array) : PatternActionFunc(PatternActionFuncDef.SPLITTING) /** * Embedding text */ class Embedding(val entries: Array) : PatternActionFunc(PatternActionFuncDef.EMBEDDING) /** * Searching text */ class Searching(val text: String, val threshold: Double = 0.5) : PatternActionFunc(PatternActionFuncDef.SEARCHING) /** * Caching semantic */ class Caching(val text: String) : PatternActionFunc(PatternActionFuncDef.CACHING) /** * Reranking the result */ class Reranking(val strategy: String) : PatternActionFunc(PatternActionFuncDef.RERANKING) /** * The Redact class is designed for handling sensitive data by applying a specified redaction strategy. * * @param strategy The redaction strategy to be used. This string defines how the sensitive data will be handled or obscured. */ class Redact(val strategy: String) : PatternActionFunc(PatternActionFuncDef.REDACT) /** * The Crawl function is used to crawl a list of urls, get markdown from html and save it to a file. * * @param urls The urls to crawl. */ class Crawl(vararg val urls: String) : PatternActionFunc(PatternActionFuncDef.CRAWL) /** * The capture function used to capture file by NodeType * * @param fileName The file name to save the capture to. * @param nodeType The node type to capture. */ class Capture(val fileName: String, val nodeType: String) : PatternActionFunc(PatternActionFuncDef.CAPTURE) /** * The thread function will run the function in a new thread * * @param fileName The file name to run */ class Thread(val fileName: String, val variableNames: Array) : PatternActionFunc(PatternActionFuncDef.THREAD) /** * The jsonpath function will parse the json and get the value by jsonpath */ class JsonPath(val obj: String?, val path: String, val sseMode: Boolean = false) : PatternActionFunc(PatternActionFuncDef.JSONPATH) class Destroy : PatternActionFunc(PatternActionFuncDef.DESTROY) class Batch(val fileName: String, val inputs: List, val batchSize: Int = 1) : PatternActionFunc(PatternActionFuncDef.BATCH) class Tokenizer(var text: String, val tokType: String) : PatternActionFunc(PatternActionFuncDef.TOKENIZER) /** * Line Number */ class LineNo(var text: String) : PatternActionFunc(PatternActionFuncDef.LINE_NO) /** * User Custom Functions */ class ToolchainFunction(override val funcName: String, val args: List) : PatternActionFunc(PatternActionFuncDef.TOOLCHAIN_FUNCTION) { override fun toString(): String { return "$funcName(${args.joinToString(", ")})" } } companion object { private val logger = logger() fun findDocByName(funcName: String?): String? { val actionFuncType = PatternActionFuncDef.entries.find { it.funcName == funcName } ?: return null return """ | ${actionFuncType.description} | | Example: | ${actionFuncType.example} """.trimMargin() } fun all(): List { return PatternActionFuncDef.entries } fun from(funcName: String, args: List): PatternActionFunc? { return when (PatternActionFuncDef.entries.find { it.funcName == funcName }) { PatternActionFuncDef.GREP -> { if (args.isEmpty()) { logger.error("PatternActionFun,`grep` func requires at least 1 argument") return null } Grep(*args.toTypedArray()) } PatternActionFuncDef.SORT -> Sort(*args.toTypedArray()) PatternActionFuncDef.FIND -> { if (args.isEmpty()) { logger.error("PatternActionFun,`find` func requires at least 1 argument") return null } Find(args[0]) } PatternActionFuncDef.SED -> { if (args.size < 2) { logger.error("PatternActionFun,`sed` func requires at least 2 arguments") return null } if (args[0].startsWith("/") && args[0].endsWith("/")) { Sed(args[0], args[1], true) } else { Sed(args[0], args[1]) } } PatternActionFuncDef.XARGS -> Xargs(*args.toTypedArray()) PatternActionFuncDef.UNIQ -> Uniq(*args.toTypedArray()) PatternActionFuncDef.HEAD -> { if (args.isEmpty()) { Head(10) } else { Head(args[0].toInt()) } } PatternActionFuncDef.TAIL -> { if (args.isEmpty()) { Tail(10) } else { Tail(args[0].toInt()) } } PatternActionFuncDef.PRINT -> Print(*args.toTypedArray()) PatternActionFuncDef.CAT -> Cat(*args.toTypedArray()) PatternActionFuncDef.EXECUTE -> { val first = args.firstOrNull() ?: "" val rest = args.drop(1).toTypedArray() Execute(first, rest) } PatternActionFuncDef.APPROVAL_EXECUTE -> { val first = args.firstOrNull() ?: "" val rest = args.drop(1).toTypedArray() ApprovalExecute(first, rest) } PatternActionFuncDef.NOTIFY -> { val first = args.firstOrNull() ?: "" Notify(first) } PatternActionFuncDef.EMBEDDING -> Embedding(args.toTypedArray()) PatternActionFuncDef.SPLITTING -> Splitting(args.toTypedArray()) PatternActionFuncDef.SEARCHING -> Searching( args[0], args.getOrNull(1)?.toDouble() ?: 0.5 ) PatternActionFuncDef.RERANKING -> { val first = args.firstOrNull() ?: "default" Reranking(first) } PatternActionFuncDef.CACHING -> Caching(args[0]) PatternActionFuncDef.REDACT -> { val first = args.firstOrNull() ?: "default" Redact(first) } PatternActionFuncDef.CRAWL -> { val urls: List = args.filter { it.trim().isNotEmpty() } Crawl(*urls.toTypedArray()) } PatternActionFuncDef.CAPTURE -> { if (args.size < 2) { logger.error("PatternActionFun,`capture` func requires at least 2 arguments") return null } Capture(args[0], args[1]) } PatternActionFuncDef.THREAD -> { if (args.isEmpty()) { logger.error("PatternActionFun,`thread` func requires at least 1 argument") return null } val rest = args.drop(1).toTypedArray() Thread(args.first(), rest) } PatternActionFuncDef.JSONPATH -> { if (args.isEmpty()) { logger.error("PatternActionFun,`jsonpath` func requires at least 1 argument") return null } if (args.size < 2) { JsonPath(null, args[0], false) } else { when (args[1]) { "true" -> JsonPath(null, args[0], true) else -> JsonPath(args[0], args[1]) } } } PatternActionFuncDef.FROM, PatternActionFuncDef.WHERE, PatternActionFuncDef.SELECT, PatternActionFuncDef.CASE_MATCH, -> { ToolchainFunction(funcName, args) } PatternActionFuncDef.BATCH -> { Batch(args[0], args.drop(1)) } PatternActionFuncDef.DESTROY -> { Destroy() } PatternActionFuncDef.TOOLCHAIN_FUNCTION -> ToolchainFunction(funcName, args) PatternActionFuncDef.TOKENIZER -> { if (args.isEmpty()) { logger.error("PatternActionFun,`tokenizer` func requires at least 1 argument") return null } Tokenizer(args[0], args.getOrNull(1) ?: "word") } PatternActionFuncDef.LINE_NO -> { if (args.isEmpty()) { logger.error("PatternActionFun,`lineNo` func requires at least 1 argument") return null } LineNo(args[0]) } else -> { ToolchainFunction(funcName, args) } } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/ast/patternaction/PatternActionFuncDef.kt ================================================ package com.phodal.shirelang.compiler.ast.patternaction /** * `PatternActionFuncType` was for show documentation when user hovers on the function. */ enum class PatternActionFuncDef(val funcName: String, val description: String, val example: String) { GREP( "grep", "`grep` function searches any given input files, selecting lines that match one or more patterns.", """ | ```shire | --- | variables: | "controllers": /.*.java/ { cat | grep("class\s+([a-zA-Z]*Controller)") } | --- | ``` """.trimMargin() ), FIND( "find", "`find` will find the first occurrence of a pattern in a file.", """ | ```shire | --- | variables: | "story": /any/ { find("epic") } | --- | ``` """.trimMargin() ), SED( "sed", "`sed` will replace text in a file.", """ | ```shire | --- | variables: | "var2": /.*ple.shire/ { cat | find("openai") | sed("(?i)\b(sk-[a-zA-Z0-9]{20}T3BlbkFJ[a-zA-Z0-9]{20})(?:['|\"|\n|\r|\s|\x60|;]|${'$'})", "sk-***") } | --- | ``` """.trimMargin() ), PRINT( "print", "`print` will print text or last output.", """ | ```shire | --- | variables: | "story": /BlogController\.java/ { print } | --- | ``` | | Text content Example: | | ```shire | --- | variables: | "story": /any/ { print("hello world") } | --- | ``` """.trimMargin() ), CAT( "cat", "`cat` will concatenate one or more files.", """ | ```shire | --- | variables: | "story": /BlogController\.java/ { cat } // Paths can be absolute or relative to the current working directory. | --- | ``` | | File path Example: | | ```shire | --- | variables: | "story": /any/ { cat("file.txt") } | --- | ``` """.trimMargin() ), EXECUTE( "execute", "`execute` will execute a new script,like `shell`, `bash`, `python`, `ruby` and `javascript`.", """ | ```shire | --- | name: "Search" | afterStreaming: { execute("search.shire") } | --- | ``` | """.trimMargin() ), APPROVAL_EXECUTE( "approvalExecute", "`approvalExecute` will show a dialog to confirm is execute to next job.", """ | ```shire | --- | name: "Search" | afterStreaming: { approvalExecute("search.shire") } | --- | ``` | """.trimMargin() ), NOTIFY( "notify", "`notify` will use IDEA's notification system to display a message.", """ | ```shire | --- | name: "Search" | afterStreaming: { notify("Failed to Generate JSON") } | --- | ``` | """.trimMargin() ), CASE_MATCH("switch", "TODO, not implemented yet.", ""), SPLITTING( "splitting", "`splitting` (RAG function) will split the file into chunks.", """ | ```shire | --- | variables: | "testTemplate": /.*.kt/ { caching("disk") | splitting | embedding } | --- | ``` | | Support format: code, txt, pdf, html, doc, xls, ppt, md. """.trimMargin() ), EMBEDDING( "embedding", "`embedding` (RAG function) will embedding text.", """ | ```shire | --- | variables: | "testTemplate": /.*.kt/ { caching("disk") | splitting | embedding } | --- | ``` """.trimMargin() ), SEARCHING( "searching", " `searching` (RAG function) will search embedding text.", """ | ```shire | --- | variables: | "story": /any/ { caching("disk") | splitting | embedding | searching("epic") } | --- | ``` | with threshold: | | ```shire | --- | variables: | "story": /any/ { caching("disk") | splitting | embedding | searching("epic", 0.5) } | --- | ``` | """.trimMargin() ), CACHING( "caching", """`caching` (RAG function) will cache the semantic. support "disk" and "memory", default is "memory".""", """ | ```shire | --- | variables: | "testTemplate": /.*.kt/ { caching("disk") } | "story": /any/ { caching("memory") } | --- | ``` | """.trimMargin() ), RERANKING( "reranking", "`reranking` (RAG function) will rerank the result. current only support \"Lost In Middle\" pattern", """ | ```shire | --- | variables: | "story": /any/ { caching("disk") | splitting | embedding | reranking } | --- | ``` | """.trimMargin() ), REDACT( "redact", "`redact` will handling sensitive data by applying a specified redaction strategy.", """ | ```shire | --- | variables: | "phoneNumber": "086-1234567890" | "var2": /.*ple.shire/ { cat | redact } | --- | ``` """.trimMargin() ), CRAWL( "crawl", "`crawl` will crawl a list of urls, get markdown from html and save it to a file.", """ | ```shire | --- | variables: | "websites": /*\.md/ { capture("docs/crawlSample.md", "link") | crawl() | thread("summary.shire") } | "confluence": { thread("confluence.bash", param1, param2) } | "pythonNode.js": { thread("python.py", param1, param2) } | --- | ``` | """.trimMargin() ), CAPTURE( "capture", "`capture` function used to capture url link by NodeType, support Markdown only for now.", """ | ```shire | --- | variables: | "websites": /*\.md/ { capture("docs/crawlSample.md", "link") | crawl() | thread("summary.shire") } | "confluence": { thread("confluence.bash", param1, param2) } | "pythonNode.js": { thread("python.py", param1, param2) } | --- | ``` | """.trimMargin() ), THREAD( "thread", "`thread` will run the function in a new thread", """ | ```shire | --- | variables: | "story": /any/ { thread(".shire/shell/dify-epic-story.curl.sh") | jsonpath("${'$'}.answer", true) } | --- | ``` | | With shire file Example: | | ```shire | --- | variables: | "story": /any/ { thread("dify-epic-story.shire") } | --- | ``` """.trimMargin() ), JSONPATH( "jsonpath", "`jsonpath` function will parse the json and get the value by jsonpath.", """ | ```shire | --- | variables: | "api": /sampl.sh/ { thread(".shire/toolchain/bigmodel.curl.sh") | jsonpath("${'$'}.choices[0].message.content") } | --- | ``` | | With SSE Example: | ```shire | --- | variables: | "story": /any/ { thread(".shire/shell/dify-epic-story.curl.sh") | jsonpath("${'$'}.answer", true) } | --- | ``` | | """.trimMargin() ), SORT("sort", "`sort` will sorting with one or more arguments.", ""), UNIQ("uniq", "`uniq` will removing duplicates based on one or more arguments.", ""), HEAD( "head", "`head` will retrieving the first few lines.", """ | ```shire | --- | variables: | "controllers": /.*.java/ { find("Controller") | grep("src/main/java/.*") | head(1) | cat } | --- | ``` """.trimMargin() ), TAIL("tail", "`tail` will retrieving the last few lines.", ""), XARGS("xargs", "`xargs` will processing one or more variables.", ""), FROM("from", "`select` (ShireQL) will selecting one or more elements.", ""), WHERE("where", "`where` (ShireQL) will filtering elements.", ""), SELECT("select", "`select` (ShireQL) will select element.", ""), BATCH( "batch", "`batch` will execute a batch Shire script.", """ | ```shire | --- | name: "Generate Swagger Doc" | variables: | "controllers": /BlogController.java/ { cat } | "gen-swagger": /any/ { batch("controller-with-swagger.shire", ${"$"}controllers, 2) } | --- | | ``` """.trimMargin() ), DESTROY("destroy", "Destroy the current task.", ""), TOOLCHAIN_FUNCTION( "toolchain", "Toolchain functions are defined by the different IDEA plugins. Supports JetBrains' plugin: Database, Shell Script plugin etc, Community plugin: SonarLint etc.", """ | | | Database Plugin Example: | | ```shire | --- | variables: | "relatedTableInfo": /./ { column("user", "post", "tag") } | --- | ``` | | For more goto: [https://shire.phodal.com/shire/shire-toolchain-function](https://shire.phodal.com/shire/shire-toolchain-function) """.trimMargin() ), TOKENIZER( "tokenizer", "`tokenizer` tokenizes text using traditional NLP methods. Support type for : " + "`word`, `naming`, `stopwords`,`jieba`.", """ | ```shire | --- | variables: | "controllers": /.*.java/ { cat } | "tokens": /any/ { tokenizer(${'$'}controllers, "word") } | --- | ``` | | Output Example: package, com, phodal, shirelang, controller, import, org, springframework, web, bind, ... | """.trimMargin() ), LINE_NO( "lineNo", "`lineNo` will add line number to the text.", """ | ```shire | --- | variables: | "controllers": /.*.java/ { cat | lineNo } | --- | ``` | | Output Example: 1: package com.phodal.shirelang.controller; 2: import org.springframework.web.bind.annotation.GetMapping; 3: import org.springframework.web.bind.annotation.RestController; 4: import org.springframework.web.bind.annotation.RequestMapping; 5: import org.springframework.web.bind.annotation.RequestParam; | """.trimMargin() ) ; override fun toString(): String = description } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/ast/patternaction/PatternProcessor.kt ================================================ package com.phodal.shirelang.compiler.ast.patternaction interface PatternProcessor { val type: PatternActionFuncDef } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/ast/patternaction/VariableTransform.kt ================================================ package com.phodal.shirelang.compiler.ast.patternaction /** * The `PatternActionTransform` class is a utility class in Kotlin that is used to perform various actions based on a provided pattern and action. * The class takes a pattern of type `String` and an action of type `PatternAction` as parameters. * * @property variable This property represents the pattern to be used for the transformation. * @property patternActionFuncs This property represents the action to be performed on the input. * * @constructor This constructor creates a new instance of `PatternActionTransform` with the specified pattern and action. * * The `execute` function is a member function of this class which is used to perform the action on the input and return the result as a `String`. * * @param variable This parameter represents the input on which the action is to be performed. * @return The result of the action performed on the input as a `String`. * * The `execute` function iterates over each action in `patternActionFuncs` and performs the corresponding action on the input. * The result of each action is stored in the `result` variable which is initially set to the input. * The type of action to be performed is determined using a `when` statement that checks the type of each action. * The result of the `execute` function is the final value of the `result` variable converted to a `String`. * * @see PatternAction */ class VariableTransform( val variable: String, val pattern: String, val patternActionFuncs: List, val isQueryStatement: Boolean = false ) { } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/FunctionStatementProcessor.kt ================================================ package com.phodal.shirelang.compiler.execute import com.intellij.openapi.application.runReadAction import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.nfeld.jsonpathkt.JsonPath import com.nfeld.jsonpathkt.extension.read import com.phodal.shirecore.variable.vcs.ShireGitCommit import com.phodal.shirelang.compiler.ast.* import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole import com.phodal.shirelang.compiler.execute.shireql.ShireDateSchema import com.phodal.shirelang.compiler.execute.shireql.ShireQLSchema import com.phodal.shirelang.compiler.execute.variable.ShireQLVariableBuilder import com.phodal.shirelang.compiler.execute.variable.VariableContainerManager import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFunc import com.phodal.shirelang.compiler.ast.patternaction.VariableTransform import kotlinx.coroutines.runBlocking /** * The `FunctionStatementProcessor` class is responsible for processing function statements within a project context. * It extends the `PatternFuncProcessor` class, which is part of a larger framework likely dealing with pattern matching and processing within a domain-specific language or a scripting environment. * * This class operates on statements that can be comparisons, processor invocations, or method calls, and it manages a variable table to keep track of variable values during execution. * * @property myProject The project in which the processing occurs, extending the functionality of the base class `PatternFuncProcessor`. * @property hole The hobbit hole, which seems to be a metaphorical representation of a context or a scope, also extending the base class functionality. * * ### Methods: * * This class uses the Kotlin `runBlocking` coroutine scope to handle asynchronous operations and may throw an `IllegalArgumentException` for unknown types during evaluation. */ open class FunctionStatementProcessor(override val myProject: Project, override val hole: HobbitHole) : PatternFuncProcessor(myProject, hole) { open fun execute(transform: VariableTransform): String { val fromStmt = transform.patternActionFuncs.find { it is PatternActionFunc.From } as PatternActionFunc.From val selectStmt = transform.patternActionFuncs.find { it is PatternActionFunc.Select } as PatternActionFunc.Select val whereStmt = transform.patternActionFuncs.find { it is PatternActionFunc.Where } as PatternActionFunc.Where val variableElementsMap: Map> = runReadAction { ShireQLVariableBuilder(myProject, hole).buildVariables(fromStmt) } val valuedType = evalValueByElementStmt(variableElementsMap, whereStmt.statement) val handledElements = processStatement(whereStmt.statement, variableElementsMap, valuedType) val selectElements = processSelect(selectStmt, handledElements) return selectElements.joinToString("\n") } fun processSelect(selectStmt: PatternActionFunc.Select, handledElements: List): List { return selectStmt.statements.flatMap { processSelectStatement(it, handledElements) } } private fun processSelectStatement(statement: Statement, handledElements: List): List { val result = mutableListOf() handledElements.forEach { element -> when (element) { is PsiElement -> { when (statement) { is Value -> { result.add(statement.display()) } is MethodCall -> { invokeMethodOrField(statement, element)?.let { result.add(it.toString()) } } } } is ShireGitCommit -> { when (statement) { is Value -> { result.add(statement.display()) } is MethodCall -> { invokeMethodOrField(statement, element)?.let { result.add(it.toString()) } } } } is ShireDateSchema -> { when (statement) { is Value -> { invokeMethod(element, null)?.let { result.add(it.toString()) } } is MethodCall -> { val methodName = statement.methodName.display() val args = statement.arguments invokeMethod(element, methodName, args)?.let { result.add(it.toString()) } } } } else -> { logger().error("unknown element: $element") } } } return result } fun execute(statement: Statement, variableTable: MutableMap): Any? = runBlocking { return@runBlocking when (statement) { is Comparison -> { executeComparison(statement, variableTable) } is Processor -> { execute(statement.processors, variableTable) } is MethodCall -> { invokeLocalMethodCall(statement, variableTable) } is Value -> { statement.value } else -> { logger().error("unknown stmt: $statement expr: ${statement.display()}") null } } } private fun invokeLocalMethodCall(statement: MethodCall, variableTable: MutableMap): Any? { val objName = statement.objectName.display() val methodName = statement.methodName.display() val methodArgs = statement.arguments if (methodName == "") { val firstArg = methodArgs?.get(0) when (objName) { "jsonpath" -> { val output = (variableTable["output"] ?: "").toString() val arg: String = when (firstArg) { is FrontMatterType.STRING -> (methodArgs[0] as FrontMatterType.STRING).value.toString() else -> firstArg.toString() } val string: String = try { JsonPath.parse(output)?.read(arg).toString() } catch (e: Exception) { logger().warn("jsonpath error: $e") return null } return string } "print" -> { println(firstArg) } else -> { logger().warn("unknown method: $objName") } } } return null } suspend fun execute(processors: List, variableTable: MutableMap): Any? { val input: Any = variableTable["output"] ?: "" var result: Any = variableTable["output"] ?: "" var lastOutput: Any? = result processors.forEach { action -> result = patternFunctionExecute(action, result, input, variableTable) if (action.funcName == "execute") { if (lastOutput != null) { result = lastOutput as Any } } lastOutput = result variableTable["output"] = result } return result.toString() } private fun FunctionStatementProcessor.executeComparison( statement: Comparison, value: Any, ): Boolean { val operator = statement.operator val left = evaluate(statement.left, value) val right = evaluate(statement.right, value) return when (operator.type) { OperatorType.Equal -> left == right OperatorType.And -> left == right OperatorType.NotEqual -> left != right OperatorType.Or -> left == true || right == true OperatorType.GreaterEqual -> { if (left == null || right == null) { false } else { left as Comparable >= right as Comparable } } OperatorType.GreaterThan -> { if (left == null || right == null) { false } else { left as Comparable > right as Comparable } } OperatorType.LessEqual -> { if (left == null || right == null) { false } else { left as Comparable <= right as Comparable } } OperatorType.LessThan -> { if (left == null || right == null) { false } else { left as Comparable < right as Comparable } } else -> { logger().warn("unknown operator: $operator") false } } } /// todo: fix this for multiple variables private fun processStatement( statement: Statement, variableElementsMap: Map>, valuedType: VariableContainerManager, ): List { val result = mutableListOf() variableElementsMap.forEach { (variableName, elements) -> elements.forEach { element -> when (statement) { is Comparison -> { val operator = statement.operator /// for commit.authorDate <= date.now() /// if we use element (Date) for commit.authorDate, will be null, should use element (ShireGitCommit) val left = valuedType.getValue(element, statement.left) ?: evaluate(statement.right, element) val right = valuedType.getValue(element, statement.right) ?: evaluate(statement.right, element) if (left == null) { logger().warn("left is null: ${statement.left.display()}") return@forEach } if (right == null) { logger().warn("right is null: ${statement.right.display()}") return@forEach } when (operator.type) { OperatorType.Equal -> if (left == right) result.add(element) OperatorType.And -> if (left == right) result.add(element) OperatorType.NotEqual -> if (left != right) result.add(element) OperatorType.Or -> if (left == true || right == true) result.add(element) OperatorType.GreaterEqual -> { if (left as Comparable >= right as Comparable) { result.add(element) } } OperatorType.GreaterThan -> { if (left as Comparable > right as Comparable) { result.add(element) } } OperatorType.LessEqual -> { if (left as Comparable <= right as Comparable) { result.add(element) } } OperatorType.LessThan -> { if (left as Comparable < right as Comparable) { result.add(element) } } else -> { logger().warn("unknown operator: $operator") } } } is MethodCall -> { when (val output = invokeMethodOrField(statement, element)) { is Collection<*> -> { output.forEach { if (it is Any) { result.add(it) } } } is Any -> { result.add(output) } } } is LogicalExpression -> { val left = processStatement(statement.left, variableElementsMap, valuedType) val right = processStatement(statement.right, variableElementsMap, valuedType) when (statement.operator) { OperatorType.And -> { if (left.isNotEmpty() && right.isNotEmpty()) { result.add(element) } } OperatorType.Or -> { if (left.isNotEmpty() || right.isNotEmpty()) { result.add(element) } } else -> { logger().warn("unknown operator: ${statement.operator}") } } } else -> { logger().warn("unknown statement: ${statement.display()}") } } } } return result } /// a dirty implementation for multiple variables private fun evalValueByElementStmt( variableElementsMap: Map>, statement: Statement, ): VariableContainerManager { val typeValued = VariableContainerManager() variableElementsMap.forEach { (variableName, elements) -> elements.forEach { element -> when (statement) { is Comparison -> { evaluateComparison(element, typeValued, statement.left, statement.right) } is LogicalExpression -> { val left = evalValueByElementStmt(variableElementsMap, statement.left) val right = evalValueByElementStmt(variableElementsMap, statement.right) when (statement.operator) { OperatorType.And -> { if (left.isNotEmpty() && right.isNotEmpty()) { left.variables.forEach { evaluatorEntry -> evaluatorEntry.value.valued.forEach { (key, value) -> if (value != null) { typeValued.putValue(element, key, value) } } } right.variables.forEach { evaluatorEntry -> evaluatorEntry.value.valued.forEach { (key, value) -> if (value != null) { typeValued.putValue(element, key, value) } } } } } OperatorType.Or -> { if (left.isNotEmpty() || right.isNotEmpty()) { left.variables.forEach { evaluatorEntry -> evaluatorEntry.value.valued.forEach { (key, value) -> if (value != null) { typeValued.putValue(element, key, value) } } } right.variables.forEach { evaluatorEntry -> evaluatorEntry.value.valued.forEach { (key, value) -> if (value != null) { typeValued.putValue(element, key, value) } } } } } else -> { logger().warn("unknown operator: ${statement.operator}") } } } else -> { logger().warn("unknown statement: ${statement.display()}") } } } } return typeValued } private fun evaluateComparison( element: Any, typeValued: VariableContainerManager, leftStmt: FrontMatterType, rightStmt: FrontMatterType, ) { if (element is ShireQLSchema) { val left = evaluate(leftStmt, element) if (left != null) { typeValued.putValue(element, leftStmt, left) } else { val right = evaluate(rightStmt, element) if (right != null) { typeValued.putValue(element, rightStmt, right) } } return } val left = evaluate(leftStmt, element) if (left != null) { typeValued.putValue(element, leftStmt, left) } else { val right = evaluate(rightStmt, element) if (right != null) { typeValued.putValue(element, rightStmt, right) } } } fun evaluate(type: FrontMatterType, element: T): Any? { return when (type) { is FrontMatterType.ARRAY -> { (type.value as List).map { evaluate(it, element) } } is FrontMatterType.EXPRESSION -> { evalExpression(type, element) } is FrontMatterType.BOOLEAN, is FrontMatterType.DATE, is FrontMatterType.IDENTIFIER, is FrontMatterType.STRING, -> { type.value } is FrontMatterType.NUMBER -> { (type.value as Int).toLong() } else -> { throw IllegalArgumentException("unknown type: $type") } } } open fun evalExpression(type: FrontMatterType, element: T): Any? { when (type.value) { is MethodCall -> { return invokeMethodOrField(type.value, element) } else -> { throw IllegalArgumentException("unknown type: $type") } } } open fun invokeMethodOrField(methodCall: MethodCall, element: T): Any? { val objName = methodCall.objectName.display() when (element) { is Map<*, *> -> { val variable = element[objName] as? String ?: return null return methodCall.evaluateExpression(methodCall.methodName, listOf(), variable) } else -> return null } } open fun invokeMethod(element: ShireDateSchema, methodName: String?, args: List? = null): Any? { val allMethods = element.javaClass.methods val method = allMethods.find { it.name == methodName } if (method != null) { return method.invoke(element) } return element.toString() } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/PatternActionProcessor.kt ================================================ package com.phodal.shirelang.compiler.execute import com.intellij.openapi.project.Project import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole import com.phodal.shirelang.compiler.ast.patternaction.VariableTransform import com.phodal.shirelang.compiler.execute.shireql.ShireQLProcessor import com.phodal.shirelang.debugger.snapshot.VariableSnapshotRecorder public class PatternActionProcessor( override val myProject: Project, override val hole: HobbitHole, private val variableMap: MutableMap ) : PatternFuncProcessor(myProject, hole) { private val record: VariableSnapshotRecorder = VariableSnapshotRecorder.getInstance(myProject) /** * We should execute the variable function with the given key and pipeline functions. * * Each function output will be the input of the next function. */ suspend fun execute(actionTransform: VariableTransform): String { if (actionTransform.patternActionFuncs.isEmpty()) { return "" } if (actionTransform.isQueryStatement) { return ShireQLProcessor(myProject, hole).execute(actionTransform) } var input: Any = "" if (actionTransform.pattern.isNotBlank() && actionTransform.pattern != "any" && actionTransform.pattern != "null") { input = com.phodal.shirelang.compiler.execute.searcher.PatternSearcher.findFilesByRegex(myProject, actionTransform.pattern) .map { it.path } .toTypedArray() } return execute(actionTransform, input) } /** * This method is used to execute a series of transformations on the input based on the provided PatternActionTransform. * The transformations are applied in the order they are defined in the PatternActionTransform. * The input can be of any type, but the transformations are applied as if the input is a String. * If the input is not a String, it will be converted to a String before applying the transformations. * The result of each transformation is used as the input for the next transformation. * If the transformation is a Cat, the executeCatFunc method is called with the action and the original input. * The result of the last transformation is returned as a String. * * @param transform The PatternActionTransform that defines the transformations to be applied. * @param input The input on which the transformations are to be applied. * @return The result of applying the transformations to the input as a String. */ suspend fun execute(transform: VariableTransform, input: Any): String { record.addSnapshot(transform.variable, input) var result = input val data = PostProcessorContext.getData() if (data?.lastTaskOutput != null && data.lastTaskOutput != "null") { if (variableMap["output"] == null) { variableMap["output"] = data.lastTaskOutput } } transform.patternActionFuncs.forEach { action -> result = patternFunctionExecute(action, result, input, variableMap) record.addSnapshot(transform.variable, result, action.funcName, result) } variableMap[transform.variable] = result return result.toString() } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/PatternFuncProcessor.kt ================================================ package com.phodal.shirelang.compiler.execute import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.findFile import com.intellij.openapi.vfs.readText import com.phodal.shirecore.ShirelangNotifications import com.phodal.shirelang.compiler.execute.processor.RedactProcessor import com.phodal.shirecore.provider.function.ToolchainFunctionProvider import com.phodal.shirecore.search.function.ScoredText import com.phodal.shirecore.search.function.SemanticService import com.phodal.shirecore.workerThread import com.phodal.shirelang.ShireBundle import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole import com.phodal.shirelang.compiler.ast.FrontMatterType import com.phodal.shirelang.compiler.ast.Statement import com.phodal.shirelang.compiler.execute.processor.JsonPathProcessor import com.phodal.shirelang.compiler.execute.processor.* import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFunc import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import java.io.File open class PatternFuncProcessor(open val myProject: Project, open val hole: HobbitHole) { private val logger = logger() /** * This function `patternFunctionExecute` is used to execute a specific action based on the type of `PatternActionFunc` provided. * It takes three parameters: `action`, `input`, and `lastResult`. * * @param action This is an instance of `PatternActionFunc` which is a sealed class. The function behavior changes based on the type of `PatternActionFunc`. * @param input This is a generic parameter which can be of any type. It is used in the `PatternActionFunc.Cat` case. * @param lastResult This is a generic parameter which can be of any type. It is used in all cases except `PatternActionFunc.Prompt`, `PatternActionFunc.Cat`, `PatternActionFunc.`Print`` and `PatternActionFunc.Xargs`. * * @return The return type is `Any`. The actual return type depends on the type of `PatternActionFunc`. For example, if `PatternActionFunc` is `Prompt`, it returns a `String`. If `PatternActionFunc` is `Grep`, it returns a `String` joined by "\n" from an `Array` or `String` that contains the specified patterns. If `PatternActionFunc` is `Sed`, it returns a `String` joined by "\n" from an `Array` or `String` where the specified pattern has been replaced. If `PatternActionFunc` is `Sort`, it returns a sorted `String` joined by "\n" from an `Array` or `String`. If `PatternActionFunc` is `Uniq`, it returns a `String` joined by "\n" from an `Array` or `String` with distinct elements. If `PatternActionFunc` is `Head`, it returns a `String` joined by "\n" from the first 'n' elements of an `Array` or `String`. If `PatternActionFunc` is `Tail`, it returns a `String` joined by "\n" from the last 'n' elements of an `Array` or `String`. If `PatternActionFunc` is `Cat`, it executes the `executeCatFunc` function. If `PatternActionFunc` is `Print`, it returns a `String` joined by "\n" from the `texts` property of `Print`. If `PatternActionFunc` is `Xargs`, it returns the `variables` property of `Xargs`. If `PatternActionFunc` is `UserCustom`, it logs an error message. If `PatternActionFunc` is of an unknown type, it logs an error message and returns an empty `String`. */ open suspend fun patternFunctionExecute( action: PatternActionFunc, lastResult: Any, input: Any, variableTable: MutableMap = mutableMapOf(), ): Any { val semanticService = myProject.getService(SemanticService::class.java) return when (action) { is PatternActionFunc.Find -> { when (lastResult) { is Array<*> -> { (lastResult as Array) .filter { line -> line.contains(action.text) } .toTypedArray() } else -> { (lastResult as String).split("\n") .filter { line -> line.contains(action.text) } .joinToString("\n") } } } is PatternActionFunc.Grep -> { val regexs = action.patterns.map { it.toRegex() } when (lastResult) { is Array<*> -> { val inputArray = (lastResult as Array) val result = regexs.map { regex -> inputArray.map { line -> regex.findAll(line) .map { if (it.groupValues.size > 1) { it.groupValues[1] } else { it.groupValues[0] } }.toList() }.flatten() }.flatten() result.toTypedArray() } is String -> { val result = regexs.map { regex -> regex.findAll(lastResult) .map { if (it.groupValues.size > 1) { it.groupValues[1] } else { it.groupValues[0] } }.toList() }.flatten().joinToString("\n") result } else -> { logger.error("Unknown pattern input for ${action.funcName}, lastResult: $lastResult") "" } } } is PatternActionFunc.Sed -> { when (lastResult) { is Array<*> -> { (lastResult as Array).joinToString("\n") { line -> line.replace( action.pattern.toRegex(), action.replacements ) } } else -> { (lastResult as String).split("\n").joinToString("\n") { line -> line.replace( action.pattern.toRegex(), action.replacements ) } } } } is PatternActionFunc.Sort -> { when (lastResult) { is Array<*> -> { (lastResult as Array).sorted() } else -> { (lastResult as String).split("\n").sorted().joinToString("\n") } } } is PatternActionFunc.Uniq -> { when (lastResult) { is Array<*> -> { (lastResult as Array).distinct() } else -> { (lastResult as String).split("\n").distinct().joinToString("\n") } } } is PatternActionFunc.Head -> { when (lastResult) { is Array<*> -> { (lastResult as Array).take(action.number.toInt()) } else -> { (lastResult as String).split("\n").take(action.number.toInt()).joinToString("\n") } } } is PatternActionFunc.Tail -> { when (lastResult) { is Array<*> -> { (lastResult as Array).takeLast(action.number.toInt()) } else -> { (lastResult as String).split("\n").takeLast(action.number.toInt()).joinToString("\n") } } } is PatternActionFunc.Cat -> { val path: Array = action.paths.map { it.fillVariable(variableTable) }.toTypedArray() cat(path, lastResult) } is PatternActionFunc.Print -> { if (action.texts.isEmpty()) { return when (lastResult) { is Array<*> -> { (lastResult as Array).joinToString("\n") } is List<*> -> { (lastResult as List).joinToString("\n") } else -> { lastResult.toString() } } } action.texts.joinToString("\n") { it.fillVariable(variableTable) } } is PatternActionFunc.Xargs -> { action.variables } is PatternActionFunc.ToolchainFunction -> { /// maybe User custom functions val args: MutableList = action.args.toMutableList() /// add lastResult at args first when (lastResult) { is String -> { args.add(lastResult.fillVariable(variableTable)) } is List<*> -> { if (lastResult.isNotEmpty()) { args.add(lastResult) } } is Array<*> -> { if (lastResult.isNotEmpty()) { args.add(lastResult) } } else -> { args.add(lastResult) } } val result = args.map { when (it) { is String -> it.fillVariable(variableTable) else -> it } } if (hole.foreignFunctions.containsKey(action.funcName)) { val func = hole.foreignFunctions[action.funcName]!! return ForeignFunctionProcessor.execute(myProject, action.funcName, result, variableTable, func) } ToolchainFunctionProvider.provide(myProject, action.funcName) ?.execute(myProject, action.funcName, result, variableTable) ?: logger.error(ShireBundle.message("shire.toolchain.function.not.found", action.funcName)) } is PatternActionFunc.Notify -> { // action.message is empty get lastResult val message = action.message.ifEmpty { lastResult.toString() } ShirelangNotifications.info(myProject, message) // return last result for next step lastResult } is PatternActionFunc.From, is PatternActionFunc.Select, is PatternActionFunc.Where, -> { logger.error("Unknown pattern processor type: ${action.funcName}") } is PatternActionFunc.CaseMatch -> { val actions = evaluateCase(action, input) ?: return "" FunctionStatementProcessor(myProject, hole) .execute(actions.value as Statement, mutableMapOf("output" to parseInput(input))) .toString() } is PatternActionFunc.Embedding -> { var result: List = mutableListOf() if (lastResult is List<*>) { result = if (lastResult.isNotEmpty() && lastResult.first() is ScoredText) { semanticService.embedding(lastResult as List) } else { semanticService.embedList(action.entries) } } result } is PatternActionFunc.Splitting -> { semanticService.splitting(resolvePaths(action.paths, input)) } is PatternActionFunc.Searching -> { semanticService.searching(action.text, action.threshold) } is PatternActionFunc.Caching -> { semanticService.configCache(action.text) } is PatternActionFunc.Reranking -> { semanticService.reranking(action.strategy) } is PatternActionFunc.Redact -> { RedactProcessor.execute(myProject, lastResult) } is PatternActionFunc.Crawl -> { val urls: MutableList = mutableListOf() if (action.urls.isEmpty()) { when (lastResult) { is ArrayList<*> -> { (lastResult as ArrayList).forEach { urls.add(it) } } is String -> { lastResult.split("\n").forEach { urls.add(it) } } else -> { logger.warn("crawl error: $lastResult") } } } else { urls.addAll(action.urls) } val finalUrls = urls.map { it.trim() }.filter { it.isNotEmpty() } CrawlProcessor.execute(finalUrls.toTypedArray()) } is PatternActionFunc.Capture -> { CaptureProcessor.execute(myProject, action.fileName, action.nodeType) } is PatternActionFunc.Execute -> { /// don't need to fill variable for filename val variableNames: Array = action.variableNames.map { if (it.startsWith("\$")) { it.substring(1) } else { it } }.toTypedArray() ExecuteProcessor.execute(myProject, action.filename, variableNames, variableTable) } is PatternActionFunc.ApprovalExecute -> { val variableNames: Array = action.variableNames.map { if (it.startsWith("\$")) { it.substring(1) } else { it } }.toTypedArray() ApprovalExecuteProcessor.execute(myProject, action.filename, variableNames, variableTable, approve = { CoroutineScope(workerThread).launch { ExecuteProcessor.execute(myProject, action.filename, variableNames, variableTable) } }) } is PatternActionFunc.Batch -> { val inputs: List = action.inputs.map { input -> if (input.startsWith("\$")) { when (val variable = variableTable[input.substring(1)]) { is String -> listOf(variable.toString()) is List<*> -> variable.map(Any?::toString) is Array<*> -> variable.map(Any?::toString) else -> listOf() } } else { listOf(input) } }.flatten() BatchProcessor.execute(myProject, action.fileName, inputs, action.batchSize, variableTable) } is PatternActionFunc.Thread -> { val varNames = action.variableNames.toMutableList().apply { if (!contains("output")) { add("output") } }.map { it.fillVariable(variableTable) }.toTypedArray() if (!variableTable.containsKey("output")) { variableTable["output"] = lastResult } ThreadProcessor.execute(myProject, action.fileName, varNames, variableTable) } is PatternActionFunc.JsonPath -> { var jsonStr = action.obj ?: lastResult as String jsonStr = jsonStr.fillVariable(variableTable) JsonPathProcessor.execute(myProject, jsonStr, action) ?: jsonStr } is PatternActionFunc.Destroy -> { TODO() } is PatternActionFunc.Tokenizer -> { if (action.text.startsWith("\$")) { action.text = variableTable[action.text.substring(1)]?.toString() ?: action.text } TokenizerProcessor.execute(myProject, action) } is PatternActionFunc.LineNo -> { when (lastResult) { is Array<*> -> { (lastResult as Array).mapIndexed { index, line -> "${index + 1}: $line" }.toTypedArray() } else -> { (lastResult as String).split("\n").mapIndexed { index, line -> "${index + 1}: $line" }.joinToString("\n") } } } } } private fun evaluateCase(action: PatternActionFunc.CaseMatch, input: Any): FrontMatterType.EXPRESSION? { var fitCondition = action.keyValue.firstOrNull { it.key.toValue() == parseInput(input) } if (fitCondition == null) { fitCondition = action.keyValue.firstOrNull { it.key.toValue() == "default" } } return fitCondition?.value } private fun parseInput(input: Any): String { return when (input) { is String -> { input } is Array<*> -> { input.firstOrNull().toString() } else -> { input.toString() } } } fun cat(paths: Array, input: Any): String { val absolutePaths: List = resolvePaths(paths, input) return absolutePaths.joinToString("\n") { it.readText() } } /** * @param userPaths The paths provided by the user in the script: `cat("file1.txt", "file2.txt")`. * @param patterMatchPaths The paths provided by the pattern match: `/.*.txt/ { cat } `. */ private fun resolvePaths(userPaths: Array, patterMatchPaths: Any): List { val baseDir = myProject.guessProjectDir()!! var paths = userPaths if (userPaths.isEmpty()) { when (patterMatchPaths) { is List<*> -> { paths = (patterMatchPaths as List).toTypedArray() } is Array<*> -> { paths = patterMatchPaths as Array } is String -> { paths = arrayOf(patterMatchPaths) } else -> { logger.warn("resolvePaths error: $patterMatchPaths") } } } val absolutePaths: List = paths.mapNotNull { baseDir.findFile(it) ?: try { LocalFileSystem.getInstance().findFileByIoFile(File(it)) } catch (e: Exception) { null } } return absolutePaths } } fun String.fillVariable( variableTable: MutableMap, ): String { return if (this.startsWith("\$")) { variableTable[this.substring(1)]?.toString() ?: this } else { this } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/BrowseShireCommand.kt ================================================ package com.phodal.shirelang.compiler.execute.command import com.phodal.shirecore.agent.agenttool.browse.BrowseTool import com.intellij.openapi.application.runInEdt import com.intellij.openapi.project.Project import com.phodal.shirelang.completion.dataprovider.BuiltinCommand class BrowseShireCommand(val myProject: Project, private val prop: String, ) : ShireCommand { override val commandName = BuiltinCommand.BROWSE override suspend fun doExecute(): String? { var body: String? = null runInEdt { val parse = BrowseTool.parse(prop) body = parse.body } return body } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/CommitShireCommand.kt ================================================ package com.phodal.shirelang.compiler.execute.command import com.intellij.openapi.project.Project import com.phodal.shirecore.provider.shire.RevisionProvider import com.phodal.shirecore.utils.markdown.CodeFence import com.phodal.shirelang.completion.dataprovider.BuiltinCommand class CommitShireCommand(val myProject: Project, val commitMsg: String) : ShireCommand { override val commandName = BuiltinCommand.COMMIT override suspend fun doExecute(): String { RevisionProvider.provide()?.let { return it.commitCode(myProject, commitMsg) } ?: return "No revision provider found" } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/DatabaseShireCommand.kt ================================================ package com.phodal.shirelang.compiler.execute.command import com.intellij.openapi.project.Project import com.phodal.shirecore.provider.function.ToolchainFunctionProvider import com.phodal.shirecore.utils.markdown.CodeFence import com.phodal.shirelang.completion.dataprovider.BuiltinCommand class DatabaseShireCommand(val myProject: Project, private val prop: String, private val codeContent: String?) : ShireCommand { override val commandName = BuiltinCommand.DATABASE override suspend fun doExecute(): String { val args = if (codeContent != null) { listOf(codeContent) } else { listOf() } val result = ToolchainFunctionProvider.lookup("DatabaseFunctionProvider") ?.execute(myProject, prop, args, emptyMap()) return result?.toString() ?: "No database provider found" } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/DirShireCommand.kt ================================================ package com.phodal.shirelang.compiler.execute.command import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.intellij.openapi.util.text.StringUtilRt import com.intellij.openapi.vcs.FileStatus import com.intellij.openapi.vcs.FileStatusManager import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiManager import com.phodal.shirecore.lookupFile import com.phodal.shirelang.completion.dataprovider.BuiltinCommand /** * The `DirShireCommand` class is responsible for listing files and directories in a tree-like structure for a given directory path within a project. * It implements the `ShireCommand` interface and provides an `execute` method to perform the directory listing operation asynchronously. * * The tree structure is visually represented using indentation and symbols (`├──`, `└──`) to denote files and subdirectories. Files are listed * first, followed by subdirectories, which are recursively processed to display their contents. * * Example output: * ``` * myDirectory/ * ├── file1.txt * ├── file2.txt * └── subDirectory/ * ├── file3.txt * └── subSubDirectory/ * └── file4.txt * ``` * * @param myProject The project instance in which the directory resides. * @param dir The path of the directory to list. */ class DirShireCommand(private val myProject: Project, private val dir: String) : ShireCommand { override val commandName = BuiltinCommand.DIR private val defaultMaxDepth = 2 private sealed class TreeNode { abstract val name: String data class FileNode(override val name: String, val size: String?) : TreeNode() data class DirectoryNode( override val name: String, val children: MutableList = mutableListOf() ) : TreeNode() { fun addChild(child: TreeNode) { children.add(child) } } data class CompressedNode(override val name: String, val subdirNames: List) : TreeNode() data class ParallelDirsNode( override val name: String, val dirNames: List, val commonChildName: String ) : TreeNode() } override suspend fun doExecute(): String? { val virtualFile = myProject.lookupFile(dir) ?: return "File not found: $dir" val psiDirectory = PsiManager.getInstance(myProject).findDirectory(virtualFile) ?: return null val rootNode = runReadAction { buildDirectoryTree(myProject, psiDirectory, 1) } ?: return null val output = StringBuilder().apply { appendLine("$dir/") renderTree(rootNode, 1, this) } return output.toString() } private fun buildDirectoryTree(project: Project, directory: PsiDirectory, depth: Int): TreeNode.DirectoryNode? { if (isExcluded(project, directory)) return null val dirNode = TreeNode.DirectoryNode(directory.name) if (depth <= defaultMaxDepth) { directory.files.forEach { file -> val fileSize = StringUtilRt.formatFileSize(file.virtualFile.length) dirNode.addChild(TreeNode.FileNode(file.name, fileSize)) } } val subdirectories = directory.subdirectories.filter { !isExcluded(project, it) } val parallelDirsNode = detectParallelSimpleDirs(project, subdirectories) if (parallelDirsNode != null) { dirNode.addChild(parallelDirsNode) processRemainingDirs(project, subdirectories, parallelDirsNode.dirNames, dirNode, depth) return dirNode } if (shouldCompressSubdirectories(project, directory, subdirectories, depth)) { val compressableSubdirs = getCompressableSubdirectories(subdirectories) if (compressableSubdirs.isNotEmpty()) { dirNode.addChild(TreeNode.CompressedNode("compressed", compressableSubdirs.map { it.name })) } } else { subdirectories.forEach { subdir -> buildDirectoryTree(project, subdir, depth + 1)?.let { subdirNode -> dirNode.addChild(subdirNode) } } } return dirNode } /** * 处理剩余的不符合并列目录模式的子目录 */ private fun processRemainingDirs( project: Project, allDirs: List, parallelDirNames: List, parentNode: TreeNode.DirectoryNode, depth: Int ) { val remainingDirs = allDirs.filter { dir -> dir.name !in parallelDirNames } remainingDirs.forEach { dir -> buildDirectoryTree(project, dir, depth + 1)?.let { subdirNode -> parentNode.addChild(subdirNode) } } } /** * 检测并列的简单目录模式,如多个组件目录下都只有一个相同名称的子目录 */ private fun detectParallelSimpleDirs(project: Project, subdirs: List): TreeNode.ParallelDirsNode? { if (subdirs.size < 2) return null // 收集有相同子目录结构的目录组 val dirGroups = mutableMapOf>() // 对每个目录,检查它是否有单一子目录,如果有,记录子目录名 subdirs.forEach { dir -> val nonExcludedChildren = dir.subdirectories.filter { !isExcluded(project, it) } if (nonExcludedChildren.size == 1) { val childName = nonExcludedChildren.first().name dirGroups.getOrPut(childName) { mutableListOf() }.add(dir) } } // 找出最大的组(具有相同子目录名的父目录组) val largestGroup = dirGroups.maxByOrNull { it.value.size } // 如果最大组至少有2个目录且子目录名不为空,则创建并列目录节点 if (largestGroup != null && largestGroup.value.size >= 2 && largestGroup.key.isNotEmpty()) { val commonChildName = largestGroup.key val parentDirNames = largestGroup.value.map { it.name } return TreeNode.ParallelDirsNode("parallelDirs", parentDirNames, commonChildName) } return null } /** * 判断是否应该压缩显示子目录 */ private fun shouldCompressSubdirectories( project: Project, directory: PsiDirectory, subdirectories: List, depth: Int): Boolean { // 深度超过阈值且有多个子目录时考虑压缩 return depth > defaultMaxDepth + 1 && subdirectories.size > 1 && // 确保这些子目录大多是叶子节点或近似叶子节点 subdirectories.all { subdir -> val childDirs = subdir.subdirectories.filter { !isExcluded(project, it) } childDirs.isEmpty() || childDirs.all { it.subdirectories.isEmpty() } } } /** * 获取可以压缩显示的子目录 */ private fun getCompressableSubdirectories(subdirectories: List): List { // 这里可以添加更复杂的逻辑来决定哪些目录可以压缩 return subdirectories } /** * 将目录树渲染为文本输出 */ private fun renderTree(node: TreeNode, depth: Int, output: StringBuilder) { val indent = " ".repeat(depth) when (node) { is TreeNode.DirectoryNode -> { // 目录节点的子节点渲染 node.children.forEachIndexed { index, child -> val isLast = index == node.children.lastIndex val prefix = if (isLast) "└" else "├" when (child) { is TreeNode.FileNode -> { val sizeInfo = child.size?.let { " ($it)" } ?: "" output.appendLine("$indent$prefix── ${child.name}$sizeInfo") } is TreeNode.DirectoryNode -> { output.appendLine("$indent$prefix── ${child.name}/") renderTree(child, depth + 1, output) } is TreeNode.CompressedNode -> { output.appendLine("$indent$prefix── {${child.subdirNames.joinToString(",")}}/") } is TreeNode.ParallelDirsNode -> { // 以更紧凑的格式显示并列目录结构 val dirs = child.dirNames.sorted().joinToString(",") output.appendLine("$indent$prefix── {$dirs}/${child.commonChildName}/") } } } } else -> {} // 其他类型节点在这里不需要单独处理 } } /** * 判断目录是否应被排除 */ private fun isExcluded(project: Project, directory: PsiDirectory): Boolean { val excludedDirs = setOf(".idea", "build", "target", ".gradle", "node_modules") if (directory.name in excludedDirs) return true val status = FileStatusManager.getInstance(project).getStatus(directory.virtualFile) return status == FileStatus.IGNORED } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/FileFuncShireCommand.kt ================================================ package com.phodal.shirelang.compiler.execute.command import com.phodal.shirelang.compiler.parser.SHIRE_ERROR import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.vfs.VirtualFile import com.phodal.shirelang.completion.dataprovider.FileFunc import com.phodal.shirecore.canBeAdded import com.phodal.shirelang.completion.dataprovider.BuiltinCommand class FileFuncShireCommand(val myProject: Project, private val prop: String) : ShireCommand { override val commandName = BuiltinCommand.FILE_FUNC override suspend fun doExecute(): String { val (functionName, args) = parseRegex(prop) ?: return """$SHIRE_ERROR: file-func is not in the format @file-func:(, , ...) |Example: @file-func:regex(".*\.kt") """.trimMargin() val fileFunction = FileFunc.fromString(functionName) ?: return "$SHIRE_ERROR: Unknown function: $functionName" when (fileFunction) { FileFunc.Regex -> { try { val regex = Regex(args[0]) return regexFunction(regex, myProject).joinToString(", ") } catch (e: Exception) { return SHIRE_ERROR + ": ${e.message}" } } } } private fun regexFunction(regex: Regex, project: Project): MutableList { val files: MutableList = mutableListOf() ProjectFileIndex.getInstance(project).iterateContent { if (it.canBeAdded(project)) { if (regex.matches(it.path)) { files.add(it) } } true } return files } companion object { /** * Parses a given property string to extract the function name and its arguments. * * The property string is in the format (, , ...). * * @param prop The property string to parse. * @return The function name and the list of arguments as a Pair object. * @throws IllegalArgumentException if the property string has invalid regex pattern. */ fun parseRegex(prop: String): Pair>? { val regexPattern = Regex("""(\w+)\(([^)]+)\)""") val matchResult = regexPattern.find(prop) if (matchResult != null && matchResult.groupValues.size == 3) { val functionName = matchResult.groupValues[1] val args = matchResult.groupValues[2].split(',').map { it.trim() } return Pair(functionName, args) } else { return null } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/FileShireCommand.kt ================================================ package com.phodal.shirelang.compiler.execute.command import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.readText import com.intellij.psi.PsiManager import com.phodal.shirecore.ShirelangNotifications import com.phodal.shirecore.findFile import com.phodal.shirecore.lookupFile import com.phodal.shirelang.compiler.ast.LineInfo import com.phodal.shirelang.completion.dataprovider.BuiltinCommand /** * FileAutoCommand is responsible for reading a file and returning its contents. * * @param myProject the Project in which the file operations are performed * @param prop the property string containing the file name and optional line range * */ class FileShireCommand(private val myProject: Project, private val prop: String) : ShireCommand { override val commandName = BuiltinCommand.FILE override suspend fun doExecute(): String? { val range: LineInfo? = LineInfo.fromString(prop) // prop name can be src/file.name#L1-L2 val filepath = prop.split("#")[0] var virtualFile: VirtualFile? = myProject.lookupFile(filepath) if (virtualFile == null) { val filename = filepath.split("/").last() virtualFile = myProject.findFile(filename, false) } val content = virtualFile?.readText() if (content == null) { ShirelangNotifications.warn(myProject, "File not found: $prop") /// not show error message to just notify return "File not found: $prop" } val lang = PsiManager.getInstance(myProject).findFile(virtualFile)?.language?.displayName ?: "" val fileContent = if (range == null) { content } else { try { content.split("\n").slice(range.startLine - 1 until range.endLine) .joinToString("\n") } catch (e: StringIndexOutOfBoundsException) { content } } val output = StringBuilder() // add file path output.append("// File: $prop\n") output.append("\n```$lang\n") output.append(fileContent) output.append("\n```\n") return output.toString() } companion object { fun file(project: Project, path: String): VirtualFile? { val filename = path.split("#")[0] val virtualFile = project.lookupFile(filename) return virtualFile } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/GotoShireCommand.kt ================================================ package com.phodal.shirelang.compiler.execute.command import com.intellij.execution.filters.OpenFileHyperlinkInfo import com.intellij.openapi.application.runReadAction import com.intellij.openapi.editor.ScrollType import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.Project import com.phodal.shirecore.lookupFile import com.phodal.shirecore.provider.shire.ShireSymbolProvider import com.phodal.shirelang.compiler.ast.LineInfo import com.phodal.shirelang.compiler.parser.SHIRE_ERROR import com.phodal.shirelang.completion.dataprovider.BuiltinCommand import com.phodal.shirelang.psi.ShireUsed /** * The goto command will open the file and move the cursor to the specified line. * For example: * * ```shire * /goto:src/main/shire/com/phodal/shirelang/compiler/execute/command/GotoShireCommand.kt#L10C8 * ``` * * means to open the file `src/main/shire/com/phodal/shirelang/compiler/execute/command/GotoShireCommand.kt` * and move the cursor to line 10, column 8. */ class GotoShireCommand(val myProject: Project, private val argument: String, val used: ShireUsed) : ShireCommand { override val commandName = BuiltinCommand.GOTO override suspend fun doExecute(): String { if (argument.contains(".")) { return gotoSymbol() } val range: LineInfo? = LineInfo.fromString(used.text) return gotoFile(range) } private fun gotoSymbol(): String { val symbols = ShireSymbolProvider.all().map { it.resolveSymbol(myProject, argument) }.flatten() if (symbols.isEmpty()) { return "$SHIRE_ERROR: Symbol not found: $argument" } val results: List = symbols.map { symbol -> val hyperlinkInfo = OpenFileHyperlinkInfo(myProject, symbol.containingFile.virtualFile, symbol.textOffset) hyperlinkInfo.navigate(myProject) symbol.containingFile.virtualFile.path } return results.joinToString("\n") } private fun gotoFile(range: LineInfo?): String { val virtualFile = runReadAction { myProject.lookupFile(argument) } if (virtualFile == null) { return "$SHIRE_ERROR: File not found: $argument" } val editor = FileEditorManager.getInstance(myProject).selectedTextEditor ?: return "$SHIRE_ERROR: Editor not found" val line = range?.startLine ?: 0 val column = range?.startColumn ?: 0 runReadAction { val offset = editor.document.let { val lineStartOffset = it.getLineStartOffset(line.coerceIn(0, it.lineCount - 1)) (lineStartOffset + column).coerceAtMost(it.textLength) } editor.caretModel.moveToOffset(offset) editor.scrollingModel.scrollToCaret(ScrollType.CENTER) } return "Navigated to $argument at line $line, column $column" } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/LocalSearchShireCommand.kt ================================================ package com.phodal.shirelang.compiler.execute.command import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.vfs.VirtualFile import com.phodal.shirecore.canBeAdded import com.phodal.shirecore.relativePath import com.phodal.shirelang.completion.dataprovider.BuiltinCommand import kotlin.collections.component1 import kotlin.collections.component2 /** * Todo: Spike different search API in Intellij * - [com.intellij.util.indexing.FileBasedIndex] * - [com.intellij.find.FindManager] or [com.intellij.find.impl.FindInProjectUtil] * - [com.intellij.psi.search.PsiSearchHelper] * - [com.intellij.structuralsearch.StructuralSearchUtil] (Structural search API) * - [com.intellij.find.EditorSearchSession] * * ```java * EditorSearchSession.start(editor,project).setTextInField("Your Text to search"); * ``` * */ class LocalSearchShireCommand(val myProject: Project, private val scope: String, val text: String?) : ShireCommand { override val commandName = BuiltinCommand.LOCAL_SEARCH private val MAX_LINE_SIZE = 180 private val OVERLAP = 4 override suspend fun doExecute(): String { val text = (text ?: scope).trim() /// check text length if less then 3 return alert slowly if (text.length < 3) { throw IllegalArgumentException("Text length should be more than 4") } val textSearch = runReadAction { search(myProject, text, OVERLAP) } return textSearch.map { (file, lines) -> val filePath = file.relativePath(myProject) val linesWithContext = lines.joinToString("\n") "file: $filePath\n$linesWithContext" }.joinToString("\n") } /** * Searches for occurrences of a specified keyword within the content of files in the project. * For each occurrence, it retrieves a specified number of lines before and after the matched line, * providing context around the keyword. The results are grouped by the file in which the keyword was found. * * @param project The project in which to search for the keyword. This is used to access the project's file index. * @param keyword The keyword to search for within the files. The search is case-sensitive. * @param overlap The number of lines to retrieve before and after each matched line. This determines the context size around the keyword. * @return A map where each key is a `VirtualFile` containing the keyword, and the value is a list of strings representing * the lines of context around the keyword in that file. The context includes the matched line and the specified * number of lines before and after it. */ private fun search(project: Project, keyword: String, overlap: Int = OVERLAP): Map> { val result = mutableMapOf>() ProjectFileIndex.getInstance(project).iterateContent { file -> if (!file.canBeAdded(project) || !ProjectFileIndex.getInstance(project).isInContent(file)) { return@iterateContent true } if (ProjectFileIndex.getInstance(project).isUnderIgnored(file)) return@iterateContent true /// skip for .idea/ if (file.path.contains(".idea")) return@iterateContent true val content = file.contentsToByteArray().toString(Charsets.UTF_8).lines() val matchedIndices = content.withIndex() .filter { (_, line) -> line.length < MAX_LINE_SIZE && line.contains(keyword) } .map { it.index } if (matchedIndices.isNotEmpty()) { val linesWithContext = matchedIndices.flatMap { index -> val start = (index - overlap).coerceAtLeast(0) val end = (index + overlap).coerceAtMost(content.size - 1) content.subList(start, end + 1).mapIndexed { offset, line -> "${start + offset + 1} $line" } }.distinct() result[file] = linesWithContext } true } return result } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/OpenShireCommand.kt ================================================ package com.phodal.shirelang.compiler.execute.command import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.Project import com.phodal.shirecore.lookupFile import com.phodal.shirelang.completion.dataprovider.BuiltinCommand class OpenShireCommand(val myProject: Project, private val filename: String) : ShireCommand { override val commandName = BuiltinCommand.OPEN override suspend fun doExecute(): String? { FileDocumentManager.getInstance().saveAllDocuments() val file = myProject.lookupFile(filename) if (file != null) { FileEditorManager.getInstance(myProject).openFile(file, true) return "Opening $filename..." } else { return "File not found: $filename" } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/PatchShireCommand.kt ================================================ package com.phodal.shirelang.compiler.execute.command import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.diff.impl.patch.FilePatch import com.intellij.openapi.diff.impl.patch.PatchReader import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.project.Project import com.intellij.openapi.vcs.changes.patch.AbstractFilePatchInProgress import com.intellij.openapi.vcs.changes.patch.ApplyPatchDefaultExecutor import com.intellij.openapi.vcs.changes.patch.MatchPatchPaths import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.containers.MultiMap import com.phodal.shirelang.completion.dataprovider.BuiltinCommand class PatchShireCommand(val myProject: Project, private val prop: String, private val codeContent: String) : ShireCommand { override val commandName = BuiltinCommand.PATCH override suspend fun doExecute(): String { FileDocumentManager.getInstance().saveAllDocuments() val shelfExecutor = ApplyPatchDefaultExecutor(myProject) val myReader = PatchReader(codeContent) myReader.parseAllPatches() val filePatches: MutableList = myReader.allPatches ApplicationManager.getApplication().invokeLater { val matchedPatches = MatchPatchPaths(myProject).execute(filePatches, true) val patchGroups = MultiMap>() for (patchInProgress in matchedPatches) { patchGroups.putValue(patchInProgress.base, patchInProgress) } val additionalInfo = myReader.getAdditionalInfo(ApplyPatchDefaultExecutor.pathsFromGroups(patchGroups)) shelfExecutor.apply(filePatches, patchGroups, null, prop, additionalInfo) } return "Patch in Progress..." } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/PrintShireCommand.kt ================================================ package com.phodal.shirelang.compiler.execute.command import com.phodal.shirelang.completion.dataprovider.BuiltinCommand class PrintShireCommand(private val value: String) : ShireCommand { override val commandName = BuiltinCommand.FILE_FUNC override suspend fun doExecute(): String { return value } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/RefactorShireCommand.kt ================================================ package com.phodal.shirelang.compiler.execute.command import com.intellij.lang.Language import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.phodal.shirelang.completion.dataprovider.BuiltinRefactorCommand import com.phodal.shirecore.provider.shire.RefactoringTool import com.phodal.shirelang.completion.dataprovider.BuiltinCommand import com.phodal.shirelang.psi.ShireFile /** * `RefactorShireCommand` is a class that implements the `ShireCommand` interface. It is responsible for executing * refactoring commands within a project based on the provided argument and text segment. * * The class has three private properties: * - `myProject`: A `Project` instance representing the current project. * - `argument`: A `String` containing the refactoring command to be executed. * - `textSegment`: A `String` containing the text segment relevant to the refactoring command. * * The `execute` method is the main entry point for executing a refactoring command. It first attempts to parse the * `argument` into a `BuiltinRefactorCommand` using the `fromString` method. If the command is not recognized, a * message indicating that it is unknown is returned. * * Depending on the type of refactoring command, the `execute` method performs different actions: * - For `BuiltinRefactorCommand.RENAME`: The method splits the `textSegment` using " to " and assigns the result to * the variables `from` and `to`. It then performs a rename operation on a class in Java or Kotlin. The actual * implementation of the rename operation is not provided in the code snippet, but it suggests using `RenameQuickFix`. * @see com.intellij.jvm.analysis.quickFix.RenameQuickFix for kotlin * @see com.intellij.spellchecker.quickfixes.RenameTo for by typos rename which will be better * - For `BuiltinRefactorCommand.SAFEDELETE`: The method checks the usage of the symbol before deleting it. It * suggests using `SafeDeleteFix` as an example. * @see org.jetbrains.kotlin.idea.inspections.SafeDeleteFix for Kotlin * @see com.intellij.codeInsight.daemon.impl.quickfix.SafeDeleteFix for Java * - For `BuiltinRefactorCommand.DELETE`: The method does not perform any specific action, but it is expected to be * implemented to handle the deletion of elements. * @see com.intellij.codeInspection.LocalQuickFixOnPsiElement * - For `BuiltinRefactorCommand.MOVE`: The method suggests using ` as an example for moving elements move package fix to a different package. * @see com.intellij.codeInspection.MoveToPackageFix * * * The `execute` method always returns `null`, indicating that the refactoring command has been executed, but the * actual result of the refactoring is not returned. * * This class is designed to be used within a refactoring tool or plugin that provides built-in refactoring commands. * It demonstrates how to handle different refactoring scenarios */ class RefactorShireCommand(val myProject: Project, private val argument: String, private val textSegment: String) : ShireCommand { override val commandName = BuiltinCommand.REFACTOR override suspend fun doExecute(): String? { var currentEditFile: PsiFile? = null val editor = FileEditorManager.getInstance(myProject).selectedTextEditor if (editor != null) { val currentFile = FileDocumentManager.getInstance().getFile(editor.document) ?: return "File not found" val currentPsiFile = PsiManager.getInstance(myProject).findFile(currentFile) // will not handle the case where the current file is not a ShireFile currentEditFile = if (currentPsiFile is ShireFile) { null } else { currentPsiFile } } val language = currentEditFile?.language ?: Language.findLanguageByID("JAVA") ?: return "Language not found" val refactoringTool = RefactoringTool.forLanguage(language) ?: return "Refactoring tool not found for Java" val command = BuiltinRefactorCommand.fromString(argument) ?: return "Unknown refactor command: $argument" when (command) { BuiltinRefactorCommand.RENAME -> { val (from, to) = textSegment.split(" to ") refactoringTool.rename(from.trim(), to.trim(), currentEditFile) } BuiltinRefactorCommand.SAFE_DELETE -> { val psiFile = refactoringTool.lookupFile(textSegment.trim()) ?: return "File not found" refactoringTool.safeDelete(psiFile) } BuiltinRefactorCommand.DELETE -> { val psiFile = refactoringTool.lookupFile(textSegment.trim()) ?: return "File not found" refactoringTool.safeDelete(psiFile) } BuiltinRefactorCommand.MOVE -> { val (from, to) = textSegment.split(" to ") val psiFile = refactoringTool.lookupFile(from.trim()) ?: return "File not found" refactoringTool.move(psiFile, to.trim()) } } return null } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/RelatedSymbolInsCommand.kt ================================================ package com.phodal.shirelang.compiler.execute.command import com.intellij.openapi.project.Project import com.phodal.shirecore.provider.psi.RelatedClassesProvider import com.phodal.shirecore.provider.shire.ShireSymbolProvider import com.phodal.shirelang.completion.dataprovider.BuiltinCommand class RelatedSymbolInsCommand(val myProject: Project, private val symbol: String) : ShireCommand { override val commandName = BuiltinCommand.RELATED override suspend fun doExecute(): String? { val elements = ShireSymbolProvider.all().map { it.resolveSymbol(myProject, symbol) }.flatten() if (elements.isEmpty()) return null val psiElements = elements.mapNotNull { RelatedClassesProvider.provide(it.language)?.lookup(it) }.flatten() if (psiElements.isEmpty()) return null return psiElements.joinToString("\n") { it.text } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/RevShireCommand.kt ================================================ package com.phodal.shirelang.compiler.execute.command import com.intellij.openapi.project.Project import com.phodal.shirecore.provider.shire.RevisionProvider import com.phodal.shirelang.completion.dataprovider.BuiltinCommand /** * RevAutoCommand is used to execute a command that retrieves the committed change list for a given revision using Git. * * @param myProject the Project instance associated with the command * @param revision the Git revision for which the committed change list is to be retrieved * */ class RevShireCommand(private val myProject: Project, private val revision: String) : ShireCommand { override val commandName = BuiltinCommand.REV override suspend fun doExecute(): String { return RevisionProvider.provide()?.let { val changes = it.fetchChanges(myProject, revision) if (changes != null) { return changes } else { return "No changes found for revision $revision" } } ?: "No revision provider found" } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/RipgrepSearchShireCommand.kt ================================================ package com.phodal.shirelang.compiler.execute.command import com.intellij.openapi.project.Project import com.phodal.shirelang.compiler.execute.command.search.RipgrepSearcher import com.phodal.shirelang.completion.dataprovider.BuiltinCommand class RipgrepSearchShireCommand( val myProject: Project, private val scope: String, val text: String?, ) : ShireCommand { override val commandName = BuiltinCommand.RIPGREP_SEARCH override fun isApplicable(): Boolean { return RipgrepSearcher.findRipgrepBinary() != null } override suspend fun doExecute(): String? { val searchDirectory = myProject.baseDir!!.path return RipgrepSearcher.searchFiles(myProject, searchDirectory, text ?: scope, null).get() } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/RunShireCommand.kt ================================================ package com.phodal.shirelang.compiler.execute.command import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.phodal.shirecore.lookupFile import com.phodal.shirecore.provider.TestingService import com.phodal.shirecore.provider.shire.ProjectRunService import com.phodal.shirelang.compiler.parser.SHIRE_ERROR import com.phodal.shirelang.completion.dataprovider.BuiltinCommand /** * The `RunAutoCommand` class is responsible for executing an auto command on a given project. * * @property myProject The project to execute the auto command on. * @property argument The name of the file to find and run tests for. * */ class RunShireCommand(val myProject: Project, private val argument: String) : ShireCommand { override val commandName = BuiltinCommand.RUN override suspend fun doExecute(): String { val task = ProjectRunService.all().mapNotNull { projectRun -> val hasTasks = projectRun.tasks(myProject).any { task -> task.contains(argument) } if (hasTasks) projectRun else null } if (task.isNotEmpty()) { task.first().run(myProject, argument) return "Task run successfully: $argument" } val virtualFile = myProject.lookupFile(argument.trim()) ?: return "$SHIRE_ERROR: [RunShireCommand] File not found: $argument" try { val psiFile: PsiFile = PsiManager.getInstance(myProject).findFile(virtualFile) ?: return "$SHIRE_ERROR: [RunShireCommand] File not found: $argument" val testService = TestingService.context(psiFile) ?: return "$SHIRE_ERROR: [RunShireCommand] No test service found for file: $argument" testService.runFile(myProject, virtualFile, null) return "Tests run successfully for file: $argument" } catch (e: Exception) { return "$SHIRE_ERROR: ${e.message}" } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/ShellShireCommand.kt ================================================ package com.phodal.shirelang.compiler.execute.command import com.intellij.execution.RunnerAndConfigurationSettings import com.phodal.shirelang.compiler.parser.SHIRE_ERROR import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.project.Project import com.intellij.psi.PsiManager import com.intellij.sh.psi.ShFile import com.intellij.sh.run.ShRunner import com.phodal.shirecore.lookupFile import com.phodal.shirelang.completion.dataprovider.BuiltinCommand import com.phodal.shirelang.runner.ShellFileRunService /** * A class that implements the `InsCommand` interface to execute a shell command within the IntelliJ IDEA environment. * * This class is designed to run a shell command specified by a given `prop` string, which is assumed to be the path to a file within the project. * The command is executed in a shell runner service provided by IntelliJ IDEA, using the specified file's path and its parent directory as the working directory. * * @param myProject The current project context. * @param argument The path to the file within the project whose content should be executed as a shell command. */ class ShellShireCommand(val myProject: Project, private val argument: String) : ShireCommand { override val commandName = BuiltinCommand.SHELL override suspend fun doExecute(): String { val virtualFile = myProject.lookupFile(argument.trim()) ?: return "$SHIRE_ERROR: File not found: $argument" val psiFile = PsiManager.getInstance(myProject).findFile(virtualFile) as? ShFile val shellRunService = ShellFileRunService() val settings: RunnerAndConfigurationSettings? = shellRunService.createRunSettings(myProject, virtualFile, psiFile) if (settings != null) { shellRunService.runFile(myProject, virtualFile, psiFile) return "Running shell file: $argument" } val workingDirectory = virtualFile.parent.path val shRunner = ApplicationManager.getApplication().getService(ShRunner::class.java) ?: return "$SHIRE_ERROR: Shell runner not found" if (shRunner.isAvailable(myProject)) { shRunner.run(myProject, virtualFile.path, workingDirectory, "RunShireShell", true) } return "Running shell command: $argument" } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/ShireCommand.kt ================================================ package com.phodal.shirelang.compiler.execute.command import com.phodal.shirelang.completion.dataprovider.BuiltinCommand interface ShireCommand { val commandName: BuiltinCommand fun isApplicable(): Boolean = true suspend fun doExecute(): String? } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/StructureShireCommand.kt ================================================ package com.phodal.shirelang.compiler.execute.command import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.runReadAction import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.phodal.shirecore.provider.codemodel.FileStructureProvider import com.phodal.shirelang.completion.dataprovider.BuiltinCommand import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext class StructureShireCommand(val myProject: Project, val prop: String) : ShireCommand { override val commandName = BuiltinCommand.STRUCTURE override suspend fun doExecute(): String? { val virtualFile = FileShireCommand.file(myProject, prop) if (virtualFile == null) { logger().warn("File not found: $prop") return null } val psiFile: PsiFile = withContext(Dispatchers.IO) { ApplicationManager.getApplication().executeOnPooledThread { runReadAction { PsiManager.getInstance(myProject).findFile(virtualFile) } }.get() } ?: return null FileStructureProvider.from(psiFile).let { return it?.format() } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/SymbolShireCommand.kt ================================================ package com.phodal.shirelang.compiler.execute.command import com.phodal.shirelang.compiler.parser.SHIRE_ERROR import com.intellij.openapi.project.Project import com.phodal.shirecore.provider.shire.ShireSymbolProvider import com.phodal.shirelang.completion.dataprovider.BuiltinCommand class SymbolShireCommand(val myProject: Project, private val prop: String) : ShireCommand { override val commandName = BuiltinCommand.SYMBOL override suspend fun doExecute(): String { val result = ShireSymbolProvider.all().mapNotNull { val found = it.resolveSymbol(myProject, prop) if (found.isEmpty()) return@mapNotNull null "```${it.language}\n${found.map { namedElement -> namedElement.name }.joinToString { "\n" }}\n```\n" } if (result.isEmpty()) { return "$SHIRE_ERROR No symbol found: $prop" } return result.joinToString("\n") } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/WriteShireCommand.kt ================================================ package com.phodal.shirelang.compiler.execute.command import com.phodal.shirelang.compiler.parser.SHIRE_ERROR import com.phodal.shirelang.compiler.ast.LineInfo import com.intellij.openapi.application.runReadAction import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.editor.Document import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.phodal.shirecore.lookupFile import com.phodal.shirelang.psi.ShireUsed import com.phodal.shirelang.completion.dataprovider.BuiltinCommand class WriteShireCommand(val myProject: Project, val argument: String, val content: String, private val used: ShireUsed) : ShireCommand { override val commandName = BuiltinCommand.WRITE private val pathSeparator = "/" override suspend fun doExecute(): String { val range: LineInfo? = LineInfo.fromString(used.text) val filepath = argument.split("#")[0] val projectDir = myProject.guessProjectDir() ?: return "$SHIRE_ERROR: Project directory not found" val virtualFile = runReadAction { myProject.lookupFile(filepath) } if (virtualFile == null) { // filepath maybe just a file name, so we need to create parent directory val hasChildPath = filepath.contains(pathSeparator) if (hasChildPath) { val parentPath = filepath.substringBeforeLast(pathSeparator) var parentDir = projectDir.findChild(parentPath) if (parentDir == null) { // parentDir maybe multiple level, so we need to create all parent directory val parentDirs = parentPath.split(pathSeparator) parentDir = projectDir for (dir in parentDirs) { if (dir.isEmpty()) continue //check child folder if exist? if not, create it parentDir = if (parentDir?.findChild(dir) == null) { runWriteAction { parentDir?.createChildDirectory(this, dir) } } else { parentDir.findChild(dir) } } if (parentDir == null) { return "$SHIRE_ERROR: Create Directory failed: $parentPath" } } return runWriteAction { createNewContent(parentDir, filepath, content) ?: return@runWriteAction "$SHIRE_ERROR: Create File failed: $argument" return@runWriteAction "Create file: $argument" } } else { return runWriteAction { createNewContent(projectDir, filepath, content) ?: return@runWriteAction "$SHIRE_ERROR: Create File failed: $argument" return@runWriteAction "Create file: $argument" } } } val psiFile = PsiManager.getInstance(myProject).findFile(virtualFile) ?: return "$SHIRE_ERROR: File not found: $argument" return executeInsert(psiFile, range, content) } private fun createNewContent(parentDir: VirtualFile, filepath: String, content: String): Document? { val newFile = parentDir.createChildData(this, filepath.substringAfterLast(pathSeparator)) val document = FileDocumentManager.getInstance().getDocument(newFile) ?: return null document.setText(content) return document } private fun executeInsert( psiFile: PsiFile, range: LineInfo?, content: String ): String { val document = runReadAction { PsiDocumentManager.getInstance(myProject).getDocument(psiFile) } ?: return "$SHIRE_ERROR: File not found: $argument" val startLine = range?.startLine ?: 0 val endLine = if (document.lineCount == 0) 1 else range?.endLine ?: document.lineCount try { val startOffset = document.getLineStartOffset(startLine) val endOffset = document.getLineEndOffset(endLine - 1) WriteCommandAction.runWriteCommandAction(myProject) { document.replaceString(startOffset, endOffset, content) } return "Writing to file: $argument" } catch (e: Exception) { return "$SHIRE_ERROR: ${e.message}" } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/search/RipgrepOutputProcessor.kt ================================================ package com.phodal.shirelang.compiler.execute.command.search import com.google.gson.JsonParser import com.intellij.execution.process.ProcessAdapter import com.intellij.execution.process.ProcessEvent import com.intellij.execution.process.ProcessOutputTypes import com.intellij.openapi.util.Key import kotlinx.serialization.Serializable @Serializable public data class RipgrepSearchResult( var filePath: String? = null, var line: Int = 0, var column: Int = 0, var match: String? = null, var beforeContext: MutableList = ArrayList(), var afterContext: MutableList = ArrayList() ) class RipgrepOutputProcessor : ProcessAdapter() { private val results: MutableList = ArrayList() private var currentResult: RipgrepSearchResult? = null override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) { if (outputType === ProcessOutputTypes.STDOUT) { parseJsonLine(event.text) } } private val jsonBuffer = StringBuilder() fun parseJsonLine(line: String) { if (line.isBlank()) { return } jsonBuffer.append(line) // Try to parse the buffer as JSON val json = try { JsonParser.parseString(jsonBuffer.toString()) } catch (e: Exception) { // If parsing fails, it might be because the JSON is incomplete // So we just return and wait for more lines return } // If parsing succeeds, clear the buffer and process the JSON jsonBuffer.clear() if (json.isJsonObject) { val jsonObject = json.asJsonObject val type = jsonObject.get("type").asString when (type) { "match" -> { val data = jsonObject.getAsJsonObject("data") val path = data.getAsJsonObject("path").get("text").asString val lines = data.getAsJsonObject("lines").get("text").asString val lineNumber = data.get("line_number").asInt val absoluteOffset = data.get("absolute_offset").asInt val submatches = data.getAsJsonArray("submatches") currentResult = RipgrepSearchResult( filePath = path, line = lineNumber, column = absoluteOffset, match = lines.trim() ) submatches.forEach { submatch -> val submatchObj = submatch.asJsonObject val matchText = submatchObj.get("match").asJsonObject.get("text").asString currentResult?.match = matchText } results.add(currentResult!!) } "context" -> { val data = jsonObject.getAsJsonObject("data") val lines = data.getAsJsonObject("lines").get("text").asString val lineNumber = data.get("line_number").asInt if (currentResult != null) { if (lineNumber < currentResult!!.line) { currentResult!!.beforeContext.add(lines.trim()) } else { currentResult!!.afterContext.add(lines.trim()) } } } } } } fun getResults(): MutableList { if (currentResult != null) { results.add(currentResult!!) } return results } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/search/RipgrepSearcher.kt ================================================ // Copyright 2024 Cline Bot Inc. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.phodal.shirelang.compiler.execute.command.search import com.intellij.execution.configurations.GeneralCommandLine import com.intellij.execution.process.* import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.project.Project import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.SystemIndependent import java.io.IOException import java.nio.charset.StandardCharsets import java.nio.file.Path import java.nio.file.Paths import java.util.* import java.util.concurrent.CompletableFuture import java.util.concurrent.TimeUnit /** * 使用Ripgrep进行文件搜索 * Inspired by: https://github.com/cline/cline/blob/main/src/services/ripgrep/index.ts Apache-2.0 */ object RipgrepSearcher { private val LOG = Logger.getInstance(RipgrepSearcher::class.java) fun searchFiles( project: Project, searchDirectory: String, regexPattern: String, filePattern: String? ): CompletableFuture { return CompletableFuture.supplyAsync { try { val rgPath = findRipgrepBinary() ?: throw IOException("Ripgrep binary not found") val results = executeRipgrep( project, rgPath, searchDirectory, regexPattern, filePattern ) return@supplyAsync formatResults(results, project.basePath!!) } catch (e: Exception) { LOG.error("Search failed", e) return@supplyAsync "Search error: " + e.message } } } @Throws(IOException::class) fun findRipgrepBinary(): Path? { val osName = System.getProperty("os.name").lowercase(Locale.getDefault()) val binName = if (osName.contains("win")) "rg.exe" else "rg" val pb = ProcessBuilder("which", binName) val process = pb.start() try { if (process.waitFor(1, TimeUnit.SECONDS) && process.exitValue() == 0) { val path = String(process.inputStream.readAllBytes(), StandardCharsets.UTF_8).trim { it <= ' ' } return Paths.get(path) } } catch (_: InterruptedException) { return null } return null } @Throws(IOException::class) private fun executeRipgrep(project: Project, rgPath: Path, directory: String, regex: String, filePattern: String?): MutableList { val cmd = getCommandLine(rgPath, regex, filePattern, directory, project.basePath) val handler: OSProcessHandler = ColoredProcessHandler(cmd) val processor = RipgrepOutputProcessor() handler.addProcessListener(processor) handler.startNotify() handler.waitFor() return processor.getResults() } fun getCommandLine( rgPath: Path, regex: String? = null, filePattern: String? = null, directory: String? = null, basePath: @SystemIndependent @NonNls String? = null, ): GeneralCommandLine { val cmd = GeneralCommandLine(rgPath.toString()) cmd.withWorkDirectory(basePath) cmd.addParameters("--json") if (regex != null) { cmd.addParameters("-e", regex) } if (filePattern != null) { cmd.addParameters("--glob", filePattern) } cmd.addParameters("--context", "1") if (directory != null) { cmd.addParameters(directory) } cmd.charset = StandardCharsets.UTF_8 return cmd } private fun formatResults(results: MutableList, basePath: String): String { val output = StringBuilder() val grouped: MutableMap?> = LinkedHashMap?>() for (result in results) { val relPath = getRelativePath(basePath, result.filePath!!) grouped.computeIfAbsent(relPath) { k: String? -> ArrayList() }!!.add(result) } for (entry in grouped.entries) { output.append("### filepath: ").append(entry.key).append("\n") val filePath = Paths.get(basePath, entry.key) val content = filePath.toFile().readLines() val lineNumbers = entry.value!!.map { it!!.line } val displayLines = mutableSetOf() for (lineNumber in lineNumbers) { val start = 1.coerceAtLeast(lineNumber - 4) val end = content.size.coerceAtMost(lineNumber + 4) for (i in start..end) { displayLines.add(i) } } val sortedDisplayLines = displayLines.sorted() for (lineNumber in sortedDisplayLines) { val line = content.getOrNull(lineNumber - 1) if (line != null) { output.append(lineNumber).append(" ").append(line).append("\n") } } output.append("\n") } return output.toString() } private fun getRelativePath(basePath: String, absolutePath: String): String { val base = Paths.get(basePath) val target = Paths.get(absolutePath) return base.relativize(target).toString().replace('\\', '/') } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/status/ShireCommandStatus.kt ================================================ package com.phodal.shirelang.compiler.execute.command.status enum class ShireCommandStatus { SUCCESS, FAILED, RUNNING } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/command/status/ShireCommandStatusListener.kt ================================================ package com.phodal.shirelang.compiler.execute.command.status import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.messages.Topic import com.phodal.shirelang.compiler.execute.command.ShireCommand /** * Provide for listening to the status of InsCommand */ interface ShireCommandStatusListener { fun onFinish(command: ShireCommand, status: ShireCommandStatus, file: VirtualFile?) companion object { val TOPIC = Topic.create("shire.command.status", ShireCommandStatusListener::class.java) fun notify(command: ShireCommand, status: ShireCommandStatus, file: VirtualFile?) { ApplicationManager.getApplication().messageBus .syncPublisher(TOPIC) .onFinish(command, status, file) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/processor/ApprovalExecuteProcessor.kt ================================================ package com.phodal.shirelang.compiler.execute.processor import com.intellij.ide.DataManager import com.intellij.openapi.application.runInEdt import com.intellij.openapi.project.Project import com.intellij.openapi.ui.popup.JBPopupFactory import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFuncDef import com.phodal.shirelang.compiler.ast.patternaction.PatternProcessor import com.phodal.shirelang.compiler.execute.processor.ui.PendingApprovalPanel import java.util.concurrent.CompletableFuture object ApprovalExecuteProcessor: PatternProcessor { override val type: PatternActionFuncDef = PatternActionFuncDef.APPROVAL_EXECUTE fun execute( myProject: Project, filename: Any, variableNames: Array, variableTable: MutableMap, approve: ((Any) -> Unit)? = null, reject: (() -> Unit?)? = null ): Any { val dataContext = DataManager.getInstance().dataContextFromFocusAsync.blockingGet(10000) ?: throw IllegalStateException("No data context") val panel = PendingApprovalPanel() val future = CompletableFuture() runInEdt { val popup = JBPopupFactory.getInstance() .createComponentPopupBuilder(panel, null) .setResizable(true) .setMovable(true) .setFocusable(true) .setRequestFocus(true) .setCancelOnClickOutside(false) .setCancelOnOtherWindowOpen(false) .setCancelOnWindowDeactivation(false) .setKeyboardActions(listOf()) .createPopup() panel.setupKeyShortcuts(popup, { popup.closeOk(null) approve?.invoke("") future.complete("") }, { popup.cancel() reject?.invoke() future.complete("") }) popup.showInBestPositionFor(dataContext) } return future.get() } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/processor/BatchProcessor.kt ================================================ package com.phodal.shirelang.compiler.execute.processor import com.intellij.openapi.application.runReadAction import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.phodal.shirelang.ShireActionStartupActivity import com.phodal.shirelang.actions.ShireRunFileAction import com.phodal.shirelang.compiler.execute.FunctionStatementProcessor import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFuncDef import com.phodal.shirelang.compiler.ast.patternaction.PatternProcessor object BatchProcessor : PatternProcessor { override val type: PatternActionFuncDef = PatternActionFuncDef.BATCH fun execute( myProject: Project, filename: String, inputs: List, batchSize: Int, variableTable: MutableMap, ): Any { val file = runReadAction { ShireActionStartupActivity.findShireFile(myProject, filename) } if (file == null) { logger().error("execute shire error: file not found") return "" } var files = inputs /// maybe inputs ["a.txt\nb.txt", "c.txt\nd.txt"] or ["a.txt", "b.txt", "c.txt", "d.txt"] we need to split it if (inputs.size == 1) { files = inputs[0].split("\n") } return files.forEach { chunk: String -> try { val variableNames = arrayOf("input") variableTable["input"] = chunk ShireRunFileAction.suspendExecuteFile(myProject, file, variableNames, variableTable) ?: "" } catch (e: Exception) { logger().error("execute shire error: $e") } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/processor/CaptureProcessor.kt ================================================ package com.phodal.shirelang.compiler.execute.processor import com.intellij.openapi.application.ReadAction import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.phodal.shirecore.lookupFile import com.phodal.shirecore.provider.psi.PsiCapture import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFuncDef import com.phodal.shirelang.compiler.ast.patternaction.PatternProcessor object CaptureProcessor: PatternProcessor { override val type: PatternActionFuncDef = PatternActionFuncDef.CAPTURE fun execute(myProject: Project, fileName: String, nodeType: String): Any { // first lookup file in the file system val lookupFile = myProject.lookupFile(fileName) ?: return "File not found: $fileName" // convert to psi val psiFile = ReadAction.compute { PsiManager.getInstance(myProject).findFile(lookupFile) } ?: return "Failed to find PSI file for $fileName" val language = psiFile.language PsiCapture.provide(language)?.let { val text = ReadAction.compute { psiFile.text } return it.capture(text, nodeType) } // execute the capture function val result = psiFile.children.filter { it.node.elementType.toString() == nodeType } return result } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/processor/CrawlProcessor.kt ================================================ package com.phodal.shirelang.compiler.execute.processor import com.phodal.shirecore.agent.agenttool.browse.BrowseTool import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFuncDef import com.phodal.shirelang.compiler.ast.patternaction.PatternProcessor import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.runBlocking object CrawlProcessor: PatternProcessor { override val type: PatternActionFuncDef = PatternActionFuncDef.CRAWL suspend fun doExecute(url: String): String? { /// todo: parse github README.md if it's a github repo return BrowseTool.parse(url).body } fun execute(urls: Array): List { val results = runBlocking { coroutineScope { urls.mapNotNull { try { doExecute(it) } catch (e: Exception) { null } } } } return results } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/processor/ExecuteProcessor.kt ================================================ package com.phodal.shirelang.compiler.execute.processor import com.intellij.openapi.application.runReadAction import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.phodal.shirecore.lookupFile import com.phodal.shirecore.provider.shire.FileRunService import com.phodal.shirecore.workerThread import com.phodal.shirelang.ShireActionStartupActivity import com.phodal.shirelang.actions.ShireRunFileAction import com.phodal.shirelang.compiler.parser.SHIRE_ERROR import com.phodal.shirelang.compiler.execute.command.RunShireCommand import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFuncDef import com.phodal.shirelang.compiler.ast.patternaction.PatternProcessor import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch object ExecuteProcessor : PatternProcessor { override val type: PatternActionFuncDef = PatternActionFuncDef.EXECUTE private val logger = logger() fun execute( myProject: Project, filename: Any, variableNames: Array, variableTable: MutableMap, ): Any { val file = filename.toString() if (file.endsWith(".shire")) { return executeShireFile(myProject, filename, variableNames, variableTable) } if (file.startsWith(":")) { CoroutineScope(workerThread).launch { RunShireCommand(myProject, file).doExecute() } } val virtualFile = myProject.lookupFile(file) ?: return "$SHIRE_ERROR: File not found: $filename" val runService = FileRunService.provider(myProject, virtualFile) return runService?.runFileAsync(myProject, virtualFile, null) ?: "$SHIRE_ERROR: [ExecuteProcessor] No run service found for file: $filename" } private fun executeShireFile( myProject: Project, filename: Any, variableNames: Array, variableTable: MutableMap, ): String { try { val file = runReadAction { ShireActionStartupActivity.findShireFile(myProject, filename.toString()) } if (file == null) { logger.warn("execute shire error: file not found") return "" } return ShireRunFileAction.suspendExecuteFile(myProject, file, variableNames, variableTable) ?: "" } catch (e: Exception) { logger.warn("execute shire error: $e") return "" } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/processor/ForeignFunctionProcessor.kt ================================================ package com.phodal.shirelang.compiler.execute.processor import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.phodal.shirecore.findFile import com.phodal.shirecore.provider.shire.FileRunService import com.phodal.shirelang.compiler.parser.SHIRE_ERROR import com.phodal.shirelang.compiler.ast.ForeignFunction object ForeignFunctionProcessor { fun execute( project: Project, funcName: String, args: List, allVariables: Map, func: ForeignFunction, ): Any { val filename = func.funcPath val virtualFile = runReadAction { project.findFile(filename) } ?: return "$SHIRE_ERROR: File not found: $filename" /// last args will be file path, should be skip val argList: List = args.dropLast(1).map { // handle for arrayList and map type when (it) { is List<*> -> it.joinToString(",") is Map<*, *> -> it.entries.joinToString(",") { (k, v) -> "$k=$v" } else -> it.toString() } } return FileRunService.runInCli(project, virtualFile, argList) ?: "$SHIRE_ERROR: [ForeignFunctionProcessor] No run service found for file: $filename" } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/processor/JsonPathProcessor.kt ================================================ package com.phodal.shirelang.compiler.execute.processor import com.intellij.execution.console.ConsoleViewWrapperBase import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.execution.ui.RunContentManager import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.nfeld.jsonpathkt.JsonPath import com.nfeld.jsonpathkt.extension.read import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFunc import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFuncDef import com.phodal.shirelang.compiler.ast.patternaction.PatternProcessor import com.phodal.shirelang.compiler.execute.FunctionStatementProcessor object JsonPathProcessor : PatternProcessor { override val type: PatternActionFuncDef = PatternActionFuncDef.JSONPATH fun execute( project: Project, jsonStr: String, action: PatternActionFunc.JsonPath, ): String? { if (action.sseMode || jsonStr.startsWith("data: ")) { return parseSSEResult(jsonStr, action.path.trim()) } val result: String = try { JsonPath.parse(jsonStr)?.read(action.path.trim()).toString() } catch (e: Exception) { console(project,"jsonpath error: $e") return null } if (result == "null") { console(project,"jsonpath error: $result for $jsonStr") return null } return result } fun console(project: Project, str: String) { val contentManager = RunContentManager.getInstance(project) val console = contentManager.selectedContent?.executionConsole as? ConsoleViewWrapperBase ?: return console.print(str, ConsoleViewContentType.ERROR_OUTPUT) } /** * Parses the server-sent events (SSE) result from a given input string using a specified JSON path expression. * This method is useful for extracting relevant information from a stream of SSE data. * * @param input The raw input string containing one or more SSE data lines. * @param jsonPath The JSON path expression used to query the JSON data within each data line. * @return A string containing the results of applying the JSON path to each data line, separated by newline characters. * * The method processes the input string as follows: * input example * ```bash * data: {"event": "agent_thought", "conversation_id": "48929266-a58f-46cc-a5eb-33145e6a96ef", "message_id": "91ad550b-1109-4062-88f8-07be18238e0e", "created_at": 1725437154, "task_id": "4f846104-8571-42f1-b04c-f6f034b2fe9e", "id": "cf621bc0-3daa-45ae-9346-9a386f9c73b0", "position": 1, "thought": "", "observation": "", "tool": "", "tool_labels": {}, "tool_input": "", "message_files": []} * data: {"event": "agent_message", "conversation_id": "48929266-a58f-46cc-a5eb-33145e6a96ef", "message_id": "91ad550b-1109-4062-88f8-07be18238e0e", "created_at": 1725437154, "task_id": "4f846104-8571-42f1-b04c-f6f034b2fe9e", "id": "91ad550b-1109-4062-88f8-07be18238e0e", "answer": "The"} * data: {"event": "message_end", "conversation_id": "48929266-a58f-46cc-a5eb-33145e6a96ef", "message_id": "91ad550b-1109-4062-88f8-07be18238e0e", "created_at": 1725437154, "task_id": "4f846104-8571-42f1-b04c-f6f034b2fe9e", "id": "91ad550b-1109-4062-88f8-07be18238e0e", "metadata": {"usage": {"prompt_tokens": 20, "prompt_unit_price": "0.15", "prompt_price_unit": "0.000001", "prompt_price": "0.0000030", "completion_tokens": 481, "completion_unit_price": "0.60", "completion_price_unit": "0.000001", "completion_price": "0.0002886", "total_tokens": 501, "total_price": "0.0002916", "currency": "USD", "latency": 0.46675555396359414}}} * data: {"event": "tts_message_end", "conversation_id": "48929266-a58f-46cc-a5eb-33145e6a96ef", "message_id": "91ad550b-1109-4062-88f8-07be18238e0e", "created_at": 1725437154, "task_id": "4f846104-8571-42f1-b04c-f6f034b2fe9e", "audio": ""} */ fun parseSSEResult(input: String, jsonPath: String): String { val lines = input.split("\n") val dataLines = lines.filter { it.startsWith("data: ") }.map { it.substring(6) }.mapNotNull { try { JsonPath.parse(it)?.read(jsonPath) } catch (e: Exception) { logger().warn("jsonpath error: $e") null } } return dataLines.joinToString(separator = "") } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/processor/RedactProcessor.kt ================================================ package com.phodal.shirelang.compiler.execute.processor import com.intellij.openapi.project.Project import com.phodal.shirecore.function.guard.scanner.SecretPatternsScanner object RedactProcessor { fun execute(project: Project, lastResult: Any): Any { if (lastResult is String) { return project.getService(SecretPatternsScanner::class.java).maskInput(lastResult) } return lastResult } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/processor/ThreadProcessor.kt ================================================ package com.phodal.shirelang.compiler.execute.processor import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ReadAction import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.readText import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.intellij.sh.psi.ShFile import com.intellij.sh.run.ShRunner import com.phodal.shirecore.lookupFile import com.phodal.shirecore.provider.http.HttpHandler import com.phodal.shirecore.provider.http.HttpHandlerType import com.phodal.shirecore.provider.shire.FileRunService import com.phodal.shirelang.actions.ShireRunFileAction import com.phodal.shirelang.compiler.parser.SHIRE_ERROR import com.phodal.shirelang.compiler.execute.processor.shell.ShireShellCommandRunner import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFuncDef import com.phodal.shirelang.compiler.ast.patternaction.PatternProcessor import com.phodal.shirelang.psi.ShireFile import java.util.concurrent.CompletableFuture object ThreadProcessor : PatternProcessor { override val type: PatternActionFuncDef = PatternActionFuncDef.THREAD fun execute( myProject: Project, fileName: String, variablesName: Array, variableTable: MutableMap, ): String { val file = myProject.lookupFile(fileName) ?: return "File not found: $fileName" val filename = file.name.lowercase() val content = file.readText() // if ends with .cURL.sh, try call cURL service if (filename.endsWith(".curl.sh")) { val execute = HttpHandler.provide(HttpHandlerType.CURL) ?.execute(myProject, content, variablesName, variableTable) if (execute != null) { return execute } } val psiFile = ReadAction.compute { PsiManager.getInstance(myProject).findFile(file) } ?: return "Failed to find PSI file for $fileName" when (psiFile) { is ShireFile -> { return when (val output = variableTable["output"]) { is List<*> -> { val results = output.mapNotNull { try { variableTable["output"] = it executeTask(myProject, variablesName, variableTable, psiFile) } catch (e: Exception) { null } } results.joinToString("\n") } is Array<*> -> { output.joinToString("\n") { variableTable["output"] = it executeTask(myProject, variablesName, variableTable, psiFile) ?: "$SHIRE_ERROR - Thread: No run service found" } } else -> { return executeTask(myProject, variablesName, variableTable, psiFile) ?: "$SHIRE_ERROR - Thread: No run service found" } } } is ShFile -> { val processVariables: Map = variablesName.associateWith { (variableTable[it] as? String ?: "") } return executeShFile(psiFile, myProject, processVariables) } else -> { val fileRunService = FileRunService.provider(myProject, file) ?: return "$SHIRE_ERROR No run service found for $psiFile, $fileName" return fileRunService.runFileAsync(myProject, file, psiFile) ?: "$SHIRE_ERROR Run service failure: $fileName" } } } private fun executeShFile(psiFile: ShFile, myProject: Project, processVariables: Map): String { val virtualFile = psiFile.virtualFile val shRunner = ApplicationManager.getApplication().getService(ShRunner::class.java) ?: return "$SHIRE_ERROR: Shell runner not found" val future = CompletableFuture() ApplicationManager.getApplication().invokeLater { if (shRunner.isAvailable(myProject)) { try { val output = ShireShellCommandRunner.runShellCommand(virtualFile, myProject, processVariables) future.complete(output) } catch (t: Throwable) { future.completeExceptionally(t) } } } return future.get() } private fun executeTask( myProject: Project, variables: Array, variableTable: MutableMap, psiFile: ShireFile, ): String? { return ShireRunFileAction.suspendExecuteFile(myProject, psiFile, variables, variableTable) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/processor/TokenizerProcessor.kt ================================================ package com.phodal.shirelang.compiler.execute.processor import com.huaban.analysis.jieba.JiebaSegmenter import com.huaban.analysis.jieba.JiebaSegmenter.SegMode import com.intellij.openapi.project.Project import com.phodal.shirecore.search.tokenizer.* import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFunc import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFuncDef import com.phodal.shirelang.compiler.ast.patternaction.PatternProcessor /** * For [PatternActionFuncDef.TOKENIZER] */ object TokenizerProcessor : PatternProcessor { override val type: PatternActionFuncDef = PatternActionFuncDef.TOKENIZER fun execute(project: Project, action: PatternActionFunc.Tokenizer): Any { if (action.tokType.startsWith("/") && action.tokType.endsWith("/")) { val regex = action.tokType.substring(1, action.tokType.length - 1) val tokenizer = RegexpTokenizer( object : RegexTokenizerOptions { override val pattern: Regex get() = Regex(regex) override val discardEmpty: Boolean get() = true override val gaps: Boolean? get() = false } ) return tokenizer.tokenize(action.text).distinct() } when (action.tokType) { "word" -> { val tokenizer = WordTokenizer() return tokenizer.tokenize(action.text).distinct() } "naming" -> { val tokenizer = CodeNamingTokenizer() return tokenizer.tokenize(action.text).distinct() } "stopwords" -> { return StopwordsBasedTokenizer.instance().tokenize(action.text).distinct() } "jieba" -> { return JiebaSegmenter().process(action.text, SegMode.SEARCH).mapNotNull { val result = it.word.trim() result.ifEmpty { null } } } else -> { val tokenizer = WordTokenizer() return tokenizer.tokenize(action.text).distinct() } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/processor/shell/ShireShellCommandRunner.kt ================================================ package com.phodal.shirelang.compiler.execute.processor.shell import com.intellij.execution.configurations.GeneralCommandLine import com.intellij.execution.process.CapturingProcessHandler import com.intellij.execution.process.OSProcessHandler import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.runReadAction import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.readText import com.intellij.psi.search.ProjectScope import com.phodal.shire.json.ShireEnvReader import com.phodal.shire.json.ShireEnvVariableFiller import java.io.File import java.nio.charset.StandardCharsets object ShireShellCommandRunner { private const val DEFAULT_TIMEOUT: Int = 30000 fun fill(project: Project, file: VirtualFile, processVariables: Map): String { return runReadAction { val scope = ProjectScope.getContentScope(project) val envName = ShireEnvReader.getAllEnvironments(project, scope).firstOrNull() ?: ShireEnvReader.DEFAULT_ENV_NAME val envObject = ShireEnvReader.getEnvObject(envName, scope, project) val content = file.readText() val envVariables: List> = ShireEnvReader.fetchEnvironmentVariables(envName, scope) val filledContent = ShireEnvVariableFiller.fillVariables(content, envVariables, envObject, processVariables) filledContent } } fun runShellCommand(virtualFile: VirtualFile, myProject: Project, processVariables: Map): String { val workingDirectory = virtualFile.parent.path val fileContent = fill(myProject, virtualFile, processVariables) val tempFile = File.createTempFile("tempScript", ".sh"); tempFile.writeText(fileContent) val commandLine: GeneralCommandLine = GeneralCommandLine() .withParentEnvironmentType(GeneralCommandLine.ParentEnvironmentType.CONSOLE) .withWorkDirectory(workingDirectory) .withCharset(StandardCharsets.UTF_8) .withExePath("sh") .withParameters(tempFile.path) val future = ApplicationManager.getApplication().executeOnPooledThread { val processOutput = runCatching { CapturingProcessHandler(commandLine).runProcess(DEFAULT_TIMEOUT) }.apply { deleteFileOnTermination(commandLine, tempFile) }.getOrThrow() val exitCode = processOutput.exitCode if (exitCode != 0) { throw RuntimeException("Cannot execute ${commandLine}: exit code $exitCode, error output: ${processOutput.stderr}") } processOutput.stdout } return try { future.get() // 阻塞获取结果,可以选择添加超时控制 } catch (e: Exception) { logger().error("Command execution failed", e) throw RuntimeException("Execution failed: ${e.message}", e) } } /** * We need to ensure that the file is deleted after the process is executed. * for example,the file also needs to be deleted when [create-process][OSProcessHandler.startProcess] fails. */ private fun deleteFileOnTermination(commandLine: GeneralCommandLine, tempFile: File) { // OSProcessHandler.deleteFileOnTermination(commandLine, tempFile) // is Internal API try { FileUtil.delete(tempFile) } catch (e: Exception) { // ignore } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/processor/ui/PendingApprovalPanel.kt ================================================ package com.phodal.shirelang.compiler.execute.processor.ui import com.intellij.openapi.ui.popup.JBPopup import com.intellij.ui.dsl.builder.panel import com.intellij.util.ui.JBUI import java.awt.event.InputEvent import java.awt.event.KeyEvent import javax.swing.JButton import javax.swing.JPanel import javax.swing.KeyStroke class PendingApprovalPanel : JPanel() { private val approveButton = JButton("Approve") private val rejectButton = JButton("Reject") init { val layoutBuilder = panel { row { label(getShortcutLabel("⌘ + ↵", "Ctrl + ↵")) cell(approveButton) label(getShortcutLabel("⌘ + ⌦", "Ctrl + Del")) cell(rejectButton) } } layoutBuilder.border = JBUI.Borders.empty(0, 10) this.add(layoutBuilder) } private fun getShortcutLabel(shortcutForMac: String, shortcutForOthers: String): String { return if (System.getProperty("os.name").contains("Mac")) { shortcutForMac } else { shortcutForOthers } } fun setupKeyShortcuts(popup: JBPopup, approve: (Any) -> Unit, reject: (Any) -> Unit) { approveButton.addActionListener(approve) approveButton.registerKeyboardAction( { popup.closeOk(null) approveButton.doClick() }, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.CTRL_DOWN_MASK), WHEN_IN_FOCUSED_WINDOW ) rejectButton.addActionListener(reject) rejectButton.registerKeyboardAction( { popup.closeOk(null) rejectButton.doClick() }, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, InputEvent.CTRL_DOWN_MASK), WHEN_IN_FOCUSED_WINDOW ) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/searcher/PatternSearcher.kt ================================================ package com.phodal.shirelang.compiler.execute.searcher import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.openapi.vfs.VirtualFileVisitor import com.intellij.util.io.URLUtil import java.util.regex.Pattern object PatternSearcher { private val cache: MutableMap> = mutableMapOf() /** * This function is used to find files in a given project that match a specified regular expression. * * @param project The project within which to search for files. This is an instance of the Project class. * @param regex The regular expression to match file names against. This is a string. * * The function first checks if the regular expression is already present in the cache. If it is, it returns the corresponding list of files. * If the regular expression is not in the cache, the function compiles the regular expression into a pattern. * It then refreshes the file system and gets the base directory of the project. * If the base directory is not null, it refreshes the file system and finds the file by path. * It then creates a visitor for each file in the base directory. If the file name matches the pattern, it is added to the list of matching files. * The function finally returns the list of matching files. * * @return A list of VirtualFile objects that match the specified regular expression. If no matching files are found, an empty list is returned. */ fun findFilesByRegex(project: Project, regex: String): List { if (cache.containsKey(regex)) { return cache[regex]!! } val pattern: Pattern = try { Pattern.compile(regex) } catch (e: Exception) { logger().error("Invalid regex: $regex") return emptyList() } val baseDir: VirtualFile = project.guessProjectDir() ?: return emptyList() val matchingFiles: MutableList = ArrayList() VirtualFileManager.getInstance().getFileSystem(URLUtil.FILE_PROTOCOL).refresh(false) VfsUtilCore.visitChildrenRecursively(baseDir, object : VirtualFileVisitor() { override fun visitFile(file: VirtualFile): Boolean { if (pattern.matcher(file.name).matches()) { matchingFiles.add(file) } return true } }) return matchingFiles } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/shireql/ShireDateSchema.kt ================================================ package com.phodal.shirelang.compiler.execute.shireql import kotlinx.datetime.* import kotlinx.datetime.TimeZone import java.util.* import java.time.format.DateTimeFormatter class ShireDateSchema : ShireQLSchema { private val date: Instant = Clock.System.now() fun now(): Long { return date.toEpochMilliseconds() } fun dayOfWeek(): Int { return Calendar.getInstance().get(Calendar.DAY_OF_WEEK) } fun year(): Int { return Calendar.getInstance().get(Calendar.YEAR) } fun month(): Int { return Calendar.getInstance().get(Calendar.MONTH) + 1 } fun dayOfMonth(): Int { return Calendar.getInstance().get(Calendar.DAY_OF_MONTH) } /** * Formats the date using the specified output format. * * ```shire * format("yyyy-MM-dd HH:mm:ss") * ``` */ fun format(outputFormat: String) : String { val localDateTime = date.toLocalDateTime(TimeZone.currentSystemDefault()) val formatter = DateTimeFormatter.ofPattern(outputFormat) return localDateTime.toJavaLocalDateTime().format(formatter) } override fun toString(): String { return "ShireDate(date=$date)" } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/shireql/ShireQLProcessor.kt ================================================ package com.phodal.shirelang.compiler.execute.shireql import com.intellij.openapi.application.runReadAction import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.phodal.shirecore.provider.variable.ShireQLInterpreter import com.phodal.shirelang.compiler.ast.MethodCall import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole import com.phodal.shirelang.compiler.execute.FunctionStatementProcessor import java.lang.reflect.Method import java.util.* class ShireQLProcessor(override val myProject: Project, hole: HobbitHole) : FunctionStatementProcessor(myProject, hole) { override fun invokeMethodOrField(methodCall: MethodCall, element: T): Any? { val methodName = methodCall.methodName.display() val objectName = methodCall.objectName.display() val methodArgs = methodCall.arguments if (element is PsiElement) { ShireQLInterpreter.provide(element.language)?.let { psiQLInterpreter -> val hasPqlInterpreter = psiQLInterpreter.supportsMethod(element.language, methodName).any { it == methodName } if (hasPqlInterpreter) { return runReadAction { psiQLInterpreter.resolveCall(element, methodName, methodCall.parameters() ?: emptyList()) } } } } val isField = methodArgs == null if (isField) { val field = element.javaClass.fields.find { it.name == methodName } if (field != null) { return field.get(element) } } // use reflection to call method val allMethods = element.javaClass.methods val method = allMethods.find { it.name == methodName } if (method != null) { if (methodArgs == null) { return method.invoke(element) } return method.invoke(element, methodArgs) } if (isField) { // maybe getter, we try to find getter, first upper case method name first letter val getterName = "get${ methodName.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } }" val getter = allMethods.find { it.name == getterName } if (getter != null) { return getter.invoke(element) } } // if not found, show error log return showErrorLog(allMethods, element, methodName, objectName) } private fun showErrorLog( allMethods: Array, element: T, methodName: String, objectName: String, ): Nothing? { val supportMethodNames: List = allMethods.map { it.name } val supportFieldNames: List = element.javaClass.fields.map { it.name } logger().error( """ method or field not found: $objectName.$methodName supported methods: $supportMethodNames supported fields: $supportFieldNames """.trimIndent() ) return null } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/shireql/ShireQLSchema.kt ================================================ package com.phodal.shirelang.compiler.execute.shireql interface ShireQLSchema { } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/variable/ShireQLFromType.kt ================================================ package com.phodal.shirelang.compiler.execute.variable enum class ShireQLFromType(val typeName: String) { // PSI Query PsiFile("PsiFile"), PsiPackage("PsiPackage"), PsiClass("PsiClass"), PsiMethod("PsiMethod"), PsiField("PsiField"), // GitQuery GitCommit("GitCommit"), GitBranch("GitBranch"), // Others Date("Date"), } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/variable/ShireQLVariableBuilder.kt ================================================ package com.phodal.shirelang.compiler.execute.variable import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.phodal.shirecore.provider.shire.ShireQLDataProvider import com.phodal.shirecore.provider.shire.ShireSymbolProvider import com.phodal.shirecore.variable.vcs.ShireGitCommit import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole import com.phodal.shirelang.compiler.ast.VariableElement import com.phodal.shirelang.compiler.execute.shireql.ShireDateSchema import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFunc class ShireQLVariableBuilder(val myProject: Project, hole: HobbitHole) { fun buildVariables(fromStmt: PatternActionFunc.From): Map> { return fromStmt.variables.associate { when { it.variableType.startsWith("Psi") -> { it.value to lookupElement(it) } it.variableType.startsWith("Git") -> { it.value to lookupVcsCommit(it) } it.variableType == ShireQLFromType.Date.typeName -> { it.value to createDateFunc(it) } else -> { it.value to lookupElement(it) } } } } // cache private val cache = mutableMapOf>() private fun lookupElement(it: VariableElement): List { if (cache.containsKey(it.variableType)) { return cache[it.variableType] ?: emptyList() } val elements: List = ShireSymbolProvider.all().flatMap { provider -> provider.lookupElementByName(myProject, it.variableType) ?: emptyList() } cache[it.variableType] = elements return elements } private fun lookupVcsCommit(it: VariableElement): List { val elements: List = ShireQLDataProvider.all().flatMap { provider -> provider.lookup(myProject, it.variableType) ?: emptyList() } return elements } private fun createDateFunc(it: VariableElement): List { return listOf(ShireDateSchema()) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/execute/variable/VariableEvaluator.kt ================================================ package com.phodal.shirelang.compiler.execute.variable import com.phodal.shirelang.compiler.ast.FrontMatterType class VariableEvaluator(val element: Any) { val valued: MutableMap = mutableMapOf() fun putValue(key: FrontMatterType, value: Any?) { valued[key] = value } fun getValue(key: FrontMatterType): Any? { return valued[key] } } class VariableContainerManager { val variables: MutableMap = mutableMapOf() fun putValue(key: Any, prop: FrontMatterType, value: Any) { if (!variables.containsKey(key)) { variables[key] = VariableEvaluator(value) } variables[key]?.putValue(prop, value) } fun getValue(key: Any, prop: FrontMatterType): Any? { return variables[key].let { it?.getValue(prop) } } fun isNotEmpty(): Boolean { return variables.isNotEmpty() } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/parser/HobbitHoleParser.kt ================================================ package com.phodal.shirelang.compiler.parser import com.intellij.openapi.application.runReadAction import com.intellij.openapi.diagnostic.logger import com.intellij.psi.PsiElement import com.intellij.psi.TokenType.WHITE_SPACE import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.elementType import com.phodal.shirelang.compiler.ast.* import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole import com.phodal.shirelang.compiler.ast.action.RuleBasedPatternAction import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFunc import com.phodal.shirelang.psi.* object HobbitHoleParser { private val logger = logger() fun hasFrontMatter(file: ShireFile): Boolean { return PsiTreeUtil.getChildrenOfTypeAsList(file, ShireFrontMatterHeader::class.java).isNotEmpty() } fun frontMatterOffset(file: ShireFile): Int { val frontMatterHeader = PsiTreeUtil.getChildrenOfTypeAsList(file, ShireFrontMatterHeader::class.java).firstOrNull() return frontMatterHeader?.textOffset ?: 0 } /** * Parses the given ShireFrontMatterHeader and returns a FrontMatterShireConfig object. * * @param psiElement the ShireFrontMatterHeader to be parsed * @return a FrontMatterShireConfig object if parsing is successful, null otherwise */ fun parse(psiElement: ShireFrontMatterHeader): HobbitHole? { return psiElement.children.firstOrNull()?.let { val fm = processFrontMatter(it.children) HobbitHole.from(fm) } } fun parse(file: ShireFile): HobbitHole? { return runReadAction { PsiTreeUtil.getChildrenOfTypeAsList(file, ShireFrontMatterHeader::class.java)?.firstOrNull()?.let { parse(it) } } } private fun processFrontMatter(frontMatterEntries: Array): MutableMap { val frontMatter: MutableMap = mutableMapOf() var lastKey = "" frontMatterEntries.forEach { entry -> entry.children.forEach { child -> when (child.elementType) { ShireTypes.LIFECYCLE_ID, ShireTypes.FRONT_MATTER_KEY, -> { lastKey = child.text } ShireTypes.FRONT_MATTER_VALUE -> { frontMatter[lastKey] = parseFrontMatterValue(child) ?: FrontMatterType.STRING("FrontMatter value parsing failed: ${child.text}") } ShireTypes.PATTERN_ACTION -> { frontMatter[lastKey] = parsePatternAction(child) ?: FrontMatterType.STRING("Pattern action parsing failed: ${child.text}") } ShireTypes.LOGICAL_AND_EXPR -> { frontMatter[lastKey] = parseLogicAndExprToType(child as ShireLogicalAndExpr) ?: FrontMatterType.STRING("Logical expression parsing failed: ${child.text}") } ShireTypes.LOGICAL_OR_EXPR -> { frontMatter[lastKey] = parseLogicOrExprToType(child as ShireLogicalOrExpr) ?: FrontMatterType.STRING("Logical expression parsing failed: ${child.text}") } ShireTypes.CALL_EXPR -> { parseExpr(child)?.let { frontMatter[lastKey] = FrontMatterType.EXPRESSION(it) } } ShireTypes.FUNCTION_STATEMENT -> { frontMatter[lastKey] = parseFunction(child as ShireFunctionStatement) } /** * For blocked when condition * * ```shire * when: { $filePath.contains("src/main/java") && $fileName.contains(".java") } * ``` */ ShireTypes.VARIABLE_EXPR -> { val childExpr = (child as? ShireVariableExpr)?.expr if (childExpr != null) { parseExpr(childExpr)?.let { frontMatter[lastKey] = FrontMatterType.EXPRESSION(it) } } } ShireTypes.LITERAL_EXPR -> { parseExpr(child)?.let { frontMatter[lastKey] = FrontMatterType.EXPRESSION(it) } } ShireTypes.FOREIGN_FUNCTION -> { parseForeignFunc(lastKey, child as ShireForeignFunction)?.let { frontMatter[lastKey] = FrontMatterType.EXPRESSION(it) } } else -> { logger.warn("processFrontMatter, Unknown FrontMatter type: ${child.elementType}, value: $child") } } } } return frontMatter } private fun parseForeignFunc(fmKey: String, function: ShireForeignFunction): Statement { val funcPath = function.foreignPath.quoteString.text.removeSurrounding("\"") val accessFuncName = function.foreignFuncName?.text ?: "" val inputTypes = function.foreignTypeList.map { it.text } val returnVars = function.foreignOutput?.children?.associate { it.text to "" } ?: emptyMap() return ForeignFunctionStmt(fmKey, funcPath, accessFuncName, inputTypes, returnVars) } private fun parseFunction(statement: ShireFunctionStatement): FrontMatterType { return when (val body = statement.functionBody?.firstChild) { is ShireQueryStatement -> { ShireAstQLParser.parse(body) } is ShireActionBody -> { val expressions = body.actionExprList.mapNotNull { parseExpr(it) }.map { FrontMatterType.EXPRESSION(it) } FrontMatterType.ARRAY(expressions) } null -> { FrontMatterType.EMPTY() } else -> { val expr = parseExpr(body) if (expr is Statement) { return FrontMatterType.EXPRESSION(expr) } logger.error("parseFunction, Unknown function type: ${body.elementType}") FrontMatterType.STRING("Unknown function type: ${body.elementType}") } } } private fun parseLogicAndExprToType(child: ShireLogicalAndExpr): FrontMatterType? { val logicalExpression = parseLogicAndExpr(child) ?: return null return FrontMatterType.EXPRESSION(logicalExpression) } private fun parseLogicAndExpr(child: ShireLogicalAndExpr): LogicalExpression? { val left = child.exprList?.firstOrNull() ?: return null val right = child.exprList?.lastOrNull() ?: return null val leftStmt = parseExpr(left) ?: return null val rightStmt = parseExpr(right) ?: return null val logicalExpression = LogicalExpression( left = leftStmt, operator = OperatorType.And, right = rightStmt ) return logicalExpression } private fun parseLogicOrExprToType(child: ShireLogicalOrExpr): FrontMatterType? { val logicOrExpr = parseLogicOrExpr(child) ?: return null return FrontMatterType.EXPRESSION(logicOrExpr) } private fun parseLogicOrExpr(child: ShireLogicalOrExpr): LogicalExpression? { val left = child.exprList.firstOrNull() ?: return null val right = child.exprList.lastOrNull() ?: return null val leftStmt = parseExpr(left) ?: return null val rightStmt = parseExpr(right) ?: return null val logicOrExpr = LogicalExpression( left = leftStmt, operator = OperatorType.Or, right = rightStmt ) return logicOrExpr } /** * This function is used to parse an expression of type PsiElement into a Statement. The type of Statement returned depends on the type of the expression. * * @param expr The PsiElement expression to be parsed. This expression can be of type CALL_EXPR, EQ_COMPARISON_EXPR, INEQ_COMPARISON_EXPR, or any other type. * * If the expression is of type CALL_EXPR, the function finds the first child of type ShireExpr and builds a method call with the found ShireExpr and the list of expressions in the ShireCallExpr. * * If the expression is of type EQ_COMPARISON_EXPR, the function parses the first and last child of the expression into a Comparison statement with an equal operator. * * If the expression is of type INEQ_COMPARISON_EXPR, the function parses the first and last child of the expression into a Comparison statement with an operator determined by the ineqComparisonOp text of the ShireIneqComparisonExpr. * * If the expression is of any other type, the function logs a warning and returns a Comparison statement with an equal operator and empty string operands. * * @return A Statement parsed from the given expression. The type of Statement depends on the type of the expression. */ fun parseExpr(expr: PsiElement): Statement? = when (expr) { is ShireCallExpr -> { val expressionList = expr.expressionList val hasParentheses = expressionList?.prevSibling?.text == "(" buildMethodCall(expr.refExpr, expressionList?.children, hasParentheses) } is ShireEqComparisonExpr -> { val variable = parseRefExpr(expr.children.firstOrNull()) val value = parseRefExpr(expr.children.lastOrNull()) Comparison(variable, Operator(OperatorType.Equal), value) } is ShireIneqComparisonExpr -> { val variable = parseRefExpr(expr.children.firstOrNull()) val value = parseRefExpr(expr.children.lastOrNull()) val operatorType = OperatorType.fromString(expr.ineqComparisonOp.text) Comparison(variable, Operator(operatorType), value) } is ShireLogicalAndExpr -> { parseLogicAndExpr(expr) ?: Comparison(FrontMatterType.STRING(""), Operator(OperatorType.Equal), FrontMatterType.STRING("")) } is ShireLogicalOrExpr -> { parseLogicOrExpr(expr) ?: Comparison(FrontMatterType.STRING(""), Operator(OperatorType.Equal), FrontMatterType.STRING("")) } is ShireRefExpr -> { if (expr.expr == null) { Value(FrontMatterType.IDENTIFIER(expr.identifier.text)) } else { val methodCall = buildMethodCall(expr, null, false) methodCall } } is ShireLiteralExpr -> { Value(parseLiteral(expr)) } is ShireActionExpr -> { when (expr.firstChild) { is ShireFuncCall -> { val args = parseParameters(expr.funcCall) MethodCall(FrontMatterType.IDENTIFIER(expr.funcCall!!.funcName.text), FrontMatterType.EMPTY(), args) } is ShireCaseBody -> { parseExprCaseBody(expr.firstChild as ShireCaseBody) } else -> { logger.warn("parseExpr, Unknown action expression type: ${expr.firstChild.elementType}") null } } } is ShireConditionStatement -> { val condition = parseLiteral(expr.caseCondition) val body = parseRefExpr(expr.expr) when (body) { is FrontMatterType.EXPRESSION -> { CaseKeyValue(condition, body) } is FrontMatterType.STRING -> { CaseKeyValue(condition, FrontMatterType.EXPRESSION(Value(body))) } else -> { logger.warn("parseExpr, Unknown condition type: ${expr.expr?.elementType}") null } } } else -> { logger.warn("parseExpr, Unknown expression type: ${expr.elementType}") null } } private fun parseExprCaseBody(caseBody: ShireCaseBody): ConditionCase? { val condition = caseBody.conditionFlag?.conditionStatementList?.mapNotNull { val condition = parseExpr(it) if (condition != null) { FrontMatterType.EXPRESSION(condition) } else { logger.warn("parseExprCaseBody, Unknown condition type: ${it.elementType}") null } } ?: emptyList() val body = caseBody.casePatternActionList.mapNotNull { val key = parseLiteral(it.caseCondition) val processor = parseActionBodyFuncCall(it.actionBody.actionExprList) FrontMatterType.EXPRESSION(CaseKeyValue(key, FrontMatterType.EXPRESSION(processor))) } return ConditionCase(condition, body) } private fun parseRefExpr(expr: PsiElement?): FrontMatterType { return when (expr) { is ShireLiteralExpr -> { parseLiteral(expr) } // fake refExpr ::= expr? '.' IDENTIFIER is ShireRefExpr -> { if (expr.expr == null) { FrontMatterType.IDENTIFIER(expr.identifier.text) } else { val methodCall = buildMethodCall(expr, null, false) FrontMatterType.EXPRESSION(methodCall) } } is ShireCallExpr -> { val expressionList = expr.expressionList val hasParentheses = expressionList?.prevSibling?.text == "(" val methodCall = buildMethodCall(expr.refExpr, expressionList?.children, hasParentheses) FrontMatterType.EXPRESSION(methodCall) } is ShireIneqComparisonExpr -> { val variable = parseRefExpr(expr.children.firstOrNull()) val value = parseRefExpr(expr.children.lastOrNull()) val operator = Operator(OperatorType.fromString(expr.ineqComparisonOp.text)) val comparison = Comparison(variable, operator, value) FrontMatterType.EXPRESSION(comparison) } is ShireLogicalAndExpr -> { parseLogicAndExpr(expr)?.let { FrontMatterType.EXPRESSION(it) } ?: FrontMatterType.ERROR("cannot parse ShireLogicalAndExpr: ${expr.text}") } is ShireLogicalOrExpr -> { parseLogicOrExpr(expr)?.let { FrontMatterType.EXPRESSION(it) } ?: FrontMatterType.ERROR("cannot parse ShireLogicalOrExpr: ${expr.text}") } is ShireEqComparisonExpr -> { val variable = parseRefExpr(expr.children.firstOrNull()) val value = parseRefExpr(expr.children.lastOrNull()) val operator = Operator(OperatorType.Equal) val comparison = Comparison(variable, operator, value) FrontMatterType.EXPRESSION(comparison) } else -> { logger.warn("parseRefExpr, Unknown expression type: ${expr?.elementType}") FrontMatterType.STRING("") } } } private fun buildMethodCall( refExpr: ShireRefExpr, expressionList: Array?, hasParentheses: Boolean, ): MethodCall { val left = if (refExpr.expr == null) { FrontMatterType.IDENTIFIER(refExpr.identifier.text) } else { parseRefExpr(refExpr.expr) } val id = refExpr.expr?.nextSibling?.nextSibling val right = FrontMatterType.IDENTIFIER(id?.text ?: "") var args = expressionList?.map { parseRefExpr(it) } // fix for () lost in display() if (hasParentheses && args == null) { args = emptyList() } return MethodCall(left, right, args) } private fun parseLiteral(ref: PsiElement): FrontMatterType { val firstChild = ref.firstChild return when (firstChild.elementType) { ShireTypes.IDENTIFIER -> { FrontMatterType.IDENTIFIER(ref.text) } ShireTypes.NUMBER -> { FrontMatterType.NUMBER(ref.text.toInt()) } ShireTypes.QUOTE_STRING -> { val value = ref.text.substring(1, ref.text.length - 1) FrontMatterType.STRING(value) } ShireTypes.VARIABLE_START -> { val next = ref.lastChild FrontMatterType.VARIABLE(next.text) } ShireTypes.DEFAULT -> { FrontMatterType.IDENTIFIER(ref.text) } else -> { logger.warn("parseLiteral, Unknown ref type: ${firstChild.elementType}") FrontMatterType.STRING(ref.text) } } } private fun parseFrontMatterValue(element: PsiElement): FrontMatterType? { when (element) { is ShireObjectKeyValue -> { val map: MutableMap = mutableMapOf() element.children.mapNotNull { if (it.elementType == ShireTypes.KEY_VALUE) { processFrontMatter(it.children) } else { null } }.forEach { map.putAll(it) } return FrontMatterType.OBJECT(map) } } return when (element.firstChild.elementType) { ShireTypes.IDENTIFIER -> { FrontMatterType.IDENTIFIER(element.text) } ShireTypes.DATE -> { FrontMatterType.DATE(element.text) } ShireTypes.QUOTE_STRING -> { val value = element.text.substring(1, element.text.length - 1) FrontMatterType.STRING(value) } ShireTypes.NUMBER -> { FrontMatterType.NUMBER(element.text.toInt()) } ShireTypes.BOOLEAN -> { FrontMatterType.BOOLEAN(element.text.toBoolean()) } ShireTypes.FRONT_MATTER_ARRAY -> { val array: List = parseArray(element) FrontMatterType.ARRAY(array) } ShireTypes.NEWLINE -> { return parseFrontMatterValue(element.firstChild.nextSibling) } ShireTypes.LBRACKET, ShireTypes.RBRACKET, ShireTypes.COMMA, WHITE_SPACE, null, -> { null } else -> { logger.warn("parseFrontMatterValue, Unknown frontmatter type: ${element.firstChild}") null } } } private fun parsePatternAction(element: PsiElement): FrontMatterType? { val pattern = element.children.firstOrNull()?.text ?: return null val actionBlock = PsiTreeUtil.getChildOfType(element, ShireActionBlock::class.java) val actionBody = actionBlock?.actionBody ?: return null val processor: List = parseActionBodyFuncCall(actionBody.actionExprList).processors return FrontMatterType.PATTERN(RuleBasedPatternAction(pattern, processor)) } private fun parseActionBodyFuncCall(shireActionExprs: List?): Processor { val processor: MutableList = mutableListOf() shireActionExprs?.forEach { expr: ShireActionExpr -> expr.funcCall?.let { funcCall -> parseActionBodyFunCall(funcCall)?.let { processor.add(it) } } expr.caseBody?.let { caseBody -> parseExprCaseBody(caseBody)?.let { conditionCase -> val cases = conditionCase.cases.map { (it as FrontMatterType.EXPRESSION).value as CaseKeyValue } processor.add(PatternActionFunc.CaseMatch(cases)) } } } return Processor(processor) } private fun parseActionBodyFunCall(funcCall: ShireFuncCall?): PatternActionFunc? { val args = parseParameters(funcCall) ?: emptyList() val funcName = funcCall?.funcName?.text ?: return null return PatternActionFunc.from(funcName, args) } private fun parseParameters(funcCall: ShireFuncCall?): List? = runReadAction { PsiTreeUtil.findChildOfType(funcCall, ShirePipelineArgs::class.java) ?.let { it.pipelineArgList.map { arg -> arg } }?.map { when (it.firstChild.elementType) { ShireTypes.QUOTE_STRING -> it.text .removeSurrounding("\"") .removeSurrounding("'") ShireTypes.IDENTIFIER -> it.text.removeSurrounding("\"") else -> it.text } } } private fun parseArray(element: PsiElement): List { val array = mutableListOf() var arrayElement: PsiElement? = element.children.firstOrNull()?.firstChild while (arrayElement != null) { parseFrontMatterValue(arrayElement)?.let { array.add(it) } arrayElement = arrayElement.nextSibling } return array } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/parser/ShireAstQLParser.kt ================================================ package com.phodal.shirelang.compiler.parser import com.phodal.shirelang.compiler.ast.FrontMatterType import com.phodal.shirelang.compiler.ast.ShirePsiQueryStatement import com.phodal.shirelang.compiler.ast.Statement import com.phodal.shirelang.compiler.ast.VariableElement import com.phodal.shirelang.psi.ShireFromClause import com.phodal.shirelang.psi.ShireQueryStatement import com.phodal.shirelang.psi.ShireSelectClause import com.phodal.shirelang.psi.ShireWhereClause object ShireAstQLParser { fun parse(statement: ShireQueryStatement): FrontMatterType { val value = ShirePsiQueryStatement( parseFrom(statement.fromClause), parseWhere(statement.whereClause)!!, parseSelect(statement.selectClause) ) return FrontMatterType.QUERY_STATEMENT(value) } private fun parseFrom(fromClause: ShireFromClause): List { return fromClause.psiElementDecl.psiVarDeclList.map { VariableElement(it.psiType.identifier.text, it.identifier.text) } } private fun parseWhere(whereClause: ShireWhereClause): Statement? { return HobbitHoleParser.parseExpr(whereClause.expr) } private fun parseSelect(selectClause: ShireSelectClause): List { return selectClause.exprList.mapNotNull { HobbitHoleParser.parseExpr(it) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/parser/ShireError.kt ================================================ package com.phodal.shirelang.compiler.parser const val SHIRE_ERROR = "" ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/parser/ShireParsedResult.kt ================================================ package com.phodal.shirelang.compiler.parser import com.phodal.shirecore.agent.CustomAgent import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole import com.phodal.shirelang.compiler.variable.VariableTable import com.phodal.shirelang.psi.ShireFile data class ShireParsedResult( /** * The origin Shire content */ var sourceCode: String = "", /** * Output String of a compiler result, not the final result */ var shireOutput: String = "", /** * Is local command only */ var isLocalCommand: Boolean = false, /** * Has error */ var hasError: Boolean = false, /** * Execute agent */ var executeAgent: CustomAgent? = null, /** * Next job file to be executed */ var nextJob: ShireFile? = null, /** * The frontmatter of the file, which contains the configuration of Shire */ var config: HobbitHole? = null, /** * Symbol table for all variables */ var variableTable: VariableTable = VariableTable(), ) ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/parser/ShireSyntaxAnalyzer.kt ================================================ package com.phodal.shirelang.compiler.parser import com.intellij.lang.parser.GeneratedParserUtilBase.DUMMY_BLOCK import com.intellij.openapi.application.runReadAction import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement import com.intellij.psi.TokenType.WHITE_SPACE import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.elementType import com.phodal.shirecore.agent.CustomAgent import com.phodal.shirelang.compiler.execute.command.* import com.phodal.shirelang.compiler.template.TemplateCompiler import com.phodal.shirelang.compiler.variable.VariableTable import com.phodal.shirelang.completion.dataprovider.BuiltinCommand import com.phodal.shirelang.completion.dataprovider.CustomCommand import com.phodal.shirelang.parser.CodeBlockElement import com.phodal.shirelang.psi.* import com.phodal.shirelang.psi.ShireTypes.MARKDOWN_HEADER import kotlinx.coroutines.runBlocking import java.util.* val CACHED_COMPILE_RESULT = mutableMapOf() /** * ShireCompiler class is responsible for compiling Shire files by processing different elements such as text segments, newlines, code blocks, used commands, comments, agents, variables, and builtin commands. * It takes a Project, ShireFile, Editor, and PsiElement as input parameters. * The compile() function processes the elements in the ShireFile and generates a ShireCompiledResult object containing the compiled output. * The processUsed() function handles the processing of used commands, agents, and variables within the ShireFile. * The processingCommand() function executes the specified builtin command with the provided properties and updates the output accordingly. * The lookupNextCode() function looks up the next code block element following a used command. * The lookupNextTextSegment() function looks up the next text segment following a used command. */ class ShireSyntaxAnalyzer( private val myProject: Project, private val file: ShireFile, private val editor: Editor? = null, private val element: PsiElement? = null, ) { private var skipNextCode: Boolean = false private val logger = logger() private val result = ShireParsedResult() private val output: StringBuilder = StringBuilder() private val variableTable = VariableTable() companion object { const val FLOW_FALG = "[flow]:" } /** * @return ShireCompiledResult object containing the compiled result */ fun parseAndExecuteLocalCommand(): ShireParsedResult { result.sourceCode = file.text val iterator = file.children.iterator() while (iterator.hasNext()) { val psiElement = iterator.next() when (psiElement.elementType) { ShireTypes.TEXT_SEGMENT -> output.append(psiElement.text) ShireTypes.NEWLINE -> output.append("\n") ShireTypes.CODE -> { if (skipNextCode) { skipNextCode = false continue } output.append(psiElement.text) } ShireTypes.USED -> processUsed(psiElement as ShireUsed) ShireTypes.COMMENTS -> { if (psiElement.text.startsWith(FLOW_FALG)) { val fileName = psiElement.text.substringAfter(FLOW_FALG).trim() val content = myProject.guessProjectDir()?.findFileByRelativePath(fileName)?.let { virtualFile -> virtualFile.inputStream.bufferedReader().use { reader -> reader.readText() } } if (content != null) { val shireFile = ShireFile.fromString(myProject, content) result.nextJob = shireFile } } } ShireTypes.FRONTMATTER_START -> { val nextElement = PsiTreeUtil.findChildOfType( psiElement.parent, ShireFrontMatterHeader::class.java ) ?: continue result.config = HobbitHoleParser.parse(nextElement) } ShireTypes.FRONT_MATTER_HEADER -> { result.config = HobbitHoleParser.parse(psiElement as ShireFrontMatterHeader) } WHITE_SPACE, DUMMY_BLOCK -> output.append(psiElement.text) ShireTypes.VELOCITY_EXPR -> { processVelocityExpr(psiElement as ShireVelocityExpr) logger.info("Velocity expression found: ${psiElement.text}") } MARKDOWN_HEADER -> { output.append("#[[${psiElement.text}]]#") } else -> { output.append(psiElement.text) logger.warn("Unknown element type: ${psiElement.elementType}, text: ${psiElement.text}") } } } result.shireOutput = output.toString() result.variableTable = variableTable CACHED_COMPILE_RESULT[file.name] = result return result } private fun processVelocityExpr(velocityExpr: ShireVelocityExpr) { handleNextSiblingForChild(velocityExpr) { next -> if (next is ShireIfExpr) { handleNextSiblingForChild(next) { when (it) { is ShireIfClause, is ShireElseifClause, is ShireElseClause -> { handleNextSiblingForChild(it, ::processIfClause) } else -> output.append(it.text) } } } else { output.append(next.text) } } } private fun processIfClause(clauseContent: PsiElement) { when (clauseContent) { is ShireExpr -> { addVariable(clauseContent) if (!result.hasError) output.append(clauseContent.text) } is ShireVelocityBlock -> { ShireFile.fromString(myProject, clauseContent.text).let { file -> ShireSyntaxAnalyzer(myProject, file).parseAndExecuteLocalCommand().let { output.append(it.shireOutput) variableTable.addVariable(it.variableTable) result.hasError = it.hasError } } } else -> { output.append(clauseContent.text) } } } private fun addVariable(psiElement: PsiElement?) { if (psiElement == null) return val queue = LinkedList() queue.push(psiElement) while (!queue.isEmpty() && !result.hasError) { val e = queue.pop() if (e.firstChild.elementType == ShireTypes.VARIABLE_START) { processVariable(e.firstChild) } else { e.children.forEach { queue.push(it) } } } } private fun handleNextSiblingForChild(element: PsiElement?, handle: (PsiElement) -> Unit) { var child: PsiElement? = element?.firstChild while (child != null && !result.hasError) { handle(child) child = child.nextSibling } } private fun processUsed(used: ShireUsed) { val firstChild = used.firstChild val id = firstChild.nextSibling when (firstChild.elementType) { ShireTypes.COMMAND_START -> { val command = BuiltinCommand.fromString(id?.text ?: "") if (command == null) { CustomCommand.fromString(myProject, id?.text ?: "")?.let { cmd -> ShireFile.fromString(myProject, cmd.content).let { file -> ShireSyntaxAnalyzer(myProject, file).parseAndExecuteLocalCommand().let { output.append(it.shireOutput) result.hasError = it.hasError } } return } output.append(used.text) logger.warn("Unknown command: ${id?.text}") result.hasError = true return } if (!command.requireProps) { processingCommand(command, "", used, fallbackText = used.text) return } val propElement = id.nextSibling?.nextSibling val isProp = (propElement.elementType == ShireTypes.COMMAND_PROP) if (!isProp) { output.append(used.text) logger.warn("No command prop found: ${used.text}") result.hasError = true return } processingCommand(command, propElement!!.text, used, fallbackText = used.text) } ShireTypes.AGENT_START -> { val shireAgentId = id as ShireAgentId val configs = CustomAgent.loadFromProject(myProject).filter { it.name == shireAgentId.quoteString?.text?.removeSurrounding("\"")?.removeSurrounding("'") || it.name == shireAgentId.identifier?.text } if (configs.isNotEmpty()) { result.executeAgent = configs.first() } } ShireTypes.VARIABLE_START -> { processVariable(firstChild) if (!result.hasError) output.append(used.text) } else -> { logger.warn("Unknown [cc.unitmesh.devti.language.psi.ShireUsed] type: ${firstChild.elementType}") output.append(used.text) } } } private fun processVariable(variableStart: PsiElement) { if (variableStart.elementType != ShireTypes.VARIABLE_START) { logger.warn("Illegal type: ${variableStart.elementType}") return } val variableId = variableStart.nextSibling?.text val currentEditor = editor ?: TemplateCompiler.defaultEditor(myProject) val currentElement = element ?: TemplateCompiler.defaultElement(myProject, currentEditor) if (currentElement == null) { output.append("$SHIRE_ERROR No element found for variable: ${variableStart.text}") result.hasError = true return } val lineNo = try { runReadAction { val containingFile = currentElement.containingFile val document: Document? = PsiDocumentManager.getInstance(variableStart.project).getDocument(containingFile) document?.getLineNumber(variableStart.textRange.startOffset) ?: 0 } } catch (e: Exception) { 0 } variableTable.addVariable(variableId ?: "", VariableTable.VariableType.String, lineNo) } private fun processingCommand(commandNode: BuiltinCommand, prop: String, used: ShireUsed, fallbackText: String) { val command: ShireCommand = when (commandNode) { BuiltinCommand.FILE -> { FileShireCommand(myProject, prop) } BuiltinCommand.REV -> { RevShireCommand(myProject, prop) } BuiltinCommand.SYMBOL -> { result.isLocalCommand = true SymbolShireCommand(myProject, prop) } BuiltinCommand.WRITE -> { result.isLocalCommand = true val shireCode: CodeBlockElement? = lookupNextCode(used) if (shireCode == null) { PrintShireCommand("/" + commandNode.commandName + ":" + prop) } else { WriteShireCommand(myProject, prop, shireCode.codeText(), used) } } BuiltinCommand.PATCH -> { result.isLocalCommand = true val shireCode: CodeBlockElement? = lookupNextCode(used) if (shireCode == null) { PrintShireCommand("/" + commandNode.commandName + ":" + prop) } else { PatchShireCommand(myProject, prop, shireCode.codeText()) } } BuiltinCommand.COMMIT -> { result.isLocalCommand = true val shireCode: CodeBlockElement? = lookupNextCode(used) if (shireCode == null) { PrintShireCommand("/" + commandNode.commandName + ":" + prop) } else { CommitShireCommand(myProject, shireCode.codeText()) } } BuiltinCommand.RUN -> { result.isLocalCommand = true RunShireCommand(myProject, prop) } BuiltinCommand.FILE_FUNC -> { result.isLocalCommand = true FileFuncShireCommand(myProject, prop) } BuiltinCommand.SHELL -> { result.isLocalCommand = true ShellShireCommand(myProject, prop) } BuiltinCommand.BROWSE -> { result.isLocalCommand = true BrowseShireCommand(myProject, prop) } BuiltinCommand.REFACTOR -> { result.isLocalCommand = true val nextTextSegment = lookupNextTextSegment(used) RefactorShireCommand(myProject, prop, nextTextSegment) } BuiltinCommand.GOTO -> { result.isLocalCommand = true GotoShireCommand(myProject, prop, used) } BuiltinCommand.STRUCTURE -> { result.isLocalCommand = true StructureShireCommand(myProject, prop) } BuiltinCommand.DATABASE -> { result.isLocalCommand = true val shireCode: String? = lookupNextCode(used)?.text DatabaseShireCommand(myProject, prop, shireCode) } BuiltinCommand.DIR -> { result.isLocalCommand = true DirShireCommand(myProject, prop) } BuiltinCommand.LOCAL_SEARCH -> { result.isLocalCommand = true val shireCode: String? = lookupNextCode(used)?.text LocalSearchShireCommand(myProject, prop, shireCode) } BuiltinCommand.RELATED -> { result.isLocalCommand = true RelatedSymbolInsCommand(myProject, prop) } BuiltinCommand.OPEN -> { result.isLocalCommand = true OpenShireCommand(myProject, prop) } BuiltinCommand.RIPGREP_SEARCH -> { result.isLocalCommand = true val shireCode: String? = lookupNextCode(used)?.text RipgrepSearchShireCommand(myProject, prop, shireCode) } } val execResult = runBlocking { command.doExecute() } val isSucceed = execResult?.contains(SHIRE_ERROR) == false val result = if (isSucceed) { val hasReadCodeBlock = commandNode in listOf( BuiltinCommand.WRITE, BuiltinCommand.PATCH, BuiltinCommand.COMMIT, BuiltinCommand.DATABASE, ) if (hasReadCodeBlock) { skipNextCode = true } execResult } else { execResult ?: fallbackText } output.append(result) } private fun lookupNextCode(used: ShireUsed): CodeBlockElement? { val shireCode: CodeBlockElement? var next: PsiElement? = used while (true) { next = next?.nextSibling if (next == null) { shireCode = null break } if (next.elementType == ShireTypes.CODE) { shireCode = next as CodeBlockElement break } } return shireCode } private fun lookupNextTextSegment(used: ShireUsed): String { val textSegment: StringBuilder = StringBuilder() var next: PsiElement? = used while (true) { next = next?.nextSibling if (next == null) { break } if (next.elementType == ShireTypes.TEXT_SEGMENT) { textSegment.append(next.text) break } } return textSegment.toString() } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/template/ShireVariableTemplateCompiler.kt ================================================ package com.phodal.shirelang.compiler.template import com.intellij.openapi.application.runReadAction import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiManager import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole import com.phodal.shirelang.compiler.variable.VariableTable import com.phodal.shirelang.compiler.variable.resolver.CompositeVariableResolver import com.phodal.shirelang.compiler.variable.resolver.base.VariableResolverContext /** * The `ShireTemplateCompiler` class is responsible for compiling templates in a Kotlin project. * It takes a `Project`, a `HobbitHole`, a `SymbolTable`, and an `input` string as parameters. */ class ShireVariableTemplateCompiler( private val myProject: Project, private val hole: HobbitHole?, private val variableTable: VariableTable, private val input: String, private val editor: Editor?, ) { private val customVariables: MutableMap = mutableMapOf() var compiledVariables: Map = mapOf() suspend fun compile(): String { val currentEditor = editor ?: TemplateCompiler.defaultEditor(myProject) if (currentEditor != null) { val prompt = doExecuteCompile(currentEditor) return cleanUp(prompt) } return input } suspend fun doExecuteCompile(editor: Editor): String { // val record = VariableSnapshotRecorder.getInstance(myProject) val additionalMap: Map = compileVariable(editor, customVariables) // record.printSnapshot() compiledVariables = additionalMap.mapValues { it.value.toString() } val file = runReadAction { PsiManager.getInstance(myProject).findFile(editor.virtualFile ?: return@runReadAction null) } val templateCompiler = TemplateCompiler(file?.language, file) templateCompiler.putAll(additionalMap) templateCompiler.putAll(customVariables) return templateCompiler.compile(input) } suspend fun compileVariable(editor: Editor, customVariables: MutableMap): Map { val context = VariableResolverContext(myProject, editor, hole, variableTable, null) return CompositeVariableResolver(context).resolve(customVariables) } fun putCustomVariable(varName: String, varValue: String) { customVariables[varName] = varValue } private fun cleanUp(prompt: String) = prompt.trim().replace("\n\n\n", "\n\n") } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/template/TemplateCompiler.kt ================================================ package com.phodal.shirelang.compiler.template import com.intellij.lang.Language import com.intellij.openapi.application.ReadAction import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.editor.Editor import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import org.apache.velocity.VelocityContext import org.apache.velocity.app.Velocity import java.io.StringWriter class TemplateCompiler( val language: Language?, val file: PsiFile?, ) { private val logger = logger() private val variableMap: MutableMap = mutableMapOf() fun putAll(map: Map) { variableMap.putAll(map) } fun compile(template: String): String { val oldContextClassLoader = Thread.currentThread().contextClassLoader Thread.currentThread().contextClassLoader = TemplateCompiler::class.java.classLoader // for compatibility with older versions of AutoDev val context = VelocityContext(variableMap as Map?) val sw = StringWriter() try { // for compatibility with older versions of AutoDev context.put("context", variableMap) Velocity.evaluate(context, sw, "#" + this.javaClass.name, template) } catch (e: Exception) { logger.error("Failed to compile template: $template", e) sw.write(template) } Thread.currentThread().contextClassLoader = oldContextClassLoader return sw.toString() } companion object { /** * This function returns the default editor for the given project. * It takes a Project object as a parameter and returns an Editor object. * It uses the FileEditorManager to get the selected text editor for the project. * If no editor is selected, it returns null. */ fun defaultEditor(myProject: Project): Editor? { return FileEditorManager.getInstance(myProject).selectedTextEditor } /** * This function returns the PsiElement at the current caret position in the editor. * * @param myProject the project to which the editor belongs * @param currentEditor the current editor where the caret position is located * @return the PsiElement at the current caret position, or null if not found */ fun defaultElement(myProject: Project, currentEditor: Editor?): PsiElement? = ReadAction.compute { currentEditor?.caretModel?.currentCaret?.offset?.let { val psiFile = currentEditor.let { editor -> val psiFile = editor.virtualFile?.let { file -> PsiManager.getInstance(myProject).findFile(file) } psiFile } ?: return@let null psiFile.findElementAt(it) ?: return@let psiFile } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/variable/CompositeVariableProvider.kt ================================================ package com.phodal.shirelang.compiler.variable import com.phodal.shirecore.provider.variable.model.ContextVariable import com.phodal.shirecore.provider.variable.model.PsiContextVariable import com.phodal.shirecore.provider.variable.model.ToolchainVariable import com.phodal.shirecore.provider.variable.model.toolchain.VcsToolchainVariable import com.phodal.shirecore.provider.variable.model.SystemInfoVariable import com.phodal.shirecore.provider.variable.model.toolchain.DatabaseToolchainVariable data class VariableDisplay( val name: String, val description: String, val priority: Double = 0.0 ) object CompositeVariableProvider { fun all(): List { val results = mutableListOf() ContextVariable.entries.forEach { results.add(VariableDisplay(it.variableName, it.description, 99.0)) } PsiContextVariable.entries.forEach { results.add(VariableDisplay(it.variableName, it.description ?: "", 90.0)) } VcsToolchainVariable.entries.forEach { results.add(VariableDisplay(it.variableName, it.description, 80.0)) } DatabaseToolchainVariable.entries.forEach { results.add(VariableDisplay(it.variableName, it.description, 70.0)) } ToolchainVariable.all().forEach { results.add(VariableDisplay(it.variableName, it.description, 70.0)) } SystemInfoVariable.all().forEach { results.add(VariableDisplay(it.variableName, it.description, 60.0)) } return results } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/variable/VariableTable.kt ================================================ package com.phodal.shirelang.compiler.variable class VariableTable { private val table: MutableMap = mutableMapOf() fun addVariable(name: String, varType: VariableType, lineDeclared: Int, scope: VariableScope = VariableScope.BuiltIn) { var varName = name; // {context.frameworkContext} if (varName.startsWith("{") && varName.endsWith("}")) { varName = varName.substring(1, varName.length - 1) } // remove the context prefix if (varName.startsWith("context.")) { varName = varName.substring(8) } if (!table.containsKey(varName)) { table[varName] = VariableInfo(varType, scope, lineDeclared) } else { // Ignore duplicate keys to avoid startup failures. // Affected: the lineDeclared value is only valid for the first time, but it is not used anywhere yet. // throw Exception("Variable $varName already declared.") } } fun addVariable(variableTable: VariableTable) { variableTable.getAllVariables().forEach { table[it.key] = it.value } } fun getVariable(name: String): VariableInfo { return table[name] ?: throw Exception("Variable $name not found.") } fun updateVariable(name: String, newType: VariableType? = null, newScope: VariableScope? = null, newLineDeclared: Int? = null) { val variable = table[name] ?: throw Exception("Variable $name not found.") val updatedVariable = variable.copy( type = newType ?: variable.type, scope = newScope ?: variable.scope, lineDeclared = newLineDeclared ?: variable.lineDeclared ) table[name] = updatedVariable } fun removeVariable(name: String) { if (table.containsKey(name)) { table.remove(name) } else { throw Exception("Variable $name not found.") } } fun getAllVariables(): Map = table.toMap() data class VariableInfo( val type: VariableType, val scope: VariableScope, val lineDeclared: Int ) enum class VariableType { String, Boolean, Number, } enum class VariableScope { BuiltIn, UserDefined } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/variable/resolver/CompositeVariableResolver.kt ================================================ package com.phodal.shirelang.compiler.variable.resolver import com.intellij.openapi.application.ReadAction import com.intellij.psi.PsiElement import com.phodal.shirecore.middleware.select.SelectElementStrategy import com.phodal.shirelang.compiler.variable.resolver.base.VariableResolver import com.phodal.shirelang.compiler.variable.resolver.base.VariableResolverContext class CompositeVariableResolver(private val context: VariableResolverContext) : VariableResolver { init { context.element = ReadAction.compute { SelectElementStrategy.resolvePsiElement(context.myProject, context.editor) } } override suspend fun resolve(initVariables: Map): Map { val resolverList = listOf( PsiContextVariableResolver(context), ToolchainVariableResolver(context), ContextVariableResolver(context), SystemInfoVariableResolver(context), UserCustomVariableResolver(context), ) val initial = initVariables.toMutableMap() return resolverList.fold(initial) { acc: MutableMap, resolver: VariableResolver -> acc.putAll(resolver.resolve(acc)) acc } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/variable/resolver/ContextVariableResolver.kt ================================================ package com.phodal.shirelang.compiler.variable.resolver import com.intellij.lang.Language import com.intellij.openapi.application.ReadAction import com.intellij.openapi.editor.CaretModel import com.intellij.psi.PsiNameIdentifierOwner import com.phodal.shirelang.compiler.variable.resolver.base.VariableResolver import com.phodal.shirelang.compiler.variable.resolver.base.VariableResolverContext import com.phodal.shirecore.provider.variable.model.ContextVariable import com.phodal.shirecore.provider.variable.model.ContextVariable.* class ContextVariableResolver( private val context: VariableResolverContext, ) : VariableResolver { fun all(): List = entries override suspend fun resolve(initVariables: Map): Map = ReadAction.compute, Throwable> { val file = context.element?.containingFile val caretModel = context.editor.caretModel all().associate { variable -> variable.variableName to when (variable) { SELECTION -> context.editor.selectionModel.selectedText ?: context.editor.document.text.substring(caretModel.offset) SELECTION_WITH_NUM -> { lineNumberedSelection(caretModel) } BEFORE_CURSOR -> file?.text?.substring(0, caretModel.offset) ?: context.editor.document.text.substring(0, caretModel.offset) AFTER_CURSOR -> file?.text?.substring(caretModel.offset) ?: context.editor.document.text.substring(caretModel.offset) FILE_NAME -> file?.name ?: "" FILE_PATH -> file?.virtualFile?.path ?: "" METHOD_NAME -> when (context.element) { is PsiNameIdentifierOwner -> (context.element as PsiNameIdentifierOwner).nameIdentifier?.text ?: "" else -> "" } LANGUAGE -> context.element?.language?.displayName ?: "" COMMENT_SYMBOL -> getCommentSymbol(context.element?.language) ALL -> file?.text ?: context.editor.document.text ?: "" } } } private fun lineNumberedSelection(caretModel: CaretModel): String { val selection = context.editor.selectionModel.selectedText ?: context.editor.document.text.substring(caretModel.offset) var lineNo = caretModel.logicalPosition.line + 1 return selection.split("\n").joinToString("\n") { val line = "$lineNo: $it" lineNo++ line } } private fun getCommentSymbol(language: Language?): String { return when (language?.displayName?.lowercase()) { "java", "kotlin" -> "//" "python" -> "#" "javascript" -> "//" "typescript" -> "//" "go" -> "//" "c", "c++", "c#" -> "//" "rust" -> "//" "ruby" -> "#" "shell" -> "#" "php" -> "//" "perl" -> "#" "swift" -> "//" "r" -> "#" "scala" -> "//" "groovy" -> "//" "lua" -> "--" else -> "-" } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/variable/resolver/PsiContextVariableResolver.kt ================================================ package com.phodal.shirelang.compiler.variable.resolver import com.intellij.openapi.application.runReadAction import com.intellij.openapi.diagnostic.logger import com.intellij.psi.PsiManager import com.phodal.shirecore.provider.variable.PsiContextVariableProvider import com.phodal.shirecore.provider.variable.impl.DefaultPsiContextVariableProvider import com.phodal.shirecore.provider.variable.model.PsiContextVariable import com.phodal.shirelang.compiler.variable.resolver.base.VariableResolver import com.phodal.shirelang.compiler.variable.resolver.base.VariableResolverContext /** * Include ToolchainVariableProvider and PsiContextVariableProvider */ class PsiContextVariableResolver(private val context: VariableResolverContext) : VariableResolver { private val variableProvider: PsiContextVariableProvider init { val psiFile = runReadAction { PsiManager.getInstance(context.myProject).findFile(context.editor.virtualFile ?: return@runReadAction null) } variableProvider = if (psiFile?.language != null) { PsiContextVariableProvider.provide(psiFile.language) } else { DefaultPsiContextVariableProvider() } } override suspend fun resolve(initVariables: Map): Map { val result = mutableMapOf() context.variableTable.getAllVariables().forEach { val psiContextVariable = PsiContextVariable.from(it.key) if (psiContextVariable != null) { result[it.key] = try { runReadAction { variableProvider.resolve(psiContextVariable, context.myProject, context.editor, context.element) } } catch (e: Exception) { logger().error("Failed to resolve variable: ${it.key}", e) "" } return@forEach } } return result } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/variable/resolver/SystemInfoVariableResolver.kt ================================================ package com.phodal.shirelang.compiler.variable.resolver import com.phodal.shirecore.provider.variable.model.SystemInfoVariable import com.phodal.shirelang.compiler.variable.resolver.base.VariableResolver import com.phodal.shirelang.compiler.variable.resolver.base.VariableResolverContext /** * SystemInfoVariableResolver is a class that provides a way to resolve system information variables. */ class SystemInfoVariableResolver( private val context: VariableResolverContext, ) : VariableResolver { override suspend fun resolve(initVariables: Map): Map { return SystemInfoVariable.all().associate { it.variableName to it.value!! } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/variable/resolver/ToolchainVariableResolver.kt ================================================ package com.phodal.shirelang.compiler.variable.resolver import com.intellij.openapi.diagnostic.logger import com.phodal.shirecore.provider.variable.* import com.phodal.shirecore.provider.variable.model.ToolchainVariable import com.phodal.shirelang.compiler.variable.resolver.base.VariableResolver import com.phodal.shirelang.compiler.variable.resolver.base.VariableResolverContext /** * Include ToolchainVariableProvider and PsiContextVariableProvider */ class ToolchainVariableResolver( private val context: VariableResolverContext, ) : VariableResolver { override suspend fun resolve(initVariables: Map): Map { val result = mutableMapOf() context.variableTable.getAllVariables().forEach { val variable = ToolchainVariable.from(it.key) ?: return@forEach val provider = ToolchainVariableProvider .provide(variable, context.element, context.myProject) ?: return@forEach result[it.key] = try { val resolvedValue = provider.resolve(variable, context.myProject, context.editor, context.element) val value = (resolvedValue as? ToolchainVariable)?.value ?: resolvedValue logger().info("start to resolve variable: $value") value } catch (e: Exception) { logger().error("Failed to resolve variable: ${it.key}", e) "" } } return result } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/variable/resolver/UserCustomVariableResolver.kt ================================================ package com.phodal.shirelang.compiler.variable.resolver import com.phodal.shirelang.compiler.execute.PatternActionProcessor import com.phodal.shirelang.compiler.variable.resolver.base.VariableResolver import com.phodal.shirelang.compiler.variable.resolver.base.VariableResolverContext import com.phodal.shirelang.debugger.snapshot.VariableSnapshotRecorder class UserCustomVariableResolver( private val context: VariableResolverContext, ) : VariableResolver { private val record = VariableSnapshotRecorder.getInstance(context.myProject) override suspend fun resolve(initVariables: Map): Map { record.clear() val vars: MutableMap = initVariables.toMutableMap() return context.hole?.variables?.mapValues { PatternActionProcessor(context.myProject, context.hole, vars).execute(it.value) } ?: emptyMap() } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/variable/resolver/base/VariableResolver.kt ================================================ package com.phodal.shirelang.compiler.variable.resolver.base /** * The `VariableResolver` interface is designed to provide a mechanism for resolving variables. */ interface VariableResolver { suspend fun resolve(initVariables: Map): Map } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/compiler/variable/resolver/base/VariableResolverContext.kt ================================================ package com.phodal.shirelang.compiler.variable.resolver.base import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.phodal.shirelang.compiler.variable.VariableTable import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole data class VariableResolverContext( val myProject: Project, val editor: Editor, val hole: HobbitHole?, val variableTable: VariableTable, var element: PsiElement? ) ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/ShireCompletionContributor.kt ================================================ package com.phodal.shirelang.completion import com.intellij.codeInsight.completion.CompletionContributor import com.intellij.codeInsight.completion.CompletionType import com.intellij.patterns.ElementPattern import com.intellij.patterns.PlatformPatterns import com.intellij.patterns.PsiElementPattern import com.intellij.psi.PsiElement import com.intellij.psi.tree.IElementType import com.phodal.shirelang.completion.dataprovider.BuiltinCommand import com.phodal.shirelang.completion.provider.* import com.phodal.shirelang.psi.ShireFrontMatterEntry import com.phodal.shirelang.psi.ShireTypes import com.phodal.shirelang.psi.ShireUsed class ShireCompletionContributor : CompletionContributor() { init { extend( CompletionType.BASIC, PlatformPatterns.psiElement(ShireTypes.LANGUAGE_IDENTIFIER), CodeFenceLanguageCompletion() ) extend(CompletionType.BASIC, identifierAfter(ShireTypes.AGENT_START), CustomAgentCompletion()) extend(CompletionType.BASIC, identifierAfter(ShireTypes.VARIABLE_START), VariableCompletionProvider()) extend(CompletionType.BASIC, identifierAfter(ShireTypes.VARIABLE_START), AgentToolOverviewCompletion()) extend(CompletionType.BASIC, identifierAfter(ShireTypes.COMMAND_START), BuiltinCommandCompletion()) extend(CompletionType.BASIC, hobbitHoleKey(), HobbitHoleKeyCompletion()) extend(CompletionType.BASIC, hobbitHolePattern(), HobbitHoleValueCompletion()) extend(CompletionType.BASIC, identifierAfter(ShireTypes.PIPE), PostProcessorCompletion()) extend(CompletionType.BASIC, whenConditionPattern(), WhenConditionCompletionProvider()) extend(CompletionType.BASIC, whenConditionFuncPattern(), WhenConditionFunctionCompletionProvider()) // command completion extend( CompletionType.BASIC, (valuePatterns(listOf( BuiltinCommand.FILE, BuiltinCommand.RUN, BuiltinCommand.WRITE, BuiltinCommand.STRUCTURE ))), FileReferenceLanguageProvider() ) extend( CompletionType.BASIC, commandPropPattern(BuiltinCommand.REV.commandName), RevisionReferenceLanguageProvider() ) extend( CompletionType.BASIC, commandPropPattern(BuiltinCommand.SYMBOL.commandName), SymbolReferenceLanguageProvider() ) extend( CompletionType.BASIC, commandPropPattern(BuiltinCommand.FILE_FUNC.commandName), FileFunctionProvider() ) extend( CompletionType.BASIC, commandPropPattern(BuiltinCommand.REFACTOR.commandName), RefactoringFuncProvider() ) extend( CompletionType.BASIC, commandPropPattern(BuiltinCommand.RUN.commandName), ProjectRunProvider() ) } private inline fun psiElement() = PlatformPatterns.psiElement(I::class.java) private fun baseUsedPattern(): PsiElementPattern.Capture = PlatformPatterns.psiElement() .inside(psiElement()) private fun identifierAfter(type: IElementType): ElementPattern = PlatformPatterns.psiElement(ShireTypes.IDENTIFIER) .afterLeaf(PlatformPatterns.psiElement().withElementType(type)) private fun commandPropPattern(text: String): PsiElementPattern.Capture = baseUsedPattern() .withElementType(ShireTypes.COMMAND_PROP) .afterLeafSkipping( PlatformPatterns.psiElement(ShireTypes.COLON), PlatformPatterns.psiElement().withText(text) ) private fun hobbitHolePattern(): ElementPattern { return PlatformPatterns.psiElement() .inside(psiElement()) .afterLeafSkipping( PlatformPatterns.psiElement().withElementType(ShireTypes.FRONT_MATTER_KEY), PlatformPatterns.psiElement(ShireTypes.COLON) ) } private fun whenConditionPattern(): ElementPattern { return PlatformPatterns.psiElement() .inside(psiElement()) .afterLeaf(PlatformPatterns.psiElement().withText("$")) } private fun whenConditionFuncPattern(): ElementPattern { return PlatformPatterns.psiElement(ShireTypes.IDENTIFIER) .inside(psiElement()) .afterLeafSkipping( PlatformPatterns.psiElement(ShireTypes.IDENTIFIER), PlatformPatterns.psiElement(ShireTypes.DOT), ) } private fun hobbitHoleKey(): PsiElementPattern.Capture { val excludedElements = listOf( ShireTypes.COLON, ShireTypes.DOT, ShireTypes.AGENT_START, ShireTypes.VARIABLE_START, ShireTypes.COMMAND_START ).map { PlatformPatterns.psiElement().afterLeaf(PlatformPatterns.psiElement(it)) } return excludedElements.fold( PlatformPatterns.psiElement(ShireTypes.IDENTIFIER) ) { pattern, excludedPattern -> pattern.andNot(excludedPattern) } } private fun valuePatterns(listOf: List): ElementPattern { val patterns = listOf.map { commandPropPattern(it.commandName) } return PlatformPatterns.or(*patterns.toTypedArray()) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/UserCustomCompletionContributor.kt ================================================ package com.phodal.shirelang.completion import com.intellij.codeInsight.completion.CompletionContributor import com.intellij.codeInsight.completion.CompletionType import com.intellij.patterns.PlatformPatterns import com.phodal.shirelang.completion.provider.CustomCommandCompletion import com.phodal.shirelang.psi.ShireTypes class UserCustomCompletionContributor : CompletionContributor() { init { extend(CompletionType.BASIC, PlatformPatterns.psiElement(ShireTypes.COMMAND_ID), CustomCommandCompletion()) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/dataprovider/BuiltinCommand.kt ================================================ package com.phodal.shirelang.completion.dataprovider import com.intellij.icons.AllIcons import com.phodal.shirelang.ShireIcons import java.nio.charset.StandardCharsets import javax.swing.Icon enum class BuiltinCommand( val commandName: String, val description: String, val icon: Icon, val hasCompletion: Boolean = false, val requireProps: Boolean = false, val enableInSketch: Boolean = true, ) { FILE("file", "Read the content of a file by project relative path", AllIcons.Actions.Copy, true, true), REV( "rev", "Read git changes by sha hash; For other git operations, it is recommended to use native git commands", AllIcons.Vcs.History, true, true, enableInSketch = false ), /** * Every language will have a symbol completion, which is the most basic completion, for example, * - Java: [com.intellij.codeInsight.completion.JavaKeywordCompletion] * - Kotlin: [org.jetbrains.kotlin.idea.completion.KotlinCompletionContributor] * - Python: [com.jetbrains.python.codeInsight.completion.PyClassNameCompletionContributor] */ SYMBOL( "symbol", "Read content by Java/Kotlin canonical name, such as package name, class name.", AllIcons.Toolwindows.ToolWindowStructure, true, true ), WRITE( "write", "Write content to a file with markdown code block, /write:path/to/file:L1-L2", AllIcons.Actions.Edit, true, true ), PATCH( "patch", "Apply GNU unified diff format structure patch to a file, /patch:path/to/file", AllIcons.Vcs.Patch_file, false ), RUN("run", "Run the IDE's built-in command, like build tool, test.", AllIcons.Actions.Execute, true, true), SHELL( "shell", "Execute a shell command and collect (ProcessBuild) the result", AllIcons.Debugger.Console, true, true ), COMMIT("commit", "Do commit with current workspace with some messages.", AllIcons.Vcs.CommitNode, false), FILE_FUNC( "file-func", "Read the name of a file, support for: " + FileFunc.values().joinToString(",") { it.funcName }, AllIcons.Actions.GroupByFile, true, true, enableInSketch = false, ), BROWSE("browse", "Fetch the content of a given URL.", AllIcons.Toolwindows.WebToolWindow, false, true), REFACTOR( "refactor", "Refactor the content of a file, only support for rename, safeDelete and move.", ShireIcons.Idea, true, true ), GOTO("goto", "Goto the content of a file", AllIcons.Actions.Forward, true, true, enableInSketch = false), STRUCTURE( "structure", "Get the structure of a file with AST/PSI", AllIcons.Toolwindows.ToolWindowStructure, true, true ), DIR("dir", "List files and directories in a tree-like structure", AllIcons.Actions.ProjectDirectory, true, true), DATABASE( "database", "Read the content of a database, /database:query\n```sql\nSELECT * FROM table\n```", AllIcons.Toolwindows.ToolWindowHierarchy, true, true ), LOCAL_SEARCH( "localSearch", "Search text in the scope (current only support project) will return 5 line before and after", AllIcons.Actions.Search, false, true ), RELATED( "related", "Get related code by AST (abstract syntax tree) for the current file", AllIcons.Actions.Find, false, true ), OPEN("open", "Open a file in the editor", AllIcons.Actions.MenuOpen, false, true), RIPGREP_SEARCH("ripgrepSearch", "Search text in the project with ripgrep", AllIcons.Actions.Regex, false, true), ; companion object { fun all(): List { return entries } fun example(command: BuiltinCommand): String { val commandName = command.commandName val inputStream = BuiltinCommand::class.java.getResourceAsStream("/docs/agentExamples/$commandName.shire") ?: throw IllegalStateException("Example file not found: $commandName.shire") return inputStream.use { it.readAllBytes().toString(StandardCharsets.UTF_8) } } fun fromString(agentName: String): BuiltinCommand? = values().find { it.commandName == agentName } val READ_COMMANDS = setOf( DIR, LOCAL_SEARCH, FILE, REV, STRUCTURE, SYMBOL, DATABASE, RELATED, RIPGREP_SEARCH ) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/dataprovider/BuiltinRefactorCommand.kt ================================================ package com.phodal.shirelang.completion.dataprovider enum class BuiltinRefactorCommand(val funcName: String, val description: String) { RENAME("rename", "Rename a file"), SAFE_DELETE("safe-delete", "Safe delete a file"), DELETE("delete", "Delete a file"), MOVE("move", "Move a file"), ; companion object { fun all(): List { return values().toList() } fun fromString(command: String): BuiltinRefactorCommand? { return values().find { it.name.equals(command, ignoreCase = true) } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/dataprovider/CustomCommand.kt ================================================ package com.phodal.shirelang.completion.dataprovider import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.phodal.shirelang.ShireIcons import javax.swing.Icon data class CustomCommand( val commandName: String, val content: String, val icon: Icon = ShireIcons.COMMAND ) { companion object { fun all(project: Project): List { return listOf() } /** * Read the content from the given file and create a CustomCommand object with the file name and content. * @param file the VirtualFile from which the content will be read * @return CustomCommand object containing the name of the file without extension and the content of the file */ private fun fromFile(file: VirtualFile): CustomCommand { val content = file.inputStream.readBytes().toString(Charsets.UTF_8) return CustomCommand(file.nameWithoutExtension, content) } fun fromString(project: Project, agentName: String): CustomCommand? { return all(project).find { it.commandName == agentName } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/dataprovider/FileFunc.kt ================================================ package com.phodal.shirelang.completion.dataprovider import com.intellij.icons.AllIcons import javax.swing.Icon enum class FileFunc(val funcName: String, val description: String, val icon: Icon) { Regex("regex", "Read the content of a file by regex", AllIcons.Actions.Regex), ; companion object { fun all(): List { return values().toList() } fun fromString(funcName: String): FileFunc? { return values().find { it.funcName == funcName } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/dataprovider/ToolHubVariable.kt ================================================ package com.phodal.shirelang.completion.dataprovider import com.phodal.shirecore.agent.CustomAgent /** * The tool hub provides a list of tools - agents and commands for the AI Agents to decide which one to call * For example, you prompt could be: * ```shire * Here is the tools you can use: * $agents * ``` * * Or * * ```shire * Here is the tools you can use: * $commands * ``` */ enum class ToolHubVariable(val hubName: String, val type: String, val description: String) { AGENTS("agents", CustomAgent::class.simpleName.toString(), "Shire all agent for AI Agents to call"), COMMANDS("commands", BuiltinCommand::class.simpleName.toString(), "Shire all commands for AI Agents to call"), ; companion object { fun all(): List { return values().toList() } /** * @param variableId should be one of the [ToolHubVariable] name */ fun lookup(variableId: String?): List { return when (variableId) { COMMANDS.hubName -> ToolHubVariable.all().map { "- " + it.hubName + ". " + it.description } else -> emptyList() } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/provider/AgentToolOverviewCompletion.kt ================================================ package com.phodal.shirelang.completion.provider import com.phodal.shirelang.completion.dataprovider.ToolHubVariable import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.completion.PrioritizedLookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.util.ProcessingContext import com.phodal.shirelang.ShireIcons class AgentToolOverviewCompletion : CompletionProvider() { override fun addCompletions( parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet ) { ToolHubVariable.all().forEach { toolHub -> val elements = LookupElementBuilder.create(toolHub.hubName) .withIcon(ShireIcons.DEFAULT) .withTypeText("(${toolHub.description})", true) .withPresentableText(toolHub.hubName) .withTailText(toolHub.type, true) result.addElement(PrioritizedLookupElement.withPriority(elements, 0.0)) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/provider/BuiltinCommandCompletion.kt ================================================ package com.phodal.shirelang.completion.provider import com.phodal.shirelang.completion.dataprovider.BuiltinCommand import com.intellij.codeInsight.AutoPopupController import com.intellij.codeInsight.completion.* import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.util.ProcessingContext class BuiltinCommandCompletion : CompletionProvider() { override fun addCompletions( parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet, ) { BuiltinCommand.all().forEach { val lookupElement = createCommandCompletionCandidate(it) result.addElement(lookupElement) } } private fun createCommandCompletionCandidate(it: BuiltinCommand) = PrioritizedLookupElement.withPriority( LookupElementBuilder.create(it.commandName) .withIcon(it.icon) .withTypeText(it.description, true) .withInsertHandler { context, _ -> if (!it.hasCompletion) return@withInsertHandler context.document.insertString(context.tailOffset, ":") context.editor.caretModel.moveCaretRelatively(1, 0, false, false, false) val editor = context.editor AutoPopupController.getInstance(editor.project!!).scheduleAutoPopup(editor) }, // before custom 99.0 ) } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/provider/CodeFenceLanguageCompletion.kt ================================================ package com.phodal.shirelang.completion.provider import com.intellij.codeInsight.completion.* import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.lang.Language import com.intellij.lang.LanguageUtil import com.intellij.ui.DeferredIconImpl import com.intellij.util.ProcessingContext import com.phodal.shirelang.markdown.CodeFenceLanguageAliases import javax.swing.Icon class CodeFenceLanguageCompletion : CompletionProvider() { override fun addCompletions( parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet, ) { for (language in LanguageUtil.getInjectableLanguages()) { val alias = CodeFenceLanguageAliases.findMainAlias(language.id) val handler = LookupElementBuilder.create(alias) .withIcon(createLanguageIcon(language)) .withTypeText(language.displayName, true) .withInsertHandler(MyInsertHandler()) result.addElement(handler) } } private fun createLanguageIcon(language: Language): Icon { return DeferredIconImpl(null, language, true) { it.associatedFileType?.icon } } private class MyInsertHandler : InsertHandler { override fun handleInsert(context: InsertionContext, item: LookupElement) { context.document.insertString(context.tailOffset, "\n\n```\n") context.editor.caretModel.moveCaretRelatively(1, 0, false, false, false) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/provider/CustomAgentCompletion.kt ================================================ package com.phodal.shirelang.completion.provider import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.util.ProcessingContext import com.phodal.shirecore.agent.CustomAgent class CustomAgentCompletion : CompletionProvider() { override fun addCompletions( parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet, ) { val configs: List = CustomAgent.loadFromProject(parameters.originalFile.project) configs.forEach { config -> result.addElement( LookupElementBuilder.create(config.name) .withInsertHandler { context, _ -> context.document.insertString(context.tailOffset, " ") context.editor.caretModel.moveCaretRelatively(1, 0, false, true, false) } .withTypeText(config.description, true)) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/provider/CustomCommandCompletion.kt ================================================ package com.phodal.shirelang.completion.provider import com.phodal.shirelang.completion.dataprovider.CustomCommand import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.completion.PrioritizedLookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.util.ProcessingContext class CustomCommandCompletion : CompletionProvider() { override fun addCompletions( parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet ) { val project = parameters.originalFile.project ?: return CustomCommand.all(project).forEach { val element = LookupElementBuilder.create(it.commandName) .withIcon(it.icon) .withTypeText(it.content, true) result.addElement(PrioritizedLookupElement.withPriority(element, 0.0)) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/provider/FileFunctionProvider.kt ================================================ package com.phodal.shirelang.completion.provider import com.phodal.shirelang.completion.dataprovider.FileFunc import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.util.ProcessingContext class FileFunctionProvider : CompletionProvider() { override fun addCompletions( parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet ) { FileFunc.all().forEach { val element = LookupElementBuilder.create(it.funcName) .withIcon(it.icon) .withTypeText(it.description, true) result.addElement(element) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/provider/FileReferenceLanguageProvider.kt ================================================ package com.phodal.shirelang.completion.provider import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.ide.presentation.VirtualFilePresentation import com.intellij.openapi.fileEditor.impl.EditorHistoryManager import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.ProcessingContext import com.phodal.shirecore.canBeAdded import com.phodal.shirecore.completion.ShireLookupElement import org.jetbrains.annotations.NonNls import java.io.File class FileReferenceLanguageProvider : CompletionProvider() { override fun addCompletions( parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet, ) { val project = parameters.position.project val basePath = project.guessProjectDir()?.path ?: return /** * Recent open files */ EditorHistoryManager.getInstance(project).fileList.forEach { if (!it.canBeAdded(project)) return@forEach result.addElement(buildElement(it, basePath, 99.0)) } /** * Project Files */ ProjectFileIndex.getInstance(project).iterateContent { if (!it.canBeAdded(project)) return@iterateContent true // if relative to the same extention can be high priority, if can be added to project result.addElement(buildElement(it, basePath, 1.0)) true } } private fun buildElement( virtualFile: VirtualFile, basePath: @NonNls String, priority: Double, ): LookupElement { val removePrefix = virtualFile.path.removePrefix(basePath) val relativePath: String = removePrefix.removePrefix(File.separator) val elementBuilder = LookupElementBuilder.create(relativePath) .withIcon(VirtualFilePresentation.getIcon(virtualFile)) .withInsertHandler { context, _ -> context.editor.caretModel.moveCaretRelatively(1, 1, false, false, false) } return ShireLookupElement.withPriority(elementBuilder, priority, virtualFile) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/provider/HobbitHoleKeyCompletion.kt ================================================ package com.phodal.shirelang.completion.provider import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.completion.PrioritizedLookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.util.ProcessingContext import com.phodal.shirelang.ShireIcons import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole class HobbitHoleKeyCompletion : CompletionProvider() { override fun addCompletions( parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet, ) { HobbitHole.keys().forEach { val element = LookupElementBuilder.create(it.key) .withIcon(ShireIcons.Idea) .withTypeText(it.value, true) result.addElement(PrioritizedLookupElement.withPriority(element, 0.0)) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/provider/HobbitHoleValueCompletion.kt ================================================ package com.phodal.shirelang.completion.provider import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.completion.InsertionContext import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.psi.util.PsiTreeUtil import com.intellij.util.ProcessingContext import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirecore.config.InteractionType import com.phodal.shirecore.middleware.post.PostProcessor import com.phodal.shirecore.middleware.select.SelectElementStrategy import com.phodal.shirelang.ShireIcons import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole class HobbitHoleValueCompletion : CompletionProvider() { private val HOBBIT = "hobbit" override fun addCompletions( parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet, ) { val position = parameters.originalPosition ?: parameters.position val psiElement = PsiTreeUtil.prevVisibleLeaf(position)?.let { PsiTreeUtil.prevLeaf(it, true) } ?: return when (psiElement.text) { HOBBIT -> { hobbitHeroes(result) } HobbitHole.ACTION_LOCATION -> { ShireActionLocation.all().forEach { result.addElement( LookupElementBuilder .create(it.location) .withIcon(ShireIcons.DEFAULT) .withInsertHandler { context, _ -> context.document.insertString(context.startOffset, " ") } .withTypeText(it.description, true) ) } } HobbitHole.INTERACTION -> { InteractionType.entries.forEach { result.addElement( LookupElementBuilder .create(it.name) .withIcon(ShireIcons.Idea) .withInsertHandler { context, _ -> context.document.insertString(context.startOffset, " ") } .withTypeText(it.description, true) ) } } HobbitHole.STRATEGY_SELECTION -> { SelectElementStrategy.all().forEach { result.addElement( LookupElementBuilder .create(it) .withIcon(ShireIcons.Idea) .withInsertHandler { context, _ -> context.document.insertString(context.startOffset, " ") } ) } } HobbitHole.ON_STREAMING_END -> { PostProcessor.allNames().forEach { result.addElement( LookupElementBuilder .create(it) .withIcon(ShireIcons.DEFAULT) .withInsertHandler { context: InsertionContext, item: LookupElement -> val offset = context.editor.caretModel.offset val startOffset = offset - item.lookupString.length context.document.deleteString(startOffset, offset) // insert value inside `{ }`, for example, if user select $demo, the insert will be `{ $demo }` val value = " { $it }" context.document.insertString(context.startOffset, value) context.editor.caretModel.moveToOffset(startOffset + value.length - 2) } ) } } } } private fun hobbitHeroes(result: CompletionResultSet) { listOf("Frodo", "Sam", "Merry", "Pippin").forEach { result.addElement(LookupElementBuilder.create(it)) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/provider/PostProcessorCompletion.kt ================================================ package com.phodal.shirelang.completion.provider import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.completion.InsertionContext import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.util.ProcessingContext import com.phodal.shirecore.middleware.post.PostProcessor import com.phodal.shirelang.ShireIcons class PostProcessorCompletion : CompletionProvider() { override fun addCompletions( parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet, ) { PostProcessor.allNames().forEach { result.addElement( LookupElementBuilder .create(it) .withIcon(ShireIcons.DEFAULT) .withInsertHandler { context: InsertionContext, item: LookupElement -> } ) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/provider/ProjectRunProvider.kt ================================================ package com.phodal.shirelang.completion.provider import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.util.ProcessingContext import com.phodal.shirecore.provider.shire.ProjectRunService class ProjectRunProvider : CompletionProvider() { override fun addCompletions( parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet, ) { ProjectRunService.all().forEach { provider -> provider .lookupAvailableTask(parameters.editor.project!!, parameters, result) .forEach { result.addElement(it) } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/provider/QueryStatementCompletion.kt ================================================ package com.phodal.shirelang.completion.provider import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.util.ProcessingContext class QueryStatementCompletion : CompletionProvider() { /** will auto insert code * ```shire * from { * PsiClass clazz / * sample * / * } * where { * // your code here * } * select { * // output selection * } * ``` */ override fun addCompletions( parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet ) { // 添加代码补全项 result.addElement( LookupElementBuilder.create("from { ... } where { ... } select { ... }") .withInsertHandler { context, item -> val document = context.document val startOffset = context.startOffset val endOffset = context.tailOffset document.replaceString(startOffset, endOffset, "from {\n} where {\n} select {\n}") // 移动光标到 from 后面 context.editor.caretModel.moveToOffset(startOffset + 5) } ) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/provider/RefactoringFuncProvider.kt ================================================ package com.phodal.shirelang.completion.provider import com.phodal.shirelang.completion.dataprovider.BuiltinRefactorCommand import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.util.ProcessingContext class RefactoringFuncProvider : CompletionProvider() { override fun addCompletions( parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet ) { BuiltinRefactorCommand.all().forEach { val element = LookupElementBuilder.create(it.funcName) .withTypeText(it.description, true) result.addElement(element) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/provider/RevisionReferenceLanguageProvider.kt ================================================ package com.phodal.shirelang.completion.provider import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.openapi.project.Project import com.intellij.util.ProcessingContext import com.phodal.shirecore.provider.shire.RevisionProvider class RevisionReferenceLanguageProvider : CompletionProvider() { override fun addCompletions( parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet, ) { val project: Project = parameters.editor.project ?: return RevisionProvider.provide()?.fetchCompletions(project, result) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/provider/SymbolReferenceLanguageProvider.kt ================================================ package com.phodal.shirelang.completion.provider import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.util.ProcessingContext import com.phodal.shirecore.provider.shire.ShireSymbolProvider class SymbolReferenceLanguageProvider : CompletionProvider() { override fun addCompletions( parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet ) { ShireSymbolProvider.all().forEach { completionProvider -> val elements = completionProvider.lookupSymbol(parameters.editor.project!!, parameters, result) elements.forEach { result.addElement(it) } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/provider/VariableCompletionProvider.kt ================================================ package com.phodal.shirelang.completion.provider import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.completion.PrioritizedLookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.util.ProcessingContext import com.phodal.shirelang.ShireIcons import com.phodal.shirelang.compiler.variable.CompositeVariableProvider class VariableCompletionProvider : CompletionProvider() { override fun addCompletions( parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet, ) { CompositeVariableProvider.all().forEach { val withTypeText = PrioritizedLookupElement.withPriority( LookupElementBuilder.create(it.name) .withIcon(ShireIcons.Variable) .withTypeText(it.description, true), it.priority ) result.addElement(withTypeText) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/completion/provider/WhenConditionCompletionProvider.kt ================================================ package com.phodal.shirelang.completion.provider import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionProvider import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.completion.PrioritizedLookupElement import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.util.ProcessingContext import com.phodal.shirelang.ShireIcons import com.phodal.shirecore.provider.variable.model.ConditionPsiVariable import com.phodal.shirelang.compiler.ast.ExpressionBuiltInMethod class WhenConditionCompletionProvider : CompletionProvider() { override fun addCompletions( parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet, ) { ConditionPsiVariable.values().forEach { val withTypeText = PrioritizedLookupElement.withPriority( LookupElementBuilder .create(it.name) .withIcon(ShireIcons.DEFAULT) .withTypeText(it.description, true), 199.0 ) result.addElement(withTypeText) } } } class WhenConditionFunctionCompletionProvider : CompletionProvider() { override fun addCompletions( parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet, ) { ExpressionBuiltInMethod.completionProvider().forEach { val elementBuilder = LookupElementBuilder.create(it.methodName) .withTypeText(it.description, true) .withInsertHandler { context, _ -> context.document.insertString(context.tailOffset, it.postInsertString) context.editor.caretModel.moveCaretRelatively(it.moveCaret, 0, false, false, false) } val withTypeText = PrioritizedLookupElement.withPriority( elementBuilder, 99.0 ) result.addElement(withTypeText) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/debugger/ShireBreakpoint.kt ================================================ package com.phodal.shirelang.debugger import com.intellij.openapi.fileTypes.FileTypeRegistry import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.xdebugger.breakpoints.XBreakpointHandler import com.intellij.xdebugger.breakpoints.XBreakpointProperties import com.intellij.xdebugger.breakpoints.XLineBreakpoint import com.intellij.xdebugger.breakpoints.XLineBreakpointType import com.phodal.shirelang.ShireFileType class ShireBreakpointHandler(val process: ShireDebugProcess) : XBreakpointHandler>(ShireLineBreakpointType::class.java) { override fun registerBreakpoint(breakpoint: XLineBreakpoint) { process.addBreakpoint(breakpoint) } override fun unregisterBreakpoint(breakpoint: XLineBreakpoint, temporary: Boolean) { process.removeBreakpoint(breakpoint) } } class ShireBpProperties : XBreakpointProperties() { override fun getState(): ShireBpProperties = this override fun loadState(state: ShireBpProperties) {} } class ShireLineBreakpointType : XLineBreakpointType(ID, TITLE) { override fun canPutAt(file: VirtualFile, line: Int, project: Project): Boolean { return canPutAt(project, file, line) } override fun createBreakpointProperties(file: VirtualFile, line: Int): ShireBpProperties = ShireBpProperties() fun canPutAt(project: Project, file: VirtualFile, line: Int): Boolean { // val shireFile = PsiManager.getInstance(project).findFile(file) as? ShireFile ?: return false // val findLeafElementAt = shireFile.node.findLeafElementAt(line)?.elementType // findLeafElementAt?.let { // return true // } // // return false return (FileTypeRegistry.getInstance().isFileOfType(file, ShireFileType.INSTANCE)) } companion object { private const val ID = "the-shire-line" private const val TITLE = "Shire Breakpoints" } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/debugger/ShireDebugProcess.kt ================================================ package com.phodal.shirelang.debugger import com.intellij.execution.process.ProcessEvent import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.execution.ui.ExecutionConsole import com.intellij.execution.ui.RunnerLayoutUi import com.intellij.execution.ui.layout.PlaceInGrid import com.intellij.icons.AllIcons import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.ui.content.Content import com.intellij.util.Alarm import com.intellij.xdebugger.XDebugProcess import com.intellij.xdebugger.XDebugSession import com.intellij.xdebugger.XSourcePosition import com.intellij.xdebugger.breakpoints.XBreakpointHandler import com.intellij.xdebugger.breakpoints.XLineBreakpoint import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider import com.intellij.xdebugger.frame.XSuspendContext import com.intellij.xdebugger.ui.XDebugTabLayouter import com.phodal.shirecore.ShireCoroutineScope import com.phodal.shirelang.psi.ShireFile import com.phodal.shirelang.run.* import com.phodal.shirelang.run.runner.ShireRunner import com.phodal.shirelang.run.runner.ShireRunnerContext import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking class ShireDebugProcess(private val session: XDebugSession, private val environment: ExecutionEnvironment) : XDebugProcess(session), Disposable { private val debuggableConfiguration: ShireConfiguration get() = session.runProfile as ShireConfiguration private val runProfileState = debuggableConfiguration.getState(environment.executor, environment) as ShireRunConfigurationProfileState private val connection = ApplicationManager.getApplication().messageBus.connect(this) private val myRequestsScheduler: Alarm init { myRequestsScheduler = Alarm(Alarm.ThreadToUse.POOLED_THREAD, this) } private val breakpointHandlers = arrayOf>(ShireBreakpointHandler(this)) override fun getBreakpointHandlers(): Array> = breakpointHandlers override fun createTabLayouter(): XDebugTabLayouter = ShireDebugTabLayouter(runProfileState.console) override fun createConsole(): ExecutionConsole = runProfileState.console var shireRunnerContext: ShireRunnerContext? = null fun start() { ShireCoroutineScope.scope(session.project).launch { runBlocking { val psiFile: ShireFile = ShireFile.lookup(session.project, debuggableConfiguration.getScriptPath()) ?: return@runBlocking shireRunnerContext = ShireRunner.compileOnly(session.project, psiFile, mapOf(), null) session.positionReached(ShireSuspendContext(this@ShireDebugProcess, session.project)) } } val processAdapter = ShireProcessAdapter(debuggableConfiguration, runProfileState.console) processHandler.addProcessListener(processAdapter) runProfileState.console.print("Waiting for resume...", ConsoleViewContentType.NORMAL_OUTPUT) } override fun resume(context: XSuspendContext?) { connection.subscribe(ShireRunListener.TOPIC, object : ShireRunListener { override fun runFinish( allOutput: String, llmOutput: String, event: ProcessEvent, scriptPath: String, consoleView: ShireConsoleView?, ) { this@ShireDebugProcess.stop() } }) runProfileState.execute(environment.executor, environment.runner) } override fun runToPosition(position: XSourcePosition, context: XSuspendContext?) { runProfileState.execute(environment.executor, environment.runner) } override fun startStepOut(context: XSuspendContext?) { runProfileState.execute(environment.executor, environment.runner) } override fun startStepInto(context: XSuspendContext?) { runProfileState.execute(environment.executor, environment.runner) } override fun startStepOver(context: XSuspendContext?) { runProfileState.execute(environment.executor, environment.runner) } override fun startForceStepInto(context: XSuspendContext?) { runProfileState.execute(environment.executor, environment.runner) } override fun startPausing() { } override fun stop() { connection.disconnect() processHandler.destroyProcess() session.stop() } override fun dispose() { connection.disconnect() } fun addBreakpoint(breakpoint: XLineBreakpoint) { } fun removeBreakpoint(breakpoint: XLineBreakpoint) { } override fun getEditorsProvider(): XDebuggerEditorsProvider { return ShireDebuggerEditorsProvider() } } class ShireDebugTabLayouter(val console: ShireConsoleView) : XDebugTabLayouter() { override fun registerConsoleContent(ui: RunnerLayoutUi, console: ExecutionConsole): Content { val content = ui .createContent( "DebuggedConsoleContent", console.component, "Shire Debugged Console", AllIcons.Debugger.Console, console.preferredFocusableComponent ) content.isCloseable = false ui.addContent(content, 1, PlaceInGrid.bottom, false) return content } } val RUNNER_ID: String = "ShireProgramRunner" ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/debugger/ShireDebugRunner.kt ================================================ package com.phodal.shirelang.debugger import com.intellij.execution.configurations.RunProfile import com.intellij.execution.configurations.RunProfileState import com.intellij.execution.configurations.RunnerSettings import com.intellij.execution.executors.DefaultDebugExecutor import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.execution.runners.GenericProgramRunner import com.intellij.execution.ui.RunContentDescriptor import com.intellij.xdebugger.XDebugProcess import com.intellij.xdebugger.XDebugProcessStarter import com.intellij.xdebugger.XDebugSession import com.intellij.xdebugger.XDebuggerManager import com.phodal.shirelang.run.ShireConfiguration /// refs to: https://github.com/KronicDeth/intellij-elixir/pull/643/files#diff-b1ba5c87ca6f66a455e4c1539cb2d99a62722d067a3d9e8043b290426cea5470 class ShireDebugRunner : GenericProgramRunner() { override fun canRun(executorId: String, profile: RunProfile): Boolean { return (executorId == DefaultDebugExecutor.EXECUTOR_ID) && profile is ShireConfiguration } override fun getRunnerId(): String = RUNNER_ID override fun doExecute(state: RunProfileState, environment: ExecutionEnvironment): RunContentDescriptor? { val xDebuggerManager = XDebuggerManager.getInstance(environment.project) return xDebuggerManager.startSession(environment, object : XDebugProcessStarter() { override fun start(session: XDebugSession): XDebugProcess { val shireDebugProcess = ShireDebugProcess(session, environment) shireDebugProcess.start() return shireDebugProcess } }).runContentDescriptor } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/debugger/ShireDebugSettings.kt ================================================ package com.phodal.shirelang.debugger import com.intellij.openapi.Disposable import com.intellij.openapi.options.Configurable import com.intellij.openapi.options.ConfigurableUi import com.intellij.openapi.options.SimpleConfigurable import com.intellij.openapi.util.Getter import com.intellij.ui.components.JBCheckBox import com.intellij.ui.dsl.builder.Panel import com.intellij.ui.dsl.builder.panel import com.intellij.util.xmlb.XmlSerializerUtil import com.intellij.xdebugger.settings.DebuggerSettingsCategory import com.intellij.xdebugger.settings.XDebuggerSettings import javax.swing.JComponent class ShireDebugSettings : XDebuggerSettings("shire"), Getter { override fun get(): ShireDebugSettings = this override fun getState()= this override fun loadState(state: ShireDebugSettings) { XmlSerializerUtil.copyBean(state, this) } var breakOnPanic: Boolean = true override fun createConfigurables(category: DebuggerSettingsCategory): MutableCollection { val config = SimpleConfigurable.create( "ShireDebugSettings", "Shire Debugger", ShireDebugSettingsConfigurableUi::class.java, this ) return mutableListOf(config) } companion object { @JvmStatic fun getInstance(): ShireDebugSettings = getInstance(ShireDebugSettings::class.java) } } class ShireDebugSettingsConfigurableUi : ConfigurableUi, Disposable { private val components: List = run { val components = mutableListOf() components.add(RsBreakOnPanicConfigurableUi()) components } override fun isModified(settings: ShireDebugSettings): Boolean = components.any { it.isModified(settings) } override fun reset(settings: ShireDebugSettings) { components.forEach { it.reset(settings) } } override fun apply(settings: ShireDebugSettings) { components.forEach { it.apply(settings) } } override fun getComponent(): JComponent { return panel { for (component in components) { component.buildUi(this) } } } override fun dispose() { components.forEach { it.dispose() } } } abstract class ShireDebuggerUiComponent: ConfigurableUi, Disposable { abstract fun buildUi(panel: Panel) override fun getComponent(): JComponent { return panel { buildUi(this) } } override fun dispose() {} } class RsBreakOnPanicConfigurableUi : ShireDebuggerUiComponent() { private val breakOnPanicCheckBox: JBCheckBox = JBCheckBox("Debug", ShireDebugSettings.getInstance().breakOnPanic) override fun reset(settings: ShireDebugSettings) { breakOnPanicCheckBox.isSelected = settings.breakOnPanic } override fun isModified(settings: ShireDebugSettings): Boolean = settings.breakOnPanic != breakOnPanicCheckBox.isSelected override fun apply(settings: ShireDebugSettings) { settings.breakOnPanic = breakOnPanicCheckBox.isSelected } override fun buildUi(panel: Panel) { with(panel) { row { cell(breakOnPanicCheckBox) } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/debugger/ShireDebuggerEditorsProvider.kt ================================================ package com.phodal.shirelang.debugger import com.intellij.openapi.fileTypes.FileType import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.impl.PsiManagerEx import com.intellij.testFramework.LightVirtualFile import com.intellij.xdebugger.XDebuggerUtil import com.intellij.xdebugger.evaluation.XDebuggerEditorsProviderBase import com.phodal.shirelang.ShireFileType import com.phodal.shirelang.psi.ShireFile class ShireDebuggerEditorsProvider: XDebuggerEditorsProviderBase() { override fun getFileType(): FileType = ShireFileType.INSTANCE override fun createExpressionCodeFragment(project: Project, text: String, context: PsiElement?, isPhysical: Boolean): PsiFile { val name = "fragment" + "." + ShireFileType.INSTANCE.defaultExtension val viewProvider = PsiManagerEx.getInstanceEx(project).fileManager.createFileViewProvider( LightVirtualFile(name, ShireFileType.INSTANCE, text), isPhysical) return ShireFile(viewProvider) } override fun getContextElement(virtualFile: VirtualFile, offset: Int, project: Project): PsiElement? { return XDebuggerUtil.getInstance().findContextElement(virtualFile, offset, project, true) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/debugger/ShireStackFrame.kt ================================================ package com.phodal.shirelang.debugger import com.intellij.icons.AllIcons import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.project.Project import com.intellij.ui.ColoredTextContainer import com.intellij.ui.SimpleTextAttributes import com.intellij.xdebugger.XExpression import com.intellij.xdebugger.XSourcePosition import com.intellij.xdebugger.evaluation.XDebuggerEvaluator import com.intellij.xdebugger.frame.* import com.phodal.shirelang.debugger.snapshot.UserCustomVariableSnapshot import com.phodal.shirelang.debugger.snapshot.VariableSnapshotRecorder import org.jetbrains.concurrency.Promise class ShireExecutionStack(private val process: ShireDebugProcess, project: Project) : XExecutionStack("Variables") { private val stackFrames: MutableList = mutableListOf() init { stackFrames.add(ShireStackFrame(process, project, null)) val variableSnapshots = VariableSnapshotRecorder.getInstance(project).all() variableSnapshots.forEach { stackFrames.add(ShireStackFrame(process, project, it)) } stackFrames.reverse() } override fun getTopFrame(): XStackFrame? = stackFrames.firstOrNull() override fun computeStackFrames(firstFrameIndex: Int, container: XStackFrameContainer) { container.addStackFrames(stackFrames, true) } } class ShireStackFrame( val process: ShireDebugProcess, val project: Project, private val snapshot: UserCustomVariableSnapshot? = null, ) : XStackFrame(), Disposable { private var snapshotValue: ShireDebugValue? = null override fun customizePresentation(component: ColoredTextContainer) { if (snapshot == null) { component.append("Init", SimpleTextAttributes.REGULAR_ATTRIBUTES) component.setIcon(AllIcons.Debugger.Frame) return } val variableOperation = snapshot.operations.firstOrNull() if (variableOperation == null) { component.append( snapshot.variableName + " -> " + "Init" + "(" + ")", SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES ) component.setIcon(AllIcons.Debugger.Frame) } else { val functionName = variableOperation.functionName val value = variableOperation.value.toString() snapshotValue = ShireDebugValue(snapshot.variableName, "String", value, snapshot) component.append( snapshot.variableName + " -> " + functionName + "(" + value + ")", SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES ) component.setIcon(AllIcons.Debugger.Frame) } } override fun computeChildren(node: XCompositeNode) { val root = XValueChildrenList() snapshotValue?.let { root.add(it) } node.addChildren(root, false) val filteredChildren = XValueChildrenList() process.shireRunnerContext?.compiledVariables?.forEach { filteredChildren.add(ShireDebugValue(it.key, "String", it.value.toString())) } node.addChildren(filteredChildren, true) } override fun getEvaluator(): XDebuggerEvaluator? { return ShireDebugEvaluator() } override fun dispose() { } } class ShireDebugEvaluator : XDebuggerEvaluator() { override fun evaluate(expr: String, callback: XEvaluationCallback, expressionPosition: XSourcePosition?) { ApplicationManager.getApplication().executeOnPooledThread { val expression = expr.trim() if (expression.isEmpty()) { callback.evaluated(getNone()) return@executeOnPooledThread } val value = ShireDebugValue(expression, "String", expression) callback.evaluated(value) } } private fun getNone(): XValue = ShireDebugValue("", "None", "") } class ShireDebugValue( private val myName: String, val type: String = "String", val value: String, val snapshot: UserCustomVariableSnapshot? = null, ) : XNamedValue(myName) { override fun computePresentation(node: XValueNode, place: XValuePlace) { if (snapshot != null) { node.setPresentation(AllIcons.Debugger.Value, myName, value, true) return } node.setPresentation(AllIcons.Debugger.Value, myName, value, false) } override fun calculateEvaluationExpression(): Promise { return super.calculateEvaluationExpression() } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/debugger/ShireSuspendContext.kt ================================================ package com.phodal.shirelang.debugger import com.intellij.openapi.project.Project import com.intellij.xdebugger.frame.XExecutionStack import com.intellij.xdebugger.frame.XSuspendContext class ShireSuspendContext(val process: ShireDebugProcess, project: Project) : XSuspendContext() { private val shireExecutionStacks: Array = arrayOf( ShireExecutionStack(process, project) ) override fun getActiveExecutionStack(): XExecutionStack? = shireExecutionStacks.firstOrNull() override fun getExecutionStacks(): Array = shireExecutionStacks } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/debugger/snapshot/ShireFileSnapshot.kt ================================================ package com.phodal.shirelang.debugger.snapshot import com.intellij.openapi.vfs.VirtualFile import kotlinx.datetime.Clock /** * the snapshot of TimeTravel Debugger * ```shire * --- * name: "Context Variable" * description: "Here is a description of the action." * interaction: RunPanel * variables: * "contextVariable": /ContextVariable\.kt/ { cat } * "psiContextVariable": /PsiContextVariable\.kt/ { cat } * onStreamingEnd: { parseCode | saveFile("docs/shire/shire-builtin-variable.md") } * --- * * 根据如下的信息,编写对应的 ContextVariable 相关信息的 markdown 文档。 * ``` */ class ShireFileSnapshot( val file: VirtualFile, val rnd: Int, // seed for random number generator var variables: Map, var allCode: String = "", /** * execute to current code line */ var executedCode: String = "", val metadata: SnapshotMetadata = SnapshotMetadata(Clock.System.now(), "0.0.1", file), ) { } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/debugger/snapshot/UserCustomVariableSnapshot.kt ================================================ package com.phodal.shirelang.debugger.snapshot import com.intellij.openapi.util.UserDataHolderBase import com.intellij.openapi.vfs.VirtualFile import com.phodal.shirelang.compiler.variable.resolver.base.VariableResolver import kotlinx.datetime.Instant data class SnapshotMetadata( val createdAt: Instant, // 创建时间 val version: String, // 版本号或其他标识 val file: VirtualFile, // 文件的虚拟路径 ) /** * Variable Snapshot will store all change flow of a variable. For example: * ```shire * --- * variables: * "controllers": /.*.java/ { cat | grep("class\s+([a-zA-Z]*Controller)") } * --- * ``` * * The variable snapshot should store: * * - the value after cat function * - the value after grep function */ data class VariableOperation( val functionName: String, val timestamp: Long, val value: Any?, ) class UserCustomVariableSnapshot( val variableName: String, val value: Any? = null, val className: String? = VariableResolver::class.java.name, val operations: List = mutableListOf(), private val context: ExecutionContext = ExecutionContext(), ) : UserDataHolderBase() { private val valueHistory = mutableListOf() private var currentValue: Any? = null fun recordValue(value: Any, functionIndex: Int = -1) { currentValue = value valueHistory.add(value) } fun getCurrentValue(): Any? = currentValue fun getHistory(): List = valueHistory.toList() } data class ExecutionContext( val variables: MutableMap = mutableMapOf(), val environment: MutableMap = mutableMapOf(), val metadata: MutableMap = mutableMapOf(), ) ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/debugger/snapshot/VariableSnapshotRecorder.kt ================================================ package com.phodal.shirelang.debugger.snapshot import com.intellij.openapi.components.Service import com.intellij.openapi.project.Project @Service(Service.Level.PROJECT) class VariableSnapshotRecorder { private val snapshots = mutableListOf() private val listeners = mutableListOf() fun addSnapshot(variableName: String, value: Any, operation: String? = null, operationArg: Any? = null) { val operationList = mutableListOf() if (operation != null) { operationList.add(VariableOperation(operation, System.currentTimeMillis(), operationArg)) } val result = when (value) { is Array<*> -> { value.joinToString(", ") } is List<*> -> { value.joinToString(", ") } else -> { value.toString() } } snapshots.add(UserCustomVariableSnapshot(variableName, result, operations = operationList)) listeners.forEach { it.onSnapshot(variableName, result, operationList) } } fun clear() { snapshots.clear() } fun all(): List { return snapshots } fun addListener(listener: VariableSnapshotListener) { listeners.add(listener) } companion object { fun getInstance(project: Project): VariableSnapshotRecorder { return project.getService(VariableSnapshotRecorder::class.java) } } } interface VariableSnapshotListener { fun onSnapshot(variableName: String, value: String, operations: List) } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/documentation/ShireDocumentationProvider.kt ================================================ package com.phodal.shirelang.documentation import com.intellij.lang.documentation.AbstractDocumentationProvider import com.intellij.openapi.editor.Editor import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.util.elementType import com.phodal.shirelang.psi.ShireTypes import com.phodal.shirecore.agent.CustomAgent import com.phodal.shirecore.utils.markdown.MarkdownUtil import com.phodal.shirelang.ShireLanguage import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFunc import com.phodal.shirelang.completion.dataprovider.BuiltinCommand import com.phodal.shirelang.compiler.variable.CompositeVariableProvider class ShireDocumentationProvider : AbstractDocumentationProvider() { override fun generateDoc(element: PsiElement?, originalElement: PsiElement?): String? { val project = element?.project ?: return null val markdownDoc = when (element.elementType) { ShireTypes.IDENTIFIER -> { when (element.parent.elementType) { ShireTypes.AGENT_ID -> { val agentConfigs = CustomAgent.loadFromProject(project).filter { it.name == element.text } if (agentConfigs.isEmpty()) return null agentConfigs.joinToString("\n") { it.description } } ShireTypes.COMMAND_ID -> { val command = BuiltinCommand.all().find { it.commandName == element.text } ?: return null val example = BuiltinCommand.example(command) val lang = ShireLanguage.INSTANCE.displayName "${command.description}\nExample:\n```$lang\n$example\n```\n " } ShireTypes.VARIABLE_ID -> { CompositeVariableProvider.all().find { it.name == element.text }?.description } ShireTypes.FUNC_NAME -> { val funcName = element.text PatternActionFunc.findDocByName(funcName) ?: return null } else -> null } } ShireTypes.PATTERN_ACTION -> { "Pattern action is a way to define a pattern for the agent to match. It's a JSONPath expression." } else -> { null } } ?: return null return MarkdownUtil.toHtml(markdownDoc) } override fun getCustomDocumentationElement( editor: Editor, file: PsiFile, contextElement: PsiElement?, targetOffset: Int, ): PsiElement? = contextElement ?: file.findElementAt(targetOffset) } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/editor/FileFilterPopup.kt ================================================ package com.phodal.shirelang.editor import com.intellij.codeInsight.AutoPopupController import com.intellij.codeInsight.completion.InsertHandler import com.intellij.codeInsight.lookup.LookupElement import com.intellij.ide.presentation.VirtualFilePresentation import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.runReadAction import com.intellij.openapi.editor.event.DocumentListener import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.ui.popup.JBPopup import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.openapi.vfs.VirtualFile import com.intellij.ui.TextFieldWithAutoCompletion import com.intellij.ui.TextFieldWithAutoCompletionListProvider import com.phodal.shirecore.canBeAdded import java.awt.Component import java.awt.Dimension import javax.swing.Icon /** * @author lk */ class FileFilterPopup(project: Project, onSelect: (VirtualFile) -> Unit) { private val textField: TextFieldWithAutoCompletion private var popup: JBPopup? = null init { val fileList = mutableListOf() ApplicationManager.getApplication().executeOnPooledThread { runReadAction { ProjectFileIndex.getInstance(project).iterateContent({ file -> fileList.add(file) true }) { it.canBeAdded(project) } } } val basePath = project.guessProjectDir()?.path ?: "" textField = TextFieldWithAutoCompletion( project, object : TextFieldWithAutoCompletionListProvider(fileList) { override fun getLookupString(item: VirtualFile): String { return item.path.removePrefix(basePath) } override fun getIcon(item: VirtualFile): Icon? { return VirtualFilePresentation.getIcon(item) } override fun createInsertHandler(item: VirtualFile): InsertHandler { return InsertHandler { _, _ -> onSelect(item); popup?.cancel() } } }, false, null ) textField.addDocumentListener(object : DocumentListener { override fun documentChanged(e: com.intellij.openapi.editor.event.DocumentEvent) { if (e.oldLength > e.newLength && textField.editor != null) { AutoPopupController.getInstance(project).autoPopupMemberLookup(textField.editor) { true } } } }) } fun show(component: Component) { textField.preferredSize = Dimension(180, 30) popup = JBPopupFactory.getInstance() .createComponentPopupBuilder(textField, textField) .setResizable(true) .setMovable(false) .setRequestFocus(true) .createPopup() popup?.showUnderneathOf(component) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/editor/ShireFileEditorWithPreview.kt ================================================ package com.phodal.shirelang.editor import com.intellij.icons.AllIcons import com.intellij.ide.BrowserUtil import com.intellij.openapi.actionSystem.* import com.intellij.openapi.editor.event.VisibleAreaEvent import com.intellij.openapi.editor.event.VisibleAreaListener import com.intellij.openapi.editor.ex.util.EditorUtil import com.intellij.openapi.editor.impl.EditorImpl import com.intellij.openapi.fileEditor.TextEditor import com.intellij.openapi.fileEditor.TextEditorWithPreview import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.phodal.shirelang.ShireBundle class ShireFileEditorWithPreview( private val ourEditor: TextEditor, @JvmField var preview: ShirePreviewEditor, private val project: Project, ) : TextEditorWithPreview( ourEditor, preview, "Shire Split Editor", Layout.SHOW_EDITOR_AND_PREVIEW, ) { val virtualFile: VirtualFile = ourEditor.file init { // allow launching actions while in preview mode; preview.setMainEditor(ourEditor.editor) ourEditor.editor.scrollingModel.addVisibleAreaListener(MyVisibleAreaListener(), this) } override fun dispose() { TextEditorProvider.getInstance().disposeEditor(ourEditor) } inner class MyVisibleAreaListener : VisibleAreaListener { private var previousLine = 0 override fun visibleAreaChanged(event: VisibleAreaEvent) { val editor = event.editor val y = editor.scrollingModel.verticalScrollOffset val currentLine = if (editor is EditorImpl) editor.yToVisualLine(y) else y / editor.lineHeight if (currentLine == previousLine) { return } previousLine = currentLine preview.scrollToSrcOffset(EditorUtil.getVisualLineEndOffset(editor, currentLine)) } } override fun createToolbar(): ActionToolbar { return ActionManager.getInstance() .createActionToolbar(ActionPlaces.EDITOR_TOOLBAR, createActionGroup(project), true) .also { it.targetComponent = editor.contentComponent } } private fun createActionGroup(project: Project): ActionGroup { return DefaultActionGroup( object : AnAction(ShireBundle.message("editor.preview"), ShireBundle.message("editor.preview.tip"), AllIcons.Actions.Preview) { override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT override fun update(e: AnActionEvent) { e.presentation.isEnabled = !DumbService.isDumb(project) } override fun actionPerformed(e: AnActionEvent) { DumbService.getInstance(project).runWhenSmart { preview.component.isVisible = true preview.updateDisplayedContent() } } }, object : AnAction(ShireBundle.message("editor.preview.refresh"), ShireBundle.message("editor.preview.refresh"), AllIcons.Actions.Refresh) { override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT override fun update(e: AnActionEvent) { e.presentation.isEnabled = !DumbService.isDumb(project) } override fun actionPerformed(e: AnActionEvent) { DumbService.getInstance(project).runWhenSmart { preview.updateDisplayedContent() } } }, Separator(), object : AnAction(ShireBundle.message("editor.preview.help"), ShireBundle.message("editor.preview.help"), AllIcons.Actions.Help) { override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT override fun actionPerformed(e: AnActionEvent) { BrowserUtil.browse(ShireBundle.message("editor.preview.help.url")) } } ) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/editor/ShirePreviewEditor.kt ================================================ package com.phodal.shirelang.editor import com.intellij.icons.AllIcons import com.intellij.lang.Language import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.application.smartReadAction import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.ScrollType import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.fileEditor.FileEditor import com.intellij.openapi.fileEditor.FileEditorState import com.intellij.openapi.fileTypes.LanguageFileType import com.intellij.openapi.project.Project import com.intellij.openapi.util.UserDataHolder import com.intellij.openapi.util.UserDataHolderBase import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiManager import com.intellij.testFramework.LightVirtualFile import com.intellij.ui.JBColor import com.intellij.ui.RoundedLineBorder import com.intellij.ui.components.JBLabel import com.intellij.ui.components.JBScrollPane import com.intellij.ui.dsl.builder.Align import com.intellij.ui.dsl.builder.panel import com.intellij.util.ui.JBUI import com.intellij.util.ui.UIUtil import com.phodal.shirecore.ShireCoroutineScope import com.phodal.shirecore.sketch.highlight.CodeHighlightSketch import com.phodal.shirecore.sketch.highlight.EditorFragment import com.phodal.shirecore.utils.markdown.CodeFenceLanguage import com.phodal.shirelang.psi.ShireFile import com.phodal.shirelang.run.runner.ShireRunner import com.phodal.shirelang.run.runner.ShireRunnerContext import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.intellij.plugins.markdown.lang.MarkdownLanguage import java.awt.BorderLayout import java.awt.event.MouseAdapter import java.awt.event.MouseEvent import java.beans.PropertyChangeListener import javax.swing.BorderFactory import javax.swing.JComponent import javax.swing.JPanel import javax.swing.ScrollPaneConstants import javax.swing.SwingConstants /** * Display shire file render prompt and have a sample file as view */ open class ShirePreviewEditor( val project: Project, val virtualFile: VirtualFile, ) : UserDataHolder by UserDataHolderBase(), FileEditor { val psiFile = PsiManager.getInstance(project).findFile(virtualFile) private var mainEditor = MutableStateFlow(null) private val mainPanel = JPanel(BorderLayout()) private val visualPanel: JBScrollPane = JBScrollPane( mainPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER ) private var shireRunnerContext: ShireRunnerContext? = null private val variablePanel = ShireVariableViewPanel(project) private var highlightSketch: CodeHighlightSketch? = null private var sampleEditor: Editor? = null private var language: Language? = Language.findLanguageByID("JAVA") private val javaHelloWorld = """ package com.phodal.shirelang; class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World"); } } """.trimIndent() private var editorPanel: JPanel? = null init { val corePanel = panel { row { val label = JBLabel("Shire Preview (Experimental)").apply { fontColor = UIUtil.FontColor.BRIGHTER background = JBColor(0xF5F5F5, 0x2B2D30) font = JBUI.Fonts.label(16.0f).asBold() border = JBUI.Borders.empty(0, 16) isOpaque = true } cell(label).align(Align.FILL).resizableColumn() } if (language != null) { row { cell(JBLabel("Sample file for variable").apply { fontColor = UIUtil.FontColor.BRIGHTER background = JBColor(0xF5F5F5, 0x2B2D30) font = JBUI.Fonts.label(14.0f).asBold() border = JBUI.Borders.empty(0, 16) isOpaque = true }).align(Align.FILL).resizableColumn() cell(JBLabel("(/shire.java)", AllIcons.Actions.Edit, SwingConstants.LEADING).also { it.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent?) { FileFilterPopup(project) { file -> it.text = "(${file.name})" language = (file.fileType as? LanguageFileType)?.language updatePreviewEditor(file) }.show(it) } }) }).align(Align.FILL).resizableColumn() button("", object : AnAction() { override fun actionPerformed(event: AnActionEvent) { updateDisplayedContent() } }).also { it.component.icon = AllIcons.Actions.Refresh it.component.preferredSize = JBUI.size(24, 24) } } row { val editor = CodeHighlightSketch.createCodeViewerEditor( project, javaHelloWorld, language, this@ShirePreviewEditor ) setSampleEditor(editor) { editorPanel = JPanel(BorderLayout()).apply { add(it, BorderLayout.CENTER) cell(this).align(Align.FILL).resizableColumn() } } } } row { cell(JBLabel("Variables").apply { fontColor = UIUtil.FontColor.BRIGHTER background = JBColor(0xF5F5F5, 0x2B2D30) font = JBUI.Fonts.label(14.0f).asBold() border = JBUI.Borders.empty(0, 16) isOpaque = true }).align(Align.FILL).resizableColumn() } row { cell(variablePanel).align(Align.FILL) } row { cell(JBLabel("Prompt (some variable may be error)").apply { fontColor = UIUtil.FontColor.BRIGHTER background = JBColor(0xF5F5F5, 0x2B2D30) font = JBUI.Fonts.label(14.0f).asBold() border = JBUI.Borders.empty(0, 16) isOpaque = true }).align(Align.FILL).resizableColumn() } row { highlightSketch = CodeHighlightSketch(project, "", MarkdownLanguage.INSTANCE, 18).apply { initEditor("Please refresh to see the result") } highlightSketch?.editorFragment?.setCollapsed(true) highlightSketch?.editorFragment?.updateExpandCollapseLabel() val panel = JPanel(BorderLayout()) panel.border = BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(12, 12, 12, 12), RoundedLineBorder(JBColor.border(), 8, 1) ) highlightSketch?.let { panel.add(it, BorderLayout.CENTER) } cell(panel).align(Align.FILL) } } this.mainPanel.add(corePanel, BorderLayout.CENTER) } fun updateDisplayedContent() { try { ShireCoroutineScope.scope(project).launch { runBlocking { val psiFile = smartReadAction(project) { PsiManager.getInstance(project).findFile(virtualFile) as? ShireFile } ?: return@runBlocking shireRunnerContext = ShireRunner.compileOnly(project, psiFile, mapOf(), sampleEditor) val variables = shireRunnerContext?.compiledVariables if (variables != null) { variablePanel.updateVariables(variables) } highlightSketch?.updateViewText(shireRunnerContext!!.finalPrompt) highlightSketch?.repaint() mainPanel.revalidate() mainPanel.repaint() } } } catch (e: Exception) { e.printStackTrace() } } fun setMainEditor(editor: Editor) { check(mainEditor.value == null) mainEditor.value = editor } fun scrollToSrcOffset(offset: Int) { val highlightEditor = highlightSketch?.editorFragment?.editor if (highlightEditor == null) { visualPanel.verticalScrollBar.value = offset return } val position = highlightEditor.offsetToLogicalPosition(offset) highlightEditor.scrollingModel.scrollTo(position, ScrollType.MAKE_VISIBLE) } private fun updatePreviewEditor(file: VirtualFile) { FileDocumentManager.getInstance().getDocument(file)?.text?.let { text -> val language = language ?: CodeFenceLanguage.findLanguage("Plain text") val lightFile = object : LightVirtualFile(file.name, language, text) { override fun getPath() = file.path } val document = FileDocumentManager.getInstance().getDocument(lightFile) ?: return@let val editor = CodeHighlightSketch.createCodeViewerEditor( project, lightFile, document, this ) setSampleEditor(editor) { editorPanel?.removeAll() editorPanel?.add(it, BorderLayout.CENTER) } updateDisplayedContent() } } private fun setSampleEditor(editor: EditorEx, consume: (JComponent) -> Unit) { editor.isViewer = false editor.settings.isLineNumbersShown = true val editorFragment = EditorFragment(editor) editorFragment.setCollapsed(true) editorFragment.updateExpandCollapseLabel() sampleEditor = editor consume(editorFragment.getContent()) } override fun getComponent(): JComponent = visualPanel override fun getName(): String = "Shire Prompt Preview" override fun setState(state: FileEditorState) {} override fun isModified(): Boolean = false override fun isValid(): Boolean = true override fun getFile(): VirtualFile = virtualFile override fun getPreferredFocusedComponent(): JComponent? = null override fun addPropertyChangeListener(listener: PropertyChangeListener) {} override fun removePropertyChangeListener(listener: PropertyChangeListener) {} override fun dispose() {} } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/editor/ShirePreviewEditorProvider.kt ================================================ package com.phodal.shirelang.editor import com.intellij.openapi.fileEditor.AsyncFileEditorProvider import com.intellij.openapi.fileEditor.FileEditor import com.intellij.openapi.fileEditor.FileEditorPolicy import com.intellij.openapi.fileEditor.WeighedFileEditorProvider import com.intellij.openapi.fileTypes.FileTypeRegistry import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.phodal.shirelang.ShireFileType class ShirePreviewEditorProvider : WeighedFileEditorProvider(), AsyncFileEditorProvider { override fun accept(project: Project, file: VirtualFile): Boolean { return FileTypeRegistry.getInstance().isFileOfType(file, ShireFileType.INSTANCE) } override fun createEditor(project: Project, virtualFile: VirtualFile): FileEditor { return ShirePreviewEditor(project, virtualFile) } override fun createEditorAsync(project: Project, file: VirtualFile): AsyncFileEditorProvider.Builder { return object : AsyncFileEditorProvider.Builder() { override fun build(): FileEditor { return ShirePreviewEditor(project, file) } } } override fun getEditorTypeId(): String = "shire-preview-editor" override fun getPolicy(): FileEditorPolicy = FileEditorPolicy.PLACE_AFTER_DEFAULT_EDITOR } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/editor/ShireSnapshotViewPanel.kt ================================================ package com.phodal.shirelang.editor import com.intellij.ui.JBColor import com.intellij.ui.components.JBLabel import com.intellij.ui.components.JBPanel import com.intellij.ui.components.JBScrollPane import com.intellij.ui.table.JBTable import com.intellij.util.ui.JBUI import com.intellij.util.ui.UIUtil import com.phodal.shirelang.ShireBundle import com.phodal.shirelang.debugger.snapshot.UserCustomVariableSnapshot import java.awt.BorderLayout import javax.swing.JPanel import javax.swing.ScrollPaneConstants import javax.swing.table.DefaultTableModel class ShireSnapshotViewPanel : JPanel(BorderLayout()) { private val contentPanel = JBPanel>(BorderLayout()) private val tableModel = DefaultTableModel(arrayOf("Variable", "Operation", "Value", "Timestamp"), 0) init { val table = JBTable(tableModel).apply { tableHeader.reorderingAllowed = true tableHeader.resizingAllowed = true setShowGrid(true) gridColor = JBColor.PanelBackground intercellSpacing = JBUI.size(0, 0) val columnModel = columnModel columnModel.getColumn(0).preferredWidth = 80 columnModel.getColumn(1).preferredWidth = 60 columnModel.getColumn(2).preferredWidth = 300 columnModel.getColumn(3).preferredWidth = 80 autoResizeMode = JBTable.AUTO_RESIZE_LAST_COLUMN } val scrollPane = JBScrollPane( table, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED ).apply { minimumSize = JBUI.size(0, 160) preferredSize = JBUI.size(0, 160) } setupPanel() add(scrollPane, BorderLayout.CENTER) } private fun setupPanel() { contentPanel.background = JBColor(0xF5F5F5, 0x2B2D30) val titleLabel = JBLabel(ShireBundle.message("editor.preview.variable.panel")).apply { font = JBUI.Fonts.label(14f).asBold() fontColor = UIUtil.FontColor.BRIGHTER background = JBColor(0xF5F5F5, 0x2B2D30) font = JBUI.Fonts.label(14.0f).asBold() border = JBUI.Borders.empty(0, 16) isOpaque = true } contentPanel.add(titleLabel, BorderLayout.NORTH) add(contentPanel, BorderLayout.NORTH) } fun updateSnapshots(snapshots: List) { if (snapshots.isEmpty()) { isVisible = false return } isVisible = true tableModel.rowCount = 0 /// remove all rows tableModel.dataVector.removeAllElements() snapshots.forEach { snapshot -> val operation = snapshot.operations.firstOrNull() tableModel.addRow( arrayOf( snapshot.variableName, operation?.functionName ?: "", snapshot.value.toString(), formatTimestamp(operation?.timestamp ?: 0) ) ) } revalidate() repaint() } private fun formatTimestamp(timestamp: Long): String { if (timestamp == 0L) { return "N/A" } return java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(timestamp) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/editor/ShireSplitEditorProvider.kt ================================================ package com.phodal.shirelang.editor import com.intellij.openapi.fileEditor.* import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider import com.intellij.openapi.fileTypes.FileTypeRegistry import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.testFramework.LightVirtualFile import com.phodal.shirelang.ShireFileType class ShireSplitEditorProvider : WeighedFileEditorProvider() { override fun getEditorTypeId() = "shire-split-editor" private val mainProvider: TextEditorProvider = TextEditorProvider.getInstance() private val previewProvider: FileEditorProvider = ShirePreviewEditorProvider() override fun accept(project: Project, file: VirtualFile) = FileTypeRegistry.getInstance().isFileOfType(file, ShireFileType.INSTANCE) override fun createEditor(project: Project, file: VirtualFile): FileEditor { val editor = TextEditorProvider.getInstance().createEditor(project, file) if (editor.file is LightVirtualFile) { return editor } val mainEditor = mainProvider.createEditor(project, file) as TextEditor val preview = previewProvider.createEditor(project, file) as ShirePreviewEditor return ShireFileEditorWithPreview(mainEditor, preview, project) } override fun getPolicy() = FileEditorPolicy.HIDE_OTHER_EDITORS } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/editor/ShireVariableViewPanel.kt ================================================ package com.phodal.shirelang.editor import com.intellij.openapi.project.Project import com.intellij.ui.JBColor import com.intellij.ui.components.JBLabel import com.intellij.ui.components.JBPanel import com.intellij.ui.components.JBScrollPane import com.intellij.ui.table.JBTable import com.intellij.util.ui.JBUI import com.phodal.shirecore.provider.variable.model.DebugValue import com.phodal.shirecore.provider.variable.model.Variable import com.phodal.shirelang.debugger.snapshot.VariableSnapshotRecorder import java.awt.BorderLayout import javax.swing.JPanel import javax.swing.ScrollPaneConstants import javax.swing.table.DefaultTableModel class ShireVariableViewPanel(val project: Project) : JPanel(BorderLayout()) { private val contentPanel = JBPanel>(BorderLayout()) private val tableModel = DefaultTableModel(arrayOf("Name", "Description", "Value"), 0).apply { background = JBColor.WHITE } private val snapshotViewPanel = ShireSnapshotViewPanel() private val snapshotRecorder = VariableSnapshotRecorder.getInstance(project) init { val table = JBTable(tableModel).apply { tableHeader.reorderingAllowed = true tableHeader.resizingAllowed = true setShowGrid(true) gridColor = JBColor.PanelBackground intercellSpacing = JBUI.size(0, 0) val columnModel = columnModel columnModel.getColumn(0).preferredWidth = 150 columnModel.getColumn(1).preferredWidth = 450 autoResizeMode = JBTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS } val scrollPane = JBScrollPane( table, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER ).apply { minimumSize = JBUI.size(0, 160) preferredSize = JBUI.size(0, 160) } add(scrollPane, BorderLayout.CENTER) add(snapshotViewPanel, BorderLayout.SOUTH) setupPanel() } private fun setupPanel() { contentPanel.background = JBColor(0xF5F5F5, 0x2B2D30) val titleLabel = JBLabel("Variables").apply { font = JBUI.Fonts.label(14f).asBold() border = JBUI.Borders.empty(4, 8) } contentPanel.add(titleLabel, BorderLayout.NORTH) } fun updateVariables(variables: Map) { tableModel.rowCount = 0 val allVariables: MutableMap = DebugValue.all().associateBy { it.variableName }.toMutableMap() snapshotViewPanel.updateSnapshots(snapshotRecorder.all()) variables.toSortedMap().forEach { (key, value) -> val valueStr = value.toString() val description = DebugValue.description(key) /// remove existing variables if (allVariables.containsKey(key)) { allVariables.remove(key) } tableModel.addRow(java.util.Vector().apply { add(key) add(description) add(valueStr) }) } allVariables.forEach { (_, value) -> tableModel.addRow(java.util.Vector().apply { add(value.variableName) add(value.description) add("N/A") }) } revalidate() repaint() } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/folding/ShireFoldingBuilder.kt ================================================ package com.phodal.shirelang.folding import com.intellij.lang.ASTNode import com.intellij.lang.folding.FoldingBuilderEx import com.intellij.lang.folding.FoldingDescriptor import com.intellij.openapi.editor.Document import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.text.StringUtil import com.intellij.psi.PsiElement import com.intellij.psi.util.PsiUtilCore import com.intellij.psi.util.elementType import com.phodal.shirelang.compiler.execute.command.FileShireCommand import com.phodal.shirelang.completion.dataprovider.BuiltinCommand import com.phodal.shirelang.psi.* class ShireFoldingBuilder : FoldingBuilderEx() { override fun isCollapsedByDefault(node: ASTNode): Boolean = true override fun getPlaceholderText(node: ASTNode): String = node.text override fun buildFoldRegions(root: PsiElement, document: Document, quick: Boolean): Array { val descriptors = mutableListOf() root.accept(ShireFoldingVisitor(descriptors)) return descriptors.toTypedArray() } override fun getPlaceholderText(node: ASTNode, range: TextRange): String { val elementType = PsiUtilCore.getElementType(node) when (elementType) { ShireTypes.USED -> { val commandId = (node.psi as ShireUsed).commandId when (commandId?.text) { BuiltinCommand.FILE.commandName -> { val prop = (node.psi as ShireUsed).commandProp?.text ?: return "" val virtualFile = FileShireCommand.file((node.psi as ShireUsed).project, prop) return "/${BuiltinCommand.FILE.commandName}:${virtualFile?.name}" } BuiltinCommand.STRUCTURE.commandName -> { val prop = (node.psi as ShireUsed).commandProp?.text ?: return "" val virtualFile = FileShireCommand.file((node.psi as ShireUsed).project, prop) return "/${BuiltinCommand.STRUCTURE.commandName}:${virtualFile?.name}" } } } } val explicitName = foldedElementsPresentations[elementType] val elementText = StringUtil.shortenTextWithEllipsis(node.text, 30, 5) return explicitName?.let { "$it: $elementText" } ?: elementText } private val foldedElementsPresentations = hashMapOf( ShireTypes.FRONT_MATTER_HEADER to "Hobbit Hole", ShireTypes.CODE to "Code Block", ShireTypes.QUERY_STATEMENT to "Shire AstQL", ShireTypes.BLOCK_COMMENT to "/* ... */", ) override fun isCollapsedByDefault(foldingDescriptor: FoldingDescriptor): Boolean { return when (foldingDescriptor.element.elementType) { ShireTypes.FRONT_MATTER_HEADER -> true ShireTypes.CODE -> false ShireTypes.USED -> true else -> false } } } class ShireFoldingVisitor(private val descriptors: MutableList) : ShireVisitor() { override fun visitElement(element: PsiElement) { when (element.elementType) { ShireTypes.FRONT_MATTER_HEADER -> { descriptors.add(FoldingDescriptor(element.node, element.textRange)) } ShireTypes.CODE -> { descriptors.add(FoldingDescriptor(element.node, element.textRange)) } ShireTypes.USED -> { val commandId = (element as? ShireUsed)?.commandId if (commandId?.text == BuiltinCommand.FILE.commandName) { descriptors.add(FoldingDescriptor(element.node, element.textRange)) } } } element.acceptChildren(this) } override fun visitQueryStatement(o: ShireQueryStatement) { descriptors.add(FoldingDescriptor(o.node, o.textRange)) } override fun visitCaseBody(o: ShireCaseBody) { descriptors.add(FoldingDescriptor(o.node, o.textRange)) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/formatter/ShireFormattingModelBuilder.kt ================================================ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.phodal.shirelang.formatter import com.intellij.formatting.* import com.intellij.lang.ASTNode import com.intellij.psi.PsiFile import com.intellij.psi.codeStyle.CodeStyleSettings import com.intellij.psi.formatter.DocumentBasedFormattingModel import com.intellij.psi.formatter.common.AbstractBlock import com.intellij.psi.tree.IElementType import com.intellij.psi.util.PsiUtilCore import com.intellij.util.SmartList import com.intellij.util.containers.FactoryMap import com.phodal.shirelang.psi.ShireElementType import com.phodal.shirelang.psi.ShireTypes class ShireFormattingModelBuilder : FormattingModelBuilder { override fun createModel(formattingContext: FormattingContext): FormattingModel { val file = formattingContext.containingFile val settings = formattingContext.codeStyleSettings val rootBlock = createBlock(ShireFormattingContext(settings, file), formattingContext.node) return DocumentBasedFormattingModel(rootBlock, settings, file) } companion object { fun createBlock(context: ShireFormattingContext, node: ASTNode): Block { val nodeType = PsiUtilCore.getElementType(node) return ShireFormattingBlock(context, node) } } } class ShireFormattingBlock(private val myContext: ShireFormattingContext, val myNode: ASTNode) : AbstractBlock(myNode, null, myContext.computeAlignment(myNode)) { private val myIndent: Indent? init { myIndent = myContext.computeBlockIndent(myNode) } override fun getIndent(): Indent? { return myIndent } override fun getSpacing(child1: Block?, child2: Block): Spacing? = myContext.computeSpacing(this, child1, child2) override fun isLeaf(): Boolean = false override fun buildChildren(): List = buildSubBlocks(myContext, myNode) private fun buildSubBlocks(context: ShireFormattingContext, node: ASTNode): List { val res: MutableList = SmartList() var subNode = node.firstChildNode while (subNode != null) { val subNodeType = PsiUtilCore.getElementType(subNode) if (ShireElementType.SPACE_ELEMENTS.contains(subNodeType)) { // just skip them (comment processed above) } else if (ShireTypes.QUOTE_STRING === subNodeType) { res.addAll(buildSubBlocks(context, subNode)) } else if (ShireElementType.CONTAINERS.contains(subNodeType)) { // } else { res.add(ShireFormattingModelBuilder.createBlock(context, subNode)) } subNode = subNode.treeNext } return res } } class ShireFormattingContext(val mySettings: CodeStyleSettings, file: PsiFile) { private val myChildIndentAlignments: Map = FactoryMap.create { node: ASTNode? -> Alignment.createAlignment(true) } private val myChildValueAlignments: Map = FactoryMap.create { node: ASTNode? -> Alignment.createAlignment(true) } fun computeAlignment(node: ASTNode): Alignment? { val type: IElementType = PsiUtilCore.getElementType(node) if (type === ShireTypes.COLON) { return myChildValueAlignments[node.treeParent.treeParent] } if (type === ShireTypes.KEY_VALUE) { return myChildIndentAlignments[node.treeParent] } return null } fun computeSpacing(shireFormattingBlock: ShireFormattingBlock, child1: Block?, child2: Block): Spacing? { return null } private val DIRECT_NORMAL_INDENT: Indent = Indent.getNormalIndent(true) private val SAME_AS_PARENT_INDENT: Indent = Indent.getSpaceIndent(0, true) private val SAME_AS_INDENTED_ANCESTOR_INDENT: Indent = Indent.getSpaceIndent(0) fun computeBlockIndent(node: ASTNode): Indent? { val nodeType: IElementType = PsiUtilCore.getElementType(node) ?: return null if (nodeType === ShireTypes.KEY_VALUE) { return computeKeyValuePairIndent(node) } return null } private fun computeKeyValuePairIndent(node: ASTNode): Indent? { val parentType = PsiUtilCore.getElementType(node.treeParent) val grandParentType = if (parentType == null) null else PsiUtilCore.getElementType(node.treeParent.treeParent) return DIRECT_NORMAL_INDENT } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/highlight/ShireErrorFilter.kt ================================================ package com.phodal.shirelang.highlight import com.intellij.codeInsight.highlighting.HighlightErrorFilter import com.phodal.shirelang.ShireLanguage import com.intellij.psi.PsiErrorElement import com.intellij.psi.PsiFile class ShireErrorFilter : HighlightErrorFilter() { override fun shouldHighlightErrorElement(element: PsiErrorElement): Boolean { val containingFile: PsiFile = element.containingFile return !(containingFile.language === ShireLanguage.INSTANCE && containingFile.context != null) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/highlight/ShireHighlightingAnnotator.kt ================================================ package com.phodal.shirelang.highlight import com.intellij.lang.annotation.AnnotationHolder import com.intellij.lang.annotation.Annotator import com.intellij.lang.annotation.HighlightSeverity import com.intellij.openapi.editor.DefaultLanguageHighlighterColors import com.intellij.psi.PsiElement import com.intellij.psi.util.PsiUtilCore import com.phodal.shirelang.psi.ShireTypes class ShireHighlightingAnnotator : Annotator { override fun annotate(element: PsiElement, holder: AnnotationHolder) { when (PsiUtilCore.getElementType(element)) { ShireTypes.IDENTIFIER -> { holder.newSilentAnnotation(HighlightSeverity.INFORMATION) .textAttributes(DefaultLanguageHighlighterColors.IDENTIFIER) .create() } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/highlight/ShireSyntaxHighlighter.kt ================================================ package com.phodal.shirelang.highlight import com.intellij.lexer.Lexer import com.intellij.openapi.editor.DefaultLanguageHighlighterColors import com.intellij.openapi.editor.colors.TextAttributesKey import com.intellij.openapi.fileTypes.SyntaxHighlighter import com.intellij.openapi.fileTypes.SyntaxHighlighterBase import com.intellij.psi.tree.IElementType import com.intellij.psi.tree.TokenSet import com.phodal.shirelang.lexer.ShireLexerAdapter import com.phodal.shirelang.psi.ShireTypes class ShireSyntaxHighlighter : SyntaxHighlighterBase() { override fun getHighlightingLexer(): Lexer = ShireLexerAdapter() override fun getTokenHighlights(tokenType: IElementType?): Array { return pack(ATTRIBUTES[tokenType]) } companion object { private val ATTRIBUTES: MutableMap = HashMap() private val KEYWORDS: TokenSet = TokenSet.create( ShireTypes.CASE, ShireTypes.DEFAULT, ShireTypes.SELECT, ShireTypes.WHERE, ShireTypes.FROM, ShireTypes.IF, ShireTypes.ELSE, ShireTypes.ELSEIF, ShireTypes.END, ShireTypes.ENDIF, ShireTypes.AND, // true and false ShireTypes.BOOLEAN, // lifecycle ShireTypes.WHEN, ShireTypes.BEFORE_STREAMING, ShireTypes.ON_STREAMING, ShireTypes.ON_STREAMING_END, ShireTypes.AFTER_STREAMING, ) init { fillMap( ATTRIBUTES, KEYWORDS, DefaultLanguageHighlighterColors.KEYWORD ) ATTRIBUTES[ShireTypes.COMMENTS] = DefaultLanguageHighlighterColors.LINE_COMMENT ATTRIBUTES[ShireTypes.CONTENT_COMMENTS] = DefaultLanguageHighlighterColors.LINE_COMMENT ATTRIBUTES[ShireTypes.BLOCK_COMMENT] = DefaultLanguageHighlighterColors.BLOCK_COMMENT ATTRIBUTES[ShireTypes.VARIABLE_START] = DefaultLanguageHighlighterColors.KEYWORD ATTRIBUTES[ShireTypes.VARIABLE_ID] = DefaultLanguageHighlighterColors.CONSTANT ATTRIBUTES[ShireTypes.FOREIGN_TYPE] = DefaultLanguageHighlighterColors.KEYWORD ATTRIBUTES[ShireTypes.OUTPUT_VAR] = DefaultLanguageHighlighterColors.LOCAL_VARIABLE ATTRIBUTES[ShireTypes.ACCESS] = DefaultLanguageHighlighterColors.KEYWORD ATTRIBUTES[ShireTypes.PROCESS] = DefaultLanguageHighlighterColors.KEYWORD ATTRIBUTES[ShireTypes.AGENT_START] = DefaultLanguageHighlighterColors.KEYWORD ATTRIBUTES[ShireTypes.AGENT_ID] = DefaultLanguageHighlighterColors.CONSTANT ATTRIBUTES[ShireTypes.COMMAND_START] = DefaultLanguageHighlighterColors.KEYWORD ATTRIBUTES[ShireTypes.COMMAND_ID] = DefaultLanguageHighlighterColors.KEYWORD ATTRIBUTES[ShireTypes.COMMAND_PROP] = DefaultLanguageHighlighterColors.STRING ATTRIBUTES[ShireTypes.SHARP] = DefaultLanguageHighlighterColors.CONSTANT ATTRIBUTES[ShireTypes.MARKDOWN_HEADER] = DefaultLanguageHighlighterColors.CONSTANT ATTRIBUTES[ShireTypes.LINE_INFO] = DefaultLanguageHighlighterColors.NUMBER ATTRIBUTES[ShireTypes.CODE_BLOCK_START] = DefaultLanguageHighlighterColors.KEYWORD ATTRIBUTES[ShireTypes.CODE_BLOCK_END] = DefaultLanguageHighlighterColors.KEYWORD ATTRIBUTES[ShireTypes.LANGUAGE_ID] = DefaultLanguageHighlighterColors.CONSTANT ATTRIBUTES[ShireTypes.NUMBER] = DefaultLanguageHighlighterColors.NUMBER ATTRIBUTES[ShireTypes.FRONTMATTER_START] = DefaultLanguageHighlighterColors.KEYWORD ATTRIBUTES[ShireTypes.FRONTMATTER_END] = DefaultLanguageHighlighterColors.KEYWORD ATTRIBUTES[ShireTypes.FRONT_MATTER_ID] = DefaultLanguageHighlighterColors.CONSTANT // func name ATTRIBUTES[ShireTypes.IDENTIFIER] = DefaultLanguageHighlighterColors.IDENTIFIER ATTRIBUTES[ShireTypes.NUMBER] = DefaultLanguageHighlighterColors.KEYWORD ATTRIBUTES[ShireTypes.QUOTE_STRING] = DefaultLanguageHighlighterColors.STRING ATTRIBUTES[ShireTypes.DATE] = DefaultLanguageHighlighterColors.LABEL ATTRIBUTES[ShireTypes.LBRACKET] = DefaultLanguageHighlighterColors.BRACKETS ATTRIBUTES[ShireTypes.RBRACKET] = DefaultLanguageHighlighterColors.BRACKETS ATTRIBUTES[ShireTypes.OPEN_BRACE] = DefaultLanguageHighlighterColors.BRACES ATTRIBUTES[ShireTypes.CLOSE_BRACE] = DefaultLanguageHighlighterColors.BRACES ATTRIBUTES[ShireTypes.LPAREN] = DefaultLanguageHighlighterColors.PARENTHESES ATTRIBUTES[ShireTypes.RPAREN] = DefaultLanguageHighlighterColors.PARENTHESES } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/highlight/ShireSyntaxHighlighterFactory.kt ================================================ package com.phodal.shirelang.highlight import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile class ShireSyntaxHighlighterFactory : SyntaxHighlighterFactory() { override fun getSyntaxHighlighter(project: Project?, virtualFile: VirtualFile?) = ShireSyntaxHighlighter() } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/highlight/braces/ShireBraceMatcher.kt ================================================ package com.phodal.shirelang.highlight.braces import com.intellij.codeInsight.highlighting.PairedBraceMatcherAdapter import com.intellij.lang.BracePair import com.intellij.lang.PairedBraceMatcher import com.intellij.psi.PsiFile import com.intellij.psi.tree.IElementType import com.phodal.shirelang.ShireLanguage import com.phodal.shirelang.psi.ShireTypes class ShireBraceMatcher : PairedBraceMatcherAdapter( MyPairedBraceMatcher(), ShireLanguage.INSTANCE ) { class MyPairedBraceMatcher : PairedBraceMatcher { override fun getPairs(): Array { return arrayOf( BracePair(ShireTypes.LPAREN, ShireTypes.RPAREN, false), BracePair(ShireTypes.LBRACKET, ShireTypes.RBRACKET, false), BracePair(ShireTypes.LT, ShireTypes.GT, false), BracePair(ShireTypes.OPEN_BRACE, ShireTypes.CLOSE_BRACE, true), BracePair(ShireTypes.CODE_BLOCK_START, ShireTypes.CODE_BLOCK_END, true) ) } override fun isPairedBracesAllowedBeforeType(lbraceType: IElementType, type: IElementType?): Boolean { return type == null || type === ShireTypes.RPAREN || type === ShireTypes.RBRACKET || type === ShireTypes.GT } override fun getCodeConstructStart(file: PsiFile, openingBraceOffset: Int): Int { return openingBraceOffset } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/highlight/braces/ShireQuoteHandler.kt ================================================ package com.phodal.shirelang.highlight.braces import com.intellij.codeInsight.editorActions.SimpleTokenSetQuoteHandler import com.phodal.shirelang.psi.ShireTypes class ShireQuoteHandler : SimpleTokenSetQuoteHandler( ShireTypes.QUOTE_STRING ) ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/index/ShireIdentifierIndex.kt ================================================ package com.phodal.shirelang.index import com.intellij.psi.PsiElement import com.intellij.util.indexing.* import com.intellij.util.io.DataExternalizer import com.intellij.util.io.EnumeratorStringDescriptor import com.intellij.util.io.KeyDescriptor import com.phodal.shirelang.ShireFileType import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole import com.phodal.shirelang.psi.ShireFrontMatterKey import com.phodal.shirelang.psi.ShireVisitor import java.io.DataInput import java.io.DataOutput internal val SHIRE_CONFIG_IDENTIFIER_INDEX_ID = ID.create("shire.index.name") internal val isIndexing = ThreadLocal() class ShireIdentifierIndex: FileBasedIndexExtension() { override fun getValueExternalizer() = object : DataExternalizer { override fun save(out: DataOutput, value: Int) = out.writeInt(value) override fun read(`in`: DataInput) = `in`.readInt() } override fun getIndexer() = DataIndexer { val result = mutableMapOf() val visitor = object : ShireVisitor() { override fun visitElement(element: PsiElement) { if (element is ShireFrontMatterKey && element.text == HobbitHole.NAME) { result[element.text] = element.textOffset } super.visitElement(element) } } isIndexing.set(true) it.psiFile.accept(visitor) isIndexing.set(false) result } override fun getName() = SHIRE_CONFIG_IDENTIFIER_INDEX_ID override fun getVersion() = 1 override fun dependsOnFileContent() = true override fun getInputFilter() = inputFilter override fun getKeyDescriptor(): KeyDescriptor = EnumeratorStringDescriptor.INSTANCE private val inputFilter = DefaultFileTypeSpecificInputFilter(ShireFileType.INSTANCE) } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/lexer/ShireLexerAdapter.kt ================================================ package com.phodal.shirelang.lexer import com.intellij.lexer.FlexAdapter class ShireLexerAdapter : FlexAdapter(_ShireLexer()) ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/lexer/ShireTokenType.kt ================================================ package com.phodal.shirelang.lexer import com.intellij.psi.tree.IElementType import com.phodal.shirelang.ShireLanguage import org.jetbrains.annotations.NonNls class ShireTokenType(debugName: @NonNls String) : IElementType(debugName, ShireLanguage.INSTANCE) { override fun toString(): String = "ShireTokenType." + super.toString() } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/lints/ShireDuplicateAgentInspection.kt ================================================ package com.phodal.shirelang.lints import com.intellij.codeInspection.LocalInspectionTool import com.intellij.codeInspection.ProblemsHolder import com.intellij.psi.PsiElementVisitor import com.intellij.psi.util.elementType import com.phodal.shirelang.ShireBundle import com.phodal.shirelang.psi.ShireTypes import com.phodal.shirelang.psi.ShireUsed import com.phodal.shirelang.psi.ShireVisitor class ShireDuplicateAgentInspection : LocalInspectionTool() { override fun getGroupDisplayName() = ShireBundle.message("inspection.group.name") override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { return ShireDuplicateAgentVisitor(holder) } private class ShireDuplicateAgentVisitor(val holder: ProblemsHolder) : ShireVisitor() { private var agentIds: MutableSet = mutableSetOf() override fun visitUsed(o: ShireUsed) { if (o.firstChild.nextSibling.elementType == ShireTypes.AGENT_ID) { agentIds.add(o) if (agentIds.contains(o)) { agentIds.forEachIndexed { index, it -> if (index > 0) { holder.registerProblem(it, ShireBundle.message("inspection.duplicate.agent")) } } } } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/markdown/CodeFenceLanguageAliases.kt ================================================ // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.phodal.shirelang.markdown import com.intellij.openapi.util.text.StringUtil /** * Service to work with Markdown code fence's info-string. * * [CodeFenceLanguageAliases] is able to find possible IntelliJ Language ID * for info string (including resolution of standard aliases) and * (backwards) suggest correct info string for IntelliJ Language ID */ object CodeFenceLanguageAliases { private data class Entry( val id: String, val main: String, val aliases: Set ) private val aliases = setOf( Entry("go", "go", setOf("golang")), Entry("HCL", "hcl", setOf("hcl")), Entry("ApacheConfig", "apacheconf", setOf("aconf", "apache", "apacheconfig")), Entry("Batch", "batch", setOf("bat", "batchfile")), Entry("CoffeeScript", "coffeescript", setOf("coffee", "coffee-script")), Entry("JavaScript", "javascript", setOf("js", "node")), Entry("Markdown", "markdown", setOf("md")), Entry("PowerShell", "powershell", setOf("posh", "pwsh")), Entry("Python", "python", setOf("python2", "python3", "py")), Entry("R", "r", setOf("rlang", "rscript")), Entry("RegExp", "regexp", setOf("regex")), Entry("Ruby", "ruby", setOf("ruby", "rb")), Entry("Yaml", "yaml", setOf("yml")), Entry("Kotlin", "kotlin", setOf("kt", "kts")), Entry("HCL-Terraform", "terraform", setOf("hcl-terraform", "tf")), Entry("C#", "csharp", setOf("cs", "c#")), Entry("F#", "fsharp", setOf("fs", "f#")), Entry("Shell Script", "shell", setOf("shell script", "bash", "zsh", "sh")) ) /** * Finds the registered entry based on the provided value. * * @param value the value to search for in the registered entries * @return the ID of the entry if found, null otherwise */ fun findRegisteredEntry(value: String): String? { val lower = value.lowercase() val entry = aliases.singleOrNull { lower == it.main || lower in it.aliases } return entry?.id } /** * Get recommended alias for [id] * @return recommended alias if any or just [id] */ fun findMainAlias(id: String): String { return findMainAliasIfRegistered(id) ?: StringUtil.toLowerCase(id) } private fun findMainAliasIfRegistered(id: String): String? { return aliases.singleOrNull { id == it.id }?.main } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/navigation/ShireGotoDeclarationHandler.kt ================================================ package com.phodal.shirelang.navigation import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandlerBase import com.intellij.openapi.application.runInEdt import com.intellij.openapi.editor.Editor import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.impl.source.tree.LeafPsiElement import com.intellij.psi.util.PsiTreeUtil.getChildrenOfTypeAsList import com.intellij.psi.util.elementType import com.phodal.shirecore.findFile import com.phodal.shirecore.lookupFile import com.phodal.shirecore.middleware.post.PostProcessorType import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFuncDef import com.phodal.shirelang.psi.* class ShireGotoDeclarationHandler : GotoDeclarationHandlerBase(), GotoDeclarationHandler { private val validFunctionNames = setOf( PatternActionFuncDef.EXECUTE.funcName, PatternActionFuncDef.THREAD.funcName, PatternActionFuncDef.BATCH.funcName, PostProcessorType.SaveFile.handleName, PatternActionFuncDef.APPROVAL_EXECUTE.funcName, "mock" ) override fun getGotoDeclarationTarget(element: PsiElement?, editor: Editor?): PsiElement? { if (element !is LeafPsiElement) return null val project = element.project gotoSourceFile(element, project) return gotoToFunctionDecl(element) } private fun gotoToFunctionDecl(element: LeafPsiElement): ShireFrontMatterEntry? { val psiFile = element.containingFile // handle for foreign function val func = element.parent as? ShireFuncName ?: return null val header = getChildrenOfTypeAsList(psiFile, ShireFrontMatterHeader::class.java).firstOrNull() val functionsNode = header?.frontMatterEntries?.frontMatterEntryList?.firstOrNull { it.firstChild.text == "functions" } ?: return null val functionEntries: List = functionsNode.frontMatterValue?.objectKeyValue?.keyValueList?.filter { it.frontMatterEntry.foreignFunction != null }?.map { it.frontMatterEntry } ?: return null val funcName = func.text val foreignFunc = functionEntries.find { it.frontMatterKey?.text == funcName } ?: return null return foreignFunc } private fun gotoSourceFile( element: LeafPsiElement, project: Project, ) { if (element.elementType != ShireTypes.QUOTE_STRING) return if (element.parent?.elementType != ShireTypes.PIPELINE_ARG) return val funcCall = element.parent?.parent?.parent as? ShireFuncCall ?: return val funcName = funcCall.funcName.text if (funcName !in validFunctionNames) return val fileName = element.text.removeSurrounding("\"") val file = project.lookupFile(fileName) ?: project.findFile(fileName) ?: return runInEdt { FileEditorManager.getInstance(project).openFile(file, true) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/parser/CodeBlockElement.kt ================================================ // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.phodal.shirelang.parser import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode import com.intellij.openapi.util.TextRange import com.intellij.psi.ElementManipulators import com.intellij.psi.LiteralTextEscaper import com.intellij.psi.PsiElement import com.intellij.psi.PsiLanguageInjectionHost import com.intellij.psi.impl.source.tree.injected.InjectionBackgroundSuppressor import com.intellij.psi.templateLanguages.OuterLanguageElement import com.intellij.psi.tree.IElementType import com.intellij.psi.util.* import com.phodal.shirelang.psi.ShireExpr import com.phodal.shirelang.psi.ShireTypes import com.phodal.shirecore.utils.markdown.CodeFence class CodeBlockElement(node: ASTNode) : ASTWrapperPsiElement(node), PsiLanguageInjectionHost, InjectionBackgroundSuppressor { override fun isValidHost(): Boolean { return isAbleToAcceptInjections(this) } private fun isAbleToAcceptInjections(host: CodeBlockElement): Boolean { val hasStartBlock = host.firstChild?.elementType != ShireTypes.CODE_BLOCK_START val hasEndBlock = host.lastChild?.elementType != ShireTypes.CODE_BLOCK_END return !(hasStartBlock && hasEndBlock) } override fun updateText(text: String): PsiLanguageInjectionHost { return ElementManipulators.handleContentChange(this, text) } override fun createLiteralTextEscaper(): LiteralTextEscaper { return CodeBlockLiteralTextEscaper(this) } fun getLanguageId(): PsiElement? { return findChildByType(ShireTypes.LANGUAGE_ID) } fun codeText(): String { return CodeFence.parse(this.text).text } fun isShireTemplateCodeBlock(): Boolean { return PsiTreeUtil.findChildOfType(this, ShireExpr::class.java) != null } companion object { fun obtainFenceContent(element: CodeBlockElement): List? { return CachedValuesManager.getCachedValue(element) { CachedValueProvider.Result.create(getContent(element), element) } } private fun getContent(host: CodeBlockElement): List? { val children = host.firstChild ?.siblings(forward = true, withSelf = true) ?: return null val elements = children.filter { it !is OuterLanguageElement && (it.node.elementType == ShireTypes.CODE_CONTENTS || it == ShireTypes.NEWLINE) } .toList() if (elements.isNotEmpty() && elements.first() == ShireTypes.NEWLINE) { elements.drop(1) } if (elements.isNotEmpty() && elements.last() == ShireTypes.NEWLINE) { elements.dropLast(1) } return elements.takeIf { it.isNotEmpty() } } fun obtainRelevantTextRange(element: CodeBlockElement): TextRange { val elements = obtainFenceContent(element) ?: return getEmptyRange(element) val first = elements.first() val last = elements.last() return TextRange.create(first.startOffsetInParent, last.startOffsetInParent + last.textLength) } private fun getEmptyRange(host: CodeBlockElement): TextRange { val start = host.children.find { it.hasType(ShireTypes.LANGUAGE_ID) } ?: host.children.find { it.hasType(ShireTypes.CODE_BLOCK_START) } return TextRange.from(start!!.startOffsetInParent + start.textLength + 1, 0) } } } fun PsiElement.hasType(type: IElementType): Boolean { return PsiUtilCore.getElementType(this) == type } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/parser/CodeBlockLiteralTextEscaper.kt ================================================ // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.phodal.shirelang.parser import com.intellij.openapi.util.TextRange import com.intellij.psi.LiteralTextEscaper class CodeBlockLiteralTextEscaper(host: CodeBlockElement) : LiteralTextEscaper(host) { override fun getRelevantTextRange() = CodeBlockElement.obtainRelevantTextRange(myHost) override fun isOneLine(): Boolean = false; override fun decode(rangeInsideHost: TextRange, outChars: StringBuilder): Boolean { val elements = CodeBlockElement.obtainFenceContent(myHost) ?: return true for (element in elements) { val intersected = rangeInsideHost.intersection(element.textRangeInParent) ?: continue outChars.append(intersected.substring(myHost.text)) } return true } override fun getOffsetInHost(offsetInDecoded: Int, rangeInsideHost: TextRange): Int { val elements = CodeBlockElement.obtainFenceContent(myHost) ?: return -1 var cur = 0 for (element in elements) { val intersected = rangeInsideHost.intersection(element.textRangeInParent) if (intersected == null || intersected.isEmpty) continue if (cur + intersected.length == offsetInDecoded) { return intersected.startOffset + intersected.length } else if (cur == offsetInDecoded) { return intersected.startOffset } else if (cur < offsetInDecoded && cur + intersected.length > offsetInDecoded) { return intersected.startOffset + (offsetInDecoded - cur) } cur += intersected.length } val last = elements[elements.size - 1] val intersected = rangeInsideHost.intersection(last.textRangeInParent) if (intersected == null || intersected.isEmpty) return -1 val result = intersected.startOffset + (offsetInDecoded - (cur - intersected.length)) return if (rangeInsideHost.startOffset <= result && result <= rangeInsideHost.endOffset) { result } else -1 } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/parser/PatternElement.kt ================================================ package com.phodal.shirelang.parser import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode import com.intellij.psi.ElementManipulators import com.intellij.psi.LiteralTextEscaper import com.intellij.psi.PsiLanguageInjectionHost import com.intellij.psi.impl.source.tree.injected.InjectionBackgroundSuppressor class PatternElement(node: ASTNode) : ASTWrapperPsiElement(node), PsiLanguageInjectionHost, InjectionBackgroundSuppressor { override fun isValidHost(): Boolean = true override fun updateText(text: String): PsiLanguageInjectionHost { return ElementManipulators.handleContentChange(this, text) } override fun createLiteralTextEscaper(): LiteralTextEscaper { return LiteralTextEscaper.createSimple(this, false) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/parser/ShireGrepFuncCall.kt ================================================ package com.phodal.shirelang.parser import com.intellij.lang.ASTNode import com.intellij.psi.ElementManipulators import com.intellij.psi.LiteralTextEscaper import com.intellij.psi.PsiLanguageInjectionHost import com.intellij.psi.impl.source.tree.injected.InjectionBackgroundSuppressor import com.phodal.shirelang.psi.impl.ShireFuncCallImpl class ShireGrepFuncCall(node: ASTNode) : ShireFuncCallImpl(node), PsiLanguageInjectionHost, InjectionBackgroundSuppressor { override fun isValidHost(): Boolean = true override fun updateText(text: String): PsiLanguageInjectionHost { return ElementManipulators.handleContentChange(this, text) } override fun createLiteralTextEscaper(): LiteralTextEscaper { return LiteralTextEscaper.createSimple(this, false) } } class ShireSedFuncCall(node: ASTNode) : ShireFuncCallImpl(node), PsiLanguageInjectionHost, InjectionBackgroundSuppressor { override fun isValidHost(): Boolean = true override fun updateText(text: String): PsiLanguageInjectionHost { return ElementManipulators.handleContentChange(this, text) } override fun createLiteralTextEscaper(): LiteralTextEscaper { return LiteralTextEscaper.createSimple(this, false) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/parser/ShireParserDefinition.kt ================================================ package com.phodal.shirelang.parser import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode import com.intellij.lang.ParserDefinition import com.intellij.lang.PsiParser import com.intellij.lexer.Lexer import com.intellij.openapi.project.Project import com.intellij.psi.FileViewProvider import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.tree.IFileElementType import com.intellij.psi.tree.TokenSet import com.phodal.shirelang.ShireLanguage import com.phodal.shirelang.lexer.ShireLexerAdapter import com.phodal.shirelang.psi.ShireFile import com.phodal.shirelang.psi.ShireTypes import org.jetbrains.annotations.NotNull internal class ShireParserDefinition : ParserDefinition { @NotNull override fun createLexer(project: Project?): Lexer = ShireLexerAdapter() @NotNull override fun getCommentTokens(): TokenSet = ShireTokenTypeSets.SHIRE_COMMENTS @NotNull override fun getStringLiteralElements(): TokenSet = TokenSet.EMPTY override fun getWhitespaceTokens(): TokenSet = ShireTokenTypeSets.WHITESPACES @NotNull override fun createParser(project: Project?): PsiParser = ShireParser() @NotNull override fun getFileNodeType(): IFileElementType = FILE @NotNull override fun createFile(@NotNull viewProvider: FileViewProvider): PsiFile = ShireFile(viewProvider) @NotNull override fun createElement(node: ASTNode?): PsiElement { return when (node!!.elementType) { ShireTypes.CODE -> { CodeBlockElement(node) } ShireTypes.PATTERN -> { PatternElement(node) } ShireTypes.FUNC_CALL -> { when (node.firstChildNode.text) { "grep" -> { ShireGrepFuncCall(node) } "sed" -> { ShireSedFuncCall(node) } else -> { ShireTypes.Factory.createElement(node) } } } ShireTypes.CODE_CONTENTS -> { ASTWrapperPsiElement(node) } else -> ShireTypes.Factory.createElement(node) } } companion object { val FILE: IFileElementType = IFileElementType(ShireLanguage.INSTANCE) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/parser/ShireTokenTypeSets.kt ================================================ package com.phodal.shirelang.parser import com.intellij.psi.TokenType import com.intellij.psi.tree.TokenSet import com.phodal.shirelang.psi.ShireTypes object ShireTokenTypeSets { // ShireTypes.NEWLINE val WHITESPACES: TokenSet = TokenSet.create(TokenType.WHITE_SPACE) val SHIRE_COMMENTS = TokenSet.create(ShireTypes.CONTENT_COMMENTS, ShireTypes.COMMENTS, ShireTypes.BLOCK_COMMENT) } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/provider/ChatBoxShireFileCreateService.kt ================================================ package com.phodal.shirelang.provider import com.intellij.ide.scratch.ScratchFileService import com.intellij.ide.scratch.ScratchRootType import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.phodal.shirecore.SHIRE_CHAT_BOX_FILE import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirecore.provider.shire.FileCreateService import com.phodal.shirelang.actions.base.DynamicShireActionService import org.intellij.lang.annotations.Language class ChatBoxShireFileCreateService : FileCreateService { override fun createFile(prompt: String, project: Project): VirtualFile? { val actions = DynamicShireActionService.getInstance(project).getActions(ShireActionLocation.CHAT_BOX) var baseContent = "" if (actions.isNotEmpty()) { baseContent = actions.first().shireFile.text ?: "" } if (baseContent.isNotEmpty()) { return createInputWithBase(prompt, project, baseContent) } return createInputOnly(prompt, project) } private fun createInputOnly( prompt: String, project: Project, ): VirtualFile? { @Language("Shire") val header = """ |--- |name: "shire-temp" |description: "Shire Temp File" |interaction: RightPanel |--- | """.trimMargin() val content = header + prompt val virtualFile = ScratchRootType.getInstance().createScratchFile( project, SHIRE_CHAT_BOX_FILE, com.intellij.lang.Language.findLanguageByID("Shire"), content, ScratchFileService.Option.create_if_missing ) return virtualFile } private fun createInputWithBase( prompt: String, project: Project, baseContent: String, ): VirtualFile? { val content = baseContent.replace("\$chatPrompt", prompt) val virtualFile = ScratchRootType.getInstance().createScratchFile( project, SHIRE_CHAT_BOX_FILE, com.intellij.lang.Language.findLanguageByID("Shire"), content, ScratchFileService.Option.create_if_missing ) return virtualFile } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/provider/ShireActionPromptBuilder.kt ================================================ package com.phodal.shirelang.provider import com.intellij.openapi.project.Project import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirecore.provider.ide.ShirePromptBuilder import com.phodal.shirecore.provider.streaming.OnStreamingService import com.phodal.shirelang.actions.base.DynamicShireActionService import com.phodal.shirelang.run.runner.ShireRunner import kotlinx.coroutines.runBlocking class ShireActionPromptBuilder : ShirePromptBuilder { override fun build(project: Project, actionLocation: String, originPrompt: String): String { val location: ShireActionLocation = ShireActionLocation.valueOf(actionLocation) val action = DynamicShireActionService.getInstance(project).getActions(location) .firstOrNull() ?: return originPrompt val initVariables = mapOf("chatPrompt" to originPrompt) val finalPrompt = runBlocking { val runnerContext = ShireRunner.compileOnly(project, action.shireFile, initVariables, null) val service = project.getService(OnStreamingService::class.java) service?.onStart(project, runnerContext.finalPrompt) runnerContext }.finalPrompt return finalPrompt } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/provider/ShireLanguageToolchainProvider.kt ================================================ package com.phodal.shirelang.provider import com.intellij.openapi.project.Project import com.phodal.shirecore.provider.context.LanguageToolchainProvider import com.phodal.shirecore.provider.context.ToolchainContextItem import com.phodal.shirecore.provider.context.ToolchainPrepareContext import com.phodal.shirelang.ShireLanguage class ShireLanguageToolchainProvider : LanguageToolchainProvider { override fun isApplicable(project: Project, context: ToolchainPrepareContext): Boolean { return context.element?.language is ShireLanguage } override suspend fun collect(project: Project, context: ToolchainPrepareContext): List { val text = "Shire is a DSL for building AI Agent for IDEs." return listOf(ToolchainContextItem(ShireLanguageToolchainProvider::class, text)) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/provider/ShirePsiVariableProvider.kt ================================================ package com.phodal.shirelang.provider import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.phodal.shirecore.provider.variable.PsiContextVariableProvider import com.phodal.shirecore.provider.variable.model.PsiContextVariable import com.phodal.shirelang.ShireLanguage class ShirePsiVariableProvider : PsiContextVariableProvider { override fun resolve(variable: PsiContextVariable, project: Project, editor: Editor, psiElement: PsiElement?): Any { if (psiElement == null) return "" if (psiElement.language != ShireLanguage.INSTANCE) return "" return when (variable) { PsiContextVariable.CURRENT_CLASS_NAME -> "" PsiContextVariable.CURRENT_CLASS_CODE -> "" PsiContextVariable.CURRENT_METHOD_NAME -> "" PsiContextVariable.CURRENT_METHOD_CODE -> "" PsiContextVariable.RELATED_CLASSES -> "" PsiContextVariable.SIMILAR_TEST_CASE -> "" PsiContextVariable.IMPORTS -> "" PsiContextVariable.IS_NEED_CREATE_FILE -> "" PsiContextVariable.TARGET_TEST_FILE_NAME -> "" PsiContextVariable.UNDER_TEST_METHOD_CODE -> "" PsiContextVariable.FRAMEWORK_CONTEXT -> { collectFrameworkContext(psiElement, project) } PsiContextVariable.CODE_SMELL -> "" PsiContextVariable.METHOD_CALLER -> "" PsiContextVariable.CALLED_METHOD -> "" PsiContextVariable.SIMILAR_CODE -> "" PsiContextVariable.STRUCTURE -> "" PsiContextVariable.CHANGE_COUNT -> calculateChangeCount(psiElement) PsiContextVariable.LINE_COUNT -> calculateLineCount(psiElement) PsiContextVariable.COMPLEXITY_COUNT -> calculateComplexityCount(psiElement) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/provider/ShireToolchainFunctionProvider.kt ================================================ package com.phodal.shirelang.provider import com.intellij.openapi.project.Project import com.phodal.shirecore.middleware.post.PostProcessor import com.phodal.shirecore.provider.function.ToolchainFunctionProvider import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFunc import com.phodal.shirelang.compiler.variable.CompositeVariableProvider enum class ShireProvideType(val type: String) { Variables("variables"), Functions("functions"), Processor("processors") ; companion object { fun fromString(value: String): ShireProvideType? { return entries.firstOrNull { it.type == value } } } } enum class ShireToolchainFunction(val funName: String) { /** * The provider function offers, Built-in functions in Shire, Built-in variables in Shire, lifecycle in Shire * for example: `provider("variable")` will return all variables in tables */ Provider("provider"); companion object { fun fromString(value: String): ShireToolchainFunction? { return entries.firstOrNull { it.funName == value } } } } class ShireToolchainFunctionProvider : ToolchainFunctionProvider { override fun isApplicable(project: Project, funcName: String): Boolean { return ShireToolchainFunction.entries.any { it.funName == funcName } } override fun execute(project: Project, funcName: String, args: List, allVariables: Map): Any { val shireFunc = ShireToolchainFunction.fromString(funcName) ?: throw IllegalArgumentException("Shire[Toolchain]: Invalid Toolchain function name") when (shireFunc) { ShireToolchainFunction.Provider -> { val type = args.first() as String val withExample = args.getOrNull(1) as? Boolean ?: false return when (ShireProvideType.fromString(type)) { ShireProvideType.Variables -> { /// name and description to markdown table var result = "| Name | Description |" result += "\n| --- | --- |" CompositeVariableProvider.all().forEach { result += "\n| ${it.name} | ${it.description} |" } result } ShireProvideType.Functions -> { /// funcName and example to markdown table var result = "| Function | Description |" result += "\n| --- | --- |" PatternActionFunc.all().forEach { result += "\n| ${it.funcName} | ${it.description} |" if (withExample) { result += "Example: `${it.example}` |" } } result } ShireProvideType.Processor -> { var result = "| Processor | Description |" result += "\n| --- | --- |" PostProcessor.all().map { result += "\n| ${it.processorName} | ${it.description} |" } result } null -> "" } } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/psi/ShireElementType.kt ================================================ package com.phodal.shirelang.psi import com.intellij.psi.TokenType import com.intellij.psi.tree.IElementType import com.intellij.psi.tree.TokenSet import com.phodal.shirelang.ShireLanguage class ShireElementType(debugName: String) : IElementType(debugName, ShireLanguage.INSTANCE) { companion object { val SPACE_ELEMENTS: TokenSet = TokenSet.create( TokenType.WHITE_SPACE, ShireTypes.INDENT ) val CONTAINERS: TokenSet = TokenSet.create( ShireTypes.CODE_BLOCK, ShireTypes.FRONT_MATTER_ENTRY, ) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/psi/ShireFile.kt ================================================ package com.phodal.shirelang.psi import com.intellij.extapi.psi.PsiFileBase import com.intellij.openapi.application.runReadAction import com.intellij.openapi.fileTypes.FileType import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.psi.* import com.phodal.shirelang.ShireFileType import com.phodal.shirelang.ShireLanguage import java.util.* class ShireFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, ShireLanguage.INSTANCE) { override fun getFileType(): FileType = ShireFileType.INSTANCE override fun getOriginalFile(): ShireFile = super.getOriginalFile() as ShireFile override fun toString(): String = "ShireFile" override fun getStub(): ShireFileStub? = super.getStub() as ShireFileStub? companion object { private val shireFileCache = mutableMapOf() /** * Create a tempShireFile from a string. */ fun fromString(project: Project, text: String): ShireFile { val filename = ShireLanguage.INSTANCE.displayName + "-${UUID.randomUUID()}." + ShireFileType.INSTANCE.defaultExtension val shireFile = runReadAction { PsiFileFactory.getInstance(project) .createFileFromText(filename, ShireLanguage.INSTANCE, text) as ShireFile } return shireFile } fun lookup(project: Project, path: String) = VirtualFileManager.getInstance() .findFileByUrl("file://$path") ?.let { lookup(project, it) } fun lookup( project: Project, virtualFile: VirtualFile, ): ShireFile? { shireFileCache[virtualFile]?.let { if (it.isValid) { return it } else { shireFileCache.remove(virtualFile) } } val psiFile = runReadAction { PsiManager.getInstance(project).findFile(virtualFile) as? ShireFile } if (psiFile != null) { shireFileCache[virtualFile] = psiFile } return psiFile } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/psi/ShireFileStub.kt ================================================ package com.phodal.shirelang.psi import com.intellij.psi.PsiFile import com.intellij.psi.StubBuilder import com.intellij.psi.stubs.* import com.intellij.psi.tree.IStubFileElementType import com.phodal.shirelang.ShireLanguage class ShireFileStub(file: ShireFile?, private val flags: Int) : PsiFileStubImpl(file) { override fun getType() = Type object Type : IStubFileElementType(ShireLanguage.INSTANCE) { override fun getStubVersion(): Int = 1 override fun getExternalId(): String = "shire.file" override fun serialize(stub: ShireFileStub, dataStream: StubOutputStream) { dataStream.writeByte(stub.flags) } override fun deserialize(dataStream: StubInputStream, parentStub: StubElement<*>?): ShireFileStub { return ShireFileStub(null, dataStream.readUnsignedByte()) } override fun getBuilder(): StubBuilder = object : DefaultStubBuilder() { override fun createStubForFile(file: PsiFile): StubElement<*> { return ShireFileStub(file as ShireFile, 0) } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/run/ShireBeforeRunProviderDelegate.kt ================================================ package com.phodal.shirelang.run import com.intellij.execution.configurations.RunConfiguration import com.intellij.execution.impl.RunConfigurationBeforeRunProviderDelegate import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.openapi.util.Key class ShireBeforeRunProviderDelegate : RunConfigurationBeforeRunProviderDelegate { private val SHIRE_BEFORE_RUN_TASK_KEY: String = "Shire.BeforeRunTask" private val KEY_MAP: MutableMap> = HashMap() override fun beforeRun(environment: ExecutionEnvironment) { val settings = environment.runnerAndConfigurationSettings ?: return val configuration = settings.configuration if (configuration is ShireConfiguration) { val userDataKey = getRunBeforeUserDataKey(configuration) configuration.project.putUserData(userDataKey, true) } } private fun getRunBeforeUserDataKey(runConfiguration: RunConfiguration): Key { return KEY_MAP.computeIfAbsent(runConfiguration.name) { key: String -> Key.create( SHIRE_BEFORE_RUN_TASK_KEY + "_" + key ) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/run/ShireConfiguration.kt ================================================ package com.phodal.shirelang.run import com.phodal.shirelang.ShireIcons import com.intellij.execution.Executor import com.intellij.execution.configurations.* import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.openapi.options.SettingsEditor import com.intellij.openapi.project.Project import com.intellij.openapi.util.io.FileUtil import com.phodal.shirelang.ShireBundle import org.jdom.Element import javax.swing.Icon class ShireConfiguration(project: Project, factory: ConfigurationFactory, name: String) : LocatableConfigurationBase(project, factory, name) { override fun getIcon(): Icon = ShireIcons.DEFAULT private var myScriptPath = "" private val SCRIPT_PATH_TAG: String = "SCRIPT_PATH" private var varMap: Map = mutableMapOf() private val VAR_MAP_TAG: String = "VAR_MAP" override fun getState(executor: Executor, environment: ExecutionEnvironment): RunProfileState { return ShireRunConfigurationProfileState(project, this) } override fun checkConfiguration() { if (!FileUtil.exists(myScriptPath)) { throw RuntimeConfigurationError(ShireBundle.message("shire.run.error.script.not.found")) } } override fun writeExternal(element: Element) { super.writeExternal(element) element.writeString(SCRIPT_PATH_TAG, myScriptPath) element.writeString(VAR_MAP_TAG, varMap.toString()) } override fun readExternal(element: Element) { super.readExternal(element) myScriptPath = element.readString(SCRIPT_PATH_TAG) ?: "" varMap = mapStringToMap(element.readString(VAR_MAP_TAG) ?: "") } override fun getConfigurationEditor(): SettingsEditor = ShireSettingsEditor(project) fun getScriptPath(): String = myScriptPath fun setScriptPath(scriptPath: String) { myScriptPath = scriptPath.trim { it <= ' ' } } private fun Element.writeString(name: String, value: String) { val opt = Element("option") opt.setAttribute("name", name) opt.setAttribute("value", value) addContent(opt) } private fun Element.readString(name: String): String? = children .find { it.name == "option" && it.getAttributeValue("name") == name } ?.getAttributeValue("value") fun getVariables(): Map = varMap fun setVariables(variables: Map) { varMap = variables } companion object { fun mapStringToMap(varMapString: String) = varMapString .removePrefix("{") .removeSuffix("}") .split(", ") .map { it.split("=") } .filter { it.size >= 2 } .associate { it[0] to it[1] } .toMutableMap() } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/run/ShireConfigurationType.kt ================================================ package com.phodal.shirelang.run import com.intellij.execution.configurations.ConfigurationTypeUtil.findConfigurationType import com.intellij.execution.configurations.RunConfiguration import com.intellij.execution.configurations.SimpleConfigurationType import com.intellij.openapi.project.Project import com.intellij.openapi.util.NotNullLazyValue import com.phodal.shirelang.ShireBundle import com.phodal.shirelang.ShireIcons import com.phodal.shirelang.ShireLanguage class ShireConfigurationType : SimpleConfigurationType( "ShiresConfigurationType", ShireLanguage.INSTANCE.id, ShireBundle.message("shire.line.marker.run.0", ShireLanguage.INSTANCE.id), NotNullLazyValue.lazy { ShireIcons.DEFAULT } ) { override fun isDumbAware(): Boolean = true override fun isEditableInDumbMode(): Boolean = true override fun createTemplateConfiguration(project: Project): RunConfiguration = ShireConfiguration(project, this, "ShireConfiguration") companion object { fun getInstance(): ShireConfigurationType { return findConfigurationType(ShireConfigurationType::class.java) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/run/ShireConsoleView.kt ================================================ package com.phodal.shirelang.run import com.intellij.build.BuildView import com.intellij.build.DefaultBuildDescriptor import com.intellij.build.events.BuildEvent import com.intellij.execution.impl.ConsoleViewImpl import com.intellij.execution.process.ProcessHandler import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.openapi.editor.Editor import com.intellij.openapi.externalSystem.model.ProjectSystemId import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfigurationViewManager import com.intellij.openapi.project.Project import com.intellij.ui.components.panels.NonOpaquePanel import com.phodal.shirecore.runner.ShireProcessHandler import com.phodal.shirecore.runner.console.ShireConsoleViewBase import com.phodal.shirelang.run.runner.ShireRunner import java.awt.BorderLayout import javax.swing.JComponent class ShireConsoleView(private val executionConsole: ShireExecutionConsole) : ShireConsoleViewBase(executionConsole) { override fun getComponent(): JComponent = myPanel private var myPanel: NonOpaquePanel = NonOpaquePanel(BorderLayout()) private var shireRunner: ShireRunner? = null private val id = ProjectSystemId("Shire") private fun createTaskId() = ExternalSystemTaskId.create(id, ExternalSystemTaskType.RESOLVE_PROJECT, executionConsole.project) private val scriptPath = executionConsole.configuration.getScriptPath() val task = createTaskId() val buildDescriptor: DefaultBuildDescriptor = DefaultBuildDescriptor(task.id, "Shire", scriptPath, System.currentTimeMillis()) val viewManager: ExternalSystemRunConfigurationViewManager = executionConsole.project.getService(ExternalSystemRunConfigurationViewManager::class.java) private val buildView: BuildView = object : BuildView( executionConsole.project, executionConsole, buildDescriptor, "build.toolwindow.run.selection.state", viewManager ) { override fun onEvent(buildId: Any, event: BuildEvent) { super.onEvent(buildId, event) viewManager.onEvent(buildId, event) } } init { val baseComponent = buildView.component myPanel.add(baseComponent, BorderLayout.EAST) executionConsole.getProcessHandler()?.let { buildView.attachToProcess(it) } myPanel.add(delegate.component, BorderLayout.CENTER) } fun output(clearAndStop: Boolean = true) = executionConsole.getOutput(clearAndStop) override fun cancelCallback(callback: (String) -> Unit) { shireRunner?.addCancelListener(callback) } fun getEditor(): Editor? { return executionConsole.editor } override fun isCanceled(): Boolean = shireRunner?.isCanceled() ?: super.isCanceled() fun bindShireRunner(runner: ShireRunner) { shireRunner = runner } override fun dispose() { super.dispose() executionConsole.dispose() } } class ShireExecutionConsole( project: Project, viewer: Boolean, private var isStopped: Boolean = false, val configuration: ShireConfiguration, ) : ConsoleViewImpl(project, viewer) { private val outputBuilder = StringBuilder() private var processHandler: ShireProcessHandler? = null fun getProcessHandler(): ShireProcessHandler? { return processHandler } override fun attachToProcess(processHandler: ProcessHandler) { super.attachToProcess(processHandler) this.processHandler = processHandler as ShireProcessHandler } override fun print(text: String, contentType: ConsoleViewContentType) { super.print(text, contentType) if (!isStopped) outputBuilder.append(text) } fun getOutput(clearAndStop: Boolean): String { val output = outputBuilder.toString() if (clearAndStop) { isStopped = true outputBuilder.clear() } return output } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/run/ShirePluginDisposable.kt ================================================ package com.phodal.shirelang.run import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.Service import com.intellij.openapi.project.Project /** * The service is a parent disposable that represents the entire plugin lifecycle * and is intended to be used instead of the project/application as a parent disposable, * ensures that disposables registered using it as parents will be processed when the plugin is unloaded to avoid memory leaks. * * @author lk */ @Service(Service.Level.APP, Service.Level.PROJECT) class ShirePluginDisposable : Disposable { override fun dispose() { } companion object { fun getInstance(): ShirePluginDisposable { return ApplicationManager.getApplication().getService(ShirePluginDisposable::class.java) } fun getInstance(project: Project): ShirePluginDisposable { return project.getService(ShirePluginDisposable::class.java) } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/run/ShireProcessAdapter.kt ================================================ package com.phodal.shirelang.run import com.intellij.execution.process.ProcessAdapter import com.intellij.execution.process.ProcessEvent import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.util.Key /** * Adapter for the process of the Shire run configuration. * Will be used to get the result of the process and to notify the listeners when the process is terminated. */ class ShireProcessAdapter(val configuration: ShireConfiguration, val consoleView: ShireConsoleView?) : ProcessAdapter() { var result = "" private var llmOutput: String = "" override fun processTerminated(event: ProcessEvent) { super.processTerminated(event) ApplicationManager.getApplication().messageBus .syncPublisher(ShireRunListener.TOPIC) .runFinish(result, llmOutput, event, configuration.getScriptPath(), consoleView) } override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) { super.onTextAvailable(event, outputType) result = consoleView?.output().toString() } /** * When the process is terminated, will use the given llmOutput to set the llmOutput of the adapter. */ fun setLlmOutput(llmOutput: String?) { if (llmOutput != null) { this.llmOutput = llmOutput } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/run/ShireProgramRunner.kt ================================================ package com.phodal.shirelang.run import com.intellij.execution.ExecutionResult import com.intellij.execution.configurations.RunProfile import com.intellij.execution.configurations.RunProfileState import com.intellij.execution.configurations.RunnerSettings import com.intellij.execution.executors.DefaultRunExecutor import com.intellij.execution.process.ProcessEvent import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.execution.runners.GenericProgramRunner import com.intellij.execution.runners.showRunContent import com.intellij.execution.ui.RunContentDescriptor import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.util.Disposer import com.phodal.shirelang.run.flow.ShireProcessProcessor import java.util.concurrent.atomic.AtomicReference class ShireProgramRunner : GenericProgramRunner(), Disposable { private val connection = ApplicationManager.getApplication().messageBus.connect(this) private var isSubscribed = false init { Disposer.register(ShirePluginDisposable.getInstance(), this) } override fun getRunnerId(): String = RUNNER_ID override fun canRun(executorId: String, profile: RunProfile): Boolean { return (executorId == DefaultRunExecutor.EXECUTOR_ID) && profile is ShireConfiguration } // environment.executor.id == Debug override fun doExecute(state: RunProfileState, environment: ExecutionEnvironment): RunContentDescriptor? { if (environment.runProfile !is ShireConfiguration) return null val shireState = state as ShireRunConfigurationProfileState var executeResult: ExecutionResult? val result = AtomicReference() if (!isSubscribed) { connection.subscribe(ShireRunListener.TOPIC, object : ShireRunListener { override fun runFinish( allOutput: String, llmOutput: String, event: ProcessEvent, scriptPath: String, consoleView: ShireConsoleView?, ) { environment.project.getService(ShireProcessProcessor::class.java) .process(allOutput, event, scriptPath, consoleView) } }) isSubscribed = true } ApplicationManager.getApplication().invokeAndWait { executeResult = shireState.execute(environment.executor, this) if (shireState.isShowRunContent) { result.set(showRunContent(executeResult, environment)) } } return result.get() } override fun dispose() { connection.disconnect() } companion object { val RUNNER_ID: String = "ShireProgramRunner" } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/run/ShireRunConfigurationProducer.kt ================================================ package com.phodal.shirelang.run import com.intellij.execution.actions.ConfigurationContext import com.intellij.execution.actions.LazyRunConfigurationProducer import com.intellij.openapi.util.Ref import com.intellij.psi.PsiElement import com.phodal.shirelang.psi.ShireFile class ShireRunConfigurationProducer : LazyRunConfigurationProducer() { override fun getConfigurationFactory() = ShireConfigurationType.getInstance() override fun setupConfigurationFromContext( configuration: ShireConfiguration, context: ConfigurationContext, sourceElement: Ref, ): Boolean { val psiFile = sourceElement.get().containingFile as? ShireFile ?: return false val virtualFile = psiFile.virtualFile ?: return false configuration.name = virtualFile.presentableName configuration.setScriptPath(virtualFile.path) return true } override fun isConfigurationFromContext( configuration: ShireConfiguration, context: ConfigurationContext, ): Boolean { val psiLocation = context.psiLocation ?: return false val psiFile = psiLocation.containingFile as? ShireFile ?: return false val virtualFile = psiFile.virtualFile ?: return false return virtualFile.path == configuration.getScriptPath() } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/run/ShireRunConfigurationProfileState.kt ================================================ package com.phodal.shirelang.run import com.intellij.execution.DefaultExecutionResult import com.intellij.execution.ExecutionResult import com.intellij.execution.Executor import com.intellij.execution.configurations.RunProfileState import com.intellij.execution.process.ProcessEvent import com.intellij.execution.process.ProcessListener import com.intellij.execution.process.ProcessTerminatedListener import com.intellij.execution.runners.ProgramRunner import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.openapi.Disposable import com.intellij.openapi.project.Project import com.phodal.shirecore.ShireCoroutineScope import com.phodal.shirecore.config.InteractionType import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirecore.provider.streaming.OnStreamingService import com.phodal.shirecore.runner.ShireProcessHandler import com.phodal.shirelang.psi.ShireFile import com.phodal.shirelang.run.runner.ShireRunner import kotlinx.coroutines.launch /** * ShireRunConfigurationProfileState is a class that represents the state of a run configuration profile in the Shire plugin for Kotlin. * It implements the RunProfileState interface. * */ class ShireRunConfigurationProfileState( private val myProject: Project, private val configuration: ShireConfiguration, ) : RunProfileState, Disposable { private var executionConsole: ShireExecutionConsole = ShireExecutionConsole(myProject, true, configuration = configuration) var console: ShireConsoleView = ShireConsoleView(executionConsole) var isShowRunContent = true override fun execute(executor: Executor?, runner: ProgramRunner<*>): ExecutionResult { val processHandler = ShireProcessHandler(configuration.name) ProcessTerminatedListener.attach(processHandler) val processAdapter = ShireProcessAdapter(configuration, console) processHandler.addProcessListener(processAdapter) console.attachToProcess(processHandler) val shireFile: ShireFile? = ShireFile.lookup(myProject, configuration.getScriptPath()) if (shireFile == null) { console.print("File not found: ${configuration.getScriptPath()}", ConsoleViewContentType.ERROR_OUTPUT) processHandler.exitWithError() return DefaultExecutionResult(console, processHandler) } val shireRunner = ShireRunner( myProject, console, configuration, configuration.getVariables(), processHandler ).also { console.bindShireRunner(it) processHandler.addProcessListener(object : ProcessListener { override fun processTerminated(event: ProcessEvent) { it.cancel() } }) } val parsedResult = ShireRunner.preAnalysisAndLocalExecute(shireFile, myProject) val location = parsedResult.config?.actionLocation if (location == ShireActionLocation.TERMINAL_MENU || location == ShireActionLocation.COMMIT_MENU) { isShowRunContent = false } val interaction = parsedResult.config?.interaction if (interaction == InteractionType.RightPanel) { isShowRunContent = false } console.print("Prepare for running ${configuration.name}...\n", ConsoleViewContentType.NORMAL_OUTPUT) ShireCoroutineScope.scope(myProject).launch { try { val llmOutput = shireRunner.execute(parsedResult) processAdapter.setLlmOutput(llmOutput) processAdapter.processTerminated(ProcessEvent(processHandler, 0)) myProject.getService(OnStreamingService::class.java)?.onDone(myProject) } catch (e: Exception) { console.print( "Failed to run ${configuration.name}: ${e.message}\n", ConsoleViewContentType.LOG_ERROR_OUTPUT ) console.print(e.stackTraceToString(), ConsoleViewContentType.ERROR_OUTPUT) } } return DefaultExecutionResult(console, processHandler) } override fun dispose() { console.dispose() executionConsole.dispose() } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/run/ShireRunLineMarkersProvider.kt ================================================ package com.phodal.shirelang.run import com.phodal.shirelang.actions.ShireRunFileAction import com.intellij.execution.lineMarker.RunLineMarkerContributor import com.intellij.icons.AllIcons import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.project.DumbAware import com.intellij.openapi.project.DumbService import com.intellij.psi.PsiElement import com.phodal.shirelang.ShireBundle import com.phodal.shirelang.ShireLanguage import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole import com.phodal.shirelang.psi.ShireFile class ShireRunLineMarkersProvider : RunLineMarkerContributor(), DumbAware { override fun getInfo(element: PsiElement): Info? { if (DumbService.isDumb(element.project)) return null if (element.language !is ShireLanguage) return null val psiFile = element as? ShireFile ?: return null val actions = arrayOf(ActionManager.getInstance().getAction(ShireRunFileAction.ID)) val displayName = HobbitHole.from(psiFile)?.name ?: psiFile.name return Info( AllIcons.RunConfigurations.TestState.Run, { ShireBundle.message("shire.line.marker.run.0", displayName) }, *actions ) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/run/ShireRunListener.kt ================================================ package com.phodal.shirelang.run import com.intellij.execution.process.ProcessEvent import com.intellij.util.messages.Topic import java.util.* @FunctionalInterface interface ShireRunListener : EventListener { /** * Run finish event * * @param allOutput all output with Console and debug output, it's design for debug * @param llmOutput LLM output * @param event ProcessEvent * @param scriptPath script path * @param consoleView shire consoleView */ fun runFinish(allOutput: String, llmOutput: String, event: ProcessEvent, scriptPath: String, consoleView: ShireConsoleView?) companion object { @Topic.AppLevel val TOPIC: Topic = Topic( ShireRunListener::class.java, Topic.BroadcastDirection.TO_DIRECT_CHILDREN ) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/run/ShireSettingsEditor.kt ================================================ package com.phodal.shirelang.run import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory import com.intellij.openapi.options.SettingsEditor import com.intellij.openapi.project.Project import com.intellij.openapi.ui.TextFieldWithBrowseButton import com.intellij.ui.dsl.builder.AlignX import com.intellij.ui.dsl.builder.panel import com.phodal.shirelang.ShireBundle import javax.swing.JComponent class ShireSettingsEditor(val project: Project) : SettingsEditor() { private val myScriptSelector: TextFieldWithBrowseButton = TextFieldWithBrowseButton() init { val descriptor = FileChooserDescriptorFactory.createSingleFileDescriptor() val message = ShireBundle.message("shire.label.choose.file") myScriptSelector.addBrowseFolderListener(message, "", project, descriptor) } override fun createEditor(): JComponent = panel { row { cell(myScriptSelector).align(AlignX.FILL) } } override fun resetEditorFrom(configuration: ShireConfiguration) { myScriptSelector.text = configuration.getScriptPath() } override fun applyEditorTo(configuration: ShireConfiguration) { configuration.setScriptPath(myScriptSelector.text) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/run/ShireSyntaxLineMarkerProvider.kt ================================================ package com.phodal.shirelang.run import com.intellij.codeInsight.daemon.LineMarkerInfo import com.intellij.codeInsight.daemon.LineMarkerProvider import com.intellij.openapi.editor.markup.GutterIconRenderer.Alignment.LEFT import com.intellij.psi.PsiElement import com.intellij.psi.util.elementType import com.phodal.shirelang.ShireIcons import com.phodal.shirelang.psi.* class ShireSyntaxLineMarkerProvider : LineMarkerProvider { override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? { if (element !is ShireFrontMatterEntry) return null // only leaf elements can have line markers, or IDEA will throw an exception val leafElement = getLeafElement(element) ?: return null val patternAction = element.patternAction if (patternAction != null) { val firstExpr = patternAction.actionBlock.actionBody.actionExprList.firstOrNull() when (firstExpr?.firstChild) { is ShireCaseBody -> { return LineMarkerInfo(leafElement, leafElement.textRange, ShireIcons.Case, null, null, LEFT) { "" } } } } when (element.functionStatement?.functionBody?.firstChild) { is ShireQueryStatement -> { return LineMarkerInfo(leafElement, leafElement.textRange, ShireIcons.PsiExpr, null, null, LEFT) { "" } } is ShireActionBody -> { return LineMarkerInfo(leafElement, leafElement.textRange, ShireIcons.Pipeline, null, null, LEFT) { "" } } } return null } private fun getLeafElement(element: ShireFrontMatterEntry): PsiElement? { val firstChild = element.frontMatterKey?.firstChild when (firstChild?.elementType) { ShireTypes.PATTERN, ShireTypes.FRONT_MATTER_ID -> { return firstChild?.firstChild ?: firstChild } ShireTypes.IDENTIFIER -> { return firstChild } } return firstChild } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/run/executor/CustomRemoteAgentLlmExecutor.kt ================================================ package com.phodal.shirelang.run.executor import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.openapi.application.ApplicationManager import com.phodal.shirecore.agent.CustomAgent import com.phodal.shirelang.ShireBundle import com.phodal.shirelang.run.flow.ShireConversationService import com.phodal.shirecore.ShireCoroutineScope import com.phodal.shirecore.config.interaction.PostFunction import com.phodal.shirecore.runner.console.cancelWithConsole import com.phodal.shirecore.sse.CustomAgentSSEExecutor import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking class CustomRemoteAgentLlmExecutor( override val context: ShireLlmExecutorContext, private val agent: CustomAgent, ) : ShireLlmExecutor(context) { override fun execute(postFunction: PostFunction) { ApplicationManager.getApplication().invokeLater { val stringFlow: Flow? = CustomAgentSSEExecutor(project = context.myProject).execute(context.prompt, agent) val console = context.console if (stringFlow == null) { console?.print( "CustomRemoteAgent:" + ShireBundle.message("shire.llm.notfound"), ConsoleViewContentType.ERROR_OUTPUT ) context.processHandler.detachProcess() postFunction(null, null) return@invokeLater } ShireCoroutineScope.scope(context.myProject).launch { val llmResult = StringBuilder() runBlocking { stringFlow.cancelWithConsole(console).collect { llmResult.append(it) console?.print(it, ConsoleViewContentType.NORMAL_OUTPUT) } } console?.print("\nDone!", ConsoleViewContentType.SYSTEM_OUTPUT) val llmResponse = llmResult.toString() context.myProject.getService(ShireConversationService::class.java) .refreshLlmResponseCache(context.configuration.getScriptPath(), llmResponse) postFunction(llmResponse, null) context.processHandler.detachProcess() } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/run/executor/ShireDefaultLlmExecutor.kt ================================================ package com.phodal.shirelang.run.executor import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ModalityState import com.phodal.shirecore.ShireCoroutineScope import com.phodal.shirecore.config.InteractionType import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirecore.config.interaction.PostFunction import com.phodal.shirecore.llm.LlmProvider import com.phodal.shirecore.provider.ide.LocationInteractionContext import com.phodal.shirecore.provider.ide.LocationInteractionProvider import com.phodal.shirecore.runner.console.cancelWithConsole import com.phodal.shirelang.ShireBundle import com.phodal.shirelang.run.flow.ShireConversationService import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking class ShireDefaultLlmExecutor( override val context: ShireLlmExecutorContext, private val isLocalMode: Boolean, ) : ShireLlmExecutor(context) { override fun execute(postFunction: PostFunction) { ApplicationManager.getApplication().invokeLater({ val console = context.console if (isLocalMode && context.hole == null) { console?.print(ShireBundle.message("shire.run.local.mode"), ConsoleViewContentType.SYSTEM_OUTPUT) context.processHandler.detachProcess() return@invokeLater } val interaction = context.hole?.interaction val interactionContext = LocationInteractionContext( location = context.hole?.actionLocation ?: ShireActionLocation.RUN_PANEL, interactionType = interaction ?: InteractionType.AppendCursorStream, editor = context.editor, project = context.myProject, prompt = context.prompt, console = console, ) if (interaction != null) { if (context.hole!!.interaction == InteractionType.OnPaste) { return@invokeLater } val interactionProvider = LocationInteractionProvider.provide(interactionContext) if (interactionProvider != null) { interactionProvider.execute(interactionContext) { response, textRange -> postFunction(response, textRange) try { context.processHandler.detachProcess() } catch (e: Exception) { console?.print(e.message ?: "Error", ConsoleViewContentType.ERROR_OUTPUT) } } return@invokeLater } } ShireCoroutineScope.scope(context.myProject).launch { val llmResult = StringBuilder() runBlocking { try { LlmProvider.provider(context.myProject)?.stream(context.prompt, "", false) ?.cancelWithConsole(console)?.collect { llmResult.append(it) console?.print(it, ConsoleViewContentType.NORMAL_OUTPUT) } ?: console?.print( "DefaultLlm" + ShireBundle.message("shire.llm.notfound"), ConsoleViewContentType.ERROR_OUTPUT ) } catch (e: Exception) { console?.print(e.message ?: "Error", ConsoleViewContentType.ERROR_OUTPUT) context.processHandler.detachProcess() } } console?.print(ShireBundle.message("shire.llm.done"), ConsoleViewContentType.SYSTEM_OUTPUT) val response = llmResult.toString() context.myProject.getService(ShireConversationService::class.java) .refreshLlmResponseCache(context.configuration.getScriptPath(), response) postFunction(response, null) context.processHandler.detachProcess() } }, ModalityState.nonModal()) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/run/executor/ShireLlmExecutor.kt ================================================ package com.phodal.shirelang.run.executor import com.intellij.execution.process.ProcessHandler import com.intellij.execution.ui.ConsoleView import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.phodal.shirecore.config.interaction.PostFunction import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole import com.phodal.shirelang.run.ShireConfiguration data class ShireLlmExecutorContext( val configuration: ShireConfiguration, val processHandler: ProcessHandler, val console: ConsoleView?, val myProject: Project, val hole: HobbitHole?, val prompt: String, val editor: Editor?, ) abstract class ShireLlmExecutor(open val context: ShireLlmExecutorContext) { abstract fun execute(postFunction: PostFunction) } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/run/flow/ShireConversationService.kt ================================================ package com.phodal.shirelang.run.flow import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.openapi.components.Service import com.intellij.openapi.project.Project import com.phodal.shirecore.runner.console.cancelWithConsole import com.phodal.shirecore.llm.LlmProvider import com.phodal.shirelang.ShireBundle import com.phodal.shirelang.compiler.parser.ShireParsedResult import com.phodal.shirelang.run.ShireConsoleView import kotlinx.coroutines.runBlocking @Service(Service.Level.PROJECT) class ShireConversationService(val project: Project) { private val cachedConversations: MutableMap = mutableMapOf() fun createConversation(scriptPath: String, result: ShireParsedResult): ShireProcessContext { val conversation = ShireProcessContext(scriptPath, result, "", "") cachedConversations[scriptPath] = conversation return conversation } fun getConversation(scriptPath: String): ShireProcessContext? { return cachedConversations[scriptPath] } fun getLlmResponse(scriptPath: String): String { return cachedConversations[scriptPath]?.llmResponse ?: "" } /** * Updates the LLM response for a given script path in the cached conversations. * If the script path exists in the cached conversations, the LLM response is updated with the provided value. * * @param scriptPath The script path for which the LLM response needs to be updated. * @param llmResponse The new LLM response to be updated for the given script path. */ fun refreshLlmResponseCache(scriptPath: String, llmResponse: String) { cachedConversations[scriptPath]?.let { cachedConversations[scriptPath] = it.copy(llmResponse = llmResponse) } } /** * Updates the IDE output for a conversation at the specified path. * * @param path The path of the conversation to update. * @param ideOutput The new IDE output to set for the conversation. */ fun refreshIdeOutput(path: String, ideOutput: String) { cachedConversations[path]?.let { cachedConversations[path] = it.copy(ideOutput = ideOutput) } } /** * Function to try re-running a conversation script. * * @param scriptPath The path of the script to re-run */ fun retryScriptExecution(scriptPath: String, consoleView: ShireConsoleView?) { if (cachedConversations.isEmpty()) return val conversation = cachedConversations[scriptPath] ?: return if (conversation.alreadyReRun) return conversation.alreadyReRun = true val prompt = StringBuilder() val compiledResult = conversation.compiledResult if (compiledResult.isLocalCommand) { val message = ShireBundle.message("shire.prompt.fix.command", compiledResult.sourceCode, compiledResult.shireOutput) prompt.append(message) } prompt.append(ShireBundle.message("shire.prompt.fix.run-result", conversation.ideOutput)) val finalPrompt = prompt.toString() if (consoleView != null) { runBlocking { try { LlmProvider.provider(project) ?.stream(finalPrompt, "", true) ?.cancelWithConsole(consoleView) ?.collect { consoleView.print(it, ConsoleViewContentType.NORMAL_OUTPUT) } } catch (e: Exception) { consoleView.print(e.message ?: "Error", ConsoleViewContentType.ERROR_OUTPUT) } } } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/run/flow/ShireProcessContext.kt ================================================ package com.phodal.shirelang.run.flow import com.phodal.shirecore.llm.ChatMessage import com.phodal.shirelang.compiler.parser.ShireParsedResult /** * The `ShireProcessContext` class represents the context of a Shire process. */ data class ShireProcessContext( val scriptPath: String, val compiledResult: ShireParsedResult, val llmResponse: String, val ideOutput: String, val chatMessages: MutableList = mutableListOf(), var alreadyReRun: Boolean = false ) ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/run/flow/ShireProcessProcessor.kt ================================================ package com.phodal.shirelang.run.flow import com.intellij.execution.process.ProcessEvent import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.openapi.application.runInEdt import com.intellij.openapi.application.runReadAction import com.intellij.openapi.components.Service import com.intellij.openapi.project.Project import com.intellij.psi.PsiComment import com.intellij.psi.PsiElement import com.intellij.psi.util.PsiUtilBase import com.intellij.openapi.fileEditor.FileEditorManager import com.phodal.shirecore.llm.LlmProvider import com.phodal.shirecore.ShirelangNotifications import com.phodal.shirecore.runner.console.cancelWithConsole import com.phodal.shirecore.middleware.select.SelectElementStrategy import com.phodal.shirelang.ShireLanguage import com.phodal.shirelang.compiler.parser.ShireSyntaxAnalyzer import com.phodal.shirelang.psi.ShireFile import com.phodal.shirelang.psi.ShireVisitor import com.phodal.shirelang.run.ShireConsoleView import com.phodal.shirecore.utils.markdown.CodeFence import kotlinx.coroutines.runBlocking @Service(Service.Level.PROJECT) class ShireProcessProcessor(val project: Project) { private val conversationService = project.getService(ShireConversationService::class.java) /** * This function takes a ShireFile as input and returns a list of PsiElements that are comments. * It iterates through the ShireFile and adds any comments it finds to the list. * * @param shireFile the ShireFile to search for comments * @return a list of PsiElements that are comments */ private fun collectComments(shireFile: ShireFile): List { val comments = mutableListOf() shireFile.accept(object : ShireVisitor() { override fun visitComment(comment: PsiComment) { comments.add(comment) } }) return comments } /** * Process the output of a script based on the exit code and flag comment. * If LLM returns a Shire code, execute it. * If the exit code is not 0, attempts to fix the script with LLM. * If the exit code is 0 and there is a flag comment, process it. * * Flag comment format: * ```shire * [flow]:flowable.shire, means next step is flowable.shire * ``` * * @param output The output of the script * @param event The process event containing the exit code * @param scriptPath The path of the script file */ fun process(output: String, event: ProcessEvent, scriptPath: String, consoleView: ShireConsoleView?) { conversationService.refreshIdeOutput(scriptPath, output) val code = CodeFence.parse(conversationService.getLlmResponse(scriptPath)) if (code.ideaLanguage == ShireLanguage.INSTANCE) { runInEdt { executeTask(ShireFile.fromString(project, code.text), consoleView) } } when { event.exitCode == 0 -> { val shireFile: ShireFile = runReadAction { ShireFile.lookup(project, scriptPath) } ?: return val firstComment = collectComments(shireFile).firstOrNull() ?: return if (firstComment.textRange.startOffset == 0) { val text = firstComment.text if (text.startsWith(ShireSyntaxAnalyzer.FLOW_FALG)) { val nextScript = text.substring(ShireSyntaxAnalyzer.FLOW_FALG.length) val newScript = ShireFile.lookup(project, nextScript) ?: return this.executeTask(newScript, consoleView) } } } event.exitCode != 0 -> { conversationService.retryScriptExecution(scriptPath, consoleView) } } } /** * This function is responsible for running a task with a new script. * @param newScript The new script to be run. */ private fun executeTask(newScript: ShireFile, consoleView: ShireConsoleView?) { val shireCompiler = createCompiler(project, newScript) val result = shireCompiler.parseAndExecuteLocalCommand() if (result.shireOutput != "") { ShirelangNotifications.info(project, result.shireOutput) } if (result.hasError) { if (consoleView == null) return runBlocking { try { LlmProvider.provider(project)?.stream(result.shireOutput, "Shirelang", true)?.cancelWithConsole(consoleView)?.collect { consoleView.print(it, ConsoleViewContentType.NORMAL_OUTPUT) } } catch (e: Exception) { consoleView.print(e.message ?: "Error", ConsoleViewContentType.ERROR_OUTPUT) } } } else { if (result.nextJob == null) return val nextJob = result.nextJob!! val nextResult = createCompiler(project, nextJob).parseAndExecuteLocalCommand() if (nextResult.shireOutput != "") { ShirelangNotifications.info(project, nextResult.shireOutput) } } } private fun createCompiler(project: Project, shireFile: ShireFile): ShireSyntaxAnalyzer { val editor = FileEditorManager.getInstance(project).selectedTextEditor val element: PsiElement? = editor?.caretModel?.currentCaret?.offset?.let { val psiFile = PsiUtilBase.getPsiFileInEditor(editor, project) ?: return@let null SelectElementStrategy.getElementAtOffset(psiFile, it) } return ShireSyntaxAnalyzer(project, shireFile, editor, element) } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/run/runner/ShireRunner.kt ================================================ package com.phodal.shirelang.run.runner import com.intellij.execution.console.ConsoleViewWrapperBase import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.openapi.application.runReadAction import com.intellij.openapi.editor.Editor import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.Project import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiManager import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirecore.config.interaction.PostFunction import com.phodal.shirecore.runner.console.cancelWithConsole import com.phodal.shirecore.llm.LlmProvider import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirecore.provider.action.TerminalLocationExecutor import com.phodal.shirecore.provider.context.ActionLocationEditor import com.phodal.shirecore.workerThread import com.phodal.shirelang.ShireBundle import com.phodal.shirelang.compiler.parser.SHIRE_ERROR import com.phodal.shirelang.compiler.parser.ShireParsedResult import com.phodal.shirelang.compiler.template.ShireVariableTemplateCompiler import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole import com.phodal.shirelang.psi.ShireFile import com.phodal.shirelang.run.ShireConfiguration import com.phodal.shirelang.run.ShireConsoleView import com.phodal.shirecore.runner.ShireProcessHandler import com.phodal.shirelang.run.executor.CustomRemoteAgentLlmExecutor import com.phodal.shirelang.run.executor.ShireDefaultLlmExecutor import com.phodal.shirelang.run.executor.ShireLlmExecutor import com.phodal.shirelang.run.executor.ShireLlmExecutorContext import com.phodal.shirelang.run.flow.ShireConversationService import com.phodal.shirecore.provider.streaming.OnStreamingService import com.phodal.shirelang.compiler.parser.ShireSyntaxAnalyzer import kotlinx.coroutines.* import java.util.concurrent.CompletableFuture class ShireRunner( private val project: Project, private val console: ShireConsoleView?, private val configuration: ShireConfiguration, private val variableMap: Map, private val processHandler: ShireProcessHandler, ) { private var `compiledVariables`: Map = mapOf() private val terminalLocationExecutor = TerminalLocationExecutor.provide(project) private var isCanceled: Boolean = false private val cancelListeners = mutableSetOf<(String) -> Unit>() suspend fun execute(parsedResult: ShireParsedResult): String? { prepareExecute(parsedResult, compiledVariables, project, console) val runResult = CompletableFuture() val varsMap = variableFromPostProcessorContext(variableMap) val runnerContext = processTemplateCompile(parsedResult, varsMap, project, configuration, console) if (runnerContext.hasError) { processHandler.exitWithError() return null } val service = project.getService(OnStreamingService::class.java) service?.onStart(project, runnerContext.finalPrompt) this.compiledVariables = runnerContext.compiledVariables project.getService(ShireConversationService::class.java) .createConversation(configuration.getScriptPath(), runnerContext.compileResult) if (runnerContext.hole?.actionLocation == ShireActionLocation.TERMINAL_MENU) { executeTerminalUiTask(runnerContext) { response, textRange -> runResult.complete(response) executePostFunction(runnerContext, runnerContext.hole, response, textRange) } } else { executeNormalUiTask(runnerContext) { response, textRange -> runResult.complete(response) executePostFunction(runnerContext, runnerContext.hole, response, textRange) } } return withContext(Dispatchers.IO) { runResult.get() } } private fun executeTerminalUiTask(context: ShireRunnerContext, postFunction: PostFunction) { CoroutineScope(workerThread).launch { val handler = terminalLocationExecutor?.bundler(project, variableMap["input"] ?: "") if (handler == null) { console?.print("Terminal not found", ConsoleViewContentType.ERROR_OUTPUT) processHandler.exitWithError() return@launch } val llmResult = StringBuilder() runBlocking { try { LlmProvider.provider(project)?.stream(context.finalPrompt, "", false) ?.cancelWithConsole(console)?.collect { llmResult.append(it) handler.onChunk.invoke(it) } ?: console?.print( "ShireRunner:" + ShireBundle.message("shire.llm.notfound"), ConsoleViewContentType.ERROR_OUTPUT ) } catch (e: Exception) { console?.print(e.message ?: "Error", ConsoleViewContentType.ERROR_OUTPUT) handler.onFinish?.invoke(null) processHandler.exitWithError() } } val response = llmResult.toString() handler.onFinish?.invoke(response) postFunction(response, null) processHandler.detachProcess() } } private fun executeNormalUiTask(runData: ShireRunnerContext, postFunction: PostFunction) { val agent = runData.compileResult.executeAgent val hobbitHole = runData.hole val shireLlmExecutorContext = ShireLlmExecutorContext( configuration = configuration, processHandler = processHandler, console = console, myProject = project, hole = hobbitHole, prompt = runData.finalPrompt, editor = runData.editor, ) val shireLlmExecutor: ShireLlmExecutor = when { agent != null -> { CustomRemoteAgentLlmExecutor(shireLlmExecutorContext, agent) } else -> { val isLocalMode = runData.compileResult.isLocalCommand ShireDefaultLlmExecutor(shireLlmExecutorContext, isLocalMode) } } shireLlmExecutor.execute(postFunction) } fun executePostFunction( runnerContext: ShireRunnerContext, hobbitHole: HobbitHole?, response: String?, textRange: TextRange?, ) { if (console?.isCanceled() == true) return val currentFile = runnerContext.editor?.virtualFile?.let { runReadAction { PsiManager.getInstance(project).findFile(it) } } val context = PostProcessorContext( currentFile = currentFile, currentLanguage = currentFile?.language, genText = response, modifiedTextRange = textRange, editor = runnerContext.editor, lastTaskOutput = response, compiledVariables = compiledVariables, llmModelName = hobbitHole?.model, ) PostProcessorContext.updateContextAndVariables(context) val endStreamProcessor = hobbitHole?.executeStreamingEndProcessor(project, console, context, compiledVariables) PostProcessorContext.updateOutput(endStreamProcessor) val afterStreamHandler = hobbitHole?.executeAfterStreamingProcessor(project, console, context) PostProcessorContext.updateOutput(afterStreamHandler) try { processHandler.detachProcess() } catch (e: Exception) { // console?.print(e.message ?: "Error", ConsoleViewContentType.ERROR_OUTPUT) } } @Synchronized fun addCancelListener(listener: (String) -> Unit) { if (isCanceled) cancel(listener) else cancelListeners.add(listener) } @Synchronized fun cancel() { if (!isCanceled) { isCanceled = true cancelListeners.forEach { cancel(it) } } } fun isCanceled() = isCanceled private fun cancel(cancel: (String) -> Unit) { cancel("This job is canceled") } companion object { /** * Thi api design for compile only */ suspend fun compileOnly( project: Project, shireFile: ShireFile, initVariables: Map, sampleEditor: Editor? = null ): ShireRunnerContext { val parsedResult = runReadAction { preAnalysisAndLocalExecute(shireFile, project, sampleEditor) } prepareExecute(parsedResult, initVariables, project, null, userEditor = sampleEditor) val variables = variableFromPostProcessorContext(initVariables) val runnerContext = processTemplateCompile(parsedResult, variables, project, null, null, userEditor = sampleEditor ) val service = project.getService(OnStreamingService::class.java) service?.onStart(project, runnerContext.finalPrompt) return runnerContext } fun preAnalysisAndLocalExecute( shireFile: ShireFile, project: Project, editor: Editor? = null, ): ShireParsedResult { val baseEditor = editor ?: ActionLocationEditor.defaultEditor(project) val syntaxAnalyzer = ShireSyntaxAnalyzer(project, shireFile, baseEditor) return syntaxAnalyzer.parseAndExecuteLocalCommand() } fun prepareExecute( parsedResult: ShireParsedResult, variables: Map, project: Project, consoleView: ShireConsoleView?, userEditor: Editor? = null, ): PostProcessorContext { val hobbitHole = parsedResult.config val editor = userEditor ?: FileEditorManager.getInstance(project).selectedTextEditor hobbitHole?.pickupElement(project, editor) val file = runReadAction { editor?.let { PsiManager.getInstance(project).findFile(it.virtualFile) } } val context = PostProcessorContext.getData() ?: PostProcessorContext( currentFile = file, currentLanguage = file?.language, editor = editor, compiledVariables = variables, llmModelName = hobbitHole?.model ) PostProcessorContext.updateContextAndVariables(context) val vars: MutableMap = variables.toMutableMap() hobbitHole?.executeBeforeStreamingProcessor(project, context, consoleView, vars) val streamingService = project.getService(OnStreamingService::class.java) streamingService.clearStreamingService() hobbitHole?.onStreaming?.forEach { streamingService.registerStreamingService(it, consoleView) } hobbitHole?.setupStreamingEndProcessor(project, context) return context } private suspend fun processTemplateCompile( compileResult: ShireParsedResult, variableMap: Map, project: Project, shireConfiguration: ShireConfiguration?, shireConsoleView: ShireConsoleView?, userEditor: Editor? = null, ): ShireRunnerContext { val hobbitHole = compileResult.config val editor = userEditor ?: ActionLocationEditor.provide(project, hobbitHole?.actionLocation) val templateCompiler = ShireVariableTemplateCompiler( project, hobbitHole, compileResult.variableTable, compileResult.shireOutput, editor ) variableMap.forEach { (key, value) -> templateCompiler.putCustomVariable(key, value) } val promptTextTrim = templateCompiler.compile().trim() val compiledVariables = templateCompiler.compiledVariables PostProcessorContext.getData()?.lastTaskOutput?.let { templateCompiler.putCustomVariable("output", it) } if (shireConsoleView != null && shireConfiguration != null) { printCompiledOutput(shireConsoleView, promptTextTrim, shireConfiguration) } var hasError = false if (promptTextTrim.isEmpty()) { shireConsoleView?.print("No content to run", ConsoleViewContentType.ERROR_OUTPUT) hasError = true } if (promptTextTrim.contains(SHIRE_ERROR)) { hasError = true } return ShireRunnerContext( hobbitHole, editor = editor, compileResult, promptTextTrim, hasError, compiledVariables ) } private fun printCompiledOutput( console: ConsoleViewWrapperBase, promptText: String, shireConfiguration: ShireConfiguration, ) { console.print("Shire Script: ${shireConfiguration.getScriptPath()}\n", ConsoleViewContentType.SYSTEM_OUTPUT) console.print("Shire Script Compile output:\n", ConsoleViewContentType.SYSTEM_OUTPUT) PostProcessorContext.getData()?.llmModelName?.let { console.print("Used model: $it\n", ConsoleViewContentType.SYSTEM_OUTPUT) } promptText.split("\n").forEach { when { it.contains(SHIRE_ERROR) -> { console.print(it, ConsoleViewContentType.LOG_ERROR_OUTPUT) } else -> { console.print(it, ConsoleViewContentType.USER_INPUT) } } console.print("\n", ConsoleViewContentType.NORMAL_OUTPUT) } console.print("\n--------------------\n", ConsoleViewContentType.NORMAL_OUTPUT) } private fun variableFromPostProcessorContext(initValue: Map): MutableMap { val varsMap = initValue.toMutableMap() val data = PostProcessorContext.getData() val variables = data?.compiledVariables if (variables?.get("output") != null && initValue["output"] == null) { varsMap["output"] = variables["output"].toString() } return varsMap } } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/run/runner/ShireRunnerContext.kt ================================================ package com.phodal.shirelang.run.runner import com.intellij.openapi.editor.Editor import com.phodal.shirelang.compiler.parser.ShireParsedResult import com.phodal.shirelang.compiler.ast.hobbit.HobbitHole class ShireRunnerContext( val hole: HobbitHole?, val editor: Editor?, val compileResult: ShireParsedResult, val finalPrompt: String = "", val hasError: Boolean, val compiledVariables: Map, ) ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/runner/ShellFileRunService.kt ================================================ package com.phodal.shirelang.runner import com.intellij.execution.RunManager import com.intellij.execution.configurations.RunConfiguration import com.intellij.execution.configurations.RunProfile import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiManager import com.intellij.sh.psi.ShFile import com.intellij.sh.run.ShConfigurationType import com.intellij.sh.run.ShRunConfiguration import com.phodal.shirecore.provider.shire.FileRunService class ShellFileRunService : FileRunService { override fun isApplicable(project: Project, file: VirtualFile): Boolean { return file.extension == "sh" || file.extension == "bash" } override fun runConfigurationClass(project: Project): Class = ShRunConfiguration::class.java override fun createConfiguration(project: Project, virtualFile: VirtualFile): RunConfiguration? { val configurationSetting = runReadAction { val psiFile = PsiManager.getInstance(project).findFile(virtualFile) as? ShFile ?: return@runReadAction null RunManager.getInstance(project) .createConfiguration(psiFile.name, ShConfigurationType.getInstance()) } ?: return null val configuration = configurationSetting.configuration as ShRunConfiguration configuration.scriptPath = virtualFile.path return configurationSetting.configuration } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/runner/ShireFileRunService.kt ================================================ package com.phodal.shirelang.runner import com.intellij.execution.RunManager import com.intellij.execution.RunnerAndConfigurationSettings import com.intellij.execution.configurations.RunProfile import com.intellij.openapi.Disposable import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement import com.phodal.shirecore.provider.shire.FileRunService import com.phodal.shirelang.actions.ShireRunFileAction.Companion.executeFile import com.phodal.shirelang.actions.base.DynamicShireActionConfig import com.phodal.shirelang.psi.ShireFile import com.phodal.shirelang.run.ShireConfiguration import com.phodal.shirelang.run.ShireConfigurationType class ShireFileRunService : FileRunService, Disposable { override fun isApplicable(project: Project, file: VirtualFile): Boolean { return file.extension == "shire" } override fun runConfigurationClass(project: Project): Class = ShireConfiguration::class.java override fun createRunSettings( project: Project, virtualFile: VirtualFile, testElement: PsiElement?, ): RunnerAndConfigurationSettings? { val runManager = RunManager.getInstance(project) val psiFile = ShireFile.lookup(project, virtualFile) ?: return null val setting = runReadAction { runManager.createConfiguration(psiFile.name, ShireConfigurationType.getInstance()) } val shireConfiguration = setting.configuration as ShireConfiguration shireConfiguration.name = virtualFile.nameWithoutExtension shireConfiguration.setScriptPath(virtualFile.path) setting.isTemporary = true runManager.setTemporaryConfiguration(setting) runManager.selectedConfiguration = setting return setting } override fun runFile(project: Project, virtualFile: VirtualFile, psiElement: PsiElement?): String? { val settings = createRunSettings(project, virtualFile, psiElement) ?: return null val psiFile = ShireFile.lookup(project, virtualFile) ?: return null val config = DynamicShireActionConfig.from(psiFile) executeFile(project, config, settings) return "Running Shire file: ${virtualFile.name}" } override fun dispose() { } } ================================================ FILE: shirelang/src/main/kotlin/com/phodal/shirelang/thirdparty/ShireSonarLintToolWindowListener.kt ================================================ package com.phodal.shirelang.thirdparty import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.ActionToolbar import com.intellij.openapi.actionSystem.DefaultActionGroup import com.intellij.openapi.ui.SimpleToolWindowPanel import com.intellij.openapi.wm.ToolWindow import com.intellij.openapi.wm.ex.ToolWindowManagerListener class ShireSonarLintToolWindowListener : ToolWindowManagerListener { override fun toolWindowShown(toolWindow: ToolWindow) { if (toolWindow.id != "SonarLint") return val action = ActionManager.getInstance().getAction("ShireSonarLintAction") val contentManager = toolWindow.contentManager val content = contentManager.getContent(0) ?: return val simpleToolWindowPanel = content.component as? SimpleToolWindowPanel val actionToolbar = simpleToolWindowPanel?.toolbar?.components?.get(0) as? ActionToolbar ?: return val actionGroup = actionToolbar.actionGroup as? DefaultActionGroup if (actionGroup?.containsAction(action) == false) { actionGroup.add(action) } } } ================================================ FILE: shirelang/src/main/resources/com.phodal.shirelang.xml ================================================ messages.ShireBundle com.phodal.shirelang.actions.intention.ShireIntentionHelper shire.intention.category ================================================ FILE: shirelang/src/main/resources/docs/agentExamples/browse.shire ================================================ /browse:https://ide.unitmesh.cc ================================================ FILE: shirelang/src/main/resources/docs/agentExamples/commit.shire ================================================ /commit ```markdown [//]: # (follow Conventional Commits, like feat: add 'graphiteWidth' option) ``` ================================================ FILE: shirelang/src/main/resources/docs/agentExamples/file-func.shire ================================================ /file-func:regex(".*\.txt") ================================================ FILE: shirelang/src/main/resources/docs/agentExamples/file.shire ================================================ /file:.github/dependabot.yml#L1C1-L2C12 ================================================ FILE: shirelang/src/main/resources/docs/agentExamples/patch.shire ================================================ /patch ```patch // the patch to apply ``` ================================================ FILE: shirelang/src/main/resources/docs/agentExamples/refactor.shire ================================================ /refactor:rename cc.unitmesh.devti.language.run.DevInsProgramRunner to cc.unitmesh.devti.language.run.DevInsProgramRunnerImpl ================================================ FILE: shirelang/src/main/resources/docs/agentExamples/rev.shire ================================================ /rev:38d23de2 ================================================ FILE: shirelang/src/main/resources/docs/agentExamples/run.shire ================================================ /run:src/main/cc/unitmesh/PythonPromptStrategyTest.kt ================================================ FILE: shirelang/src/main/resources/docs/agentExamples/shell.shire ================================================ /shell:execute.sh ================================================ FILE: shirelang/src/main/resources/docs/agentExamples/symbol.shire ================================================ /symbol:cc.unitmesh.devti.language.psi ================================================ FILE: shirelang/src/main/resources/docs/agentExamples/write.shire ================================================ /write:src/main/kotlin/cc/unitmesh/context/CppFileContextBuilder.kt ```kotlin // some code ``` ================================================ FILE: shirelang/src/main/resources/fileTemplates/internal/Shire Action.shire.ft ================================================ --- name: "{{name}}" description: "Here is a description of the action." interaction: AppendCursor actionLocation: ContextMenu --- [notes]: For more options, see [the docs](https://shire.phodal.com/) You are an experienced software development engineer skilled in using Test-Driven Development (TDD) to develop software. You need to help users write test code based on their requirements. ================================================ FILE: shirelang/src/main/resources/inspectionDescriptions/ShireDuplicateAgent.html ================================================ Detects multiple @[AGENT_ID] annotations. Multiple @[AGENT_ID] are not allowed for current version. ================================================ FILE: shirelang/src/main/resources/messages/ShireBundle.properties ================================================ name=Shire shire.ref.loading=Loading git revision shire.line.marker.run.0=Run {0} shire.label.choose.file=Choice Shire File shire.run.error.script.not.found=Script not found shire.patch.cannot.read.patch=Cannot read a patch inspection.group.name=Shire language inspection.duplicate.agent=Duplicate agent calls detected. It is recommended to make only one call per agent. Please remove any duplicate agent calls. shire.intention.name=Shire Assistant shire.intention.category=Shire Intention shire.newFile=ShireAction shire.file=Shire file shire.run.local.mode=Local command detected, running in local mode shire.intention=Shire intention action shire.llm.notfound=No LLM provider found shire.llm.done=\nDone! shire.prompt.fix.command=You are a top software developer in the world, which can help me to fix the issue.\nWhen I use shell-like language and compile the script, I got an error, can you help me to fix it?\n\nOrigin script:\n```\n{0}\n```\n\nScript with result:\n####\n{1}\n#### shire.prompt.fix.run-result=You are a top software developer in the world, which can help me to fix the issue.\n\nHere is the run result, can you help me to fix it?\nRun result:\n####\n{0}\n#### intentions.request.background.process.title=Generate File shire.toolchain.function.not.found=No match function: {0}, If you using toolchain function, visit: https://shire.phodal.com/shire/shire-toolchain-function for more. TODO for User custom. intentions.assistant.name=Shire Intention Action shire.line.marker.run.comment=Run Shire shire.actions.chat-box=ChatBox shire.actions.run-from-chat-box=Run from Chatbox editor.preview.tip=Show Shire Prompt Preview editor.preview.refresh=Refresh Preview editor.preview=Show Preview editor.preview.help=Help editor.preview.help.url=https://shire.phodal.com/ editor.preview.variable.panel=Custom Variable Snapshots ================================================ FILE: shirelang/src/test/kotlin/com/phodal/shirelang/ParsingNormalTest.kt ================================================ package com.phodal.shirelang import com.intellij.testFramework.ParsingTestCase import com.phodal.shirelang.parser.ShireParserDefinition class ParsingNormalTest : ParsingTestCase("parser", "shire", ShireParserDefinition()) { override fun getTestDataPath(): String { return "src/test/testData" } fun testBasicTest() { doTest(true) } fun testJavaHelloWorld() { doTest(true) } fun testEmptyCodeFence() { doTest(true) } fun testJavaAnnotation() { doTest(true) } fun testBlockStartOnly() { doTest(true) } fun testComplexLangId() { doTest(true) } fun testAutoCommand() { doTest(true) } fun testCommandAndSymbol() { doTest(true) } fun testBrowseWeb() { doTest(true) } fun testAutoRefactor() { doTest(true) } fun testFrontMatter() { doTest(true) } fun testSingleComment() { doTest(true) } fun testShireFmObject() { doTest(true) } fun testPatternAction() { doTest(true) } fun testPatternCaseAction() { doTest(true) } fun testWhenCondition() { doTest(true) } fun testVariableAccess() { doTest(true) } fun testShirePsiQueryExpression() { doTest(true) } fun testMultipleFMVariable() { doTest(true) } fun testAfterStream() { doTest(true) } fun testMarkdownCompatible() { doTest(true) } fun testCustomFunctions() { doTest(true) } fun testIfExpression() { doTest(true) } } ================================================ FILE: shirelang/src/test/kotlin/com/phodal/shirelang/ParsingRealWorldTest.kt ================================================ package com.phodal.shirelang import com.intellij.testFramework.ParsingTestCase import com.phodal.shirelang.parser.ShireParserDefinition class ParsingRealWorldTest : ParsingTestCase("realworld", "shire", ShireParserDefinition()) { override fun getTestDataPath(): String { return "src/test/testData" } fun testAutotest() { doTest(true) } fun testLifeCycle() { doTest(true) } fun testContentTee() { doTest(true) } fun testWhenAfterStreaming() { doTest(true) } fun testAfterStreamingOnly() { doTest(true) } fun testOutputInVariable() { doTest(true) } fun testOnPaste() { doTest(true) } fun testLoginCommit() { doTest(true) } } ================================================ FILE: shirelang/src/test/kotlin/com/phodal/shirelang/ShireCompileTest.kt ================================================ package com.phodal.shirelang import com.intellij.testFramework.fixtures.BasePlatformTestCase import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirecore.config.InteractionType import com.phodal.shirelang.compiler.parser.HobbitHoleParser import com.phodal.shirelang.compiler.parser.ShireSyntaxAnalyzer import com.phodal.shirelang.compiler.ast.LogicalExpression import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFunc import com.phodal.shirelang.compiler.execute.PatternActionProcessor import com.phodal.shirelang.psi.ShireFile import junit.framework.TestCase import kotlinx.coroutines.runBlocking import org.intellij.lang.annotations.Language class ShireCompileTest : BasePlatformTestCase() { fun testNormalString() { val code = "Normal String /" val file = myFixture.configureByText("test.shire", code) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() assertEquals("Normal String /", compile.shireOutput) } fun testWithFrontmatter() { @Language("Shire") val code = """ --- name: Summary description: "Generate Summary" interaction: AppendCursor actionLocation: ContextMenu --- Summary webpage: """.trimIndent() val file = myFixture.configureByText("test.shire", code) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() assertEquals("\n\nSummary webpage:\n", compile.shireOutput) compile.config!!.let { assertEquals("Summary", it.name) assertEquals("Generate Summary", it.description) assertEquals(InteractionType.AppendCursor, it.interaction) assertEquals(ShireActionLocation.CONTEXT_MENU, it.actionLocation) } } fun testWithFrontMatterArray() { @Language("Shire") val code = """ --- name: Summary description: "Generate Summary" interaction: AppendCursor data: ["a", "b"] --- Summary webpage: """.trimIndent() val file = myFixture.configureByText("test.shire", code) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() assertEquals("\n\nSummary webpage:\n", compile.shireOutput) } fun testShouldCheckFile() { @Language("Shire") val code = """ --- name: Summary description: "Generate Summary" interaction: AppendCursor data: ["a", "b"] --- Summary webpage: """.trimIndent() val file = myFixture.configureByText("test.shire", code) val isFrontMatterPresent = HobbitHoleParser.hasFrontMatter(file as ShireFile) assertTrue(isFrontMatterPresent) } fun testShouldHandleForPatternAction() { @Language("Shire") val code = """ --- name: Summary description: "Generate Summary" interaction: AppendCursor data: ["a", "b"] variables: "var1": "demo" "var2": /**.java/ { grep("error.log") | sort | xargs("rm")} --- Summary webpage: """.trimIndent() val file = myFixture.configureByText("test.shire", code) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() assertEquals("\n\nSummary webpage:", compile.shireOutput) val map = compile.config!!.variables val var2 = map["var2"]!! val patterns = var2.patternActionFuncs assertEquals(3, patterns.size) assertEquals("grep", patterns[0].funcName) assertEquals("error.log", (patterns[0] as PatternActionFunc.Grep).patterns[0]) assertEquals("sort", patterns[1].funcName) assertEquals("xargs", patterns[2].funcName) } fun testShouldHandleForWhenCondition() { @Language("Shire") val code = """ --- when: ${'$'}selection.length >= 1 && ${'$'}selection.first() == 'p' --- Summary webpage: """.trimIndent() val file = myFixture.configureByText("test.shire", code) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() assertEquals("\n\nSummary webpage:", compile.shireOutput) val when_ = compile.config?.when_ assertEquals(when_!!.display(), "\$selection.length >= 1 && \$selection.first == \"p\"") val variables: Map = mapOf( "selection" to """public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World"); } }""" ) val result = (when_.value as LogicalExpression).evaluate(variables) assertTrue(result) } fun testShouldHandleForWhenConditionInVariableExpr() { @Language("Shire") val code = """ --- when: { ${'$'}selection.length >= 1 && ${'$'}selection.first() == 'p' } --- Summary webpage: """.trimIndent() val file = myFixture.configureByText("test.shire", code) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() assertEquals("\n\nSummary webpage:", compile.shireOutput) val when_ = compile.config?.when_ assertEquals(when_!!.display(), "\$selection.length >= 1 && \$selection.first == \"p\"") val variables: Map = mapOf( "selection" to """public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World"); } }""" ) val result = (when_.value as LogicalExpression).evaluate(variables) assertTrue(result) } fun testShouldHandleForWhenConditionForContains() { @Language("Shire") val code = """ --- when: ${'$'}fileName.contains(".java") && ${'$'}filePath.contains("src/main/java") --- Summary webpage: """.trimIndent() val file = myFixture.configureByText("test.shire", code) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() assertEquals("\n\nSummary webpage:", compile.shireOutput) val when_ = compile.config?.when_ assertEquals(when_!!.display(), "\$fileName.contains(\".java\") && \$filePath.contains(\"src/main/java\")") } fun testShouldHandleForWhenConditionForPattern() { @Language("Shire") val code = """ --- when: ${'$'}fileName.matches("/.*.java/") --- Summary webpage: """.trimIndent() val file = myFixture.configureByText("test.shire", code) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() assertEquals("\n\nSummary webpage:", compile.shireOutput) val when_ = compile.config?.when_ assertEquals(when_!!.display(), "\$fileName.matches(\"/.*.java/\")") } fun testShouldGetSymbolTableValueFromCompileResult() { @Language("Shire") val code = """ --- name: Summary description: "Generate Summary" interaction: AppendCursor data: ["a", "b"] when: ${'$'}fileName.matches("/.*.java/") variables: "var1": "demo" "var2": /.*.java/ { print("hello") | sort } --- Summary webpage: ${'$'}fileName """.trimIndent() val file = myFixture.configureByText("test.shire", code) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() val table = compile.variableTable val hole = compile.config!! assertEquals(1, table.getAllVariables().size) assertEquals(11, table.getVariable("fileName").lineDeclared) val results = runBlocking { hole.variables.mapValues { PatternActionProcessor(project, hole, mutableMapOf()).execute(it.value) } } assertEquals("demo", results["var1"]) assertEquals("hello", results["var2"].toString()) } fun testShouldLoadFile() { @Language("Shire") val code = """ --- name: Summary description: "Generate Summary" interaction: AppendCursor data: ["a", "b"] when: ${'$'}fileName.matches("/.*.java/") variables: "var2": /.*ple.shire/ { cat | find("fileName") | sort } --- Summary webpage: ${'$'}fileName """.trimIndent() val file = myFixture.addFileToProject("sample.shire", code) myFixture.openFileInEditor(file.virtualFile) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() val hole = compile.config!! val results = runBlocking { hole.variables.mapValues { PatternActionProcessor(project, hole, mutableMapOf()).execute(it.value) } } assertEquals( " \"var2\": /.*ple.shire/ { cat | find(\"fileName\") | sort }\n" + "Summary webpage: \$fileName\n" + "when: \$fileName.matches(\"/.*.java/\")", results["var2"].toString() ) } fun testShouldComputePatterCaseResult() { @Language("Shire") val code = """ --- variables: "var1": /.*.shire/ { case "${'$'}0" { "error" { grep("ERROR") | sort | xargs("notify_admin") } "warn" { grep("WARN") | sort | xargs("notify_admin") } "info" { grep("INFO") | sort | xargs("notify_user") } default { grep("(.*).shire") | sort } } } --- ${'$'}var1 """.trimIndent() val file = myFixture.configureByText("test.shire", code) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() val hole = compile.config!! TestCase.assertEquals(1, hole.variables.size) val results = runBlocking { hole.variables.mapValues { PatternActionProcessor(project, hole, mutableMapOf()).execute(it.value) } } assertEquals("/src/test", results["var1"]) } fun testShouldSupportCorrectGrep() { @Language("Shire") val code = """ --- name: Summary description: "Generate Summary" interaction: AppendCursor data: ["a", "b"] when: ${'$'}fileName.matches("/.*.java/") variables: "phoneNumber": "086-1234567890" "phoneNumber2": "088-1234567890" "var2": /.*ple.shire/ { cat | grep("([0-9]{3}-[0-9]{10})") } --- Summary webpage: ${'$'}fileName """.trimIndent() val file = myFixture.addFileToProject("sample.shire", code) myFixture.openFileInEditor(file.virtualFile) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() val hole = compile.config!! val results = runBlocking { hole.variables.mapValues { PatternActionProcessor(project, hole, mutableMapOf()).execute(it.value) } } assertEquals("086-1234567890\n088-1234567890", results["var2"].toString()) } fun testShouldHandleForDataRedact() { @Language("Shire") val code = """ --- name: Summary description: "Generate Summary" interaction: AppendCursor data: ["a", "b"] when: ${'$'}fileName.matches("/.*.java/") variables: "phoneNumber": "086-1234567890" "var2": /.*ple.shire/ { cat | redact } --- Summary webpage: ${'$'}fileName """.trimIndent() val file = myFixture.addFileToProject("sample.shire", code) myFixture.openFileInEditor(file.virtualFile) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() val hole = compile.config!! val results = runBlocking { hole.variables.mapValues { PatternActionProcessor(project, hole, mutableMapOf()).execute(it.value) } } assertEquals( """--- name: Summary description: "Generate Summary" interaction: AppendCursor data: ["a", "b"] when: ${'$'}fileName.matches("/.*.java/") variables: "phoneNumber": "****" "var2": /.*ple.shire/ { cat | redact } --- Summary webpage: ${'$'}fileName""", results["var2"].toString() ) } fun testShouldConvertSourceCode() { @Language("Shire") val code = """ --- name: "添加测试" actionLocation: ContextMenu variables: "sourceCode": /any/ { print(${'$'}filePath) | sed("src/test", "src/main") | sed("sample.shire", "sampleTest.shire") | print } onStreamingEnd: { parseCode | patch(${'$'}filePath, ${'$'}output) } --- """.trimIndent() val file = myFixture.addFileToProject("sample.shire", code) myFixture.openFileInEditor(file.virtualFile) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() val hole = compile.config!! val results = runBlocking { hole.variables.mapValues { PatternActionProcessor( project, hole, mutableMapOf( "filePath" to "src/test/resources/sample.shire" ) ).execute(it.value) } } assertEquals(results["sourceCode"], "src/main/resources/sampleTest.shire") } fun testShouldSupportForeignFunction() { @Language("JavaScript") val jsMainWithArgs = """ const args = process.argv.slice(2); console.log("hello, world"); console.log(args[0]); process.exit(0); """.trimIndent() myFixture.addFileToProject("hello.js", jsMainWithArgs) @Language("Shire") val code = """ --- name: Summary description: "Generate Summary" interaction: AppendCursor functions: normal: "hello.js"(string) variables: "var2": /.*ple.shire/ { normal } --- Summary webpage: ${'$'}fileName """.trimIndent() val file = myFixture.addFileToProject("sample.shire", code) myFixture.openFileInEditor(file.virtualFile) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() val hole = compile.config!! val results = runBlocking { hole.variables.mapValues { PatternActionProcessor(project, hole, mutableMapOf()).execute(it.value) } } // assertEquals("", results["var2"].toString()) /// in 241 version, the result is not empty but don't know why println(results["var2"].toString()) } } ================================================ FILE: shirelang/src/test/kotlin/com/phodal/shirelang/ShireLifecycleTest.kt ================================================ package com.phodal.shirelang import com.intellij.execution.ui.ConsoleView import com.intellij.openapi.project.Project import com.intellij.testFramework.fixtures.BasePlatformTestCase import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirecore.middleware.post.PostProcessor.Companion.handler import com.phodal.shirecore.middleware.post.LifecycleProcessorSignature import com.phodal.shirelang.compiler.parser.ShireSyntaxAnalyzer import com.phodal.shirelang.psi.ShireFile import junit.framework.TestCase import org.intellij.lang.annotations.Language import org.jetbrains.annotations.TestOnly class ShireLifecycleTest : BasePlatformTestCase() { fun testShouldHandleWhenStreamingEnd() { @Language("Shire") val code = """ --- onStreamingEnd: { parseCode | saveFile("demo.shire") | verifyCode | runCode } --- ${'$'}allController """.trimIndent() val file = myFixture.addFileToProject("sample.shire", code) myFixture.openFileInEditor(file.virtualFile) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() val hole = compile.config!! val funcNode = hole.onStreamingEnd assertEquals(funcNode.size, 4) assertEquals(funcNode[0].funcName, "parseCode") assertEquals(funcNode[0].args.size, 0) assertEquals(funcNode[1].funcName, "saveFile") assertEquals(funcNode[1].args[0], "demo.shire") assertEquals(funcNode[2].funcName, "verifyCode") assertEquals(funcNode[3].funcName, "runCode") try { val handleContext = PostProcessorContext(currentLanguage = ShireLanguage.INSTANCE, editor = null) execute(project, funcNode, handleContext, null) } catch (e: Exception) { e.printStackTrace() } } @TestOnly fun execute( project: Project, funcNodes: List, handleContext: PostProcessorContext, console: ConsoleView?, ) { funcNodes.forEach { funNode -> val handler = handler(funNode.funcName) if (handler != null) { handler.setup(handleContext) handler.execute(project, handleContext, console, funNode.args) handler.finish(handleContext) } } } fun testShouldHandleWhenAfterStreaming() { @Language("Shire") val code = """ --- afterStreaming: { condition { "error" { output.length < 1 } "success" { output.length > 1 } "json-result" { jsonpath("${'$'}.store.*") } } case condition { "error" { notify("Failed to Generate JSON") } "success" { notify("Success to Generate JSON") } "json-result" { execute("sample2.shire") } default { notify("Failed to Generate JSON") /* mean nothing */ } } } --- ${'$'}allController """.trimIndent() val file = myFixture.addFileToProject("sample.shire", code) myFixture.openFileInEditor(file.virtualFile) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() val hole = compile.config!! val funcNode = hole.afterStreaming!! TestCase.assertEquals(funcNode.conditions.size, 3) TestCase.assertEquals(funcNode.conditions[0].conditionKey, "\"error\"") assertEquals(funcNode.conditions[2].valueExpression.display(), "jsonpath(\"${'$'}.store.*\")") TestCase.assertEquals(funcNode.cases.size, 4) TestCase.assertEquals(funcNode.cases[0].caseKey, "\"error\"") val genJson = """ { "store": { "book": [ { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 } ] } } """.trimIndent() val handleContext = PostProcessorContext( currentLanguage = ShireLanguage.INSTANCE, genText = genJson, editor = null ) assertThrows(RuntimeException::class.java) { hole.afterStreaming?.execute(myFixture.project, handleContext, hole) } } fun testShouldSupportForBeforeStreaming() { @Language("Shire") val code = """ --- beforeStreaming: { caching("disk") | splitting | embedding } --- ${'$'}allController """.trimIndent() val file = myFixture.addFileToProject("sample.shire", code) myFixture.openFileInEditor(file.virtualFile) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() val hole = compile.config!!.beforeStreaming!! assertEquals(hole.processors.size, 3) assertEquals(hole.processors[0].funcName, "caching") assertEquals(hole.processors[1].funcName, "splitting") assertEquals(hole.processors[2].funcName, "embedding") } fun testShouldExecuteProcessForOnStreamingEvent() { @Language("Shire") val code = """ --- onStreaming: { logging() } --- ${'$'}allController """.trimIndent() val file = myFixture.addFileToProject("sample.shire", code) myFixture.openFileInEditor(file.virtualFile) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() val funcSigns = compile.config!!.onStreaming assertEquals(funcSigns.size, 1) assertEquals(funcSigns[0].funcName, "logging") } } ================================================ FILE: shirelang/src/test/kotlin/com/phodal/shirelang/ShirePatternPipelineTest.kt ================================================ package com.phodal.shirelang import com.intellij.testFramework.fixtures.BasePlatformTestCase import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirelang.compiler.execute.PatternActionProcessor import com.phodal.shirelang.compiler.parser.ShireSyntaxAnalyzer import com.phodal.shirelang.compiler.template.ShireVariableTemplateCompiler import com.phodal.shirelang.psi.ShireFile import kotlinx.coroutines.runBlocking import org.intellij.lang.annotations.Language class ShirePatternPipelineTest : BasePlatformTestCase() { fun testShouldSupportForTee() { @Language("Shire") val code = """ --- name: Summary description: "Generate Summary" interaction: AppendCursor data: ["a", "b"] when: ${'$'}fileName.matches("/.*.java/") variables: "var2": /.*ple.shire/ { cat | find("fileName") | sort } onStreamingEnd: { append(${'$'}var2) | saveFile("summary.md") } --- Summary webpage: ${'$'}fileName """.trimIndent() val file = myFixture.addFileToProject("sample.shire", code) myFixture.openFileInEditor(file.virtualFile) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() val hole = compile.config!! val context = PostProcessorContext( genText = "User prompt:\n\n", ) runBlocking { val shireTemplateCompiler = ShireVariableTemplateCompiler(project, hole, compile.variableTable, code, myFixture.editor) val compiledVariables = shireTemplateCompiler.compileVariable(myFixture.editor, mutableMapOf()) context.compiledVariables = compiledVariables hole.variables.mapValues { PatternActionProcessor(project, hole, mutableMapOf()).execute(it.value) } hole.setupStreamingEndProcessor(project, context = context) hole.executeStreamingEndProcessor(project, null, context = context, compiledVariables) } assertEquals("User prompt:\n\n" + " \"var2\": /.*ple.shire/ { cat | find(\"fileName\") | sort }\n" + "Summary webpage: \$fileName\n" + "when: \$fileName.matches(\"/.*.java/\")", context.genText) } fun testShouldSupportAfterStreamingPattern() { @Language("Shire") val code = """ --- name: Summary description: "Generate Summary" interaction: AppendCursor variables: "var2": "sample" afterStreaming: { case condition { default { print(${'$'}output) } } } --- Summary webpage: ${'$'}fileName """.trimIndent() val file = myFixture.addFileToProject("sample.shire", code) myFixture.openFileInEditor(file.virtualFile) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() val hole = compile.config!! val context = PostProcessorContext( genText = "User prompt:\n\n", ) runBlocking { val shireTemplateCompiler = ShireVariableTemplateCompiler(project, hole, compile.variableTable, code, myFixture.editor) val compiledVariables = shireTemplateCompiler.compileVariable(myFixture.editor, mutableMapOf()) context.compiledVariables = compiledVariables hole.variables.mapValues { PatternActionProcessor(project, hole, mutableMapOf()).execute(it.value) } hole.setupStreamingEndProcessor(project, context = context) hole.executeAfterStreamingProcessor(project, null, context = context) } assertEquals("User prompt:\n\n", context.lastTaskOutput) } fun testShouldUseSedReplaceContentInVariables() { @Language("Shire") val code = """ --- name: Summary description: "Generate Summary" interaction: AppendCursor variables: "openai": "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" "var2": /.*ple.shire/ { cat | find("openai") | sed("(?i)\b(sk-[a-zA-Z0-9]{20}T3BlbkFJ[a-zA-Z0-9]{20})(?:['|\"|\n|\r|\s|\x60|;]|${'$'})", "sk-***") } --- Summary webpage: ${'$'}var2 """.trimIndent() val file = myFixture.addFileToProject("sample.shire", code) myFixture.openFileInEditor(file.virtualFile) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() val hole = compile.config!! val context = PostProcessorContext( genText = "User prompt:\n\n", ) runBlocking { val shireTemplateCompiler = ShireVariableTemplateCompiler(project, hole, compile.variableTable, code, myFixture.editor) val compiledVariables = shireTemplateCompiler.compileVariable(myFixture.editor, mutableMapOf()) context.compiledVariables = compiledVariables hole.variables.mapValues { PatternActionProcessor(project, hole, mutableMapOf()).execute(it.value) } hole.setupStreamingEndProcessor(project, context = context) hole.executeAfterStreamingProcessor(project, null, context = context) } assertEquals(" \"openai\": \"sk-***\n" + " \"var2\": /.*ple.shire/ { cat | find(\"openai\") | sed(\"(?i)\\b(sk-[a-zA-Z0-9]{20}T3BlbkFJ[a-zA-Z0-9]{20})(?:['|\\\"|\\n|\\r|\\s|\\x60|;]|\$)\", \"sk-***\") }", context.compiledVariables["var2"] ) } } ================================================ FILE: shirelang/src/test/kotlin/com/phodal/shirelang/ShireQueryExpressionTest.kt ================================================ package com.phodal.shirelang import com.intellij.testFramework.fixtures.BasePlatformTestCase import com.jetbrains.rd.util.first import com.phodal.shirelang.compiler.parser.ShireSyntaxAnalyzer import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFunc import com.phodal.shirelang.compiler.execute.PatternActionProcessor import com.phodal.shirelang.psi.ShireFile import junit.framework.TestCase import kotlinx.coroutines.runBlocking import org.intellij.lang.annotations.Language class ShireQueryExpressionTest : BasePlatformTestCase() { fun testShouldGetFromExpression() { val sampleText = """HelloWorld.txt""".trimIndent() @Language("Shire") val code = """ --- variables: "allController": { from { File clazz // the class } where { clazz.text == "HelloWorld.txt" } select { clazz.toString(), "code" } } --- ${'$'}allController """.trimIndent() myFixture.addFileToProject("HelloWorld.txt", sampleText) val file = myFixture.addFileToProject("sample.shire", code) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() val hole = compile.config!! val patternActionFuncs = hole.variables.first().value.patternActionFuncs val whereDisplay = (patternActionFuncs[1] as PatternActionFunc.Where).statement.display() val selectDisplay = (patternActionFuncs[2] as PatternActionFunc.Select).statements.map { it.display() } assertEquals(whereDisplay, "clazz.text == \"HelloWorld.txt\"") assertEquals(selectDisplay, listOf("clazz.toString", "\"code\"")) val results = runBlocking { hole.variables.mapValues { PatternActionProcessor(project, hole, mutableMapOf()).execute(it.value) } } assertEquals(results["allController"], """ PsiFile(plain text):HelloWorld.txt "code" """.trimIndent()) } fun testShouldTestForDayNow() { @Language("Shire") val code = """ --- variables: "dayNow": { from { Date date } where { date.dayOfWeek() != 8 } select { date.toString() } } --- ${'$'}dayNow """.trimIndent() val file = myFixture.addFileToProject("sample.shire", code) myFixture.openFileInEditor(file.virtualFile) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() val hole = compile.config!! val results = runBlocking { hole.variables.mapValues { PatternActionProcessor(project, hole, mutableMapOf()).execute(it.value) } } val nowDate = results["dayNow"] TestCase.assertTrue(nowDate!!.startsWith("ShireDate(date=")) } } ================================================ FILE: shirelang/src/test/kotlin/com/phodal/shirelang/compiler/hobbit/execute/CrawlProcessorTest.kt ================================================ package com.phodal.shirelang.compiler.hobbit.execute import com.intellij.testFramework.fixtures.BasePlatformTestCase import com.phodal.shirelang.compiler.execute.processor.CrawlProcessor class CrawlProcessorTest : BasePlatformTestCase() { fun testShouldParseLink() { val urls = arrayOf("https://shire.phodal.com/") val results = CrawlProcessor.execute(urls) assertEquals(results.size, 1) } } ================================================ FILE: shirelang/src/test/kotlin/com/phodal/shirelang/compiler/hobbit/execute/JsonPathProcessorTest.kt ================================================ package com.phodal.shirelang.compiler.hobbit.execute import com.intellij.testFramework.fixtures.BasePlatformTestCase import com.phodal.shirelang.compiler.ast.patternaction.PatternActionFunc import com.phodal.shirelang.compiler.execute.processor.JsonPathProcessor import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertNull import org.junit.Test class JsonPathProcessorTest : BasePlatformTestCase() { @Test fun testShouldParseJsonStringWithValidJsonPath() { // given val jsonStr = "{\"key\":\"value\"}" val action = PatternActionFunc.JsonPath(null, "key") // when val result = JsonPathProcessor.execute(project, jsonStr, action) // then assertEquals("value", result) } @Test fun testShouldReturnNullWhenJsonPathDoesNotExistInJsonString() { // given val jsonStr = "{\"key\":\"value\"}" val action = PatternActionFunc.JsonPath(null, "invalidKey") // when val result = JsonPathProcessor.execute(project, jsonStr, action) // then assertNull(result) } @Test fun testShouldParseSseResultWithValidJsonPath() { // given val sseInput = "data: {\"event\":\"agent_message\",\"conversation_id\":\"48929266-a58f-46cc-a5eb-33145e6a96ef\",\"message_id\":\"91ad550b-1109-4062-88f8-07be18238e0e\",\"created_at\":1725437154,\"task_id\":\"4f846104-8571-42f1-b04c-f6f034b2fe9e\",\"id\":\"91ad550b-1109-4062-88f8-07be18238e0e\",\"answer\":\"The\"}\n" val jsonPath = "answer" // when val result = JsonPathProcessor.parseSSEResult(sseInput, jsonPath) // then assertEquals("The", result) } @Test fun testShouldReturnEmptyStringWhenJsonPathDoesNotExistInSseResult() { // given val sseInput = "data: {\"event\":\"agent_message\",\"conversation_id\":\"48929266-a58f-46cc-a5eb-33145e6a96ef\",\"message_id\":\"91ad550b-1109-4062-88f8-07be18238e0e\",\"created_at\":1725437154,\"task_id\":\"4f846104-8571-42f1-b04c-f6f034b2fe9e\",\"id\":\"91ad550b-1109-4062-88f8-07be18238e0e\",\"answer\":\"The\"}\n" val jsonPath = "invalidKey" // when val result = JsonPathProcessor.parseSSEResult(sseInput, jsonPath) // then assertEquals("", result) } @Test fun testShouldParseMultipleSseDataLinesWithValidJsonPath() { // given val sseInput = "data: {\"event\":\"agent_message\",\"conversation_id\":\"48929266-a58f-46cc-a5eb-33145e6a96ef\",\"message_id\":\"91ad550b-1109-4062-88f8-07be18238e0e\",\"created_at\":1725437154,\"task_id\":\"4f846104-8571-42f1-b04c-f6f034b2fe9e\",\"id\":\"91ad550b-1109-4062-88f8-07be18238e0e\",\"answer\":\"The\"}\n" + "data: {\"event\":\"message_end\",\"conversation_id\":\"48929266-a58f-46cc-a5eb-33145e6a96ef\",\"message_id\":\"91ad550b-1109-4062-88f8-07be18238e0e\",\"created_at\":1725437154,\"task_id\":\"4f846104-8571-42f1-b04c-f6f034b2fe9e\",\"id\":\"91ad550b-1109-4062-88f8-07be18238e0e\"}\n" val jsonPath = "$.event" // when val result = JsonPathProcessor.parseSSEResult(sseInput, jsonPath) // then assertEquals("agent_messagemessage_end", result) } } ================================================ FILE: shirelang/src/test/kotlin/com/phodal/shirelang/compiler/hobbit/execute/ShireShellRunnerTest.kt ================================================ package com.phodal.shirelang.compiler.hobbit.execute import com.intellij.testFramework.fixtures.BasePlatformTestCase import com.phodal.shirelang.compiler.execute.processor.shell.ShireShellCommandRunner import org.intellij.lang.annotations.Language class ShireShellRunnerTest: BasePlatformTestCase() { fun testFill() { @Language("JSON") val jsonEnv = """ { "development": { "name": "Phodal" } } """.trimIndent() myFixture.addFileToProject("demo.shireEnv.json", jsonEnv) @Language("Shell Script") val content = """ echo "Hello ${'$'}{name}, my name is ${'$'}{myName}!" """.trimIndent() val file = myFixture.addFileToProject("demo.seh", content) val fill = ShireShellCommandRunner.fill( project, file.virtualFile, mapOf( "myName" to "Shire" ) ) assertEquals("echo \"Hello Phodal, my name is Shire!\"", fill) } } ================================================ FILE: shirelang/src/test/kotlin/com/phodal/shirelang/compiler/parser/HobbitHoleParserTest.kt ================================================ package com.phodal.shirelang.compiler.parser import com.intellij.testFramework.fixtures.BasePlatformTestCase import com.phodal.shirelang.psi.ShireFile import org.intellij.lang.annotations.Language class HobbitHoleParserTest : BasePlatformTestCase() { fun testShouldParseFunctions() { @Language("Shire") val code = """ --- functions: normal: "defaultOutput.py"(string) output: "multipleOutput.py"(string) -> content, size special: "accessFunctionIfSupport.py"::resize(string, number, number) -> image --- """.trimIndent() val file = myFixture.configureByText("test.shire", code) val hobbitHole = HobbitHoleParser.parse(file as ShireFile)!! assertEquals(3, hobbitHole.foreignFunctions.size) val firstFunc = hobbitHole.foreignFunctions["normal"]!! assertEquals("normal", firstFunc.funcName) assertEquals("defaultOutput.py", firstFunc.funcPath) val secondFunc = hobbitHole.foreignFunctions["output"]!! assertEquals("output", secondFunc.funcName) assertEquals("multipleOutput.py", secondFunc.funcPath) assertEquals(listOf("content", "size"), secondFunc.returnVars.keys.toList()) val thirdFunc = hobbitHole.foreignFunctions["special"]!! assertEquals("special", thirdFunc.funcName) assertEquals("accessFunctionIfSupport.py", thirdFunc.funcPath) assertEquals(listOf("image"), thirdFunc.returnVars.keys.toList()) assertEquals(listOf("string", "number", "number"), thirdFunc.inputTypes) } } ================================================ FILE: shirelang/src/test/kotlin/com/phodal/shirelang/impl/DefaultShireSymbolProvider.kt ================================================ package com.phodal.shirelang.impl import com.intellij.codeInsight.completion.CompletionParameters import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.lookup.LookupElement import com.intellij.openapi.fileTypes.PlainTextFileType import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.PsiManager import com.intellij.psi.PsiNamedElement import com.intellij.psi.search.FileTypeIndex import com.intellij.psi.search.ProjectScope import com.phodal.shirecore.provider.shire.ShireSymbolProvider class DefaultShireSymbolProvider : ShireSymbolProvider { override val language: String = "Default" override fun lookupSymbol( project: Project, parameters: CompletionParameters, result: CompletionResultSet, ): List { return emptyList() } override fun lookupElementByName(project: Project, name: String): List? { val searchScope = ProjectScope.getProjectScope(project) val virtualFiles = FileTypeIndex.getFiles(PlainTextFileType.INSTANCE, searchScope) return when (name) { "String" -> { emptyList() } "File" -> { virtualFiles.mapNotNull { PsiManager.getInstance(project).findFile(it) }.toList() } else -> { emptyList() } } } override fun resolveSymbol(project: Project, symbol: String): List { return emptyList() } } ================================================ FILE: shirelang/src/test/kotlin/com/phodal/shirelang/regression/ShireCompileTest.kt ================================================ package com.phodal.shirelang.regression import com.intellij.testFramework.fixtures.BasePlatformTestCase import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirelang.compiler.parser.ShireSyntaxAnalyzer import com.phodal.shirelang.compiler.template.ShireVariableTemplateCompiler import com.phodal.shirelang.psi.ShireFile import kotlinx.coroutines.runBlocking import org.intellij.lang.annotations.Language class ShireCompileTest : BasePlatformTestCase() { val javaHelloController = """ package com.phodal.shirelang.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @GetMapping("/hello") public String hello() { return "Hello, World!"; } } """.trimIndent() val javaHelloEntity = """ package com.phodal.shirelang.entity; public class HelloEntity { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } """.trimIndent() fun testShouldReturnControllerCodeWithFindCat() { myFixture.addFileToProject( "src/main/java/com/phodal/shirelang/controller/HelloController.java", javaHelloController ) myFixture.addFileToProject("src/main/java/com/phodal/shirelang/entity/HelloEntity.java", javaHelloEntity) @Language("Shire") val code = """ --- name: "类图分析" variables: "controllers": /.*.java/ { find("Controller") | grep("src/main/java/.*") | cat } "outputFile": /any/ { print("name.adl") } onStreamingEnd: { parseCode | saveFile(${'$'}outputFile) } --- 请将下列信息原样输出,不要添加任何其他描述信息: ${'$'}controllers """.trimIndent() val file = myFixture.configureByText("test.shire", code) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() val hole = compile.config!! val context = PostProcessorContext( genText = "User prompt:\n\n", ) runBlocking { val templateCompiler = ShireVariableTemplateCompiler(project, hole, compile.variableTable, code, myFixture.editor) val compiledVariables = templateCompiler.compileVariable(myFixture.editor, mutableMapOf()) context.compiledVariables = compiledVariables } assertEquals( """package com.phodal.shirelang.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @GetMapping("/hello") public String hello() { return "Hello, World!"; } }""", context.compiledVariables["controllers"] ) } fun testShouldReturnControllerCodeWithFindCatWithHead() { myFixture.addFileToProject( "src/main/java/com/phodal/shirelang/controller/HelloController.java", javaHelloController ) myFixture.addFileToProject("src/main/java/com/phodal/shirelang/entity/HelloEntity.java", javaHelloEntity) @Language("Shire") val code = """ --- name: "类图分析" variables: "output": "name.adl" "con": /.*.java/ { print | head(1)} "controllers": /.*.java/ { find("Controller") | grep("src/main/java/.*") | head(1) | cat } "outputFile": /any/ { print("name.adl") } onStreamingEnd: { parseCode | saveFile(${'$'}outputFile) } --- 下面是你要执行转换的数据: ${'$'}controllers """.trimIndent() val file = myFixture.configureByText("test.shire", code) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() val hole = compile.config!! val context = PostProcessorContext( genText = "User prompt:\n\n", ) runBlocking { val templateCompiler = ShireVariableTemplateCompiler(project, hole, compile.variableTable, code, myFixture.editor) val compiledVariables = templateCompiler.compileVariable(myFixture.editor, mutableMapOf()) context.compiledVariables = compiledVariables } assertEquals( """/src/src/main/java/com/phodal/shirelang/entity/HelloEntity.java""", context.compiledVariables["con"] ) assertEquals("package com.phodal.shirelang.controller;\n" + "\n" + "import org.springframework.web.bind.annotation.GetMapping;\n" + "import org.springframework.web.bind.annotation.RestController;\n" + "\n" + "@RestController\n" + "public class HelloController {\n" + " @GetMapping(\"/hello\")\n" + " public String hello() {\n" + " return \"Hello, World!\";\n" + " }\n" + "}", context.compiledVariables["controllers"]) } // fun testShouldCompileMarkdownHeader() { // // @Language("Shire") // val code = """ // ## Header // // Body // """.trimIndent() // // val p: Properties = Properties() // p.setProperty("resource.loader", "class") // p.setProperty("class.resource.loader.description", "Velocity Classpath Resource Loader") // p.setProperty( // "class.resource.loader.class", // "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader" // ) // try { // Velocity.init(p); // } catch (e: Exception) { //// e.printStackTrace() // } // // val file = myFixture.configureByText("test.shire", code) // val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parse() // // val result = runBlocking { // val templateCompiler = ShireTemplateCompiler(project, compile.config, compile.variableTable, code) // templateCompiler.compile() // } // // assertEquals("", result) // } } ================================================ FILE: shirelang/src/test/kotlin/com/phodal/shirelang/regression/ShireTokenizerTest.kt ================================================ package com.phodal.shirelang.regression import com.intellij.testFramework.fixtures.BasePlatformTestCase import com.phodal.shirecore.middleware.post.PostProcessorContext import com.phodal.shirelang.compiler.parser.ShireSyntaxAnalyzer import com.phodal.shirelang.compiler.template.ShireVariableTemplateCompiler import com.phodal.shirelang.psi.ShireFile import kotlinx.coroutines.runBlocking import org.intellij.lang.annotations.Language class ShireTokenizerTest : BasePlatformTestCase() { val javaHelloController = """ package com.phodal.shirelang.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @GetMapping("/hello") public String hello() { return "Hello, World!"; } } """.trimIndent() fun testShouldReturnControllerCodeWithFindCat() { myFixture.addFileToProject( "HelloController.java", javaHelloController ) @Language("Shire") val code = """ --- name: "类图分析" variables: "controllers": /.*.java/ { cat } "tokens": /any/ { tokenizer(${'$'}controllers, "word") } "chinese": /any/ { tokenizer("孩子上了幼儿园 安全防拐教育要做好", "jieba") } --- ${'$'}controllers """.trimIndent() val file = myFixture.configureByText("test.shire", code) val compile = ShireSyntaxAnalyzer(project, file as ShireFile, myFixture.editor).parseAndExecuteLocalCommand() val hole = compile.config!! val context = PostProcessorContext( genText = "User prompt:\n\n", ) runBlocking { val templateCompiler = ShireVariableTemplateCompiler(project, hole, compile.variableTable, code, myFixture.editor) val compiledVariables = templateCompiler.compileVariable(myFixture.editor, mutableMapOf()) context.compiledVariables = compiledVariables } assertEquals( """[package, com, phodal, shirelang, controller, import, org, springframework, web, bind, annotation, GetMapping, RestController, public, class, HelloController, hello, String, return, Hello, World]""", context.compiledVariables["tokens"] ) assertEquals( listOf("孩子", "上", "了", "幼儿园", "安全", "防拐", "教育", "要", "做好").toString(), context.compiledVariables["chinese"].toString() ) } } ================================================ FILE: shirelang/src/test/kotlin/com/phodal/shirelang/run/ShireConfigurationTest.kt ================================================ package com.phodal.shirelang.run import com.phodal.shirelang.run.ShireConfiguration.Companion.mapStringToMap import junit.framework.TestCase.assertEquals import org.junit.Test class ShireConfigurationCompanionTest { @Test fun should_mapSimpleStringToMap_correctly() { // given val varMapString = "{key1=value1, key2=value2}" // when val result = mapStringToMap(varMapString) // then assertEquals(mapOf("key1" to "value1", "key2" to "value2"), result) } @Test fun should_mapEmptyStringToEmptyMap() { // given val varMapString = "{}" // when val result = mapStringToMap(varMapString) // then assertEquals(emptyMap(), result) } @Test fun should_handleMapWithMultipleValuesPerKey_correctly() { // given val varMapString = "{key1=value1, key1=value2}" // when val result = mapStringToMap(varMapString) // then assertEquals(mapOf("key1" to "value2"), result) // Note: As per current implementation, the last value for a key will replace the previous values. } @Test fun shouldTransformFromMapAndToString() { // given val varMap = mapOf("key1" to "value1", "key2" to "value2") // when val result = mapStringToMap(varMap.toString()) // then assertEquals(varMap, result) } } ================================================ FILE: shirelang/src/test/resources/META-INF/plugin.xml ================================================ com.phodal.shire ================================================ FILE: shirelang/src/test/testData/parser/AfterStream.shire ================================================ --- afterStreaming: { condition { "variable-success" { $selection.length > 1 } "jsonpath-success" { jsonpath("/bookstore/book[price>35]") } } case condition { "variable-sucesss" { done } "jsonpath-success" { task() } default { task() } } } --- ================================================ FILE: shirelang/src/test/testData/parser/AfterStream.txt ================================================ ShireFile ShireFrontMatterHeaderImpl(FRONT_MATTER_HEADER) PsiElement(ShireTokenType.FRONTMATTER_START)('---') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntriesImpl(FRONT_MATTER_ENTRIES) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireLifecycleIdImpl(LIFECYCLE_ID) PsiElement(ShireTokenType.afterStreaming)('afterStreaming') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFunctionStatementImpl(FUNCTION_STATEMENT) PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireFunctionBodyImpl(FUNCTION_BODY) ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireCaseBodyImpl(CASE_BODY) ShireConditionFlagImpl(CONDITION_FLAG) PsiElement(ShireTokenType.condition)('condition') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireConditionStatementImpl(CONDITION_STATEMENT) ShireCaseConditionImpl(CASE_CONDITION) PsiElement(ShireTokenType.QUOTE_STRING)('"variable-success"') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireIneqComparisonExprImpl(INEQ_COMPARISON_EXPR) ShireRefExprImpl(REF_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(VARIABLE_START)('$') PsiElement(ShireTokenType.IDENTIFIER)('selection') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('length') PsiWhiteSpace(' ') ShireIneqComparisonOpImpl(INEQ_COMPARISON_OP) PsiElement(ShireTokenType.>)('>') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.NUMBER)('1') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireConditionStatementImpl(CONDITION_STATEMENT) ShireCaseConditionImpl(CASE_CONDITION) PsiElement(ShireTokenType.QUOTE_STRING)('"jsonpath-success"') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) PsiElement(ShireTokenType.IDENTIFIER)('jsonpath') PsiElement(ShireTokenType.()('(') ShireExpressionListImpl(EXPRESSION_LIST) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.QUOTE_STRING)('"/bookstore/book[price>35]"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.case)('case') PsiWhiteSpace(' ') PsiElement(ShireTokenType.condition)('condition') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireCasePatternActionImpl(CASE_PATTERN_ACTION) ShireCaseConditionImpl(CASE_CONDITION) PsiElement(ShireTokenType.QUOTE_STRING)('"variable-sucesss"') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('done') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireCasePatternActionImpl(CASE_PATTERN_ACTION) ShireCaseConditionImpl(CASE_CONDITION) PsiElement(ShireTokenType.QUOTE_STRING)('"jsonpath-success"') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('task') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireCasePatternActionImpl(CASE_PATTERN_ACTION) ShireCaseConditionImpl(CASE_CONDITION) PsiElement(ShireTokenType.default)('default') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('task') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.FRONTMATTER_END)('---') ================================================ FILE: shirelang/src/test/testData/parser/AutoCommand.shire ================================================ /write:Sample.file#L1-L12 ================================================ FILE: shirelang/src/test/testData/parser/AutoCommand.txt ================================================ ShireFile ShireUsedImpl(USED) ShireCommandStartImpl(COMMAND_START) PsiElement(COMMAND_START)('/') ShireCommandIdImpl(COMMAND_ID) PsiElement(ShireTokenType.IDENTIFIER)('write') PsiElement(ShireTokenType.COLON)(':') PsiElement(ShireTokenType.COMMAND_PROP)('Sample.file') PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.LINE_INFO)('L1-L12') ================================================ FILE: shirelang/src/test/testData/parser/AutoRefactor.shire ================================================ /refactor:rename com.phodal.shirelang.run.ShireProgramRunner to com.phodal.shirelang.run.ShireProgramRunnerImpl /refactor:safeDelete com.phodal.shirelang.run.ShireProgramRunnerImpl /refactor:delete com.phodal.shirelang.run.ShireProgramRunnerImpl /refactor:move com.phodal.shirelang.ShireProgramRunner to com.phodal.shirelang.run.ShireProgramRunner ================================================ FILE: shirelang/src/test/testData/parser/AutoRefactor.txt ================================================ ShireFile ShireUsedImpl(USED) ShireCommandStartImpl(COMMAND_START) PsiElement(COMMAND_START)('/') ShireCommandIdImpl(COMMAND_ID) PsiElement(ShireTokenType.IDENTIFIER)('refactor') PsiElement(ShireTokenType.COLON)(':') PsiElement(ShireTokenType.COMMAND_PROP)('rename') PsiElement(ShireTokenType.TEXT_SEGMENT)(' com.phodal.shirelang.run.ShireProgramRunner to com.phodal.shirelang.run.ShireProgramRunnerImpl') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireCommandStartImpl(COMMAND_START) PsiElement(COMMAND_START)('/') ShireCommandIdImpl(COMMAND_ID) PsiElement(ShireTokenType.IDENTIFIER)('refactor') PsiElement(ShireTokenType.COLON)(':') PsiElement(ShireTokenType.COMMAND_PROP)('safeDelete') PsiElement(ShireTokenType.TEXT_SEGMENT)(' com.phodal.shirelang.run.ShireProgramRunnerImpl') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireCommandStartImpl(COMMAND_START) PsiElement(COMMAND_START)('/') ShireCommandIdImpl(COMMAND_ID) PsiElement(ShireTokenType.IDENTIFIER)('refactor') PsiElement(ShireTokenType.COLON)(':') PsiElement(ShireTokenType.COMMAND_PROP)('delete') PsiElement(ShireTokenType.TEXT_SEGMENT)(' com.phodal.shirelang.run.ShireProgramRunnerImpl') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireCommandStartImpl(COMMAND_START) PsiElement(COMMAND_START)('/') ShireCommandIdImpl(COMMAND_ID) PsiElement(ShireTokenType.IDENTIFIER)('refactor') PsiElement(ShireTokenType.COLON)(':') PsiElement(ShireTokenType.COMMAND_PROP)('move') PsiElement(ShireTokenType.TEXT_SEGMENT)(' com.phodal.shirelang.ShireProgramRunner to com.phodal.shirelang.run.ShireProgramRunner') ================================================ FILE: shirelang/src/test/testData/parser/BasicTest.shire ================================================ 你好 @hello-world sm 解释一下代码 $selection 表示选择的内容 @agent-name 调用特定的 agent /file:Sample.file 从文件中读取内容 /rev:632372da 从版本库中读取内容 ================================================ FILE: shirelang/src/test/testData/parser/BasicTest.txt ================================================ ShireFile PsiElement(ShireTokenType.TEXT_SEGMENT)('你好 ') ShireUsedImpl(USED) ShireAgentStartImpl(AGENT_START) PsiElement(AGENT_START)('@') ShireAgentIdImpl(AGENT_ID) PsiElement(ShireTokenType.IDENTIFIER)('hello-world') PsiElement(ShireTokenType.TEXT_SEGMENT)(' sm') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('解释一下代码') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('selection') PsiElement(ShireTokenType.TEXT_SEGMENT)(' 表示选择的内容') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireAgentStartImpl(AGENT_START) PsiElement(AGENT_START)('@') ShireAgentIdImpl(AGENT_ID) PsiElement(ShireTokenType.IDENTIFIER)('agent-name') PsiElement(ShireTokenType.TEXT_SEGMENT)(' 调用特定的 agent') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireCommandStartImpl(COMMAND_START) PsiElement(COMMAND_START)('/') ShireCommandIdImpl(COMMAND_ID) PsiElement(ShireTokenType.IDENTIFIER)('file') PsiElement(ShireTokenType.COLON)(':') PsiElement(ShireTokenType.COMMAND_PROP)('Sample.file') PsiElement(ShireTokenType.TEXT_SEGMENT)(' 从文件中读取内容') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireCommandStartImpl(COMMAND_START) PsiElement(COMMAND_START)('/') ShireCommandIdImpl(COMMAND_ID) PsiElement(ShireTokenType.IDENTIFIER)('rev') PsiElement(ShireTokenType.COLON)(':') PsiElement(ShireTokenType.COMMAND_PROP)('632372da') PsiElement(ShireTokenType.TEXT_SEGMENT)(' 从版本库中读取内容') ================================================ FILE: shirelang/src/test/testData/parser/BlockStartOnly.shire ================================================ ``` ================================================ FILE: shirelang/src/test/testData/parser/BlockStartOnly.txt ================================================ ShireFile CodeBlockElement(CODE) PsiElement(ShireTokenType.CODE_BLOCK_START)('```') ASTWrapperPsiElement(CODE_CONTENTS) ================================================ FILE: shirelang/src/test/testData/parser/BrowseWeb.shire ================================================ /browse:https://ide.unitmesh.cc /browse:https://www.example.com/page?param1=value1¶m2=value2 /browse:https://www.example.com/page#section1 /browse:http://localhost:3000/page /browse:https://username:password@www.example.com/page ================================================ FILE: shirelang/src/test/testData/parser/BrowseWeb.txt ================================================ ShireFile ShireUsedImpl(USED) ShireCommandStartImpl(COMMAND_START) PsiElement(COMMAND_START)('/') ShireCommandIdImpl(COMMAND_ID) PsiElement(ShireTokenType.IDENTIFIER)('browse') PsiElement(ShireTokenType.COLON)(':') PsiElement(ShireTokenType.COMMAND_PROP)('https://ide.unitmesh.cc') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireCommandStartImpl(COMMAND_START) PsiElement(COMMAND_START)('/') ShireCommandIdImpl(COMMAND_ID) PsiElement(ShireTokenType.IDENTIFIER)('browse') PsiElement(ShireTokenType.COLON)(':') PsiElement(ShireTokenType.COMMAND_PROP)('https://www.example.com/page?param1=value1¶m2=value2') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireCommandStartImpl(COMMAND_START) PsiElement(COMMAND_START)('/') ShireCommandIdImpl(COMMAND_ID) PsiElement(ShireTokenType.IDENTIFIER)('browse') PsiElement(ShireTokenType.COLON)(':') PsiElement(ShireTokenType.COMMAND_PROP)('https://www.example.com/page#section1') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireCommandStartImpl(COMMAND_START) PsiElement(COMMAND_START)('/') ShireCommandIdImpl(COMMAND_ID) PsiElement(ShireTokenType.IDENTIFIER)('browse') PsiElement(ShireTokenType.COLON)(':') PsiElement(ShireTokenType.COMMAND_PROP)('http://localhost:3000/page') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireCommandStartImpl(COMMAND_START) PsiElement(COMMAND_START)('/') ShireCommandIdImpl(COMMAND_ID) PsiElement(ShireTokenType.IDENTIFIER)('browse') PsiElement(ShireTokenType.COLON)(':') PsiElement(ShireTokenType.COMMAND_PROP)('https://username:password@www.example.com/page') ================================================ FILE: shirelang/src/test/testData/parser/CommandAndSymbol.shire ================================================ /explain /symbol:cc.unitmesh.devti#RevProvider.constructor /refactor /symbol:cc.unitmesh.devti#RevProvider.completions /write:presentation/VirtualFilePresentation.java#L1-L12 ================================================ FILE: shirelang/src/test/testData/parser/CommandAndSymbol.txt ================================================ ShireFile ShireUsedImpl(USED) ShireCommandStartImpl(COMMAND_START) PsiElement(COMMAND_START)('/') ShireCommandIdImpl(COMMAND_ID) PsiElement(ShireTokenType.IDENTIFIER)('explain') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') ShireUsedImpl(USED) ShireCommandStartImpl(COMMAND_START) PsiElement(COMMAND_START)('/') ShireCommandIdImpl(COMMAND_ID) PsiElement(ShireTokenType.IDENTIFIER)('symbol') PsiElement(ShireTokenType.COLON)(':') PsiElement(ShireTokenType.COMMAND_PROP)('cc.unitmesh.devti#RevProvider.constructor') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireCommandStartImpl(COMMAND_START) PsiElement(COMMAND_START)('/') ShireCommandIdImpl(COMMAND_ID) PsiElement(ShireTokenType.IDENTIFIER)('refactor') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') ShireUsedImpl(USED) ShireCommandStartImpl(COMMAND_START) PsiElement(COMMAND_START)('/') ShireCommandIdImpl(COMMAND_ID) PsiElement(ShireTokenType.IDENTIFIER)('symbol') PsiElement(ShireTokenType.COLON)(':') PsiElement(ShireTokenType.COMMAND_PROP)('cc.unitmesh.devti#RevProvider.completions') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireCommandStartImpl(COMMAND_START) PsiElement(COMMAND_START)('/') ShireCommandIdImpl(COMMAND_ID) PsiElement(ShireTokenType.IDENTIFIER)('write') PsiElement(ShireTokenType.COLON)(':') PsiElement(ShireTokenType.COMMAND_PROP)('presentation/VirtualFilePresentation.java') PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.LINE_INFO)('L1-L12') ================================================ FILE: shirelang/src/test/testData/parser/ComplexLangId.shire ================================================ ```typescript jsx import { Button } from '@unitmesh-ui/core'; ``` ================================================ FILE: shirelang/src/test/testData/parser/ComplexLangId.txt ================================================ ShireFile CodeBlockElement(CODE) PsiElement(ShireTokenType.CODE_BLOCK_START)('```') ShireLanguageIdImpl(LANGUAGE_ID) PsiElement(ShireTokenType.LANGUAGE_IDENTIFIER)('typescript jsx') PsiElement(ShireTokenType.NEWLINE)('\n') ASTWrapperPsiElement(CODE_CONTENTS) PsiElement(ShireTokenType.CODE_CONTENT)('import { Button } from '@unitmesh-ui/core';') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_BLOCK_END)('```') ================================================ FILE: shirelang/src/test/testData/parser/CustomFunctions.shire ================================================ --- functions: aFunc: "defaultMain.py"(string) aFunc: "multipleOutput.py"(string, number) -> content cFunc: "accessFunctionIfSupport.py"::resize(string, number, number) -> image --- ================================================ FILE: shirelang/src/test/testData/parser/CustomFunctions.txt ================================================ ShireFile ShireFrontMatterHeaderImpl(FRONT_MATTER_HEADER) PsiElement(ShireTokenType.FRONTMATTER_START)('---') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntriesImpl(FRONT_MATTER_ENTRIES) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) PsiElement(ShireTokenType.functions)('functions') PsiElement(ShireTokenType.COLON)(':') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.NEWLINE)('\n') ShireObjectKeyValueImpl(OBJECT_KEY_VALUE) PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('aFunc') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireForeignFunctionImpl(FOREIGN_FUNCTION) ShireForeignPathImpl(FOREIGN_PATH) PsiElement(ShireTokenType.QUOTE_STRING)('"defaultMain.py"') PsiElement(ShireTokenType.()('(') ShireForeignTypeImpl(FOREIGN_TYPE) PsiElement(ShireTokenType.IDENTIFIER)('string') PsiElement(ShireTokenType.))(')') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('aFunc') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireForeignFunctionImpl(FOREIGN_FUNCTION) ShireForeignPathImpl(FOREIGN_PATH) PsiElement(ShireTokenType.QUOTE_STRING)('"multipleOutput.py"') PsiElement(ShireTokenType.()('(') ShireForeignTypeImpl(FOREIGN_TYPE) PsiElement(ShireTokenType.IDENTIFIER)('string') PsiElement(ShireTokenType.,)(',') PsiWhiteSpace(' ') ShireForeignTypeImpl(FOREIGN_TYPE) PsiElement(ShireTokenType.IDENTIFIER)('number') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.PROCESS)('->') PsiWhiteSpace(' ') ShireForeignOutputImpl(FOREIGN_OUTPUT) ShireOutputVarImpl(OUTPUT_VAR) PsiElement(ShireTokenType.IDENTIFIER)('content') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('cFunc') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireForeignFunctionImpl(FOREIGN_FUNCTION) ShireForeignPathImpl(FOREIGN_PATH) PsiElement(ShireTokenType.QUOTE_STRING)('"accessFunctionIfSupport.py"') PsiElement(ShireTokenType.::)('::') ShireForeignFuncNameImpl(FOREIGN_FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('resize') PsiElement(ShireTokenType.()('(') ShireForeignTypeImpl(FOREIGN_TYPE) PsiElement(ShireTokenType.IDENTIFIER)('string') PsiElement(ShireTokenType.,)(',') PsiWhiteSpace(' ') ShireForeignTypeImpl(FOREIGN_TYPE) PsiElement(ShireTokenType.IDENTIFIER)('number') PsiElement(ShireTokenType.,)(',') PsiWhiteSpace(' ') ShireForeignTypeImpl(FOREIGN_TYPE) PsiElement(ShireTokenType.IDENTIFIER)('number') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.PROCESS)('->') PsiWhiteSpace(' ') ShireForeignOutputImpl(FOREIGN_OUTPUT) ShireOutputVarImpl(OUTPUT_VAR) PsiElement(ShireTokenType.IDENTIFIER)('image') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.FRONTMATTER_END)('---') ================================================ FILE: shirelang/src/test/testData/parser/EmptyCodeFence.shire ================================================ 解释如下的代码: ``` print("Hello, world!") ``` 请使用 Markdown 语法返回。 ================================================ FILE: shirelang/src/test/testData/parser/EmptyCodeFence.txt ================================================ ShireFile PsiElement(ShireTokenType.TEXT_SEGMENT)('解释如下的代码:') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') CodeBlockElement(CODE) PsiElement(ShireTokenType.CODE_BLOCK_START)('```') PsiElement(ShireTokenType.NEWLINE)('\n') ASTWrapperPsiElement(CODE_CONTENTS) PsiElement(ShireTokenType.CODE_CONTENT)('print("Hello, world!")') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_BLOCK_END)('```') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('请使用 Markdown 语法返回。') ================================================ FILE: shirelang/src/test/testData/parser/FrontMatter.shire ================================================ --- title: "Sample Title" date: 2022-01-01 author: "John Doe" tags: [markdown, frontmatter] --- ================================================ FILE: shirelang/src/test/testData/parser/FrontMatter.txt ================================================ ShireFile ShireFrontMatterHeaderImpl(FRONT_MATTER_HEADER) PsiElement(ShireTokenType.FRONTMATTER_START)('---') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntriesImpl(FRONT_MATTER_ENTRIES) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('title') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.QUOTE_STRING)('"Sample Title"') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('date') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.DATE)('2022-01-01') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('author') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.QUOTE_STRING)('"John Doe"') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('tags') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) ShireFrontMatterArrayImpl(FRONT_MATTER_ARRAY) PsiElement(ShireTokenType.[)('[') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.IDENTIFIER)('markdown') PsiElement(ShireTokenType.,)(',') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.IDENTIFIER)('frontmatter') PsiElement(ShireTokenType.])(']') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.FRONTMATTER_END)('---') ================================================ FILE: shirelang/src/test/testData/parser/IfExpression.shire ================================================ This is an expression that includes an if. #if(1 > 0) ${language} if #end This is an expression that includes if and elseif. #if($language.length() > 1) ${language} if #elseif($language.length() > 5) ${language} elseif #end This is an expression that includes if and else. #if($language.length() > 1 ) ${language} if #else ${language} else #end This is an expression that includes if,elseif and else. #if($language.length() > 10 ) ${language} if #elseif($language.length() > 2 ) ${language} elseif #else ${language} else #end This is an expression that includes multiple if and elseif. #if($language.length() > 1 ) ${language} if #if($language.length() > 2 ) ${language} if_if #elseif($language.length() > 20 ) ${language} if_elseif #end #elseif($language.length() > 10 ) ${language} elseif #end This is an expression that includes multiple if and else. #if($language.length() > 10 ) ${language} if #else ${language} else #if($language.length() > 2 ) ${language} else_if #else ${language} else_else #end #end Please ignore the content and don't reply to me. ================================================ FILE: shirelang/src/test/testData/parser/IfExpression.txt ================================================ ShireFile PsiElement(ShireTokenType.TEXT_SEGMENT)('This is an expression that includes an if.') PsiElement(ShireTokenType.NEWLINE)('\n') ShireVelocityExprImpl(VELOCITY_EXPR) ShireIfExprImpl(IF_EXPR) ShireIfClauseImpl(IF_CLAUSE) PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.if)('if') PsiElement(ShireTokenType.()('(') ShireIneqComparisonExprImpl(INEQ_COMPARISON_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.NUMBER)('1') PsiWhiteSpace(' ') ShireIneqComparisonOpImpl(INEQ_COMPARISON_OP) PsiElement(ShireTokenType.>)('>') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.NUMBER)('0') PsiElement(ShireTokenType.))(')') ShireVelocityBlockImpl(VELOCITY_BLOCK) PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.TEXT_SEGMENT)(' if') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.end)('end') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('This is an expression that includes if and elseif.') PsiElement(ShireTokenType.NEWLINE)('\n') ShireVelocityExprImpl(VELOCITY_EXPR) ShireIfExprImpl(IF_EXPR) ShireIfClauseImpl(IF_CLAUSE) PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.if)('if') PsiElement(ShireTokenType.()('(') ShireIneqComparisonExprImpl(INEQ_COMPARISON_EXPR) ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(VARIABLE_START)('$') PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('length') PsiElement(ShireTokenType.()('(') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') ShireIneqComparisonOpImpl(INEQ_COMPARISON_OP) PsiElement(ShireTokenType.>)('>') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.NUMBER)('1') PsiElement(ShireTokenType.))(')') ShireVelocityBlockImpl(VELOCITY_BLOCK) PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.TEXT_SEGMENT)(' if') PsiElement(ShireTokenType.NEWLINE)('\n') ShireElseifClauseImpl(ELSEIF_CLAUSE) PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.elseif)('elseif') PsiElement(ShireTokenType.()('(') ShireIneqComparisonExprImpl(INEQ_COMPARISON_EXPR) ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(VARIABLE_START)('$') PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('length') PsiElement(ShireTokenType.()('(') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') ShireIneqComparisonOpImpl(INEQ_COMPARISON_OP) PsiElement(ShireTokenType.>)('>') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.NUMBER)('5') PsiElement(ShireTokenType.))(')') ShireVelocityBlockImpl(VELOCITY_BLOCK) PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.TEXT_SEGMENT)(' elseif') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.end)('end') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('This is an expression that includes if and else.') PsiElement(ShireTokenType.NEWLINE)('\n') ShireVelocityExprImpl(VELOCITY_EXPR) ShireIfExprImpl(IF_EXPR) ShireIfClauseImpl(IF_CLAUSE) PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.if)('if') PsiElement(ShireTokenType.()('(') ShireIneqComparisonExprImpl(INEQ_COMPARISON_EXPR) ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(VARIABLE_START)('$') PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('length') PsiElement(ShireTokenType.()('(') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') ShireIneqComparisonOpImpl(INEQ_COMPARISON_OP) PsiElement(ShireTokenType.>)('>') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.NUMBER)('1') PsiWhiteSpace(' ') PsiElement(ShireTokenType.))(')') ShireVelocityBlockImpl(VELOCITY_BLOCK) PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.TEXT_SEGMENT)(' if') PsiElement(ShireTokenType.NEWLINE)('\n') ShireElseClauseImpl(ELSE_CLAUSE) PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.else)('else') ShireVelocityBlockImpl(VELOCITY_BLOCK) PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.TEXT_SEGMENT)(' else') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.end)('end') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('This is an expression that includes if,elseif and else.') PsiElement(ShireTokenType.NEWLINE)('\n') ShireVelocityExprImpl(VELOCITY_EXPR) ShireIfExprImpl(IF_EXPR) ShireIfClauseImpl(IF_CLAUSE) PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.if)('if') PsiElement(ShireTokenType.()('(') ShireIneqComparisonExprImpl(INEQ_COMPARISON_EXPR) ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(VARIABLE_START)('$') PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('length') PsiElement(ShireTokenType.()('(') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') ShireIneqComparisonOpImpl(INEQ_COMPARISON_OP) PsiElement(ShireTokenType.>)('>') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.NUMBER)('10') PsiWhiteSpace(' ') PsiElement(ShireTokenType.))(')') ShireVelocityBlockImpl(VELOCITY_BLOCK) PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.TEXT_SEGMENT)(' if') PsiElement(ShireTokenType.NEWLINE)('\n') ShireElseifClauseImpl(ELSEIF_CLAUSE) PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.elseif)('elseif') PsiElement(ShireTokenType.()('(') ShireIneqComparisonExprImpl(INEQ_COMPARISON_EXPR) ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(VARIABLE_START)('$') PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('length') PsiElement(ShireTokenType.()('(') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') ShireIneqComparisonOpImpl(INEQ_COMPARISON_OP) PsiElement(ShireTokenType.>)('>') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.NUMBER)('2') PsiWhiteSpace(' ') PsiElement(ShireTokenType.))(')') ShireVelocityBlockImpl(VELOCITY_BLOCK) PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.TEXT_SEGMENT)(' elseif') PsiElement(ShireTokenType.NEWLINE)('\n') ShireElseClauseImpl(ELSE_CLAUSE) PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.else)('else') ShireVelocityBlockImpl(VELOCITY_BLOCK) PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.TEXT_SEGMENT)(' else') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.end)('end') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('This is an expression that includes multiple if and elseif.') PsiElement(ShireTokenType.NEWLINE)('\n') ShireVelocityExprImpl(VELOCITY_EXPR) ShireIfExprImpl(IF_EXPR) ShireIfClauseImpl(IF_CLAUSE) PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.if)('if') PsiElement(ShireTokenType.()('(') ShireIneqComparisonExprImpl(INEQ_COMPARISON_EXPR) ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(VARIABLE_START)('$') PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('length') PsiElement(ShireTokenType.()('(') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') ShireIneqComparisonOpImpl(INEQ_COMPARISON_OP) PsiElement(ShireTokenType.>)('>') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.NUMBER)('1') PsiWhiteSpace(' ') PsiElement(ShireTokenType.))(')') ShireVelocityBlockImpl(VELOCITY_BLOCK) PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.TEXT_SEGMENT)(' if') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') ShireVelocityExprImpl(VELOCITY_EXPR) ShireIfExprImpl(IF_EXPR) ShireIfClauseImpl(IF_CLAUSE) PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.if)('if') PsiElement(ShireTokenType.()('(') ShireIneqComparisonExprImpl(INEQ_COMPARISON_EXPR) ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(VARIABLE_START)('$') PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('length') PsiElement(ShireTokenType.()('(') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') ShireIneqComparisonOpImpl(INEQ_COMPARISON_OP) PsiElement(ShireTokenType.>)('>') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.NUMBER)('2') PsiWhiteSpace(' ') PsiElement(ShireTokenType.))(')') ShireVelocityBlockImpl(VELOCITY_BLOCK) PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.TEXT_SEGMENT)(' if_if') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') ShireElseifClauseImpl(ELSEIF_CLAUSE) PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.elseif)('elseif') PsiElement(ShireTokenType.()('(') ShireIneqComparisonExprImpl(INEQ_COMPARISON_EXPR) ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(VARIABLE_START)('$') PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('length') PsiElement(ShireTokenType.()('(') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') ShireIneqComparisonOpImpl(INEQ_COMPARISON_OP) PsiElement(ShireTokenType.>)('>') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.NUMBER)('20') PsiWhiteSpace(' ') PsiElement(ShireTokenType.))(')') ShireVelocityBlockImpl(VELOCITY_BLOCK) PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.TEXT_SEGMENT)(' if_elseif') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.end)('end') PsiElement(ShireTokenType.NEWLINE)('\n') ShireElseifClauseImpl(ELSEIF_CLAUSE) PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.elseif)('elseif') PsiElement(ShireTokenType.()('(') ShireIneqComparisonExprImpl(INEQ_COMPARISON_EXPR) ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(VARIABLE_START)('$') PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('length') PsiElement(ShireTokenType.()('(') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') ShireIneqComparisonOpImpl(INEQ_COMPARISON_OP) PsiElement(ShireTokenType.>)('>') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.NUMBER)('10') PsiWhiteSpace(' ') PsiElement(ShireTokenType.))(')') ShireVelocityBlockImpl(VELOCITY_BLOCK) PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.TEXT_SEGMENT)(' elseif') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.end)('end') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('This is an expression that includes multiple if and else.') PsiElement(ShireTokenType.NEWLINE)('\n') ShireVelocityExprImpl(VELOCITY_EXPR) ShireIfExprImpl(IF_EXPR) ShireIfClauseImpl(IF_CLAUSE) PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.if)('if') PsiElement(ShireTokenType.()('(') ShireIneqComparisonExprImpl(INEQ_COMPARISON_EXPR) ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(VARIABLE_START)('$') PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('length') PsiElement(ShireTokenType.()('(') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') ShireIneqComparisonOpImpl(INEQ_COMPARISON_OP) PsiElement(ShireTokenType.>)('>') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.NUMBER)('10') PsiWhiteSpace(' ') PsiElement(ShireTokenType.))(')') ShireVelocityBlockImpl(VELOCITY_BLOCK) PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.TEXT_SEGMENT)(' if') PsiElement(ShireTokenType.NEWLINE)('\n') ShireElseClauseImpl(ELSE_CLAUSE) PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.else)('else') ShireVelocityBlockImpl(VELOCITY_BLOCK) PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.TEXT_SEGMENT)(' else') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') ShireVelocityExprImpl(VELOCITY_EXPR) ShireIfExprImpl(IF_EXPR) ShireIfClauseImpl(IF_CLAUSE) PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.if)('if') PsiElement(ShireTokenType.()('(') ShireIneqComparisonExprImpl(INEQ_COMPARISON_EXPR) ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(VARIABLE_START)('$') PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('length') PsiElement(ShireTokenType.()('(') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') ShireIneqComparisonOpImpl(INEQ_COMPARISON_OP) PsiElement(ShireTokenType.>)('>') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.NUMBER)('2') PsiWhiteSpace(' ') PsiElement(ShireTokenType.))(')') ShireVelocityBlockImpl(VELOCITY_BLOCK) PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.TEXT_SEGMENT)(' else_if') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') ShireElseClauseImpl(ELSE_CLAUSE) PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.else)('else') ShireVelocityBlockImpl(VELOCITY_BLOCK) PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.TEXT_SEGMENT)(' else_else') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(' ') PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.end)('end') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.end)('end') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('Please ignore the content and don't reply to me.') ================================================ FILE: shirelang/src/test/testData/parser/JavaAnnotation.shire ================================================ ```java @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface ExampleAnnotation { String value() default ""; } ``` ================================================ FILE: shirelang/src/test/testData/parser/JavaAnnotation.txt ================================================ ShireFile CodeBlockElement(CODE) PsiElement(ShireTokenType.CODE_BLOCK_START)('```') ShireLanguageIdImpl(LANGUAGE_ID) PsiElement(ShireTokenType.LANGUAGE_IDENTIFIER)('java') PsiElement(ShireTokenType.NEWLINE)('\n') ASTWrapperPsiElement(CODE_CONTENTS) PsiElement(ShireTokenType.CODE_CONTENT)('@Target({ElementType.TYPE})') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_CONTENT)('@Retention(RetentionPolicy.RUNTIME)') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_CONTENT)('public @interface ExampleAnnotation {') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_CONTENT)(' String value() default "";') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_CONTENT)('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_BLOCK_END)('```') ================================================ FILE: shirelang/src/test/testData/parser/JavaHelloWorld.shire ================================================ 解释如下的代码: ```java public class Main { public static void main(String[] args) { System.out.println("Hello, world!"); } } ``` 请使用 Markdown 语法返回。 ================================================ FILE: shirelang/src/test/testData/parser/JavaHelloWorld.txt ================================================ ShireFile PsiElement(ShireTokenType.TEXT_SEGMENT)('解释如下的代码:') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') CodeBlockElement(CODE) PsiElement(ShireTokenType.CODE_BLOCK_START)('```') ShireLanguageIdImpl(LANGUAGE_ID) PsiElement(ShireTokenType.LANGUAGE_IDENTIFIER)('java') PsiElement(ShireTokenType.NEWLINE)('\n') ASTWrapperPsiElement(CODE_CONTENTS) PsiElement(ShireTokenType.CODE_CONTENT)('public class Main {') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_CONTENT)(' public static void main(String[] args) {') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_CONTENT)(' System.out.println("Hello, world!");') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_CONTENT)(' }') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_CONTENT)('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_BLOCK_END)('```') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('请使用 Markdown 语法返回。') ================================================ FILE: shirelang/src/test/testData/parser/MarkdownCompatible.shire ================================================ ## Hello ```shire --- name: "自动 patch" variables: "codepath": /BlogController\.java/ { print } "controllerCode": /BlogController\.java/ { cat } "domainLanguage": /domain-language\.csv/ { cat } onStreamingEnd: { parseCode | patch($codepath, $output) } --- ``` ================================================ FILE: shirelang/src/test/testData/parser/MarkdownCompatible.txt ================================================ ShireFile ShireMarkdownHeaderImpl(MARKDOWN_HEADER) PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.TEXT_SEGMENT)(' Hello') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') CodeBlockElement(CODE) PsiElement(ShireTokenType.CODE_BLOCK_START)('```') ShireLanguageIdImpl(LANGUAGE_ID) PsiElement(ShireTokenType.LANGUAGE_IDENTIFIER)('shire') PsiElement(ShireTokenType.NEWLINE)('\n') ASTWrapperPsiElement(CODE_CONTENTS) PsiElement(ShireTokenType.CODE_CONTENT)('---') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_CONTENT)('name: "自动 patch"') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_CONTENT)('variables:') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_CONTENT)(' "codepath": /BlogController\.java/ { print }') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_CONTENT)(' "controllerCode": /BlogController\.java/ { cat }') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_CONTENT)(' "domainLanguage": /domain-language\.csv/ { cat }') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_CONTENT)('onStreamingEnd: { parseCode | patch($codepath, $output) }') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_CONTENT)('---') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_BLOCK_END)('```') ================================================ FILE: shirelang/src/test/testData/parser/MultipleFMVariable.shire ================================================ --- variables: "extContext": /build\.gradle\.kts/ { cat | grep("org.springframework.boot:spring-boot-starter-jdbc") | print("This project use Spring Framework")} "testTemplate": /\(.*\).java/ { case "$1" { "Controller" { cat(".shire/templates/ControllerTest.java") } "Service" { cat(".shire/templates/ServiceTest.java") } default { cat(".shire/templates/DefaultTest.java") } } } "allController": { from { PsiClass clazz /* sample */ } where { clazz.getAnAnnotation() == "org.springframework.web.bind.annotation.RequestMapping" } select { clazz.id, clazz.name, "code" } } --- ================================================ FILE: shirelang/src/test/testData/parser/MultipleFMVariable.txt ================================================ ShireFile ShireFrontMatterHeaderImpl(FRONT_MATTER_HEADER) PsiElement(ShireTokenType.FRONTMATTER_START)('---') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntriesImpl(FRONT_MATTER_ENTRIES) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('variables') PsiElement(ShireTokenType.COLON)(':') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.NEWLINE)('\n') ShireObjectKeyValueImpl(OBJECT_KEY_VALUE) PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) PsiElement(ShireTokenType.QUOTE_STRING)('"extContext"') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShirePatternActionImpl(PATTERN_ACTION) PatternElement(PATTERN) PsiElement(ShireTokenType.PATTERN_EXPR)('/build\.gradle\.kts/') PsiWhiteSpace(' ') ShireActionBlockImpl(ACTION_BLOCK) PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('cat') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireGrepFuncCall(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('grep') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"org.springframework.boot:spring-boot-starter-jdbc"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('print') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"This project use Spring Framework"') PsiElement(ShireTokenType.))(')') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) PsiElement(ShireTokenType.QUOTE_STRING)('"testTemplate"') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShirePatternActionImpl(PATTERN_ACTION) PatternElement(PATTERN) PsiElement(ShireTokenType.PATTERN_EXPR)('/\(.*\).java/') PsiWhiteSpace(' ') ShireActionBlockImpl(ACTION_BLOCK) PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireCaseBodyImpl(CASE_BODY) PsiElement(ShireTokenType.case)('case') PsiWhiteSpace(' ') PsiElement(ShireTokenType.QUOTE_STRING)('"$1"') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireCasePatternActionImpl(CASE_PATTERN_ACTION) ShireCaseConditionImpl(CASE_CONDITION) PsiElement(ShireTokenType.QUOTE_STRING)('"Controller"') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('cat') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('".shire/templates/ControllerTest.java"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireCasePatternActionImpl(CASE_PATTERN_ACTION) ShireCaseConditionImpl(CASE_CONDITION) PsiElement(ShireTokenType.QUOTE_STRING)('"Service"') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('cat') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('".shire/templates/ServiceTest.java"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireCasePatternActionImpl(CASE_PATTERN_ACTION) ShireCaseConditionImpl(CASE_CONDITION) PsiElement(ShireTokenType.default)('default') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('cat') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('".shire/templates/DefaultTest.java"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) PsiElement(ShireTokenType.QUOTE_STRING)('"allController"') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFunctionStatementImpl(FUNCTION_STATEMENT) PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireFunctionBodyImpl(FUNCTION_BODY) ShireQueryStatementImpl(QUERY_STATEMENT) ShireFromClauseImpl(FROM_CLAUSE) PsiElement(ShireTokenType.from)('from') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShirePsiElementDeclImpl(PSI_ELEMENT_DECL) ShirePsiVarDeclImpl(PSI_VAR_DECL) ShirePsiTypeImpl(PSI_TYPE) PsiElement(ShireTokenType.IDENTIFIER)('PsiClass') PsiWhiteSpace(' ') PsiElement(ShireTokenType.IDENTIFIER)('clazz') PsiWhiteSpace(' ') PsiComment(ShireTokenType.BLOCK_COMMENT)('/* sample */') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireWhereClauseImpl(WHERE_CLAUSE) PsiElement(ShireTokenType.where)('where') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireEqComparisonExprImpl(EQ_COMPARISON_EXPR) ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireRefExprImpl(REF_EXPR) PsiElement(ShireTokenType.IDENTIFIER)('clazz') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('getAnAnnotation') PsiElement(ShireTokenType.()('(') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.==)('==') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.QUOTE_STRING)('"org.springframework.web.bind.annotation.RequestMapping"') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireSelectClauseImpl(SELECT_CLAUSE) PsiElement(ShireTokenType.select)('select') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireRefExprImpl(REF_EXPR) ShireRefExprImpl(REF_EXPR) PsiElement(ShireTokenType.IDENTIFIER)('clazz') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('id') PsiElement(ShireTokenType.,)(',') PsiWhiteSpace(' ') ShireRefExprImpl(REF_EXPR) ShireRefExprImpl(REF_EXPR) PsiElement(ShireTokenType.IDENTIFIER)('clazz') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('name') PsiElement(ShireTokenType.,)(',') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.QUOTE_STRING)('"code"') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.FRONTMATTER_END)('---') ================================================ FILE: shirelang/src/test/testData/parser/PatternAction.shire ================================================ --- variables: "var1": "value2" "var2": /.*.java/ { grep("error.log") | sort | xargs("rm")} --- $var1 ================================================ FILE: shirelang/src/test/testData/parser/PatternAction.txt ================================================ ShireFile ShireFrontMatterHeaderImpl(FRONT_MATTER_HEADER) PsiElement(ShireTokenType.FRONTMATTER_START)('---') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntriesImpl(FRONT_MATTER_ENTRIES) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('variables') PsiElement(ShireTokenType.COLON)(':') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.NEWLINE)('\n') ShireObjectKeyValueImpl(OBJECT_KEY_VALUE) PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) PsiElement(ShireTokenType.QUOTE_STRING)('"var1"') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.QUOTE_STRING)('"value2"') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) PsiElement(ShireTokenType.QUOTE_STRING)('"var2"') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShirePatternActionImpl(PATTERN_ACTION) PatternElement(PATTERN) PsiElement(ShireTokenType.PATTERN_EXPR)('/.*.java/') PsiWhiteSpace(' ') ShireActionBlockImpl(ACTION_BLOCK) PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireGrepFuncCall(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('grep') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"error.log"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('sort') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('xargs') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"rm"') PsiElement(ShireTokenType.))(')') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.FRONTMATTER_END)('---') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('var1') ================================================ FILE: shirelang/src/test/testData/parser/PatternCaseAction.shire ================================================ --- variables: "var1": "demo" "var2": /.*.java/ { grep("error.log") | sort | xargs("rm")} "var3": /.*.log/ { case "$0" { "error" { grep("ERROR") | sort | xargs("notify_admin") } "warn" { grep("WARN") | sort | xargs("notify_admin") } "info" { grep("INFO") | sort | xargs("notify_user") } default { grep("ERROR") | sort | xargs("notify_admin") } } } "var4": 42 --- $var1 ================================================ FILE: shirelang/src/test/testData/parser/PatternCaseAction.txt ================================================ ShireFile ShireFrontMatterHeaderImpl(FRONT_MATTER_HEADER) PsiElement(ShireTokenType.FRONTMATTER_START)('---') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntriesImpl(FRONT_MATTER_ENTRIES) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('variables') PsiElement(ShireTokenType.COLON)(':') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.NEWLINE)('\n') ShireObjectKeyValueImpl(OBJECT_KEY_VALUE) PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) PsiElement(ShireTokenType.QUOTE_STRING)('"var1"') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.QUOTE_STRING)('"demo"') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) PsiElement(ShireTokenType.QUOTE_STRING)('"var2"') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShirePatternActionImpl(PATTERN_ACTION) PatternElement(PATTERN) PsiElement(ShireTokenType.PATTERN_EXPR)('/.*.java/') PsiWhiteSpace(' ') ShireActionBlockImpl(ACTION_BLOCK) PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireGrepFuncCall(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('grep') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"error.log"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('sort') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('xargs') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"rm"') PsiElement(ShireTokenType.))(')') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) PsiElement(ShireTokenType.QUOTE_STRING)('"var3"') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShirePatternActionImpl(PATTERN_ACTION) PatternElement(PATTERN) PsiElement(ShireTokenType.PATTERN_EXPR)('/.*.log/') PsiWhiteSpace(' ') ShireActionBlockImpl(ACTION_BLOCK) PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireCaseBodyImpl(CASE_BODY) PsiElement(ShireTokenType.case)('case') PsiWhiteSpace(' ') PsiElement(ShireTokenType.QUOTE_STRING)('"$0"') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireCasePatternActionImpl(CASE_PATTERN_ACTION) ShireCaseConditionImpl(CASE_CONDITION) PsiElement(ShireTokenType.QUOTE_STRING)('"error"') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireGrepFuncCall(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('grep') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"ERROR"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('sort') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('xargs') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"notify_admin"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireCasePatternActionImpl(CASE_PATTERN_ACTION) ShireCaseConditionImpl(CASE_CONDITION) PsiElement(ShireTokenType.QUOTE_STRING)('"warn"') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireGrepFuncCall(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('grep') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"WARN"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('sort') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('xargs') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"notify_admin"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireCasePatternActionImpl(CASE_PATTERN_ACTION) ShireCaseConditionImpl(CASE_CONDITION) PsiElement(ShireTokenType.QUOTE_STRING)('"info"') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireGrepFuncCall(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('grep') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"INFO"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('sort') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('xargs') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"notify_user"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireCasePatternActionImpl(CASE_PATTERN_ACTION) ShireCaseConditionImpl(CASE_CONDITION) PsiElement(ShireTokenType.default)('default') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireGrepFuncCall(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('grep') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"ERROR"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('sort') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('xargs') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"notify_admin"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) PsiElement(ShireTokenType.QUOTE_STRING)('"var4"') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.NUMBER)('42') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.FRONTMATTER_END)('---') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('var1') ================================================ FILE: shirelang/src/test/testData/parser/ShireFmObject.shire ================================================ --- name: "Java to Kotlin" description: "Convert Java to Kotlin file" interaction: AppendCursor actionLocation: ContextMenu enabled: false model: "codegeex-4" onStreamingEnd: { verifyCode | runCode } --- Convert follow $language code to Kotlin $all ================================================ FILE: shirelang/src/test/testData/parser/ShireFmObject.txt ================================================ ShireFile ShireFrontMatterHeaderImpl(FRONT_MATTER_HEADER) PsiElement(ShireTokenType.FRONTMATTER_START)('---') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntriesImpl(FRONT_MATTER_ENTRIES) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('name') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.QUOTE_STRING)('"Java to Kotlin"') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('description') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.QUOTE_STRING)('"Convert Java to Kotlin file"') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('interaction') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.IDENTIFIER)('AppendCursor') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('actionLocation') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.IDENTIFIER)('ContextMenu') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('enabled') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.BOOLEAN)('false') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('model') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.QUOTE_STRING)('"codegeex-4"') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireLifecycleIdImpl(LIFECYCLE_ID) PsiElement(ShireTokenType.onStreamingEnd)('onStreamingEnd') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFunctionStatementImpl(FUNCTION_STATEMENT) PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireFunctionBodyImpl(FUNCTION_BODY) ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('verifyCode') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('runCode') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.FRONTMATTER_END)('---') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('Convert follow ') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType.TEXT_SEGMENT)(' code to Kotlin') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('all') ================================================ FILE: shirelang/src/test/testData/parser/ShirePsiQueryExpression.shire ================================================ --- variables: "allController": { from { PsiClass clazz // the class } where { clazz.extends("org.springframework.web.bind.annotation.RestController") and clazz.getAnAnnotation() == "org.springframework.web.bind.annotation.RequestMapping" } select { clazz.id, clazz.name, "code" } } --- $allController ================================================ FILE: shirelang/src/test/testData/parser/ShirePsiQueryExpression.txt ================================================ ShireFile ShireFrontMatterHeaderImpl(FRONT_MATTER_HEADER) PsiElement(ShireTokenType.FRONTMATTER_START)('---') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntriesImpl(FRONT_MATTER_ENTRIES) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('variables') PsiElement(ShireTokenType.COLON)(':') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.NEWLINE)('\n') ShireObjectKeyValueImpl(OBJECT_KEY_VALUE) PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) PsiElement(ShireTokenType.QUOTE_STRING)('"allController"') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFunctionStatementImpl(FUNCTION_STATEMENT) PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireFunctionBodyImpl(FUNCTION_BODY) ShireQueryStatementImpl(QUERY_STATEMENT) ShireFromClauseImpl(FROM_CLAUSE) PsiElement(ShireTokenType.from)('from') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShirePsiElementDeclImpl(PSI_ELEMENT_DECL) ShirePsiVarDeclImpl(PSI_VAR_DECL) ShirePsiTypeImpl(PSI_TYPE) PsiElement(ShireTokenType.IDENTIFIER)('PsiClass') PsiWhiteSpace(' ') PsiElement(ShireTokenType.IDENTIFIER)('clazz') PsiWhiteSpace(' ') PsiComment(ShireTokenType.COMMENTS)('// the class') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireWhereClauseImpl(WHERE_CLAUSE) PsiElement(ShireTokenType.where)('where') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireLogicalAndExprImpl(LOGICAL_AND_EXPR) ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireRefExprImpl(REF_EXPR) PsiElement(ShireTokenType.IDENTIFIER)('clazz') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('extends') PsiElement(ShireTokenType.()('(') ShireExpressionListImpl(EXPRESSION_LIST) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.QUOTE_STRING)('"org.springframework.web.bind.annotation.RestController"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.and)('and') PsiWhiteSpace(' ') ShireEqComparisonExprImpl(EQ_COMPARISON_EXPR) ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireRefExprImpl(REF_EXPR) PsiElement(ShireTokenType.IDENTIFIER)('clazz') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('getAnAnnotation') PsiElement(ShireTokenType.()('(') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.==)('==') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.QUOTE_STRING)('"org.springframework.web.bind.annotation.RequestMapping"') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireSelectClauseImpl(SELECT_CLAUSE) PsiElement(ShireTokenType.select)('select') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireRefExprImpl(REF_EXPR) ShireRefExprImpl(REF_EXPR) PsiElement(ShireTokenType.IDENTIFIER)('clazz') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('id') PsiElement(ShireTokenType.,)(',') PsiWhiteSpace(' ') ShireRefExprImpl(REF_EXPR) ShireRefExprImpl(REF_EXPR) PsiElement(ShireTokenType.IDENTIFIER)('clazz') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('name') PsiElement(ShireTokenType.,)(',') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.QUOTE_STRING)('"code"') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.FRONTMATTER_END)('---') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('allController') ================================================ FILE: shirelang/src/test/testData/parser/SingleComment.shire ================================================ --- name: "Gandalf" // comment 2 variables: "codepath": /BlogController\.java/ { print } // comments 3 --- [flow]:flowable.devin [flow](result) [] is a symbol of comment, follow markdown syntax, and the content in [] is the comment content. The comment content is not displayed in the final result. So we can use it to add some notes to the flow. [ Normal start ] ================================================ FILE: shirelang/src/test/testData/parser/SingleComment.txt ================================================ ShireFile ShireFrontMatterHeaderImpl(FRONT_MATTER_HEADER) PsiElement(ShireTokenType.FRONTMATTER_START)('---') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntriesImpl(FRONT_MATTER_ENTRIES) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('name') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.QUOTE_STRING)('"Gandalf"') PsiWhiteSpace(' ') PsiComment(ShireTokenType.COMMENTS)('// comment 2') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('variables') PsiElement(ShireTokenType.COLON)(':') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.NEWLINE)('\n') ShireObjectKeyValueImpl(OBJECT_KEY_VALUE) PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) PsiElement(ShireTokenType.QUOTE_STRING)('"codepath"') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShirePatternActionImpl(PATTERN_ACTION) PatternElement(PATTERN) PsiElement(ShireTokenType.PATTERN_EXPR)('/BlogController\.java/') PsiWhiteSpace(' ') ShireActionBlockImpl(ACTION_BLOCK) PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('print') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiWhiteSpace(' ') PsiComment(ShireTokenType.COMMENTS)('// comments 3') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.FRONTMATTER_END)('---') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiComment(ShireTokenType.CONTENT_COMMENTS)('[flow]:flowable.devin') PsiElement(ShireTokenType.NEWLINE)('\n') PsiComment(ShireTokenType.CONTENT_COMMENTS)('[flow](result)') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiComment(ShireTokenType.CONTENT_COMMENTS)('[] is a symbol of comment, follow markdown syntax, and the content in [] is the comment content.') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(' The comment content is not displayed in the final result. So we can use it to add some notes to the flow.') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('[ Normal start') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)(']') ================================================ FILE: shirelang/src/test/testData/parser/VariableAccess.shire ================================================ --- when: $selection.length == 1 && $selection.first() == 'file' --- Write unit test for following ${context.lang} code. ${context.frameworkContext} #if($context.relatedClasses.length() > 0 ) Here is the relate code maybe you can use ${context.relatedClasses} #end ```$context.lang ${context.imports} ${context.sourceCode} ``` ================================================ FILE: shirelang/src/test/testData/parser/VariableAccess.txt ================================================ ShireFile ShireFrontMatterHeaderImpl(FRONT_MATTER_HEADER) PsiElement(ShireTokenType.FRONTMATTER_START)('---') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntriesImpl(FRONT_MATTER_ENTRIES) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireLifecycleIdImpl(LIFECYCLE_ID) PsiElement(ShireTokenType.when)('when') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireLogicalAndExprImpl(LOGICAL_AND_EXPR) ShireEqComparisonExprImpl(EQ_COMPARISON_EXPR) ShireRefExprImpl(REF_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(VARIABLE_START)('$') PsiElement(ShireTokenType.IDENTIFIER)('selection') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('length') PsiWhiteSpace(' ') PsiElement(ShireTokenType.==)('==') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.NUMBER)('1') PsiWhiteSpace(' ') PsiElement(ShireTokenType.&&)('&&') PsiWhiteSpace(' ') ShireEqComparisonExprImpl(EQ_COMPARISON_EXPR) ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(VARIABLE_START)('$') PsiElement(ShireTokenType.IDENTIFIER)('selection') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('first') PsiElement(ShireTokenType.()('(') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.==)('==') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.QUOTE_STRING)(''file'') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.FRONTMATTER_END)('---') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('Write unit test for following ') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('context') PsiElement(ShireTokenType..)('.') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('lang') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.TEXT_SEGMENT)(' code.') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('context') PsiElement(ShireTokenType..)('.') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('frameworkContext') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') ShireVelocityExprImpl(VELOCITY_EXPR) ShireIfExprImpl(IF_EXPR) ShireIfClauseImpl(IF_CLAUSE) PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.if)('if') PsiElement(ShireTokenType.()('(') ShireIneqComparisonExprImpl(INEQ_COMPARISON_EXPR) ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireRefExprImpl(REF_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(VARIABLE_START)('$') PsiElement(ShireTokenType.IDENTIFIER)('context') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('relatedClasses') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('length') PsiElement(ShireTokenType.()('(') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') ShireIneqComparisonOpImpl(INEQ_COMPARISON_OP) PsiElement(ShireTokenType.>)('>') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.NUMBER)('0') PsiWhiteSpace(' ') PsiElement(ShireTokenType.))(')') ShireVelocityBlockImpl(VELOCITY_BLOCK) PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('Here is the relate code maybe you can use') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('context') PsiElement(ShireTokenType..)('.') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('relatedClasses') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.end)('end') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') CodeBlockElement(CODE) PsiElement(ShireTokenType.CODE_BLOCK_START)('```') ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireRefExprImpl(REF_EXPR) ShireRefExprImpl(REF_EXPR) PsiElement(ShireTokenType.IDENTIFIER)('context') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('lang') PsiElement(ShireTokenType.NEWLINE)('\n') ASTWrapperPsiElement(CODE_CONTENTS) PsiElement(ShireTokenType.CODE_CONTENT)('${context.imports}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_CONTENT)('${context.sourceCode}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_BLOCK_END)('```') ================================================ FILE: shirelang/src/test/testData/parser/WhenCondition.shire ================================================ --- when: { $selection.length == 1 && $selection.first() == 'file' } --- ================================================ FILE: shirelang/src/test/testData/parser/WhenCondition.txt ================================================ ShireFile ShireFrontMatterHeaderImpl(FRONT_MATTER_HEADER) PsiElement(ShireTokenType.FRONTMATTER_START)('---') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntriesImpl(FRONT_MATTER_ENTRIES) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireLifecycleIdImpl(LIFECYCLE_ID) PsiElement(ShireTokenType.when)('when') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFunctionStatementImpl(FUNCTION_STATEMENT) PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireFunctionBodyImpl(FUNCTION_BODY) ShireLogicalAndExprImpl(LOGICAL_AND_EXPR) ShireEqComparisonExprImpl(EQ_COMPARISON_EXPR) ShireRefExprImpl(REF_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(VARIABLE_START)('$') PsiElement(ShireTokenType.IDENTIFIER)('selection') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('length') PsiWhiteSpace(' ') PsiElement(ShireTokenType.==)('==') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.NUMBER)('1') PsiWhiteSpace(' ') PsiElement(ShireTokenType.&&)('&&') PsiWhiteSpace(' ') ShireEqComparisonExprImpl(EQ_COMPARISON_EXPR) ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(VARIABLE_START)('$') PsiElement(ShireTokenType.IDENTIFIER)('selection') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('first') PsiElement(ShireTokenType.()('(') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.==)('==') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.QUOTE_STRING)(''file'') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.FRONTMATTER_END)('---') ================================================ FILE: shirelang/src/test/testData/realworld/AfterStreamingOnly.shire ================================================ --- name: "Search" variables: "testTemplate": /.*.kt/ { caching("disk") | splitting | embedding } afterStreaming: { searching($output) | execute("search.shire") } --- You are a coding assistant who helps the user answer questions about code in their workspace by providing a list of relevant keywords they can search for to answer the question. ================================================ FILE: shirelang/src/test/testData/realworld/AfterStreamingOnly.txt ================================================ ShireFile ShireFrontMatterHeaderImpl(FRONT_MATTER_HEADER) PsiElement(ShireTokenType.FRONTMATTER_START)('---') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntriesImpl(FRONT_MATTER_ENTRIES) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('name') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.QUOTE_STRING)('"Search"') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('variables') PsiElement(ShireTokenType.COLON)(':') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.NEWLINE)('\n') ShireObjectKeyValueImpl(OBJECT_KEY_VALUE) PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) PsiElement(ShireTokenType.QUOTE_STRING)('"testTemplate"') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShirePatternActionImpl(PATTERN_ACTION) PatternElement(PATTERN) PsiElement(ShireTokenType.PATTERN_EXPR)('/.*.kt/') PsiWhiteSpace(' ') ShireActionBlockImpl(ACTION_BLOCK) PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('caching') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"disk"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('splitting') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('embedding') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireLifecycleIdImpl(LIFECYCLE_ID) PsiElement(ShireTokenType.afterStreaming)('afterStreaming') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFunctionStatementImpl(FUNCTION_STATEMENT) PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireFunctionBodyImpl(FUNCTION_BODY) ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('searching') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('output') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('execute') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"search.shire"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.FRONTMATTER_END)('---') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('You are a coding assistant who helps the user answer questions about code in their workspace by providing a list of') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('relevant keywords they can search for to answer the question.') ================================================ FILE: shirelang/src/test/testData/realworld/Autotest.shire ================================================ --- name: "AutoTest" description: "AutoTest" interaction: AppendCursor actionLocation: ContextMenu when: $fileName.contains(".java") && $filePath.contains("src/main/java") fileName-rules: /.*Controller.java/: "When testing controller, you MUST use MockMvc and test API only." variables: "extContext": /build\.gradle\.kts/ { cat | grep("org.springframework.boot:spring-boot-starter-jdbc") | print("This project use Spring Framework")} "testTemplate": /\(.*\).java/ { case "$1" { "Controller" { cat(".shire/templates/ControllerTest.java") } "Service" { cat(".shire/templates/ServiceTest.java") } default { cat(".shire/templates/DefaultTest.java") } } } "allController": { from { PsiClass clazz /* sample */ } where { clazz.getMethods().length() > 0 } select { clazz.getMethods() } } --- Write unit test for following ${context.language} code. ${context.frameworkContext} #if($context.relatedClasses.length() > 0 ) Here is the relate code maybe you can use ${context.relatedClasses} #end #if($context.currentClassName.length() > 0 ) This is the class where the source code resides: ${context.currentClassCode} #end ${context.extContext} Here is the imports to help you understand: ${context.imports} Here is the source code to be tested: ```${context.language} ${context.selection} ``` #if($context.isNewFile) Should include package and imports. Start method test code with Markdown code block here: #else Should include package and imports. Start ${context.targetTestFileName} test code with Markdown code block here: #end $allController ================================================ FILE: shirelang/src/test/testData/realworld/Autotest.txt ================================================ ShireFile ShireFrontMatterHeaderImpl(FRONT_MATTER_HEADER) PsiElement(ShireTokenType.FRONTMATTER_START)('---') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntriesImpl(FRONT_MATTER_ENTRIES) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('name') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.QUOTE_STRING)('"AutoTest"') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('description') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.QUOTE_STRING)('"AutoTest"') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('interaction') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.IDENTIFIER)('AppendCursor') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('actionLocation') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.IDENTIFIER)('ContextMenu') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireLifecycleIdImpl(LIFECYCLE_ID) PsiElement(ShireTokenType.when)('when') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireLogicalAndExprImpl(LOGICAL_AND_EXPR) ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(VARIABLE_START)('$') PsiElement(ShireTokenType.IDENTIFIER)('fileName') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('contains') PsiElement(ShireTokenType.()('(') ShireExpressionListImpl(EXPRESSION_LIST) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.QUOTE_STRING)('".java"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.&&)('&&') PsiWhiteSpace(' ') ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(VARIABLE_START)('$') PsiElement(ShireTokenType.IDENTIFIER)('filePath') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('contains') PsiElement(ShireTokenType.()('(') ShireExpressionListImpl(EXPRESSION_LIST) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.QUOTE_STRING)('"src/main/java"') PsiElement(ShireTokenType.))(')') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('fileName-rules') PsiElement(ShireTokenType.COLON)(':') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.NEWLINE)('\n') ShireObjectKeyValueImpl(OBJECT_KEY_VALUE) PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) PatternElement(PATTERN) PsiElement(ShireTokenType.PATTERN_EXPR)('/.*Controller.java/') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.QUOTE_STRING)('"When testing controller, you MUST use MockMvc and test API only."') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('variables') PsiElement(ShireTokenType.COLON)(':') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.NEWLINE)('\n') ShireObjectKeyValueImpl(OBJECT_KEY_VALUE) PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) PsiElement(ShireTokenType.QUOTE_STRING)('"extContext"') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShirePatternActionImpl(PATTERN_ACTION) PatternElement(PATTERN) PsiElement(ShireTokenType.PATTERN_EXPR)('/build\.gradle\.kts/') PsiWhiteSpace(' ') ShireActionBlockImpl(ACTION_BLOCK) PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('cat') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireGrepFuncCall(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('grep') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"org.springframework.boot:spring-boot-starter-jdbc"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('print') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"This project use Spring Framework"') PsiElement(ShireTokenType.))(')') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) PsiElement(ShireTokenType.QUOTE_STRING)('"testTemplate"') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShirePatternActionImpl(PATTERN_ACTION) PatternElement(PATTERN) PsiElement(ShireTokenType.PATTERN_EXPR)('/\(.*\).java/') PsiWhiteSpace(' ') ShireActionBlockImpl(ACTION_BLOCK) PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireCaseBodyImpl(CASE_BODY) PsiElement(ShireTokenType.case)('case') PsiWhiteSpace(' ') PsiElement(ShireTokenType.QUOTE_STRING)('"$1"') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireCasePatternActionImpl(CASE_PATTERN_ACTION) ShireCaseConditionImpl(CASE_CONDITION) PsiElement(ShireTokenType.QUOTE_STRING)('"Controller"') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('cat') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('".shire/templates/ControllerTest.java"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireCasePatternActionImpl(CASE_PATTERN_ACTION) ShireCaseConditionImpl(CASE_CONDITION) PsiElement(ShireTokenType.QUOTE_STRING)('"Service"') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('cat') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('".shire/templates/ServiceTest.java"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireCasePatternActionImpl(CASE_PATTERN_ACTION) ShireCaseConditionImpl(CASE_CONDITION) PsiElement(ShireTokenType.default)('default') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('cat') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('".shire/templates/DefaultTest.java"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) PsiElement(ShireTokenType.QUOTE_STRING)('"allController"') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFunctionStatementImpl(FUNCTION_STATEMENT) PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireFunctionBodyImpl(FUNCTION_BODY) ShireQueryStatementImpl(QUERY_STATEMENT) ShireFromClauseImpl(FROM_CLAUSE) PsiElement(ShireTokenType.from)('from') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShirePsiElementDeclImpl(PSI_ELEMENT_DECL) ShirePsiVarDeclImpl(PSI_VAR_DECL) ShirePsiTypeImpl(PSI_TYPE) PsiElement(ShireTokenType.IDENTIFIER)('PsiClass') PsiWhiteSpace(' ') PsiElement(ShireTokenType.IDENTIFIER)('clazz') PsiWhiteSpace(' ') PsiComment(ShireTokenType.BLOCK_COMMENT)('/* sample */') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireWhereClauseImpl(WHERE_CLAUSE) PsiElement(ShireTokenType.where)('where') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireIneqComparisonExprImpl(INEQ_COMPARISON_EXPR) ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireRefExprImpl(REF_EXPR) PsiElement(ShireTokenType.IDENTIFIER)('clazz') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('getMethods') PsiElement(ShireTokenType.()('(') PsiElement(ShireTokenType.))(')') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('length') PsiElement(ShireTokenType.()('(') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') ShireIneqComparisonOpImpl(INEQ_COMPARISON_OP) PsiElement(ShireTokenType.>)('>') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.NUMBER)('0') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireSelectClauseImpl(SELECT_CLAUSE) PsiElement(ShireTokenType.select)('select') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireRefExprImpl(REF_EXPR) PsiElement(ShireTokenType.IDENTIFIER)('clazz') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('getMethods') PsiElement(ShireTokenType.()('(') PsiElement(ShireTokenType.))(')') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.FRONTMATTER_END)('---') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('Write unit test for following ') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('context') PsiElement(ShireTokenType..)('.') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.TEXT_SEGMENT)(' code.') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('context') PsiElement(ShireTokenType..)('.') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('frameworkContext') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') ShireVelocityExprImpl(VELOCITY_EXPR) ShireIfExprImpl(IF_EXPR) ShireIfClauseImpl(IF_CLAUSE) PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.if)('if') PsiElement(ShireTokenType.()('(') ShireIneqComparisonExprImpl(INEQ_COMPARISON_EXPR) ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireRefExprImpl(REF_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(VARIABLE_START)('$') PsiElement(ShireTokenType.IDENTIFIER)('context') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('relatedClasses') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('length') PsiElement(ShireTokenType.()('(') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') ShireIneqComparisonOpImpl(INEQ_COMPARISON_OP) PsiElement(ShireTokenType.>)('>') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.NUMBER)('0') PsiWhiteSpace(' ') PsiElement(ShireTokenType.))(')') ShireVelocityBlockImpl(VELOCITY_BLOCK) PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('Here is the relate code maybe you can use') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('context') PsiElement(ShireTokenType..)('.') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('relatedClasses') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.end)('end') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') ShireVelocityExprImpl(VELOCITY_EXPR) ShireIfExprImpl(IF_EXPR) ShireIfClauseImpl(IF_CLAUSE) PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.if)('if') PsiElement(ShireTokenType.()('(') ShireIneqComparisonExprImpl(INEQ_COMPARISON_EXPR) ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) ShireRefExprImpl(REF_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(VARIABLE_START)('$') PsiElement(ShireTokenType.IDENTIFIER)('context') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('currentClassName') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('length') PsiElement(ShireTokenType.()('(') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') ShireIneqComparisonOpImpl(INEQ_COMPARISON_OP) PsiElement(ShireTokenType.>)('>') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.NUMBER)('0') PsiWhiteSpace(' ') PsiElement(ShireTokenType.))(')') ShireVelocityBlockImpl(VELOCITY_BLOCK) PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('This is the class where the source code resides:') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('context') PsiElement(ShireTokenType..)('.') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('currentClassCode') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.end)('end') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('context') PsiElement(ShireTokenType..)('.') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('extContext') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('Here is the imports to help you understand:') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('context') PsiElement(ShireTokenType..)('.') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('imports') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('Here is the source code to be tested:') PsiElement(ShireTokenType.NEWLINE)('\n') CodeBlockElement(CODE) PsiElement(ShireTokenType.CODE_BLOCK_START)('```') ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVariableExprImpl(VARIABLE_EXPR) PsiElement(ShireTokenType.{)('{') ShireRefExprImpl(REF_EXPR) ShireRefExprImpl(REF_EXPR) PsiElement(ShireTokenType.IDENTIFIER)('context') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('language') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') ASTWrapperPsiElement(CODE_CONTENTS) PsiElement(ShireTokenType.CODE_CONTENT)('${context.selection}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_BLOCK_END)('```') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') ShireVelocityExprImpl(VELOCITY_EXPR) ShireIfExprImpl(IF_EXPR) ShireIfClauseImpl(IF_CLAUSE) PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.if)('if') PsiElement(ShireTokenType.()('(') ShireRefExprImpl(REF_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(VARIABLE_START)('$') PsiElement(ShireTokenType.IDENTIFIER)('context') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('isNewFile') PsiElement(ShireTokenType.))(')') ShireVelocityBlockImpl(VELOCITY_BLOCK) PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('Should include package and imports. Start method test code with Markdown code block here:') PsiElement(ShireTokenType.NEWLINE)('\n') ShireElseClauseImpl(ELSE_CLAUSE) PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.else)('else') ShireVelocityBlockImpl(VELOCITY_BLOCK) PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('Should include package and imports. Start ') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVarAccessImpl(VAR_ACCESS) PsiElement(ShireTokenType.{)('{') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('context') PsiElement(ShireTokenType..)('.') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('targetTestFileName') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.TEXT_SEGMENT)(' test code with Markdown code block here:') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.#)('#') PsiElement(ShireTokenType.end)('end') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('allController') ================================================ FILE: shirelang/src/test/testData/realworld/ContentTee.shire ================================================ --- name: "Context Variable" description: "Here is a description of the action." interaction: RunPanel variables: "currentCode": /HobbitHole\.kt/ { cat } "testCode": /ShireCompileTest\.kt/ { cat } "actionLocation": /ShireActionLocation\.kt/ { cat } onStreamingEnd: { append($actionLocation) | saveFile("docs/shire/shire-hobbit-hole.md") } --- 我有一份用户手册写得不好,需要你从用户容易阅读的角度,重新写一份。 根据如下的代码用例、文档,编写对应的 HobbitHole 相关信息的 markdown 文档。 现有代码: $currentCode 代码用例如下: $testCode 要求: 1. 尽详细介绍 HobbitHole 的相关信息和示例。 2. 请按现有的文档 Heading 方式编写,并去除非必要的代码。 ================================================ FILE: shirelang/src/test/testData/realworld/ContentTee.txt ================================================ ShireFile ShireFrontMatterHeaderImpl(FRONT_MATTER_HEADER) PsiElement(ShireTokenType.FRONTMATTER_START)('---') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntriesImpl(FRONT_MATTER_ENTRIES) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('name') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.QUOTE_STRING)('"Context Variable"') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('description') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.QUOTE_STRING)('"Here is a description of the action."') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('interaction') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.IDENTIFIER)('RunPanel') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('variables') PsiElement(ShireTokenType.COLON)(':') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.NEWLINE)('\n') ShireObjectKeyValueImpl(OBJECT_KEY_VALUE) PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) PsiElement(ShireTokenType.QUOTE_STRING)('"currentCode"') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShirePatternActionImpl(PATTERN_ACTION) PatternElement(PATTERN) PsiElement(ShireTokenType.PATTERN_EXPR)('/HobbitHole\.kt/') PsiWhiteSpace(' ') ShireActionBlockImpl(ACTION_BLOCK) PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('cat') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) PsiElement(ShireTokenType.QUOTE_STRING)('"testCode"') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShirePatternActionImpl(PATTERN_ACTION) PatternElement(PATTERN) PsiElement(ShireTokenType.PATTERN_EXPR)('/ShireCompileTest\.kt/') PsiWhiteSpace(' ') ShireActionBlockImpl(ACTION_BLOCK) PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('cat') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) PsiElement(ShireTokenType.QUOTE_STRING)('"actionLocation"') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShirePatternActionImpl(PATTERN_ACTION) PatternElement(PATTERN) PsiElement(ShireTokenType.PATTERN_EXPR)('/ShireActionLocation\.kt/') PsiWhiteSpace(' ') ShireActionBlockImpl(ACTION_BLOCK) PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('cat') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireLifecycleIdImpl(LIFECYCLE_ID) PsiElement(ShireTokenType.onStreamingEnd)('onStreamingEnd') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFunctionStatementImpl(FUNCTION_STATEMENT) PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireFunctionBodyImpl(FUNCTION_BODY) ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('append') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('actionLocation') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('saveFile') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"docs/shire/shire-hobbit-hole.md"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.FRONTMATTER_END)('---') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('我有一份用户手册写得不好,需要你从用户容易阅读的角度,重新写一份。') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('根据如下的代码用例、文档,编写对应的 HobbitHole 相关信息的 markdown 文档。') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('现有代码:') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('currentCode') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('代码用例如下:') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('testCode') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('要求:') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('1. 尽详细介绍 HobbitHole 的相关信息和示例。') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('2. 请按现有的文档 Heading 方式编写,并去除非必要的代码。') ================================================ FILE: shirelang/src/test/testData/realworld/LifeCycle.shire ================================================ --- when: "xxx" variables: "testTemplate": /\(.*\).java/ { case "$1" { "Controller" { cat(".shire/templates/ControllerTest.java") } "Service" { cat(".shire/templates/ServiceTest.java") } default { cat(".shire/templates/DefaultTest.java") } } } onStreaming: { /* functions */ } onStreamingEnd: { parseCode("json") | verifyCode("json") | runCode("json") } afterStreaming: { condition { "variable-success" { $selection.length > 1 } "jsonpath-success" { jsonpath("/bookstore/book[price>35]") } } case condition { "variable-sucesss" { done } "jsonpath-success" { task() } default { task() } } } --- onStreamingDone: - Array of processor - Object flow ```markdown - //bookstore/book[price>35] - $.phoneNumbers[:1].type - Regex ``` ================================================ FILE: shirelang/src/test/testData/realworld/LifeCycle.txt ================================================ ShireFile ShireFrontMatterHeaderImpl(FRONT_MATTER_HEADER) PsiElement(ShireTokenType.FRONTMATTER_START)('---') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntriesImpl(FRONT_MATTER_ENTRIES) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireLifecycleIdImpl(LIFECYCLE_ID) PsiElement(ShireTokenType.when)('when') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.QUOTE_STRING)('"xxx"') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('variables') PsiElement(ShireTokenType.COLON)(':') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.NEWLINE)('\n') ShireObjectKeyValueImpl(OBJECT_KEY_VALUE) PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) PsiElement(ShireTokenType.QUOTE_STRING)('"testTemplate"') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShirePatternActionImpl(PATTERN_ACTION) PatternElement(PATTERN) PsiElement(ShireTokenType.PATTERN_EXPR)('/\(.*\).java/') PsiWhiteSpace(' ') ShireActionBlockImpl(ACTION_BLOCK) PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireCaseBodyImpl(CASE_BODY) PsiElement(ShireTokenType.case)('case') PsiWhiteSpace(' ') PsiElement(ShireTokenType.QUOTE_STRING)('"$1"') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireCasePatternActionImpl(CASE_PATTERN_ACTION) ShireCaseConditionImpl(CASE_CONDITION) PsiElement(ShireTokenType.QUOTE_STRING)('"Controller"') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('cat') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('".shire/templates/ControllerTest.java"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireCasePatternActionImpl(CASE_PATTERN_ACTION) ShireCaseConditionImpl(CASE_CONDITION) PsiElement(ShireTokenType.QUOTE_STRING)('"Service"') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('cat') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('".shire/templates/ServiceTest.java"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireCasePatternActionImpl(CASE_PATTERN_ACTION) ShireCaseConditionImpl(CASE_CONDITION) PsiElement(ShireTokenType.default)('default') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('cat') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('".shire/templates/DefaultTest.java"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireLifecycleIdImpl(LIFECYCLE_ID) PsiElement(ShireTokenType.onStreaming)('onStreaming') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFunctionStatementImpl(FUNCTION_STATEMENT) PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') PsiComment(ShireTokenType.BLOCK_COMMENT)('/* functions */') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireLifecycleIdImpl(LIFECYCLE_ID) PsiElement(ShireTokenType.onStreamingEnd)('onStreamingEnd') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFunctionStatementImpl(FUNCTION_STATEMENT) PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireFunctionBodyImpl(FUNCTION_BODY) ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('parseCode') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"json"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('verifyCode') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"json"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('runCode') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"json"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireLifecycleIdImpl(LIFECYCLE_ID) PsiElement(ShireTokenType.afterStreaming)('afterStreaming') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFunctionStatementImpl(FUNCTION_STATEMENT) PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireFunctionBodyImpl(FUNCTION_BODY) ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireCaseBodyImpl(CASE_BODY) ShireConditionFlagImpl(CONDITION_FLAG) PsiElement(ShireTokenType.condition)('condition') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireConditionStatementImpl(CONDITION_STATEMENT) ShireCaseConditionImpl(CASE_CONDITION) PsiElement(ShireTokenType.QUOTE_STRING)('"variable-success"') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireIneqComparisonExprImpl(INEQ_COMPARISON_EXPR) ShireRefExprImpl(REF_EXPR) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(VARIABLE_START)('$') PsiElement(ShireTokenType.IDENTIFIER)('selection') PsiElement(ShireTokenType..)('.') PsiElement(ShireTokenType.IDENTIFIER)('length') PsiWhiteSpace(' ') ShireIneqComparisonOpImpl(INEQ_COMPARISON_OP) PsiElement(ShireTokenType.>)('>') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.NUMBER)('1') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireConditionStatementImpl(CONDITION_STATEMENT) ShireCaseConditionImpl(CASE_CONDITION) PsiElement(ShireTokenType.QUOTE_STRING)('"jsonpath-success"') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireCallExprImpl(CALL_EXPR) ShireRefExprImpl(REF_EXPR) PsiElement(ShireTokenType.IDENTIFIER)('jsonpath') PsiElement(ShireTokenType.()('(') ShireExpressionListImpl(EXPRESSION_LIST) ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.QUOTE_STRING)('"/bookstore/book[price>35]"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.case)('case') PsiWhiteSpace(' ') PsiElement(ShireTokenType.condition)('condition') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireCasePatternActionImpl(CASE_PATTERN_ACTION) ShireCaseConditionImpl(CASE_CONDITION) PsiElement(ShireTokenType.QUOTE_STRING)('"variable-sucesss"') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('done') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireCasePatternActionImpl(CASE_PATTERN_ACTION) ShireCaseConditionImpl(CASE_CONDITION) PsiElement(ShireTokenType.QUOTE_STRING)('"jsonpath-success"') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('task') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') ShireCasePatternActionImpl(CASE_PATTERN_ACTION) ShireCaseConditionImpl(CASE_CONDITION) PsiElement(ShireTokenType.default)('default') PsiWhiteSpace(' ') PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('task') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.FRONTMATTER_END)('---') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('onStreamingDone:') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('- Array of processor') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('- Object flow') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') CodeBlockElement(CODE) PsiElement(ShireTokenType.CODE_BLOCK_START)('```') ShireLanguageIdImpl(LANGUAGE_ID) PsiElement(ShireTokenType.LANGUAGE_IDENTIFIER)('markdown') PsiElement(ShireTokenType.NEWLINE)('\n') ASTWrapperPsiElement(CODE_CONTENTS) PsiElement(ShireTokenType.CODE_CONTENT)('- //bookstore/book[price>35]') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_CONTENT)('- $.phoneNumbers[:1].type') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_CONTENT)('- Regex') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.CODE_BLOCK_END)('```') ================================================ FILE: shirelang/src/test/testData/realworld/LoginCommit.shire ================================================ --- name: "Commit message" enabled: false description: "生成提交信息" interaction: AppendCursor actionLocation: CommitMenu variables: "story": /any/ { thread(".shire/fetch-jira.sh") | jsonpath("$.data[*].Story") } --- 请遵循常规提交规范,例如: - fix(authentication): 修复密码正则表达式模式问题 #$story ================================================ FILE: shirelang/src/test/testData/realworld/LoginCommit.txt ================================================ ShireFile ShireFrontMatterHeaderImpl(FRONT_MATTER_HEADER) PsiElement(ShireTokenType.FRONTMATTER_START)('---') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntriesImpl(FRONT_MATTER_ENTRIES) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('name') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.QUOTE_STRING)('"Commit message"') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('enabled') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.BOOLEAN)('false') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('description') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.QUOTE_STRING)('"生成提交信息"') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('interaction') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.IDENTIFIER)('AppendCursor') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('actionLocation') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.IDENTIFIER)('CommitMenu') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('variables') PsiElement(ShireTokenType.COLON)(':') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.NEWLINE)('\n') ShireObjectKeyValueImpl(OBJECT_KEY_VALUE) PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) PsiElement(ShireTokenType.QUOTE_STRING)('"story"') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShirePatternActionImpl(PATTERN_ACTION) PatternElement(PATTERN) PsiElement(ShireTokenType.PATTERN_EXPR)('/any/') PsiWhiteSpace(' ') ShireActionBlockImpl(ACTION_BLOCK) PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('thread') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('".shire/fetch-jira.sh"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('jsonpath') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"$.data[*].Story"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.FRONTMATTER_END)('---') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('请遵循常规提交规范,例如:') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('- fix(authentication): 修复密码正则表达式模式问题 ') ShireUsedImpl(USED) PsiElement(ShireTokenType.#)('#') ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireRefExprImpl(REF_EXPR) PsiElement(ShireTokenType.IDENTIFIER)('story') ================================================ FILE: shirelang/src/test/testData/realworld/OnPaste.shire ================================================ --- name: "PasteMaster" actionLocation: ContextMenu enabled: false --- [interaction: OnPaste] 代码生成。根据当前的代码上下文(光标前后),对用户粘贴的代码,生成新的代码。 光标前的代码: $beforeCursor 光标后的代码: $afterCursor 用户粘贴的代码: ================================================ FILE: shirelang/src/test/testData/realworld/OnPaste.txt ================================================ ShireFile ShireFrontMatterHeaderImpl(FRONT_MATTER_HEADER) PsiElement(ShireTokenType.FRONTMATTER_START)('---') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntriesImpl(FRONT_MATTER_ENTRIES) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('name') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.QUOTE_STRING)('"PasteMaster"') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('actionLocation') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.IDENTIFIER)('ContextMenu') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('enabled') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.BOOLEAN)('false') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.FRONTMATTER_END)('---') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiComment(ShireTokenType.CONTENT_COMMENTS)('[interaction: OnPaste]') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('代码生成。根据当前的代码上下文(光标前后),对用户粘贴的代码,生成新的代码。') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('光标前的代码:') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('beforeCursor') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('光标后的代码:') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('afterCursor') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('用户粘贴的代码:') ================================================ FILE: shirelang/src/test/testData/realworld/OutputInVariable.shire ================================================ --- name: "代码修改" variables: "controllerCode": /any/ { cat($output) } onStreamingEnd: { parseCode | saveFile($output) } --- $output ================================================ FILE: shirelang/src/test/testData/realworld/OutputInVariable.txt ================================================ ShireFile ShireFrontMatterHeaderImpl(FRONT_MATTER_HEADER) PsiElement(ShireTokenType.FRONTMATTER_START)('---') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntriesImpl(FRONT_MATTER_ENTRIES) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('name') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.QUOTE_STRING)('"代码修改"') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('variables') PsiElement(ShireTokenType.COLON)(':') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.NEWLINE)('\n') ShireObjectKeyValueImpl(OBJECT_KEY_VALUE) PsiElement(ShireTokenType.INDENT)(' ') ShireKeyValueImpl(KEY_VALUE) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) PsiElement(ShireTokenType.QUOTE_STRING)('"controllerCode"') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShirePatternActionImpl(PATTERN_ACTION) PatternElement(PATTERN) PsiElement(ShireTokenType.PATTERN_EXPR)('/any/') PsiWhiteSpace(' ') ShireActionBlockImpl(ACTION_BLOCK) PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('cat') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('output') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireLifecycleIdImpl(LIFECYCLE_ID) PsiElement(ShireTokenType.onStreamingEnd)('onStreamingEnd') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFunctionStatementImpl(FUNCTION_STATEMENT) PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireFunctionBodyImpl(FUNCTION_BODY) ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('parseCode') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('saveFile') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('output') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.FRONTMATTER_END)('---') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') ShireUsedImpl(USED) ShireVariableStartImpl(VARIABLE_START) PsiElement(VARIABLE_START)('$') ShireVariableIdImpl(VARIABLE_ID) PsiElement(ShireTokenType.IDENTIFIER)('output') ================================================ FILE: shirelang/src/test/testData/realworld/WhenAfterStreaming.shire ================================================ --- interaction: AppendCursor onStreamingEnd: { parseCode("json") | verifyCode("json") | runCode("json") } when: "$selection.length == 1 && $selection.first() == 'file'" --- hi Hello! How can I assist you today? ================================================ FILE: shirelang/src/test/testData/realworld/WhenAfterStreaming.txt ================================================ ShireFile ShireFrontMatterHeaderImpl(FRONT_MATTER_HEADER) PsiElement(ShireTokenType.FRONTMATTER_START)('---') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntriesImpl(FRONT_MATTER_ENTRIES) ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireFrontMatterKeyImpl(FRONT_MATTER_KEY) ShireFrontMatterIdImpl(FRONT_MATTER_ID) PsiElement(ShireTokenType.IDENTIFIER)('interaction') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFrontMatterValueImpl(FRONT_MATTER_VALUE) PsiElement(ShireTokenType.IDENTIFIER)('AppendCursor') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireLifecycleIdImpl(LIFECYCLE_ID) PsiElement(ShireTokenType.onStreamingEnd)('onStreamingEnd') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireFunctionStatementImpl(FUNCTION_STATEMENT) PsiElement(ShireTokenType.{)('{') PsiWhiteSpace(' ') ShireFunctionBodyImpl(FUNCTION_BODY) ShireActionBodyImpl(ACTION_BODY) ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('parseCode') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"json"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('verifyCode') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"json"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.|)('|') PsiWhiteSpace(' ') ShireActionExprImpl(ACTION_EXPR) ShireFuncCallImpl(FUNC_CALL) ShireFuncNameImpl(FUNC_NAME) PsiElement(ShireTokenType.IDENTIFIER)('runCode') PsiElement(ShireTokenType.()('(') ShirePipelineArgsImpl(PIPELINE_ARGS) ShirePipelineArgImpl(PIPELINE_ARG) PsiElement(ShireTokenType.QUOTE_STRING)('"json"') PsiElement(ShireTokenType.))(')') PsiWhiteSpace(' ') PsiElement(ShireTokenType.})('}') PsiElement(ShireTokenType.NEWLINE)('\n') ShireFrontMatterEntryImpl(FRONT_MATTER_ENTRY) ShireLifecycleIdImpl(LIFECYCLE_ID) PsiElement(ShireTokenType.when)('when') PsiElement(ShireTokenType.COLON)(':') PsiWhiteSpace(' ') ShireLiteralExprImpl(LITERAL_EXPR) PsiElement(ShireTokenType.QUOTE_STRING)('"$selection.length == 1 && $selection.first() == 'file'"') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.FRONTMATTER_END)('---') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('hi') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.NEWLINE)('\n') PsiElement(ShireTokenType.TEXT_SEGMENT)('Hello! How can I assist you today?') ================================================ FILE: src/description.html ================================================

Shire - AI Coding Agent Language

Shire offers a straightforward AI Coding & Agents Language that enables communication between an LLM and control IDE for automated programming.

Shire syntax Overview

Shire Cheatsheet

Shire Data Architecture

Shire Data Architecture

Shire code example


---
name: "AutoTest"
description: "Auto generate test in ContextMenu with use selection code"
actionLocation: ContextMenu
interaction: AppendCursor
when: $fileName.contains(".java") && $filePath.contains("src/main/java")
---

@ext-context.autotest

Write unit test for following ${context.language} code.

${context.frameworkContext}

Here is the source code to be tested:

/file:src/main/kotlin/com/phodal/blog/controller/UserController.kt

================================================ FILE: src/main/kotlin/com/phodal/shire/ShireIdeaIcons.kt ================================================ package com.phodal.shire import com.intellij.openapi.util.IconLoader import javax.swing.Icon object ShireIdeaIcons { @JvmField val Default: Icon = IconLoader.getIcon("/icons/shire-mkt.svg", ShireIdeaIcons::class.java) @JvmField val Download: Icon = IconLoader.getIcon("/icons/download.svg", ShireIdeaIcons::class.java) } ================================================ FILE: src/main/kotlin/com/phodal/shire/ShireMainBundle.kt ================================================ package com.phodal.shire import com.intellij.DynamicBundle import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.PropertyKey @NonNls private const val BUNDLE = "messages.ShireMainBundle" object ShireMainBundle : DynamicBundle(BUNDLE) { @JvmStatic fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = getMessage(key, *params) @Suppress("unused") @JvmStatic fun messagePointer(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = getLazyMessage(key, *params) } ================================================ FILE: src/main/kotlin/com/phodal/shire/inline/ShireGutterIconRenderer.kt ================================================ package com.phodal.shire.inline import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.editor.markup.GutterIconRenderer import com.phodal.shire.ShireIdeaIcons import javax.swing.Icon class ShireGutterIconRenderer( val line: Int, val onClick: () -> Unit, ) : GutterIconRenderer() { override fun getClickAction(): AnAction { return object : AnAction() { override fun actionPerformed(e: AnActionEvent) { onClick() } } } override fun getIcon(): Icon = ShireIdeaIcons.Default override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as ShireGutterIconRenderer if (line != other.line) return false if (onClick != other.onClick) return false return true } override fun hashCode(): Int { var result = line result = 31 * result + onClick.hashCode() return result } } ================================================ FILE: src/main/kotlin/com/phodal/shire/inline/ShireInlineChatPanel.kt ================================================ package com.phodal.shire.inline import com.intellij.icons.AllIcons import com.intellij.ide.KeyboardAwareFocusOwner import com.intellij.openapi.Disposable import com.intellij.openapi.actionSystem.Presentation import com.intellij.openapi.actionSystem.impl.ActionButton import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.invokeLater import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.EditorCustomElementRenderer import com.intellij.openapi.editor.Inlay import com.intellij.openapi.editor.markup.TextAttributes import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.wm.IdeFocusManager import com.intellij.ui.JBColor import com.intellij.ui.RoundedLineBorder import com.intellij.ui.components.JBTextArea import com.intellij.util.ui.JBUI.CurrentTheme import com.phodal.shirecore.ShireCoroutineScope import com.phodal.shirecore.llm.LlmProvider import com.phodal.shirecore.provider.streaming.OnStreamingService import com.phodal.shirecore.runner.console.cancelHandler import com.phodal.shirecore.ui.ShirePanelView import com.phodal.shirecore.ui.input.ShireLineBorder import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.cancellable import kotlinx.coroutines.launch import java.awt.* import java.awt.event.* import java.awt.geom.Rectangle2D import javax.swing.* class ShireInlineChatPanel(val editor: Editor) : JPanel(GridBagLayout()), EditorCustomElementRenderer, Disposable { var inlay: Inlay<*>? = null val inputPanel = ShireInlineChatInputPanel(this, onSubmit = { input -> this.centerPanel.isVisible = true val project = editor.project!! val prompt = ShireInlineChatService.getInstance().prompt(project, input) val flow: Flow? = LlmProvider.provider(project)?.stream(prompt, "", false) val panelView = ShirePanelView(project, showInput = false) panelView.minimumSize = Dimension(800, 40) setContent(panelView) ShireCoroutineScope.scope(project).launch { val suggestion = StringBuilder() panelView.onStart() flow?.cancelHandler { panelView.handleCancel = it }?.cancellable()?.collect { char -> suggestion.append(char) invokeLater { panelView.onUpdate(suggestion.toString()) panelView.resize() } } panelView.resize() panelView.onFinish(suggestion.toString()) } }) private var centerPanel: JPanel = JPanel(BorderLayout()) private var container: Container? = null init { border = BorderFactory.createCompoundBorder( BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder(0, 0, 0, 0), RoundedLineBorder(JBColor.LIGHT_GRAY, 12, 1) ), BorderFactory.createCompoundBorder( ShireLineBorder(JBColor.border(), 1, true, 8), BorderFactory.createMatteBorder(6, 8, 6, 8, JBColor.border()) ) ) isOpaque = false val c = GridBagConstraints() c.gridx = 0 c.gridy = 0 c.weightx = 1.0 c.fill = GridBagConstraints.HORIZONTAL add(this.inputPanel, c) this.centerPanel = JPanel(BorderLayout()).apply { isOpaque = false this.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { IdeFocusManager.getInstance(editor.project).requestFocus(inputPanel.getInputComponent(), true) } }) } c.gridx = 0 c.gridy = 1 c.fill = GridBagConstraints.BOTH add(this.centerPanel, c) this.inAllChildren { child -> child.addComponentListener(object : ComponentAdapter() { override fun componentResized(e: ComponentEvent) { this@ShireInlineChatPanel.redraw() } }) } } override fun calcWidthInPixels(inlay: Inlay<*>): Int = size.width override fun calcHeightInPixels(inlay: Inlay<*>): Int = size.height private fun redraw() { ApplicationManager.getApplication().invokeLater { if (this.size.height != this.getMinimumSize().height) { this.size = Dimension(800, this.getMinimumSize().height) this.inlay?.update() this.revalidate() this.repaint() } } } fun createInlay(offset: Int) { inlay = editor.inlayModel.addBlockElement(offset, false, true, 1, this) } fun setInlineContainer(container: Container) { this.container = container } override fun paint(inlay: Inlay<*>, g: Graphics2D, targetRegion: Rectangle2D, textAttributes: TextAttributes) { bounds = inlay.bounds ?: return revalidate() repaint() } private fun setContent(content: JComponent) { content.isOpaque = true ApplicationManager.getApplication().invokeLater { if (!this.centerPanel.isVisible) { this.centerPanel.isVisible = true } this.centerPanel.removeAll() this.centerPanel.add(content, BorderLayout.CENTER) this@ShireInlineChatPanel.redraw() } } fun JComponent.inAllChildren(callback: (JComponent) -> Unit) { callback(this) components.forEach { component -> if (component is JComponent) { component.inAllChildren(callback) } } } override fun dispose() { inputPanel.dispose() inlay?.dispose() inlay = null } } class ShireInlineChatInputPanel( val shireInlineChatPanel: ShireInlineChatPanel, val onSubmit: (String) -> Unit, ) : JPanel(GridBagLayout()), Disposable { private val textArea: JBTextArea init { layout = BorderLayout() textArea = object: JBTextArea(), KeyboardAwareFocusOwner { override fun skipKeyEventDispatcher(event: KeyEvent): Boolean = true init { isOpaque = false isFocusable = true lineWrap = true wrapStyleWord = true border = BorderFactory.createEmptyBorder(8, 6, 8, 6) } } border = ShireLineBorder(CurrentTheme.Focus.focusColor(), 1, true, 8) // escape to close textArea.actionMap.put("escapeAction", object : AbstractAction() { override fun actionPerformed(e: ActionEvent) { ShireInlineChatService.getInstance().closeInlineChat(shireInlineChatPanel.editor) } }) textArea.inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "escapeAction") // submit with enter textArea.actionMap.put("enterAction", object : AbstractAction() { override fun actionPerformed(e: ActionEvent) { submit() } }) textArea.inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "enterAction") // newLine with shift + enter textArea.actionMap.put("newlineAction", object : AbstractAction() { override fun actionPerformed(e: ActionEvent) { textArea.append("\n") } }) textArea.inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.SHIFT_DOWN_MASK), "newlineAction") add(textArea) val submitPresentation = Presentation("Submit") submitPresentation.icon = AllIcons.Actions.Execute val submitButton = ActionButton( DumbAwareAction.create { submit() }, submitPresentation, "", Dimension(40, 20) ) add(submitButton, BorderLayout.EAST) } private fun submit() { val trimText = textArea.text.trim() textArea.text = "" onSubmit(trimText) } fun getInputComponent(): Component = textArea override fun dispose() { } } ================================================ FILE: src/main/kotlin/com/phodal/shire/inline/ShireInlineChatProvider.kt ================================================ package com.phodal.shire.inline import com.intellij.openapi.Disposable import com.intellij.openapi.components.Service import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.EditorFactory import com.intellij.openapi.editor.event.EditorFactoryEvent import com.intellij.openapi.editor.event.EditorFactoryListener import com.intellij.openapi.editor.event.SelectionEvent import com.intellij.openapi.editor.event.SelectionListener import com.intellij.openapi.editor.markup.RangeHighlighter import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.fileEditor.TextEditor import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer import com.phodal.shirecore.provider.ide.InlineChatProvider import io.ktor.util.collections.* data class GutterIconData( val line: Int, val highlighter: RangeHighlighter, ) class ShireInlineChatProvider: InlineChatProvider { override fun addListener(project: Project) { EditorGutterHandler.getInstance(project).listen() } override fun removeListener(project: Project) { EditorGutterHandler.getInstance(project).dispose() } } @Service(Service.Level.PROJECT) class EditorGutterHandler(private val project: Project) : Disposable { val gutterIcons: ConcurrentMap = ConcurrentMap() private var disposable: Disposable? = null fun listen() { addEditorFactoryListener() } fun addEditorFactoryListener() { if (disposable != null) { return } disposable = Disposer.newDisposable().apply { EditorFactory.getInstance().addEditorFactoryListener(object : EditorFactoryListener { override fun editorCreated(event: EditorFactoryEvent) { onEditorCreated(event.editor, this@apply) } }, this) FileEditorManager.getInstance(project).allEditors.mapNotNull { it as? TextEditor }.forEach { updateGutterIconWithSelection(it.editor) onEditorCreated(it.editor, this@apply) } } } fun onEditorCreated(editor: Editor, disposable: Disposable) { editor.selectionModel.addSelectionListener(object : SelectionListener { override fun selectionChanged(e: SelectionEvent) { if (e.editor.project != project) return updateGutterIconWithSelection(editor) } }, disposable) } fun updateGutterIconWithSelection(editor: Editor) { if (!editor.selectionModel.hasSelection()) { gutterIcons[editor]?.let { removeGutterIcon(editor, it.highlighter) } return } val selectionStart = editor.document.getLineNumber(editor.selectionModel.selectionStart) if (selectionStart >= 0 && selectionStart < editor.document.lineCount) { val gutterIconInfo = gutterIcons[editor] if (gutterIconInfo?.line != selectionStart) { addGutterIcon(editor, selectionStart) } } } fun addGutterIcon(editor: Editor, line: Int) { val iconData: GutterIconData? = gutterIcons[editor] if (iconData != null) { removeGutterIcon(editor, iconData.highlighter) } FileDocumentManager.getInstance().getFile(editor.document) ?: return val highlighter = editor.markupModel.addLineHighlighter(null, line, 0) highlighter.gutterIconRenderer = ShireGutterIconRenderer(line, onClick = { ShireInlineChatService.getInstance().showInlineChat(editor) }) gutterIcons[editor] = GutterIconData(line, highlighter) } fun removeGutterIcon(editor: Editor, highlighter: RangeHighlighter? = null) { if (highlighter != null) editor.markupModel.removeHighlighter(highlighter) gutterIcons.remove(editor) } override fun dispose() { gutterIcons.forEach { removeGutterIcon(it.key, it.value?.highlighter) } disposable?.let { Disposer.dispose(it) } disposable = null } companion object { fun getInstance(project: Project): EditorGutterHandler { return project.getService(EditorGutterHandler::class.java) } } } ================================================ FILE: src/main/kotlin/com/phodal/shire/inline/ShireInlineChatService.kt ================================================ package com.phodal.shire.inline import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.Service import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.openapi.wm.IdeFocusManager import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirecore.provider.ide.ShirePromptBuilder import java.util.concurrent.ConcurrentHashMap @Service(Service.Level.APP) class ShireInlineChatService : Disposable { private val allChats: ConcurrentHashMap = ConcurrentHashMap() fun showInlineChat(editor: Editor) { var canShowInlineChat = true if (allChats.containsKey(editor.virtualFile.url)) { val chatPanel: ShireInlineChatPanel = this.allChats[editor.virtualFile.url]!! canShowInlineChat = chatPanel.inlay?.offset != editor.caretModel.primaryCaret.offset closeInlineChat(editor) } if (canShowInlineChat) { if (editor.component is ShireInlineChatPanel) return val panel = ShireInlineChatPanel(editor) editor.contentComponent.add(panel) panel.setInlineContainer(editor.contentComponent) val offset = if (editor.selectionModel.hasSelection()) { editor.selectionModel.selectionStart } else { editor.caretModel.primaryCaret.offset } panel.createInlay(offset) IdeFocusManager.getInstance(editor.project).requestFocus(panel.inputPanel.getInputComponent(), true) allChats[editor.virtualFile.url] = panel } } override fun dispose() { allChats.values.forEach { closeInlineChat(it.editor) } allChats.clear() } fun closeInlineChat(editor: Editor) { val chatPanel = this.allChats[editor.virtualFile.url] ?: return chatPanel.dispose() editor.contentComponent.remove(chatPanel) editor.contentComponent.revalidate() editor.contentComponent.repaint() allChats.remove(editor.virtualFile.url) } fun prompt(project: Project, prompt: String): String { return ShirePromptBuilder.provide()?.build(project, ShireActionLocation.INLINE_CHAT.name, prompt) ?: prompt } companion object { fun getInstance(): ShireInlineChatService { return ApplicationManager.getApplication().getService(ShireInlineChatService::class.java) } } } ================================================ FILE: src/main/kotlin/com/phodal/shire/llm/OpenAILikeProvider.kt ================================================ package com.phodal.shire.llm import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.phodal.shirecore.sse.CustomSSEHandler import com.phodal.shirecore.sse.appendCustomHeaders import com.phodal.shirecore.sse.updateCustomFormat import com.phodal.shire.settings.ShireSettingsState import com.phodal.shirecore.llm.* import kotlinx.coroutines.flow.Flow import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import java.time.Duration class OpenAILikeProvider : CustomSSEHandler(), LlmProvider { private val timeout = Duration.ofSeconds(defaultTimeout) private var modelName: String = ShireSettingsState.getInstance().modelName private var temperature: Float = ShireSettingsState.getInstance().temperature private var maxTokens: Int? = null private var key: String = ShireSettingsState.getInstance().apiToken private var url: String = ShireSettingsState.getInstance().apiHost.ifEmpty { "https://api.openai.com/v1/chat/completions" } private val messages: MutableList = ArrayList() private var client = OkHttpClient() override val requestFormat: String get() = if (maxTokens != null) { """{ "customFields": {"model": "$modelName", "temperature": $temperature, "max_tokens": $maxTokens, "stream": true} }""" } else { """{ "customFields": {"model": "$modelName", "temperature": $temperature, "stream": true} }""" } override val responseFormat: String get() = "\$.choices[0].delta.content" override var project: Project? = null override fun clearMessage() = messages.clear() override fun isApplicable(project: Project, llmConfig: LlmConfig?): Boolean { this.project = project if (llmConfig != null) return llmConfig.checkAvailable() // If the configRunLlm configuration exists, it is also available if (configRunLlm().let { it?.checkAvailable() == true }) return true // dynamic check for the API key and model name return ShireSettingsState.getInstance().apiToken.isNotEmpty() && ShireSettingsState.getInstance().modelName.isNotEmpty() } private fun configFromState() { modelName = ShireSettingsState.getInstance().modelName temperature = ShireSettingsState.getInstance().temperature key = ShireSettingsState.getInstance().apiToken url = ShireSettingsState.getInstance().apiHost.ifEmpty { "https://api.openai.com/v1/chat/completions" } } override fun stream( promptText: String, systemPrompt: String, keepHistory: Boolean, llmConfig: LlmConfig? ): Flow { (llmConfig ?: configRunLlm()).let { if (it != null && it.checkAvailable()) { modelName = it.model temperature = it.temperature.toFloat() key = it.apiKey url = it.apiBase maxTokens = it.maxTokens } else { configFromState() } } if (!keepHistory) { clearMessage() } messages += ChatMessage(ChatRole.user, promptText) val customRequest = CustomRequest(messages) val requestContent = customRequest.updateCustomFormat(requestFormat) val body = requestContent.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()) val builder = Request.Builder() if (key.isNotEmpty()) { builder.addHeader("Authorization", "Bearer $key") builder.addHeader("Content-Type", "application/json") } builder.appendCustomHeaders(requestFormat) logger().info("Requesting form: $requestContent $body") client = client.newBuilder().readTimeout(timeout).build() val call = client.newCall(builder.url(url).post(body).build()) val copyMessages = messages.toMutableList() if (!keepHistory) { clearMessage() return streamSSE(call, copyMessages, project!!) } else { return streamSSE(call, messages, project!!) } } } ================================================ FILE: src/main/kotlin/com/phodal/shire/marketplace/ShireToolWindowFactory.kt ================================================ package com.phodal.shire.marketplace import com.intellij.openapi.project.DumbAware import com.intellij.openapi.project.Project import com.intellij.openapi.wm.ToolWindow import com.intellij.openapi.wm.ToolWindowFactory import com.phodal.shire.marketplace.ui.MarketplaceView class ShireToolWindowFactory : ToolWindowFactory, DumbAware { override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { project.getService(MarketplaceView::class.java).initToolWindow(toolWindow) } companion object { const val id = "ShireToolWindow" } } ================================================ FILE: src/main/kotlin/com/phodal/shire/marketplace/model/ShirePackage.kt ================================================ package com.phodal.shire.marketplace.model data class ShirePackage( val title: String, val description: String, val link: String, val installCmd: String = "", val featured: Boolean = false ) { // for Jackson, do not remove constructor() : this("", "", "") } ================================================ FILE: src/main/kotlin/com/phodal/shire/marketplace/ui/IconButtonTableCellEditor.kt ================================================ /* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.phodal.shire.marketplace.ui import com.google.common.annotations.VisibleForTesting import java.awt.Component import javax.swing.AbstractCellEditor import javax.swing.Icon import javax.swing.JTable import javax.swing.event.ChangeEvent import javax.swing.table.TableCellEditor open class IconButtonTableCellEditor(value: Any, icon: Icon, tooltipText: String) : AbstractCellEditor(), TableCellEditor { protected var myValue: Any = value protected val myButton: IconButton = IconButton(icon).apply { setOpaque(true) setToolTipText(tooltipText) } @get:VisibleForTesting var changeEvent: ChangeEvent? get() = changeEvent set(changeEvent) { super.changeEvent = changeEvent } override fun getTableCellEditorComponent( table: JTable, value: Any, selected: Boolean, viewRowIndex: Int, viewColumnIndex: Int, ): Component { // I'd pass selected instead of hard coding true but the selection is changed after the cell is edited. selected is false when I expect // it to be true. return myButton.getTableCellComponent(table, true, true) } override fun getCellEditorValue(): Any { checkNotNull(myValue) return myValue as Any } } ================================================ FILE: src/main/kotlin/com/phodal/shire/marketplace/ui/IconButtonTableCellRenderer.kt ================================================ /* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.phodal.shire.marketplace.ui import com.google.common.annotations.VisibleForTesting import com.intellij.openapi.util.IconLoader import com.intellij.ui.scale.JBUIScale.scale import com.intellij.util.IconUtil import com.intellij.util.ui.JBDimension import java.awt.Color import java.awt.Component import java.awt.Dimension import java.awt.image.RGBImageFilter import java.util.* import java.util.function.Function import java.util.function.Supplier import java.util.stream.IntStream import javax.swing.Icon import javax.swing.JButton import javax.swing.JTable import javax.swing.UIManager import javax.swing.border.Border import javax.swing.table.TableCellRenderer import javax.swing.table.TableColumn import kotlin.math.max fun generateColoredIcon(icon: Icon, color: Color): Icon = IconLoader.createLazy(object : Supplier { private val cache = mutableMapOf() override fun get(): Icon = cache.getOrPut(color.rgb) { IconUtil.filterIcon(icon, { object : RGBImageFilter() { override fun filterRGB(x: Int, y: Int, rgb: Int) = (rgb or 0xffffff) and color.rgb } }, null) } }) object Tables { fun getBackground(table: JTable, selected: Boolean): Color { if (selected) { return table.selectionBackground } return table.background } fun getBorder(selected: Boolean, focused: Boolean): Border? { return getBorder( selected, focused ) { key: Any -> UIManager.getBorder(key) } } @VisibleForTesting fun getBorder(selected: Boolean, focused: Boolean, getBorder: Function): Border? { if (!focused) { return getBorder.apply("Table.cellNoFocusBorder") } if (selected) { return getBorder.apply("Table.focusSelectedCellHighlightBorder") } return getBorder.apply("Table.focusCellHighlightBorder") } fun getForeground(table: JTable, selected: Boolean): Color { if (selected) { return table.selectionForeground } return table.foreground } fun getIcon(table: JTable, selected: Boolean, icon: Icon): Icon { if (selected) { return generateColoredIcon(icon, table.selectionForeground) } return icon } fun setWidths(column: TableColumn, width: Int) { column.minWidth = width column.maxWidth = width column.preferredWidth = width } fun setWidths(column: TableColumn, width: Int, minWidth: Int) { column.minWidth = minWidth column.maxWidth = width column.preferredWidth = width } fun getPreferredColumnWidth(table: JTable, viewColumnIndex: Int, minPreferredWidth: Int): Int { val width = IntStream.range(-1, table.rowCount) .map { viewRowIndex: Int -> getPreferredCellWidth( table, viewRowIndex, viewColumnIndex ) } .max() if (!width.isPresent) { return minPreferredWidth } return max(width.asInt.toDouble(), minPreferredWidth.toDouble()).toInt() } private fun getPreferredCellWidth(table: JTable, viewRowIndex: Int, viewColumnIndex: Int): Int { val component: Component if (viewRowIndex == -1) { val renderer = table.tableHeader.defaultRenderer val value = table.columnModel.getColumn(viewColumnIndex).headerValue component = renderer.getTableCellRendererComponent(table, value, false, false, -1, viewColumnIndex) } else { component = table.prepareRenderer( table.getCellRenderer(viewRowIndex, viewColumnIndex), viewRowIndex, viewColumnIndex ) } return component.preferredSize.width + scale(8) } } interface IconTableCell { fun getTableCellComponent(table: JTable, selected: Boolean, focused: Boolean): Component { setBackground(Tables.getBackground(table, selected)) setBorder(Tables.getBorder(selected, focused)) val icon = defaultIcon .map { i: Icon -> Tables.getIcon(table, selected, i) } .orElse(null) setIcon(icon) return this as Component } val defaultIcon: Optional fun setBackground(background: Color?) fun setBorder(border: Border?) fun setIcon(icon: Icon?) } class IconButton(defaultIcon: Icon) : JButton(defaultIcon), IconTableCell { private var myDefaultIcon: Icon init { val size: Dimension = JBDimension(22, 22) border = null isContentAreaFilled = false maximumSize = size minimumSize = size preferredSize = size myDefaultIcon = defaultIcon } override var defaultIcon: Optional get() = Optional.ofNullable(myDefaultIcon) set(defaultIcon) { myDefaultIcon = defaultIcon.orElse(null) } fun setDefaultIcon(defaultIcon: Icon) { myDefaultIcon = defaultIcon } } open class IconButtonTableCellRenderer(icon: Icon? = null, tooltipText: String? = null) : TableCellRenderer { protected val myButton: IconButton = IconButton(icon!!) init { myButton.toolTipText = tooltipText } override fun getTableCellRendererComponent( table: JTable, value: Any, selected: Boolean, focused: Boolean, viewRowIndex: Int, viewColumnIndex: Int, ): Component { return myButton.getTableCellComponent(table, selected, focused) } companion object { fun getPreferredWidth(table: JTable, c: Class<*>): Int { val renderer = table.getDefaultRenderer(c) as IconButtonTableCellRenderer return renderer.myButton.getTableCellComponent(table, false, false).preferredSize.width + scale(8) } } } ================================================ FILE: src/main/kotlin/com/phodal/shire/marketplace/ui/MarketplaceView.kt ================================================ package com.phodal.shire.marketplace.ui import com.intellij.openapi.components.State import com.intellij.openapi.components.Storage import com.intellij.openapi.components.StoragePathMacros import com.intellij.openapi.project.Project import com.intellij.openapi.wm.ToolWindow import com.intellij.ui.content.ContentFactory import com.intellij.ui.dsl.builder.Align import com.intellij.ui.dsl.builder.panel import com.phodal.shirecore.ui.input.ShireChatBoxInput import javax.swing.JPanel @State(name = "MarketPlaceView", storages = [Storage(StoragePathMacros.PRODUCT_WORKSPACE_FILE)]) class MarketplaceView(project: Project) { private var myToolWindowPanel: JPanel? = null private val shirePackageTableComponent = ShireMarketplaceTableView(project) init { val shireInput = ShireChatBoxInput(project) myToolWindowPanel = panel { row { cell(shirePackageTableComponent.mainPanel).align(Align.FILL) }.resizableRow() row { cell(shireInput).align(Align.FILL) } } } fun initToolWindow(toolWindow: ToolWindow) { val contentFactory = ContentFactory.getInstance() val content = contentFactory.createContent(myToolWindowPanel, "Shire Marketplace", false) toolWindow.contentManager.addContent(content) } } ================================================ FILE: src/main/kotlin/com/phodal/shire/marketplace/ui/ShireMarketplaceTableView.kt ================================================ package com.phodal.shire.marketplace.ui import com.fasterxml.jackson.databind.ObjectMapper import com.intellij.icons.AllIcons import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.project.Project import com.intellij.ui.components.JBScrollPane import com.intellij.ui.dsl.builder.Align import com.intellij.ui.dsl.builder.panel import com.intellij.ui.table.TableView import com.intellij.util.ui.ColumnInfo import com.intellij.util.ui.ListTableModel import com.phodal.shire.ShireIdeaIcons import com.phodal.shire.ShireMainBundle import com.phodal.shire.marketplace.model.ShirePackage import com.phodal.shire.marketplace.util.ShireDownloader import com.phodal.shirecore.SHIRE_MKT_HOST import com.phodal.shirecore.ShirelangNotifications import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import java.awt.Component import javax.swing.JButton import javax.swing.JPanel import javax.swing.JTable import javax.swing.table.TableCellEditor import javax.swing.table.TableCellRenderer class ShireMarketplaceTableView(val project: Project) { private val columns = arrayOf( object : ColumnInfo(ShireMainBundle.message("marketplace.column.name")) { override fun valueOf(data: ShirePackage): String = data.title override fun getWidth(table: JTable?): Int = 120 }, object : ColumnInfo(ShireMainBundle.message("marketplace.column.description")) { override fun valueOf(data: ShirePackage): String = data.description }, object : ColumnInfo("") { override fun getWidth(table: JTable?): Int = 40 override fun valueOf(item: ShirePackage?): ShirePackage? = item override fun isCellEditable(item: ShirePackage?): Boolean = true override fun getEditor(item: ShirePackage): TableCellEditor { return object : IconButtonTableCellEditor(item, ShireIdeaIcons.Download, "Download") { init { myButton.addActionListener { ShirelangNotifications.info(project, "Downloading ${item.title}") ShireDownloader(project, item).downloadAndUnzip() ShirelangNotifications.info(project, "Success Downloaded ${item.title}") fireEditingStopped() } } } } override fun getRenderer(item: ShirePackage?): TableCellRenderer { return object : IconButtonTableCellRenderer(ShireIdeaIcons.Download, "Download") { override fun getTableCellRendererComponent( table: JTable, value: Any, selected: Boolean, focused: Boolean, viewRowIndex: Int, viewColumnIndex: Int, ): Component { myButton.isEnabled = true return super.getTableCellRendererComponent( table, value, selected, focused, viewRowIndex, viewColumnIndex ) } } } } ) var mainPanel: JPanel private val client = OkHttpClient() init { val tableModel = ListTableModel(columns, listOf()) val tableView = TableView(tableModel) val scrollPane = JBScrollPane(tableView) val myReloadButton = JButton(AllIcons.Actions.Refresh) mainPanel = panel { row { cell(myReloadButton.apply { addActionListener { tableModel.items = makeApiCall() tableModel.fireTableDataChanged() } }) } row { cell(scrollPane).align(Align.FILL) }.resizableRow() } ApplicationManager.getApplication().invokeLater { tableModel.items = makeApiCall() tableModel.fireTableDataChanged() } } private fun makeApiCall(): List { try { val objectMapper = ObjectMapper() val request = Request.Builder().url(SHIRE_MKT_HOST).get().build() val responses: Response = client.newCall(request).execute() val jsonData = responses.body?.string() val packages = objectMapper.readValue(jsonData, Array::class.java) return packages.toList() } catch (e: Exception) { ShirelangNotifications.error(project, "Failed to fetch data: $e") return listOf() } } } ================================================ FILE: src/main/kotlin/com/phodal/shire/marketplace/util/ShireDownloader.kt ================================================ // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.phodal.shire.marketplace.util import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.invokeLater import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.util.Computable import com.intellij.openapi.util.io.FileUtil import com.intellij.util.download.DownloadableFileService import com.intellij.util.download.FileDownloader import com.intellij.util.io.ZipUtil import com.phodal.shire.ShireMainBundle import com.phodal.shire.marketplace.model.ShirePackage import com.phodal.shirecore.ShirelangNotifications import java.io.File import java.io.IOException import java.nio.file.Files import java.nio.file.Path import java.util.concurrent.locks.ReentrantReadWriteLock import javax.swing.SwingUtilities class ShireDownloader(val project: Project, val item: ShirePackage) { private val downloadLock = ReentrantReadWriteLock() fun downloadAndUnzip(): Boolean { val service = DownloadableFileService.getInstance() val filename = item.link.substringAfterLast("/") val description = service.createFileDescription( item.link, filename ) val downloader = service.createDownloader(listOf(description), "Download Shire package: " + item.title) if (SwingUtilities.isEventDispatchThread()) { ApplicationManager.getApplication() .executeOnPooledThread { downloadWithLock(downloader) } return true } else { return downloadWithLock(downloader) } } private fun downloadWithLock(downloader: FileDownloader): Boolean { downloadLock.writeLock().lock() try { val pluginDir: File = getPluginDir() return downloadWithProgress { doDownload(pluginDir, downloader).apply { invokeLater { project.guessProjectDir()?.refresh(true, true) } } } } finally { downloadLock.writeLock().unlock() } } private fun downloadWithProgress(downloadTask: Computable): Boolean { if (ProgressManager.getInstance().hasProgressIndicator()) { return downloadTask.compute() } else { val indicator = BackgroundableProcessIndicator( project, ShireMainBundle.message("downloading.package"), null, null, true ) return ProgressManager.getInstance().runProcess(downloadTask, indicator) } } protected fun doDownload(pluginDir: File?, downloader: FileDownloader): Boolean { var tempDir: Path? = null try { tempDir = Files.createTempDirectory(".shire-download") val list = downloader.download(tempDir.toFile()) val file = list[0].first.toPath() ZipUtil.extract(file, getTargetDir(pluginDir).toPath(), null) return true } catch (e: IOException) { val message = "Can't download Shire package: " + item.title logger().warn(message, e) ShirelangNotifications.error(project, e.message ?: message) return false } finally { if (tempDir != null) { FileUtil.delete(tempDir.toFile()) } } } private fun getPluginDir(): File { val pluginDir = File(project.basePath, "") if (!pluginDir.exists()) { pluginDir.mkdirs() return pluginDir } return pluginDir } private fun getTargetDir(pluginDir: File?): File { return pluginDir?.let { File(it, "") } ?: File(project.basePath, "") } } ================================================ FILE: src/main/kotlin/com/phodal/shire/settings/ShireLlmSettingsConfigurable.kt ================================================ package com.phodal.shire.settings import com.intellij.openapi.options.ConfigurableBase class ShireLlmSettingsConfigurable @JvmOverloads constructor(private val settings: ShireSettingsState = ShireSettingsState.getInstance()) : ConfigurableBase( "com.phodal.shire.settings", "Shire Settings", "com.phodal.shire.settings" ) { override fun getSettings(): ShireSettingsState { return ShireSettingsState.getInstance() } override fun createUi(): ShireSettingUi { return ShireSettingUi() } } ================================================ FILE: src/main/kotlin/com/phodal/shire/settings/ShireSettingUi.kt ================================================ package com.phodal.shire.settings import com.intellij.openapi.options.ConfigurableUi import com.intellij.openapi.project.ProjectManager import com.intellij.ui.components.JBPasswordField import com.intellij.ui.dsl.builder.AlignX import com.intellij.ui.dsl.builder.AlignY import com.intellij.ui.dsl.builder.panel import com.intellij.util.MathUtil import com.intellij.util.ui.JBDimension import com.phodal.shirecore.ShireCoreBundle import com.phodal.shirecore.ShireCoroutineScope import com.phodal.shirecore.llm.LlmConfig import com.phodal.shirecore.llm.LlmProvider import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch import java.awt.event.KeyAdapter import java.awt.event.KeyEvent import javax.swing.* class ShireSettingUi : ConfigurableUi { private var panel: JPanel? = null private var apiHost: JTextField = JTextField() private var modelName: JTextField = JTextField() private var engineToken: JTextField = JBPasswordField() private var temperatureField: JTextField = JTextField(4) private val minTemperature = 0.0f private val maxTemperature = 1.0f private val testConnectionButton = JButton("Test LLM Connection") private val testResultField = JTextPane() private var testJob: Job? = null init { this.panel = panel { row("LLM API Host:") { cell(apiHost) .applyToComponent { minimumSize = JBDimension(200, 1) } .align(AlignX.FILL) } row("Model Name:") { cell(modelName) .applyToComponent { minimumSize = JBDimension(200, 1) } .align(AlignX.FILL) } row("Engine Token:") { cell(engineToken) .applyToComponent { minimumSize = JBDimension(200, 1) } .align(AlignX.FILL) } row("Temperature:") { cell(temperatureField.apply { this.text = "0.1" this.addKeyListener(object : KeyAdapter() { override fun keyPressed(e: KeyEvent) { if (e.keyCode != KeyEvent.VK_UP && e.keyCode != KeyEvent.VK_DOWN) return val up = e.keyCode == KeyEvent.VK_UP try { var value: Float = temperatureField.getText().toFloat() value += (if (up) 0.1 else -0.1).toFloat() value = MathUtil.clamp(value, minTemperature, maxTemperature) if (value < minTemperature) { value = minTemperature } else if (value > maxTemperature) { value = maxTemperature } temperatureField.text = value.toString() } catch (ignored: NumberFormatException) { } } }) }) } row { cell(testConnectionButton.apply { addActionListener { onTestConnection() } }) cell(testResultField).align(AlignY.CENTER) } row { text("Don't forget to APPLY change after test!") } } } override fun reset(settings: ShireSettingsState) { apiHost.text = settings.apiHost modelName.text = settings.modelName engineToken.text = settings.apiToken temperatureField.text = settings.temperature.toString() } override fun isModified(settings: ShireSettingsState): Boolean { return apiHost.text != settings.apiHost || modelName.text != settings.modelName || engineToken.text != settings.apiToken || temperatureField.text != settings.temperature.toString() } override fun apply(settings: ShireSettingsState) { settings.apiHost = apiHost.text ?: "" settings.modelName = modelName.text ?: "" settings.apiToken = engineToken.text ?: "" settings.temperature = temperatureField.text.toFloatOrNull() ?: 0.0f } override fun getComponent(): JComponent { return panel!! } private fun onTestConnection() { val project = ProjectManager.getInstance().openProjects.firstOrNull() ?: return // cancel last test job testJob?.cancel() testResultField.text = "user: hi. robot: " // use CoroutineExceptionHandler to handle exception, it will automatically ignore CancelledException testJob = ShireCoroutineScope.scope(project).launch(CoroutineExceptionHandler { coroutineContext, throwable -> testResultField.text = throwable.message ?: "Unknown error" }) { val flowString: Flow = LlmConfig( model = modelName.text, apiKey = engineToken.text, apiBase = apiHost.text, temperature = temperatureField.text.toDoubleOrNull() ?: 0.0, title = modelName.text, ).let { LlmProvider.provider(project, it) ?.stream( promptText = "hi", systemPrompt = "", keepHistory = false, llmConfig = it ) ?: throw Exception(ShireCoreBundle.message("shire.llm.notfound")) } flowString.collect { testResultField.text += it } } } } ================================================ FILE: src/main/kotlin/com/phodal/shire/settings/ShireSettingsState.kt ================================================ package com.phodal.shire.settings import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.PersistentStateComponent import com.intellij.openapi.components.Service import com.intellij.openapi.components.State import com.intellij.openapi.components.Storage import com.intellij.util.xmlb.XmlSerializerUtil @Service(Service.Level.APP) @State(name = "com.phodal.shire.settings.ShireSettingsState", storages = [Storage("ShireSettings.xml")]) class ShireSettingsState : PersistentStateComponent { var temperature: Float = 0.0f var apiHost = "" var modelName = "" var apiToken = "" @Synchronized override fun getState(): ShireSettingsState = this @Synchronized override fun loadState(state: ShireSettingsState) = XmlSerializerUtil.copyBean(state, this) companion object { fun getInstance(): ShireSettingsState { return ApplicationManager.getApplication().getService(ShireSettingsState::class.java).state } } } ================================================ FILE: src/main/resources/META-INF/docker-contrib.xml ================================================ ================================================ FILE: src/main/resources/META-INF/json-contrib.xml ================================================ ================================================ FILE: src/main/resources/META-INF/openrewrite-contrib.xml ================================================ ================================================ FILE: src/main/resources/META-INF/plugin.xml ================================================ com.phodal.shire Shire - AI Coding Agent Language Phodal Huang com.intellij.modules.platform com.intellij.modules.json com.intellij.openRewrite com.intellij.wiremock Docker messages.ShireMainBundle ================================================ FILE: src/main/resources/META-INF/wiremock-contrib.xml ================================================ ================================================ FILE: src/main/resources/intentionDescriptions/ShireIntention/after.txt.template ================================================ 生成一个 Java hello, world: ```java public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World!"); } } ``` ================================================ FILE: src/main/resources/intentionDescriptions/ShireIntention/before.txt.template ================================================ 生成一个 Java hello, world: ================================================ FILE: src/main/resources/intentionDescriptions/ShireIntention/description.html ================================================ Invoke Shire Hobbit Action on here with AI ability. ================================================ FILE: src/main/resources/intentionDescriptions/ShireIntentionHelper/after.txt.template ================================================ 生成一个 Java hello, world: ```java public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World!"); } } ``` ================================================ FILE: src/main/resources/intentionDescriptions/ShireIntentionHelper/before.txt.template ================================================ 生成一个 Java hello, world: ================================================ FILE: src/main/resources/intentionDescriptions/ShireIntentionHelper/description.html ================================================ Invoke Shire Hobbit Action on here with AI ability. ================================================ FILE: src/main/resources/messages/ShireMainBundle.properties ================================================ intentions.assistant.name=Shire intention action intentions.assistant.popup.title=Shire Intention Action marketplace.column.name=Name marketplace.column.description=Description downloading.package=Downloading Shire package chat.input.title=Create property ================================================ FILE: src/test/testData/rename/foo.xml ================================================ 1>Foo ================================================ FILE: src/test/testData/rename/foo_after.xml ================================================ Foo ================================================ FILE: toolsets/README.md ================================================ ================================================ FILE: toolsets/database/src/main/kotlin/com/phodal/shire/database/DatabaseSchemaAssistant.kt ================================================ package com.phodal.shire.database import com.intellij.database.model.DasTable import com.intellij.database.model.ObjectKind import com.intellij.database.model.RawDataSource import com.intellij.database.psi.DbDataSource import com.intellij.database.psi.DbPsiFacade import com.intellij.database.util.DasUtil import com.intellij.openapi.project.Project object DatabaseSchemaAssistant { fun getDataSources(project: Project): List = DbPsiFacade.getInstance(project).dataSources.toList() fun allRawDatasource(project: Project): List { val dbPsiFacade = DbPsiFacade.getInstance(project) return dbPsiFacade.dataSources.map { dataSource -> dbPsiFacade.getDataSourceManager(dataSource).dataSources }.flatten() } fun getDatabase(project: Project, dbName: String): RawDataSource? { return allRawDatasource(project).firstOrNull { it.name == dbName } } fun getAllTables(project: Project): List { return allRawDatasource(project).map { val schemaName = it.name.substringBeforeLast('@') DasUtil.getTables(it).filter { table -> table.kind == ObjectKind.TABLE && (table.dasParent?.name == schemaName || isSQLiteTable(it, table)) } }.flatten() } fun getTableByDataSource(dataSource: RawDataSource): List { return DasUtil.getTables(dataSource).toList() } fun getTable(dataSource: RawDataSource, tableName: String): List { val dasTables = DasUtil.getTables(dataSource) return dasTables.filter { it.name == tableName }.toList() } fun executeSqlQuery(project: Project, sql: String): String { return SQLExecutor.executeSqlQuery(project, sql) } private fun isSQLiteTable( rawDataSource: RawDataSource, table: DasTable, ) = (rawDataSource.databaseVersion.name == "SQLite" && table.dasParent?.name == "main") fun getTableColumns(project: Project, tables: List = emptyList()): List { val dasTables = getAllTables(project) if (tables.isEmpty()) { return dasTables.map(::displayTable) } return dasTables.mapNotNull { table -> if (tables.contains(table.name)) { displayTable(table) } else { null } } } fun getTableColumn(table: DasTable): String = displayTable(table) private fun displayTable(table: DasTable): String { val dasColumns = DasUtil.getColumns(table) val columns = dasColumns.map { column -> "${column.name}: ${column.dasType.toDataType()}" }.joinToString(", ") return "TableName: ${table.name}, Columns: { $columns }" } } ================================================ FILE: toolsets/database/src/main/kotlin/com/phodal/shire/database/SQLExecutor.kt ================================================ package com.phodal.shire.database import com.intellij.database.console.JdbcConsole import com.intellij.database.console.JdbcConsoleProvider import com.intellij.database.console.evaluation.EvaluationRequest import com.intellij.database.console.session.DatabaseSession import com.intellij.database.console.session.DatabaseSessionManager import com.intellij.database.console.session.getSessionTitle import com.intellij.database.datagrid.* import com.intellij.database.model.RawDataSource import com.intellij.database.script.PersistenceConsoleProvider import com.intellij.database.settings.DatabaseSettings import com.intellij.database.util.DbImplUtilCore import com.intellij.database.vfs.DbVFSUtils import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.runInEdt import com.intellij.openapi.application.runReadAction import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.intellij.sql.psi.SqlPsiFacade import com.intellij.testFramework.LightVirtualFile import com.intellij.util.Consumer import com.phodal.shire.database.DatabaseSchemaAssistant.allRawDatasource import java.util.concurrent.CompletableFuture object SQLExecutor { fun executeSqlQuery(project: Project, sql: String): String { val file = LightVirtualFile("temp.sql", sql) val psiFile = runReadAction { PsiManager.getInstance(project).findFile(file) } ?: return "ShireError[Database]: Can't find PSI file" val dataSource = allRawDatasource(project).firstOrNull() ?: throw IllegalArgumentException("ShireError[Database]: No database found") val execOptions = DatabaseSettings.getSettings().execOptions.last() val console: JdbcConsole? = JdbcConsole.getActiveConsoles(project).firstOrNull() ?: JdbcConsoleProvider.getValidConsole(project, file) ?: createConsole(project, file) if (console != null) { return executeSqlInConsole(console, sql, dataSource) } val editor = FileEditorManager.getInstance(project).selectedTextEditor ?: throw IllegalArgumentException("ShireError[Database]: No editor found") val scriptModel = SqlPsiFacade.getInstance(project).createScriptModel(psiFile) val info = JdbcConsoleProvider.Info(psiFile, psiFile, editor as EditorEx, scriptModel, execOptions, null) val runners: MutableList = runReadAction { getAttachDataSourceRunnersReflect(info) } if (runners.size == 1) { var result = "" runInEdt { result = executeSql(project, dataSource, sql) ?: "Error" } return result } else { try { val chooseAndRunRunnersMethod = Class.forName("com.intellij.database.intentions.RunQueryInConsoleIntentionAction\$Manager") .getDeclaredMethod( "chooseAndRunRunners", List::class.java, EditorEx::class.java, Any::class.java ) chooseAndRunRunnersMethod.invoke(null, runners, info.editor, null) } catch (e: Exception) { println("ShireError[Database]: Failed to run query with multiple runners") throw e } return "ShireError[Database]: Currently not support multiple runners" } } // private fun getAttachDataSourceRunners(info: JdbcConsoleProvider.Info): MutableList { // val virtualFile = info.editor!!.virtualFile // val project = info.originalFile.project // val title = getSessionTitle(virtualFile) // val consumer: Consumer = // Consumer { newSession: DatabaseSession? -> // val console = JdbcConsoleProvider.attachConsole( // project, // newSession!!, virtualFile // ) // if (console != null) { // val runnable = Runnable { JdbcConsoleProvider.doRunQueryInConsole(console, info) } // if (DbVFSUtils.isAssociatedWithDataSourceAndSchema(virtualFile)) { // runnable.run() // } else { // DatabaseRunners.chooseSchemaAndRun(info.editor!!, runnable) // } // } // } // // return DatabaseRunners.getAttachDataSourceRunners(info.file, title, consumer) // } private fun getAttachDataSourceRunnersReflect(info: JdbcConsoleProvider.Info): MutableList { val virtualFile = info.editor!!.virtualFile val project = info.originalFile.project val title = getSessionTitle(virtualFile) val consumer: Consumer = Consumer { newSession: DatabaseSession? -> val console = JdbcConsoleProvider.attachConsole(project, newSession!!, virtualFile) if (console != null) { val runnable = Runnable { JdbcConsoleProvider.doRunQueryInConsole(console, info) } try { // 使用反射调用 DbVFSUtils.isAssociatedWithDataSourceAndSchema val isAssociatedMethod = DbVFSUtils::class.java.getDeclaredMethod( "isAssociatedWithDataSourceAndSchema", virtualFile::class.java ) val isAssociated = isAssociatedMethod.invoke(null, virtualFile) as Boolean if (isAssociated) { runnable.run() } else { val chooseSchemaMethod = Class.forName("com.intellij.database.console.DatabaseRunners") .getDeclaredMethod("chooseSchemaAndRun", EditorEx::class.java, Runnable::class.java) chooseSchemaMethod.invoke(null, info.editor, runnable) } } catch (e: Exception) { println("ShireError[Database]: Failed to run query in console") throw e } } } try { // 使用反射调用 DatabaseRunners.getAttachDataSourceRunners val getRunners = Class.forName("com.intellij.database.console.DatabaseRunners") .getDeclaredMethod( "getAttachDataSourceRunners", PsiFile::class.java, String::class.java, com.intellij.util.Consumer::class.java ) @Suppress("UNCHECKED_CAST") return getRunners.invoke(null, info.file, title, consumer) as MutableList } catch (e: Exception) { println("ShireError[Database]: Failed to get runners") throw e } } private fun executeSqlInConsole(console: JdbcConsole, sql: String, dataSource: RawDataSource): String { val future: CompletableFuture = CompletableFuture() return ApplicationManager.getApplication().executeOnPooledThread { val messageBus = console.session.messageBus val newConsoleRequest = EvaluationRequest.newRequest(console, sql, dataSource.dbms) messageBus.dataProducer.processRequest(newConsoleRequest) messageBus.addConsumer(object : MyCompatDataConsumer() { var result = mutableListOf() override fun addRows(context: GridDataRequest.Context, rows: MutableList) { result += rows if (rows.size < 100) { future.complete(result.toString()) } } }) return@executeOnPooledThread future.get() }.get() } private fun executeSql(project: Project, dataSource: RawDataSource, query: String): String? { val future: CompletableFuture = CompletableFuture() val localDs = DbImplUtilCore.getLocalDataSource(dataSource) val session = DatabaseSessionManager.getSession(project, localDs) val messageBus = session.messageBus messageBus.addConsumer(object : MyCompatDataConsumer() { var result = mutableListOf() override fun addRows(context: GridDataRequest.Context, rows: MutableList) { result += rows if (rows.size < 100) { future.complete(result.toString()) } } }) val request = object : DataRequest.QueryRequest(session, query, DataRequest.newConstraints(dataSource.dbms), null) {} messageBus.dataProducer.processRequest(request) return future.get() } private fun createConsole(project: Project, file: LightVirtualFile): JdbcConsole? { val attached = JdbcConsoleProvider.findOrCreateSession(project, file) ?: return null return JdbcConsoleProvider.attachConsole(project, attached, file) } abstract class MyCompatDataConsumer : DataConsumer { override fun setColumns( context: GridDataRequest.Context, subQueryIndex: Int, resultSetIndex: Int, columns: Array, firstRowNum: Int, ) { // for Compatibility in IDEA 2023.2.8 } /// will remove in latest version, so we need to use reflection to call this method in future override fun setColumns( context: GridDataRequest.Context, resultSetIndex: Int, columns: Array, firstRowNum: Int, ) { // for Compatibility in IDEA 2023.2.8 } override fun afterLastRowAdded(context: GridDataRequest.Context, total: Int) { // for Compatibility in IDEA 2023.2.8 } } } ================================================ FILE: toolsets/database/src/main/kotlin/com/phodal/shire/database/SqlContextBuilder.kt ================================================ package com.phodal.shire.database import com.intellij.database.console.JdbcConsoleProvider import com.intellij.database.model.ObjectKind import com.intellij.database.model.basic.BasicModel import com.intellij.database.model.basic.BasicSchema import com.intellij.database.model.basic.BasicTable import com.intellij.database.model.basic.BasicTableOrViewColumn import com.intellij.database.psi.DbDataSource import com.intellij.database.util.ObjectPath import com.intellij.database.util.QNameUtil import com.intellij.openapi.project.Project import com.intellij.sql.psi.SqlFile object SqlContextBuilder { fun getCurrentNamespace(sqlFile: SqlFile): ObjectPath? { val console = JdbcConsoleProvider.getValidConsole(sqlFile.project, sqlFile.virtualFile) return console?.currentNamespace } fun getSchema(ds: DbDataSource?, currentNamespace: ObjectPath?): BasicSchema? { val basicModel = ds?.model as? BasicModel ?: return null val dasObject = QNameUtil.findByPath(basicModel, currentNamespace).firstOrNull() ?: return null return dasObject as? BasicSchema } fun formatSchema(schema: BasicSchema): String? { return schema.familyOf(ObjectKind.TABLE)?.jbi() ?.mapNotNull { it as? BasicTable } ?.joinToString("\n\n") { describeTable(it) } } private fun describeTable(table: BasicTable): String = """ |create table ${table.name} { | ${table.columns.joinToString(",\n ") { "${it.name} ${columnType(it)}" }} |} """.trimMargin() private fun columnType(it: BasicTableOrViewColumn) = it.dasType.specification fun buildDatabaseInfo(project: Project): String { val dataSources = DatabaseSchemaAssistant.allRawDatasource(project) return dataSources.joinToString("\n") { """ DatabaseName: ${it.databaseVersion.name} DatabaseVersion: ${it.databaseVersion.version} Database: ${it.name} ConnectionConfig: ${it.connectionConfig?.url} """.trimIndent() } } } ================================================ FILE: toolsets/database/src/main/kotlin/com/phodal/shire/database/provider/DatabaseFunctionProvider.kt ================================================ package com.phodal.shire.database.provider import com.intellij.database.model.DasTable import com.intellij.database.model.RawDataSource import com.intellij.database.util.DasUtil import com.intellij.database.util.DbUtil import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.phodal.shire.database.DatabaseSchemaAssistant import com.phodal.shirecore.provider.function.ToolchainFunctionProvider enum class DatabaseFunction(val funName: String) { Schema("schema"), Table("table"), Column("column"), Query("query") ; companion object { fun fromString(value: String): DatabaseFunction? { return entries.firstOrNull { it.funName == value } } } } class DatabaseFunctionProvider : ToolchainFunctionProvider { override fun isApplicable(project: Project, funcName: String): Boolean { return DatabaseFunction.values().any { it.funName == funcName } } override fun execute( project: Project, funcName: String, args: List, allVariables: Map, ): Any { val databaseFunction = DatabaseFunction.fromString(funcName) ?: throw IllegalArgumentException("Shire[Database]: Invalid Database function name") return when (databaseFunction) { DatabaseFunction.Schema -> listSchemas(args, project) DatabaseFunction.Table -> executeTableFunction(args, project) DatabaseFunction.Column -> executeColumnFunction(args, project) DatabaseFunction.Query -> executeSqlFunction(args, project) } } private fun listSchemas(args: List, project: Project): Any { val dataSources = DbUtil.getDataSources(project) if (dataSources.isEmpty) return "" return dataSources.mapNotNull { val tableSchema = DasUtil.getTables(it).toList().mapNotNull { if (it.dasParent?.name == "information_schema") return@mapNotNull null DatabaseSchemaAssistant.getTableColumn(it) } if (tableSchema.isEmpty()) return@mapNotNull null val name = it.name.substringBeforeLast('@') "DATABASE NAME: ${name};\n${tableSchema.joinToString("\n")}" }.joinToString("\n") } private fun executeTableFunction(args: List, project: Project): Any { if (args.isEmpty()) { val dataSource = DatabaseSchemaAssistant.allRawDatasource(project).firstOrNull() ?: return "ShireError[Database]: No database found" return DatabaseSchemaAssistant.getTableByDataSource(dataSource) } val dbName = args.first() // for example: [accounts, payment_limits, transactions] var result = mutableListOf() when (dbName) { is String -> { if (dbName.startsWith("[") && dbName.endsWith("]")) { val tableNames = dbName.substring(1, dbName.length - 1).split(",") result = tableNames.map { getTable(project, it.trim()) }.flatten().toMutableList() } else { result = getTable(project, dbName).toMutableList() } } is List<*> -> { result = dbName.map { getTable(project, it as String) }.flatten().toMutableList() } else -> { } } return result } private fun executeSqlFunction(args: List, project: Project): Any { if (args.isEmpty()) { return "ShireError[DBTool]: SQL function requires a SQL query" } val sqlQuery = args.first() return DatabaseSchemaAssistant.executeSqlQuery(project, sqlQuery as String) } private fun executeColumnFunction(args: List, project: Project): Any { if (args.isEmpty()) { val allTables = DatabaseSchemaAssistant.getAllTables(project) val map = allTables.map { DatabaseSchemaAssistant.getTableColumn(it) } return """ |```sql |${map.joinToString("\n")} |``` """.trimMargin() } when (val first = args[0]) { is RawDataSource -> { return if (args.size == 1) { DatabaseSchemaAssistant.getTableByDataSource(first) } else { DatabaseSchemaAssistant.getTable(first, args[1] as String) } } is DasTable -> { return DatabaseSchemaAssistant.getTableColumn(first) } is List<*> -> { return when (first.first()) { is RawDataSource -> { return first.map { DatabaseSchemaAssistant.getTableByDataSource(it as RawDataSource) } } is DasTable -> { return first.map { DatabaseSchemaAssistant.getTableColumn(it as DasTable) } } else -> { "ShireError[DBTool]: Table function requires a data source or a list of table names" } } } is String -> { val allTables = DatabaseSchemaAssistant.getAllTables(project) if (first.startsWith("[") && first.endsWith("]")) { val tableNames = first.substring(1, first.length - 1).split(",") return tableNames.mapNotNull { val dasTable = allTables.firstOrNull { table -> table.name == it.trim() } dasTable?.let { DatabaseSchemaAssistant.getTableColumn(it) } } } else { val dasTable = allTables.firstOrNull { table -> table.name == first } return dasTable?.let { DatabaseSchemaAssistant.getTableColumn(it) } ?: "ShireError[DBTool]: Table not found" } } else -> { logger().error("ShireError[DBTool] args types: ${first.javaClass}") return "ShireError[DBTool]: Table function requires a data source or a list of table names" } } } private fun getTable(project: Project, dbName: String): List { val database = DatabaseSchemaAssistant.getDatabase(project, dbName) ?: return emptyList() return DatabaseSchemaAssistant.getTableByDataSource(database) } } ================================================ FILE: toolsets/database/src/main/kotlin/com/phodal/shire/database/provider/DatabaseToolchainProvider.kt ================================================ package com.phodal.shire.database.provider import com.intellij.database.util.DbUtil import com.intellij.openapi.project.Project import com.phodal.shirecore.provider.context.LanguageToolchainProvider import com.phodal.shirecore.provider.context.ToolchainContextItem import com.phodal.shirecore.provider.context.ToolchainPrepareContext class DatabaseToolchainProvider : LanguageToolchainProvider { override fun isApplicable(project: Project, context: ToolchainPrepareContext): Boolean { return return DbUtil.getDataSources(project).isNotEmpty } override suspend fun collect(project: Project, context: ToolchainPrepareContext): List { val dataSources = DbUtil.getDataSources(project) if (dataSources.isEmpty) return emptyList() val infos = dataSources.mapNotNull { val dbNames = it.delegateDataSource?.databaseVersion ?: return@mapNotNull null val nameInfo = dbNames.name + " " + dbNames.version val text = "This project use $nameInfo" return@mapNotNull ToolchainContextItem(DatabaseToolchainProvider::class, text) }.toList() return infos } } ================================================ FILE: toolsets/database/src/main/kotlin/com/phodal/shire/database/provider/DatabaseVariableProvider.kt ================================================ package com.phodal.shire.database.provider import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.phodal.shire.database.DatabaseSchemaAssistant import com.phodal.shire.database.SqlContextBuilder import com.phodal.shirecore.provider.variable.ToolchainVariableProvider import com.phodal.shirecore.provider.variable.model.ToolchainVariable import com.phodal.shirecore.provider.variable.model.toolchain.DatabaseToolchainVariable class DatabaseVariableProvider : ToolchainVariableProvider { override fun isResolvable(variable: ToolchainVariable, psiElement: PsiElement?, project: Project): Boolean { return variable is DatabaseToolchainVariable } override fun resolve(variable: ToolchainVariable, project: Project, editor: Editor, psiElement: PsiElement?): Any { if (variable !is DatabaseToolchainVariable) { return "" } return when (variable) { DatabaseToolchainVariable.DatabaseInfo -> SqlContextBuilder.buildDatabaseInfo(project) DatabaseToolchainVariable.Databases -> DatabaseSchemaAssistant.getDataSources(project) DatabaseToolchainVariable.Tables -> DatabaseSchemaAssistant.getAllTables(project) DatabaseToolchainVariable.Columns -> DatabaseSchemaAssistant.getTableColumns(project) } } } ================================================ FILE: toolsets/database/src/main/resources/com.phodal.shire.database.xml ================================================ ================================================ FILE: toolsets/docker/src/main/kotlin/com/phodal/shire/docker/DockerContextProvider.kt ================================================ package com.phodal.shire.docker import com.intellij.docker.DockerFileSearch import com.intellij.docker.dockerFile.parser.psi.DockerFileFromCommand import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiManager import com.intellij.psi.impl.source.PsiFileImpl import com.phodal.shirecore.provider.context.LanguageToolchainProvider import com.phodal.shirecore.provider.context.ToolchainContextItem import com.phodal.shirecore.provider.context.ToolchainPrepareContext class DockerContextProvider : LanguageToolchainProvider { override fun isApplicable( project: Project, context: ToolchainPrepareContext, ): Boolean = DockerFileSearch.getInstance().getDockerFiles(project).isNotEmpty() override suspend fun collect( project: Project, context: ToolchainPrepareContext, ): List { val dockerFiles = DockerFileSearch.getInstance().getDockerFiles(project).mapNotNull { runReadAction { PsiManager.getInstance(project).findFile(it) } } if (dockerFiles.isEmpty()) return emptyList() var context = "This project use Docker." val virtualFile = dockerFiles.firstOrNull()?.virtualFile ?: return listOf(ToolchainContextItem(DockerContextProvider::class, context)) context = "This project use Docker, path: ${virtualFile.path}" var additionalCtx = "" val fromCommands = dockerFiles.map { (it as PsiFileImpl).findChildrenByClass(DockerFileFromCommand::class.java).toList() }.flatten() if (fromCommands.isEmpty()) return listOf(ToolchainContextItem(DockerContextProvider::class, context)) additionalCtx = fromCommands.joinToString("\n") { runReadAction { it.text } } val text = "This project use Docker to run in server. Here is related info:\n$additionalCtx" return listOf(ToolchainContextItem(DockerContextProvider::class, text)) } } fun VirtualFile.readText(): String { return VfsUtilCore.loadText(this) } ================================================ FILE: toolsets/docker/src/main/resources/com.phodal.shire.docker.xml ================================================ ================================================ FILE: toolsets/git/src/main/kotlin/com/phodal/shire/git/DiffSimplifier.kt ================================================ package com.phodal.shire.git import com.intellij.openapi.components.Service import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diff.impl.patch.IdeaTextPatchBuilder import com.intellij.openapi.diff.impl.patch.UnifiedDiffWriter import com.intellij.openapi.project.Project import com.intellij.openapi.util.io.FileUtilRt import com.intellij.openapi.vcs.changes.* import com.intellij.project.stateStore import org.jetbrains.annotations.NotNull import java.io.StringWriter import java.nio.file.Path import java.nio.file.PathMatcher import kotlin.math.min @Service(Service.Level.PROJECT) class DiffSimplifier(private val project: Project) { private val logger = logger() /** * Simplifies the given list of changes and returns the resulting diff as a string. * * @param changes The list of changes to be simplified. * @param ignoreFilePatterns The list of file patterns to be ignored during simplification. * @return The simplified diff as a string. * @throws RuntimeException if the project base path is null or if there is an error calculating the diff. */ fun simplify(changes: List, ignoreFilePatterns: List): String { var originChanges = "" try { val writer = StringWriter() val basePath = project.basePath ?: throw RuntimeException("Project base path is null.") val binaryOrTooLargeChanges: List = changes.stream() .filter { change -> isBinaryOrTooLarge(change!!) } .map { when(it.type) { Change.Type.NEW -> "new file ${it.afterRevision?.file?.path}" Change.Type.DELETED -> "delete file ${it.beforeRevision?.file?.path}" Change.Type.MODIFICATION -> "modify file ${it.beforeRevision?.file?.path}" Change.Type.MOVED -> "rename file from ${it.beforeRevision?.file?.path} to ${it.afterRevision?.file?.path}" } } .toList() val filteredChanges = changes.stream() .filter { change -> !isBinaryOrTooLarge(change!!) } .filter { val filePath = it.afterRevision?.file if (filePath != null) { ignoreFilePatterns.none { pattern -> pattern.matches(Path.of(it.afterRevision!!.file.path)) } } else { true } } .toList() if (filteredChanges.isEmpty()) { return "" } val patches = IdeaTextPatchBuilder.buildPatch( project, filteredChanges.subList(0, min(filteredChanges.size, 500)), Path.of(basePath), false, true ) UnifiedDiffWriter.write( project, project.stateStore.projectBasePath, patches, writer, "\n", null as CommitContext?, emptyList() ) originChanges = writer.toString() originChanges += binaryOrTooLargeChanges.joinToString("\n") return postProcess(originChanges) } catch (e: Exception) { if (originChanges.isNotEmpty()) { logger.info("Error calculating diff: $originChanges", e) } throw RuntimeException("Error calculating diff: ${e.message}", e) } } companion object { private val revisionRegex = Regex("\\(revision [^)]+\\)") private const val LINE_TIP = "\\ No newline at end of file" /** * This method is used to process the given diff string and extract relevant information from it. * * @param diffString The diff string to be processed. * @return The processed string containing the extracted information. */ @NotNull fun postProcess(@NotNull diffString: String): String { val lines = diffString.lines() val length = lines.size val destination = ArrayList() var index = 0 while (true) { if (index >= lines.size) { break } val line = lines[index] if (line.startsWith("diff --git ") || line.startsWith("index:") || line.startsWith("Index:")) { index++ continue } if (line == "===================================================================") { index++ continue } // if a patch includes `\ No newline at the end of file` remove it if (line.contains(LINE_TIP)) { index++ continue } if (line.startsWith("---\t/dev/null")) { index++ continue } // todo: spike for handle for new file if (line.startsWith("@@") && line.endsWith("@@")) { index++ continue } // handle for new file if (line.startsWith("new file mode")) { val nextLine = lines[index + 1] if (nextLine.startsWith("--- /dev/null")) { val nextNextLine = lines[index + 2] val withoutHead = nextNextLine.substring("+++ b/".length) // footer: (date 1704768267000) val withoutFooter = withoutHead.substring(0, withoutHead.indexOf("\t")) destination.add("new file $withoutFooter") index += 3 continue } } // handle for rename if (line.startsWith("rename from")) { val nextLine = lines[index + 1] if (nextLine.startsWith("rename to")) { val from = line.substring("rename from ".length) val to = nextLine.substring("rename to ".length) destination.add("rename file from $from to $to") // The next value will be "---" and the value after that will be "+++". index += 4 continue } } // handle for java and kotlin import change if (line.startsWith(" import")) { val nextLine = lines.getOrNull(index + 1) if (nextLine?.startsWith(" import") == true) { var oldImportLine = "" var newImportLine = "" // search all import lines until the next line starts with "Index:" val importLines = ArrayList() importLines.add(line) importLines.add(nextLine) var tryToFindIndex = index + 2 while (true) { if (tryToFindIndex >= length) { break } val tryLine = lines[tryToFindIndex] when { tryLine.startsWith("Index:") -> { break } tryLine.startsWith(" import") -> { importLines.add(tryLine) } tryLine.startsWith("-import ") -> { oldImportLine = tryLine.substring("-import ".length) importLines.add(tryLine) } tryLine.startsWith("+import ") -> { newImportLine = tryLine.substring("+import ".length) importLines.add(tryLine) } } tryToFindIndex++ } if (oldImportLine.isNotEmpty() && newImportLine.isNotEmpty()) { if (importLines.size == tryToFindIndex - index) { index = tryToFindIndex destination.add("change import from $oldImportLine to $newImportLine") continue } } } } // handle for delete if (line.startsWith("deleted file mode")) { val nextLine = lines[index + 1] if (nextLine.startsWith("--- a/")) { val withoutHead = nextLine.substring("--- a/".length) // footer: (date 1704768267000) val withoutFooter = withoutHead.substring(0, withoutHead.indexOf("\t")) destination.add("delete file $withoutFooter") // search for the next line starts with "Index:" while (true) { if (index + 2 >= length) { break } val nextNextLine = lines[index + 2] if (nextNextLine.startsWith("Index:")) { index += 3 break } index++ } continue } } if (line.startsWith("---") || line.startsWith("+++")) { // next line val nextLine = lines[index + 1] if (nextLine.startsWith("+++")) { // remove end date val substringBefore = line.substringBefore("(revision") val startLine = substringBefore .substring("--- a/".length).trim() var endIndex = nextLine.indexOf("(date") if (endIndex == -1) { endIndex = nextLine.indexOf("(revision") } if (endIndex == -1) { endIndex = nextLine.length } val withoutEnd = nextLine.substring("+++ b/".length, endIndex).trim() if (startLine == withoutEnd) { index += 2 destination.add("modify file $startLine") continue } } // remove revision number with regex val result = revisionRegex.replace(line, "").trim() if (result.isNotEmpty()) { destination.add(result) } } else { if (line.trim().isNotEmpty()) { destination.add(line) } } index++ } return destination.joinToString("\n") } private fun isBinaryOrTooLarge(@NotNull change: Change): Boolean { return isBinaryOrTooLarge(change.beforeRevision) || isBinaryOrTooLarge(change.afterRevision) } private fun isBinaryOrTooLarge(revision: ContentRevision?): Boolean { val virtualFile = (revision as? CurrentContentRevision)?.virtualFile ?: return false return isBinaryRevision(revision) || FileUtilRt.isTooLarge(virtualFile.length) } private fun isBinaryRevision(cr: ContentRevision?): Boolean { if (cr == null) return false return when (cr) { is BinaryContentRevision -> true else -> cr.file.fileType.isBinary } } } } ================================================ FILE: toolsets/git/src/main/kotlin/com/phodal/shire/git/VcsPrompting.kt ================================================ // MIT License // //Copyright (c) Jakob Maležič // //Permission is hereby granted, free of charge, to any person obtaining a copy //of this software and associated documentation files (the "Software"), to deal //in the Software without restriction, including without limitation the rights //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell //copies of the Software, and to permit persons to whom the Software is //furnished to do so, subject to the following conditions: // //The above copyright notice and this permission notice shall be included in all //copies or substantial portions of the Software. // //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE //SOFTWARE. package com.phodal.shire.git import com.intellij.openapi.components.Service import com.intellij.openapi.project.Project import com.intellij.openapi.vcs.VcsException import com.intellij.openapi.vcs.changes.* import com.intellij.vcs.log.VcsFullCommitDetails import java.io.IOException import java.io.StringWriter import java.nio.file.FileSystems import java.nio.file.PathMatcher @Service(Service.Level.PROJECT) class VcsPrompting(private val project: Project) { private val defaultIgnoreFilePatterns: List = listOf( "**/*.json", "**/*.jsonl", "**/*.txt", "**/*.log", "**/*.tmp", "**/*.temp", "**/*.bak", "**/*.swp", "**/*.svg", ).map { FileSystems.getDefault().getPathMatcher("glob:$it") } fun prepareContext(changes: List, ignoreFilePatterns: List = defaultIgnoreFilePatterns): String { return project.getService(DiffSimplifier::class.java).simplify(changes, ignoreFilePatterns) } /** * Builds a diff prompt for a list of VcsFullCommitDetails. * * @param details The list of VcsFullCommitDetails containing commit details. * @param project The Project object representing the current project. * @param ignoreFilePatterns The list of PathMatcher objects representing file patterns to be ignored during diff generation. Default value is an empty list. * @return A Pair object containing a list of commit message summaries and the generated diff prompt as a string. Returns null if the list is empty or no valid changes are found. * @throws VcsException If an error occurs during VCS operations. * @throws IOException If an I/O error occurs. */ @Throws(VcsException::class, IOException::class) fun buildDiffPrompt( details: List, selectList: List, project: Project, ignoreFilePatterns: List = defaultIgnoreFilePatterns, ): String? { val changeText = project.getService(DiffSimplifier::class.java).simplify(selectList, ignoreFilePatterns) if (changeText.isEmpty()) { return null } val processedText = DiffSimplifier.postProcess(changeText) val writer = StringWriter() if (details.isNotEmpty()) { writer.write("Commit Message: ") details.forEach { writer.write(it.fullMessage + "\n\n") } } writer.write("Changes:\n\n```patch\n$processedText\n```") return writer.toString() } fun getChanges(): List { val changeListManager = ChangeListManager.getInstance(project) return changeListManager.changeLists.flatMap { it.changes } } } ================================================ FILE: toolsets/git/src/main/kotlin/com/phodal/shire/git/provider/GitActionLocationEditor.kt ================================================ package com.phodal.shire.git.provider import com.intellij.openapi.application.invokeAndWaitIfNeeded import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.openapi.vcs.ui.CommitMessage import com.phodal.shirecore.config.ShireActionLocation import com.phodal.shirecore.provider.context.ActionLocationEditor class GitActionLocationEditor : ActionLocationEditor { private var commitUi: CommitMessage? = null override fun isApplicable(hole: ShireActionLocation): Boolean { val commitMessage = getCommitUi(hole) if (commitMessage != null) { commitUi = commitMessage } return hole == ShireActionLocation.COMMIT_MENU && commitMessage != null } override fun resolve(project: Project, hole: ShireActionLocation): Editor? { val commitMessageUi = commitUi ?: getCommitUi(hole) ?: return null val editorField = commitMessageUi.editorField @Suppress("UnstableApiUsage") invokeAndWaitIfNeeded { editorField.text = "" } return editorField.editor } private fun getCommitUi(hole: ShireActionLocation): CommitMessage? { if (hole != ShireActionLocation.COMMIT_MENU) return null val commitMessageUi = getCommitWorkflowUi()?.commitMessageUi as? CommitMessage if (commitMessageUi == null) { logger().error("Failed to get commit message UI") return null } return commitMessageUi } } ================================================ FILE: toolsets/git/src/main/kotlin/com/phodal/shire/git/provider/GitDataContext.kt ================================================ package com.phodal.shire.git.provider import com.intellij.ide.DataManager import com.intellij.openapi.vcs.VcsDataKeys import com.intellij.vcs.commit.CommitWorkflowUi import com.phodal.shirecore.variable.template.VariableActionEventDataHolder fun getCommitWorkflowUi(): CommitWorkflowUi? { VariableActionEventDataHolder.getData()?.dataContext?.let { val commitWorkflowUi = it.getData(VcsDataKeys.COMMIT_WORKFLOW_UI) return commitWorkflowUi as CommitWorkflowUi? } val dataContext = DataManager.getInstance().dataContextFromFocus.result val commitWorkflowUi = dataContext?.getData(VcsDataKeys.COMMIT_WORKFLOW_UI) return commitWorkflowUi } ================================================ FILE: toolsets/git/src/main/kotlin/com/phodal/shire/git/provider/GitFunctionProvider.kt ================================================ package com.phodal.shire.git.provider import com.intellij.dvcs.DvcsUtil import com.intellij.dvcs.push.PushSpec import com.intellij.dvcs.ui.DvcsBundle import com.intellij.openapi.progress.* import com.intellij.openapi.project.Project import com.intellij.openapi.vcs.AbstractVcs import com.intellij.openapi.vcs.FilePath import com.intellij.openapi.vcs.ProjectLevelVcsManager import com.intellij.openapi.vcs.changes.ChangeListManager import com.intellij.openapi.vcs.changes.CommitContext import com.intellij.openapi.vcs.changes.actions.ScheduleForAdditionActionExtension import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.ObjectUtils import com.intellij.vcsUtil.VcsUtil import com.phodal.shirecore.provider.function.ToolchainFunctionProvider import git4idea.GitRemoteBranch import git4idea.GitVcs import git4idea.i18n.GitBundle import git4idea.push.GitPushOperation import git4idea.push.GitPushSource import git4idea.push.GitPushSupport import git4idea.repo.GitRepository import git4idea.repo.GitRepositoryManager import git4idea.util.GitFileUtils import java.util.concurrent.CompletableFuture /** * Use example: * * ``` * commit("commit message") | push * ``` * */ enum class GitFunction(val funName: String) { Commit("commit"), Push("push"); companion object { fun fromString(value: String): GitFunction? { return entries.firstOrNull { it.funName == value } } } } class GitFunctionProvider : ToolchainFunctionProvider { override fun isApplicable(project: Project, funcName: String): Boolean { return GitFunction.entries.any { it.funName == funcName } } override fun execute(project: Project, funcName: String, args: List, allVariables: Map): Any { val gitFunc = GitFunction.fromString(funcName) ?: throw IllegalArgumentException("Shire[GitTool]: Invalid Git function name") val repositoryManager = GitRepositoryManager.getInstance(project) val repository = repositoryManager.repositories.stream().findFirst().orElse(null) ?: return throw IllegalArgumentException("Shire[GitTool]: No git repository found") return when (gitFunc) { GitFunction.Commit -> { commitChanges(project, repository, args.first() as String) } GitFunction.Push -> { pushChanges(project, repository).get().toString() } } } /** * * How to find code in IDEA: [GitCommand.Commit] */ fun commitChanges(project: Project, repository: GitRepository, commitMessage: String) { val root = repository.root val changeListManager = ChangeListManager.getInstance(project) val virtualFiles = changeListManager .allChanges .mapNotNull { it.virtualFile } .asSequence() val files = collectPathsFromFiles( project, virtualFiles ).toList() GitFileUtils.addPaths(project, root, files, false, false) var commitContext: CommitContext = CommitContext() val option: GitCommitOptions = GitCommitOptions(commitContext) // try { GitRepositoryCommitter(repository, option).commitStaged(commitMessage) // } catch (e: Exception) { // throw RuntimeException("Shire[GitTool]: Failed to commit changes") // } } private fun collectPathsFromFiles(project: Project, allFiles: Sequence): Sequence { val vcsManager = ProjectLevelVcsManager.getInstance(project) val changeListManager = ChangeListManager.getInstance(project) return allFiles .filter { file -> val actionExtension = getExtensionFor(project, vcsManager.getVcsFor(file)) actionExtension != null && changeListManager.getStatus(file).let { status -> if (file.isDirectory) actionExtension.isStatusForDirectoryAddition(status) else actionExtension.isStatusForAddition( status ) } } .map(VcsUtil::getFilePath) } private fun getExtensionFor(project: Project, vcs: AbstractVcs?) = if (vcs == null) null else ScheduleForAdditionActionExtension.EP_NAME.findFirstSafe { it.getSupportedVcs(project) == vcs } fun pushChanges(project: Project, repository: GitRepository): CompletableFuture { val progressIndicator = ObjectUtils.notNull(ProgressManager.getInstance().progressIndicator, EmptyProgressIndicator()) val future = CompletableFuture() val branch = repository.currentBranch ?: return CompletableFuture.failedFuture(ProcessCanceledException()) val pushTarget = GitPushSupport.getPushTargetIfExist(repository, branch) ?: return CompletableFuture.failedFuture( ProcessCanceledException() ) val gitPushSupport = DvcsUtil.getPushSupport(GitVcs.getInstance(project)) as? GitPushSupport ?: return CompletableFuture.failedFuture(ProcessCanceledException()) ProgressManager.getInstance().runProcessWithProgressAsynchronously( object : Task.Backgroundable(repository.project, DvcsBundle.message("push.process.pushing"), true) { override fun run(indicator: ProgressIndicator) { indicator.text = DvcsBundle.message("push.process.pushing") val pushSpec = PushSpec(GitPushSource.create(branch), pushTarget) val pushResult = GitPushOperation( repository.project, gitPushSupport, mapOf(repository to pushSpec), null, false, false ) .execute().results[repository] ?: error("Missing push result") check(pushResult.error == null) { GitBundle.message("push.failed.error.message", pushResult.error.orEmpty()) } } override fun onSuccess() { future.complete(pushTarget.branch) } override fun onThrowable(error: Throwable) { future.completeExceptionally(error) } override fun onCancel() { future.completeExceptionally(ProcessCanceledException()) } }, progressIndicator ) return future } } ================================================ FILE: toolsets/git/src/main/kotlin/com/phodal/shire/git/provider/GitQLDataProvider.kt ================================================ package com.phodal.shire.git.provider import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.Task import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator import com.intellij.openapi.project.Project import com.phodal.shirecore.ShireCoreBundle import com.phodal.shirecore.provider.shire.ShireQLDataProvider import com.phodal.shirecore.provider.shire.ShireQLDataType import com.phodal.shirecore.variable.vcs.GitEntity import com.phodal.shirecore.variable.vcs.ShireGitCommit import git4idea.GitCommit import git4idea.history.GitHistoryUtils import git4idea.repo.GitRepositoryManager import kotlinx.coroutines.future.await import kotlinx.coroutines.runBlocking import java.util.concurrent.CompletableFuture class GitQLDataProvider : ShireQLDataProvider { override fun lookupGitData(myProject: Project, dataTypes: List): Map?> { val result = mutableMapOf?>() dataTypes.forEach { when (it) { ShireQLDataType.GIT_COMMIT -> { val commits = buildCommits(myProject) result[it] = commits } ShireQLDataType.GIT_BRANCH -> TODO() ShireQLDataType.GIT_FILE_COMMIT -> TODO() ShireQLDataType.GIT_FILE_BRANCH -> TODO() else -> { result[it] = null } } } return result } private fun buildCommits(myProject: Project): List { val repository = GitRepositoryManager.getInstance(myProject).repositories.firstOrNull() ?: return emptyList() val branchName = repository.currentBranchName /** * Refs to [com.intellij.execution.process.OSProcessHandler.checkEdtAndReadAction], we should handle in this * way, another example can see in [git4idea.GitPushUtil.findOrPushRemoteBranch] */ val future = CompletableFuture>() val task = object : Task.Backgroundable(myProject, ShireCoreBundle.message("shire.ref.loading"), false) { override fun run(indicator: ProgressIndicator) { val commits: List = try { // in some case, maybe not repo or branch, so we should handle it GitHistoryUtils.history(project, repository.root, branchName) } catch (e: Exception) { emptyList() } future.complete(commits) } } ProgressManager.getInstance() .runProcessWithProgressAsynchronously(task, BackgroundableProcessIndicator(task)) val results: MutableList = mutableListOf() runBlocking { future.await().forEach { val commit = ShireGitCommit( it.id.asString(), it.author.name, it.author.email, it.authorTime, it.committer.name, it.committer.email, it.commitTime, it.fullMessage, it.fullMessage, ) results.add(commit) } } return results.toList() } } ================================================ FILE: toolsets/git/src/main/kotlin/com/phodal/shire/git/provider/GitRepositoryCommitter.kt ================================================ // Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.phodal.shire.git.provider import com.intellij.execution.process.ProcessOutputTypes import com.intellij.notification.NotificationAction import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.help.HelpManager import com.intellij.openapi.project.Project import com.intellij.openapi.util.Key import com.intellij.openapi.vcs.VcsException import com.intellij.openapi.vcs.changes.CommitContext import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.ThrowableConsumer import com.intellij.vcs.commit.CommitExceptionWithActions import com.intellij.vcs.commit.isAmendCommitMode import com.intellij.vcs.commit.isCleanupCommitMessage import com.intellij.vcs.log.VcsUser import git4idea.checkin.* import git4idea.commands.Git import git4idea.commands.GitCommand import git4idea.commands.GitLineHandler import git4idea.commands.GitLineHandlerListener import git4idea.i18n.GitBundle import git4idea.repo.GitRepository import org.jetbrains.annotations.NonNls import java.io.File import java.io.IOException import java.text.SimpleDateFormat import java.util.* data class GitCommitOptions( val isAmend: Boolean = false, val isSignOff: Boolean = false, val isSkipHooks: Boolean = false, val commitAuthor: VcsUser? = null, val commitAuthorDate: Date? = null, val isCleanupCommitMessage: Boolean = false, ) { constructor(context: CommitContext) : this( context.isAmendCommitMode, context.isSignOffCommit, context.isSkipHooks, context.commitAuthor, context.commitAuthorDate, context.isCleanupCommitMessage ) } internal class GitRepositoryCommitter(val repository: GitRepository, private val commitOptions: GitCommitOptions) { val project: Project get() = repository.project val root: VirtualFile get() = repository.root @Throws(VcsException::class) fun commitStaged(commitMessage: String) { runWithMessageFile(project, root, commitMessage) { messageFile -> commitStaged(messageFile) } } private fun runWithMessageFile( project: Project, root: VirtualFile, message: @NonNls String, task: ThrowableConsumer, ) { val messageFile = try { GitCheckinEnvironment.createCommitMessageFile(project, root, message) } catch (ex: IOException) { throw VcsException(GitBundle.message("error.commit.cant.create.message.file"), ex) } try { task.consume(messageFile) } finally { if (!messageFile.delete()) { logger().warn("Failed to remove temporary file: $messageFile") } } } @Throws(VcsException::class) fun commitStaged(messageFile: File) { val gpgProblemDetector = GitGpgProblemDetector() val emptyCommitProblemDetector = GitEmptyCommitProblemDetector() val handler = GitLineHandler(project, root, GitCommand.COMMIT) handler.setStdoutSuppressed(false) handler.addLineListener(gpgProblemDetector) handler.addLineListener(emptyCommitProblemDetector) handler.setCommitMessage(messageFile) handler.setCommitOptions(commitOptions) handler.endOptions() val command = Git.getInstance().runCommand(handler) try { command.throwOnError() } catch (e: VcsException) { if (gpgProblemDetector.isDetected) { throw GitGpgCommitException(e) } if (emptyCommitProblemDetector.isDetected) { throw VcsException(GitBundle.message("git.commit.nothing.to.commit.error.message")) } throw e } } } val COMMIT_DATE_FORMAT: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") private fun GitLineHandler.setCommitOptions(options: GitCommitOptions) { if (options.isAmend) addParameters("--amend") if (options.isSignOff) addParameters("--signoff") if (options.isSkipHooks) addParameters("--no-verify") if (options.isCleanupCommitMessage) addParameters("--cleanup=strip") options.commitAuthor?.let { addParameters("--author=$it") } options.commitAuthorDate?.let { addParameters("--date", COMMIT_DATE_FORMAT.format(it)) } } private fun GitLineHandler.setCommitMessage(messageFile: File) { addParameters("-F") addAbsoluteFile(messageFile) } private class GitGpgProblemDetector : GitLineHandlerListener { var isDetected = false private set override fun onLineAvailable(line: String, outputType: Key<*>) { if (outputType === ProcessOutputTypes.STDERR && line.contains(PATTERN)) { isDetected = true } } companion object { private const val PATTERN = "gpg failed to sign the data" } } private class GitEmptyCommitProblemDetector : GitLineHandlerListener { var isDetected = false private set override fun onLineAvailable(line: String, outputType: Key<*>) { if (outputType === ProcessOutputTypes.STDOUT && PATTERNS.any { line.startsWith(it, ignoreCase = true) }) { isDetected = true } } companion object { private val PATTERNS = listOf( "No changes", "no changes added to commit", "nothing added to commit", "nothing to commit" ) } } private class GitGpgCommitException(cause: VcsException) : VcsException(cause), CommitExceptionWithActions { override val actions: List get() = listOf(NotificationAction.createSimple(GitBundle.message("gpg.error.see.documentation.link.text")) { HelpManager.getInstance().invokeHelp(GitBundle.message("gpg.jb.manual.link")) }) } ================================================ FILE: toolsets/git/src/main/kotlin/com/phodal/shire/git/provider/GitRevisionProvider.kt ================================================ package com.phodal.shire.git.provider import com.intellij.codeInsight.completion.CompletionResultSet import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.icons.AllIcons import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.Task import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator import com.intellij.openapi.progress.runBlockingCancellable import com.intellij.openapi.project.Project import com.intellij.openapi.vcs.FilePath import com.intellij.openapi.vcs.changes.Change import com.intellij.openapi.vcs.changes.ChangeListManager import com.intellij.openapi.vcs.changes.CommitContext import com.intellij.openapi.vcs.changes.LocalChangeList import com.intellij.psi.PsiElement import com.intellij.vcs.commit.ChangeListCommitState import com.intellij.vcs.commit.LocalChangesCommitter import com.intellij.vcsUtil.VcsUtil import com.phodal.shire.git.VcsPrompting import com.phodal.shirecore.ShireCoreBundle import com.phodal.shirecore.provider.shire.RevisionProvider import git4idea.GitCommit import git4idea.GitRevisionNumber import git4idea.changes.GitCommittedChangeListProvider import git4idea.history.GitFileHistory import git4idea.history.GitHistoryUtils import git4idea.repo.GitRepositoryManager import kotlinx.coroutines.future.await import kotlinx.coroutines.runBlocking import java.util.concurrent.CompletableFuture class GitRevisionProvider : RevisionProvider { private val logger = logger() override fun fetchChanges(myProject: Project, revision: String): String? { val repository = GitRepositoryManager.getInstance(myProject).repositories.firstOrNull() ?: return null val future = CompletableFuture>() val task = object : Task.Backgroundable(myProject, ShireCoreBundle.message("shire.ref.loading"), false) { override fun run(indicator: ProgressIndicator) { val committedChangeList = GitCommittedChangeListProvider.getCommittedChangeList( myProject, repository.root, GitRevisionNumber(revision) )?.changes?.toList() future.complete(committedChangeList) } } ProgressManager.getInstance() .runProcessWithProgressAsynchronously(task, BackgroundableProcessIndicator(task)) return runBlocking { val changes = future.await() val diffContext = myProject.getService(VcsPrompting::class.java).prepareContext(changes) "\n```diff\n${diffContext}\n```\n" } } override fun fetchCompletions(project: Project, result: CompletionResultSet) { val repository = GitRepositoryManager.getInstance(project).repositories.firstOrNull() ?: return val branchName = repository.currentBranchName /** * Refs to [com.intellij.execution.process.OSProcessHandler.checkEdtAndReadAction], we should handle in this * way, another example can see in [git4idea.GitPushUtil.findOrPushRemoteBranch] */ val future = CompletableFuture>() val task = object : Task.Backgroundable(project, ShireCoreBundle.message("shire.ref.loading"), false) { override fun run(indicator: ProgressIndicator) { val commits: List = try { // in some case, maybe not repo or branch, so we should handle it GitHistoryUtils.history(project, repository.root, branchName) } catch (e: Exception) { logger.error("Failed to fetch commits", e) emptyList() } future.complete(commits) } } ProgressManager.getInstance() .runProcessWithProgressAsynchronously(task, BackgroundableProcessIndicator(task)) runBlockingCancellable { val commits = future.await() commits.forEach { val element = LookupElementBuilder.create(it.id.toShortString()) .withIcon(AllIcons.Vcs.Branch) .withPresentableText(it.fullMessage) .withTypeText(it.id.toShortString(), true) result.addElement(element) } } } override fun commitCode(project: Project, commitMessage: String): String { val changeListManager = ChangeListManager.getInstance(project) changeListManager.changeLists.forEach { val list: LocalChangeList = changeListManager.getChangeList(it.id) ?: return@forEach val commitState = ChangeListCommitState(list, list.changes.toList(), commitMessage) val committer = LocalChangesCommitter(project, commitState, CommitContext()) committer.runCommit("Commit", false) } return "Committing..." } override fun countHistoryChange(project: Project, element: PsiElement): Int { val file = element.containingFile.virtualFile ?: return 0 val filePath: FilePath = VcsUtil.getFilePath(file) return try { GitFileHistory.collectHistory(project, filePath).size } catch (e: Exception) { logger.error("Failed to count history changes for file: ${file.path}", e) 0 } } } ================================================ FILE: toolsets/git/src/main/kotlin/com/phodal/shire/git/provider/GitToolchainVariableProvider.kt ================================================ package com.phodal.shire.git.provider import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.openapi.vcs.VcsDataKeys import com.intellij.openapi.vcs.changes.Change import com.intellij.openapi.vcs.changes.CurrentContentRevision import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement import com.intellij.vcs.commit.CommitWorkflowUi import com.intellij.vcs.log.VcsFullCommitDetails import com.intellij.vcs.log.VcsLogDataKeys import com.intellij.vcs.log.VcsLogFilterCollection import com.intellij.vcs.log.VcsLogProvider import com.intellij.vcs.log.impl.VcsProjectLog import com.intellij.vcs.log.visible.filters.VcsLogFilterObject import com.phodal.shirecore.provider.variable.ToolchainVariableProvider import com.phodal.shirecore.provider.variable.model.ToolchainVariable import com.phodal.shirecore.provider.variable.model.toolchain.VcsToolchainVariable import com.phodal.shire.git.VcsPrompting import com.phodal.shirecore.variable.template.VariableActionEventDataHolder import java.awt.EventQueue.invokeAndWait class GitToolchainVariableProvider : ToolchainVariableProvider { private val logger = logger() override fun isResolvable(variable: ToolchainVariable, psiElement: PsiElement?, project: Project): Boolean { return when (variable) { VcsToolchainVariable.CurrentChanges -> true VcsToolchainVariable.HistoryCommitMessages -> true VcsToolchainVariable.CurrentBranch -> true VcsToolchainVariable.Diff -> true else -> false } } override fun resolve( variable: ToolchainVariable, project: Project, editor: Editor, psiElement: PsiElement?, ): ToolchainVariable { when (variable) { VcsToolchainVariable.CurrentChanges -> { val commitWorkflowUi = getCommitWorkflowUi() if (commitWorkflowUi !is CommitWorkflowUi) { logger.warn("Cannot get commit workflow UI, you may not be in a commit workflow.") return variable } var changes: List? = null invokeAndWait { changes = getDiff(commitWorkflowUi) } if (changes == null) { logger.warn("Cannot get changes.") return variable } val diffContext = project.getService(VcsPrompting::class.java).prepareContext(changes!!) if (diffContext.isEmpty() || diffContext == "\n") { logger.warn("Diff context is empty or cannot get enough useful context.") return variable } variable.value = diffContext return variable } VcsToolchainVariable.CurrentBranch -> { val logProviders = VcsProjectLog.getLogProviders(project) val entry = logProviders.entries.firstOrNull() ?: return variable val logProvider = entry.value val branch = logProvider.getCurrentBranch(entry.key) ?: return variable variable.value = branch } VcsToolchainVariable.HistoryCommitMessages -> { val exampleCommitMessages = getHistoryCommitMessages(project) if (exampleCommitMessages != null) { variable.value = exampleCommitMessages } } VcsToolchainVariable.Diff -> { val dataContext = VariableActionEventDataHolder.getData()?.dataContext variable.value = analysisLog(dataContext, project) } } return variable } private fun analysisLog(dataContext: DataContext?, project: Project): String { if (dataContext == null) { return "" } val vcsLog = dataContext.getData(VcsLogDataKeys.VCS_LOG) ?: return "" val details: List = vcsLog.selectedDetails.toList() val selectList = dataContext.getData(VcsDataKeys.SELECTED_CHANGES).orEmpty().toList() val vcsPrompting = project.getService(VcsPrompting::class.java) val fullChangeContent = vcsPrompting.buildDiffPrompt(details, selectList.toList(), project) return fullChangeContent ?: "" } /** * Finds example commit messages based on the project's VCS log, takes the first three commits. * If the no user or user has committed anything yet, the current branch name is used instead. * * @param project The project for which to find example commit messages. * @return A string containing example commit messages, or null if no example messages are found. */ private fun getHistoryCommitMessages(project: Project): String? { val logProviders = VcsProjectLog.getLogProviders(project) val entry = logProviders.entries.firstOrNull() ?: return null val logProvider = entry.value val branch = logProvider.getCurrentBranch(entry.key) ?: return null val user = logProvider.getCurrentUser(entry.key) val logFilter = if (user != null) { VcsLogFilterObject.collection(VcsLogFilterObject.fromUser(user, setOf())) } else { VcsLogFilterObject.collection(VcsLogFilterObject.fromBranch(branch)) } return collectExamples(logProvider, entry.key, logFilter) } /** * Collects examples from the VcsLogProvider based on the provided filter. * * @param logProvider The VcsLogProvider used to retrieve commit information. * @param root The root VirtualFile of the project. * @param filter The VcsLogFilterCollection used to filter the commits. * @return A string containing the collected examples, or null if no examples are found. */ private fun collectExamples( logProvider: VcsLogProvider, root: VirtualFile, filter: VcsLogFilterCollection, ): String? { val commits = logProvider.getCommitsMatchingFilter(root, filter, 3) if (commits.isEmpty()) return null val builder = StringBuilder("") val commitIds = commits.map { it.id.asString() } logProvider.readMetadata(root, commitIds) { val shortMsg = it.fullMessage.split("\n").firstOrNull() ?: it.fullMessage builder.append(shortMsg).append("\n") } return builder.toString() } private fun getDiff(commitWorkflowUi: CommitWorkflowUi): List? { val changes = commitWorkflowUi.getIncludedChanges() val unversionedFiles = commitWorkflowUi.getIncludedUnversionedFiles() val changeList = unversionedFiles.map { Change(null, CurrentContentRevision(it)) } if (changes.isNotEmpty() || changeList.isNotEmpty()) { return changes + changeList } return null } } ================================================ FILE: toolsets/git/src/main/resources/com.phodal.shire.git.xml ================================================ ================================================ FILE: toolsets/httpclient/README.md ================================================ # HttpClient Module HttpClient - convert cURL to OkHttp Request - execute HttpRequest code ================================================ FILE: toolsets/httpclient/src/main/kotlin/com/phodal/shire/httpclient/HttpClientFileRunService.kt ================================================ package com.phodal.shire.httpclient import com.intellij.execution.Executor import com.intellij.execution.RunManager import com.intellij.execution.RunnerAndConfigurationSettings import com.intellij.execution.actions.ConfigurationContext import com.intellij.execution.configurations.RunConfiguration import com.intellij.execution.configurations.RunProfile import com.intellij.httpClient.http.request.HttpRequestPsiFile import com.intellij.httpClient.http.request.run.HttpRequestExecutorExtensionFactory import com.intellij.httpClient.http.request.run.HttpRequestRunConfigurationExecutor import com.intellij.httpClient.http.request.run.config.HttpRequestRunConfiguration import com.intellij.httpClient.http.request.run.config.HttpRequestRunConfigurationType import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement import com.intellij.psi.PsiManager import com.phodal.shirecore.provider.shire.FileRunService class HttpClientFileRunService : FileRunService { override fun isApplicable(project: Project, file: VirtualFile): Boolean { return file.extension == "http" } override fun runConfigurationClass(project: Project): Class { return HttpRequestRunConfiguration::class.java } override fun runFile(project: Project, virtualFile: VirtualFile, psiElement: PsiElement?): String? { val runner: RunnerAndConfigurationSettings = runReadAction { val psiFile = PsiManager.getInstance(project).findFile(virtualFile) ?: return@runReadAction null ConfigurationContext(psiFile).configurationsFromContext?.firstOrNull()?.configurationSettings } ?: return null val factory = HttpRequestRunConfigurationType.getInstance().configurationFactories[0] val configuration = HttpRequestRunConfiguration(project, factory, "HttpRequest") val runManager: RunManager = RunManager.getInstance(project) configuration.settings.filePath = virtualFile.path runManager.setUniqueNameIfNeeded(configuration) runner.isTemporary = true runManager.addConfiguration(runner) val selectedRunner = runManager.selectedConfiguration if ((selectedRunner == null || selectedRunner.isTemporary) && runManager.shouldSetRunConfigurationFromContext()) { runManager.selectedConfiguration = runner } val executor: Executor = HttpRequestExecutorExtensionFactory.getRunExtension().executor ?: return null HttpRequestRunConfigurationExecutor.getInstance().execute( project, runner, executor ) return "Run Success" } override fun createConfiguration(project: Project, virtualFile: VirtualFile): RunConfiguration? { val factory = HttpRequestRunConfigurationType.getInstance().configurationFactories[0] val configuration = HttpRequestRunConfiguration(project, factory, "HttpRequest") configuration.settings.filePath = virtualFile.path return configuration } } ================================================ FILE: toolsets/httpclient/src/main/kotlin/com/phodal/shire/httpclient/converter/CUrlConverter.kt ================================================ package com.phodal.shire.httpclient.converter import com.intellij.httpClient.execution.RestClientRequest import com.intellij.httpClient.http.request.HttpRequestHeaderFields import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody object CUrlConverter { fun convert(request: RestClientRequest): Request { val builder = Request.Builder() builder.url(request.buildFullUrl()) request.headers.forEach { try { builder.header(it.key, it.value) } catch (e: IllegalArgumentException) { // ignore } } val body = request.textToSend val mediaType = request .getHeadersValue(HttpRequestHeaderFields.CONTENT_TYPE) ?.firstOrNull() ?.toMediaTypeOrNull() when (request.httpMethod) { "GET" -> builder.get() "POST" -> builder.post(body.toRequestBody(mediaType)) "PUT" -> builder.put(body.toRequestBody(mediaType)) "DELETE" -> builder.delete(body.toRequestBody(mediaType)) else -> builder.method(request.httpMethod, body.toRequestBody(mediaType)) } return builder.build() } } ================================================ FILE: toolsets/httpclient/src/main/kotlin/com/phodal/shire/httpclient/converter/RestClientUtil.kt ================================================ package com.phodal.shire.httpclient.converter import com.intellij.httpClient.execution.RestClientRequest import com.intellij.httpClient.execution.auth.HttpClientAuthData import com.intellij.httpClient.execution.auth.HttpRequestAuthCredentials import com.intellij.httpClient.execution.auth.HttpRequestAuthScope import com.intellij.httpClient.execution.auth.HttpRequestCommonAuthSchemes import com.intellij.httpClient.http.request.* import com.intellij.httpClient.http.request.psi.impl.HttpRequestPsiImplUtil import com.intellij.ide.scratch.ScratchFileService import com.intellij.ide.scratch.ScratchRootType import com.intellij.openapi.command.UndoConfirmationPolicy import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.project.Project import com.intellij.openapi.ui.Messages import com.intellij.openapi.util.text.StringUtil import com.intellij.psi.NavigatablePsiElement import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiManager import com.intellij.ui.UIBundle import com.intellij.util.PathUtil import com.oracle.truffle.js.runtime.objects.DefaultESModuleLoader import java.io.IOException import java.net.URL fun RestClientRequest.buildFullUrl(): URL { val url = URL(getFullUri(this.url, this)) if (url.userInfo == null) { return url } val userInfo = url.userInfo ?: throw IllegalStateException("getUserInfo(...) returned null") val usernameAndPassword = userInfo.split(":") val httpRequestAuthScope = HttpRequestAuthScope(HttpRequestCommonAuthSchemes.BASIC) val username = usernameAndPassword[0] val password = usernameAndPassword.getOrNull(1) ?: "" this.authData = HttpClientAuthData(httpRequestAuthScope, HttpRequestAuthCredentials.UsernamePassword(username, password)) val fullUrl = buildString { url.protocol?.let { append(it).append(HttpRequestPsiImplUtil.SCHEME_SEPARATOR) } url.host?.let { append(it) } val port = url.port.takeIf { it > 0 } port?.let { append(":").append(it) } url.path?.let { append(DefaultESModuleLoader.SLASH).append(it) } } return URL(fullUrl) } fun getFullUri(uri: String, request: RestClientRequest): String { var uriStr = uri if (request.parametersEnabled) { val query = request.createQueryString() if (StringUtil.isNotEmpty(query)) { uriStr = uriStr + (if (uriStr.contains("?")) "&" else "?") + query } } return uriStr } fun createAndOpenScratchFile(project: Project, request: RestClientRequest, comment: String?) { val fileName = PathUtil.makeFileName("rest-api", HttpRequestFileType.INSTANCE.defaultExtension) try { WriteCommandAction.writeCommandAction(project).withName("Create HTTP Request scratch file") .withGlobalUndo() .shouldRecordActionForActiveDocument(false) .withUndoConfirmationPolicy(UndoConfirmationPolicy.REQUEST_CONFIRMATION) .compute { val convertedRequest: String val fileService = ScratchFileService.getInstance() try { val file = fileService.findFile( ScratchRootType.getInstance(), fileName, ScratchFileService.Option.create_new_always ) fileService.scratchesMapping.setMapping(file, HttpRequestLanguage.INSTANCE) val psiFile = PsiManager.getInstance(project).findFile(file) as? HttpRequestPsiFile ?: throw Exception("Failed to create HTTP Request scratch file") val manager = PsiDocumentManager.getInstance(project) val document = manager.getDocument(psiFile) ?: throw Exception("Created HTTP Request scratch file is invalid") convertedRequest = if (comment != null) { comment + HttpRequestPsiConverter.toPsiHttpRequest(request) } else { HttpRequestPsiConverter.toPsiHttpRequest(request) } document.insertString(document.textLength, convertedRequest) manager.commitDocument(document) val updated = HttpRequestPsiUtils.getRequestBlocks(psiFile) if (updated.isNotEmpty()) { return@compute updated[updated.size - 1] } return@compute psiFile } catch (e: IOException) { throw Exception("Could not create file: $e.") } }?.navigate(true) } catch (e: Exception) { Messages.showErrorDialog(project, e.message, UIBundle.message("error.dialog.title", *arrayOfNulls(0))) } } ================================================ FILE: toolsets/httpclient/src/main/kotlin/com/phodal/shire/httpclient/handler/CUrlHttpHandler.kt ================================================ package com.phodal.shire.httpclient.handler import com.intellij.execution.console.ConsoleViewWrapperBase import com.intellij.execution.ui.ConsoleViewContentType import com.intellij.execution.ui.RunContentManager import com.intellij.httpClient.converters.curl.parser.CurlParser import com.intellij.httpClient.execution.RestClientRequest import com.intellij.httpClient.http.request.HttpRequestCollectionProvider import com.intellij.httpClient.http.request.notification.HttpClientWhatsNewContentService import com.intellij.ide.scratch.ScratchUtil import com.intellij.ide.scratch.ScratchesSearchScope import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScopesCore import com.intellij.psi.search.ProjectScope import com.intellij.psi.util.PsiUtilCore import com.phodal.shire.httpclient.converter.CUrlConverter import com.phodal.shirecore.provider.http.HttpHandler import com.phodal.shirecore.provider.http.HttpHandlerType import com.phodal.shire.json.ShireEnvReader import com.phodal.shire.json.ShireEnvVariableFiller import okhttp3.OkHttpClient import com.intellij.openapi.application.ApplicationManager import okhttp3.Request class CUrlHttpHandler : HttpHandler { override fun isApplicable(type: HttpHandlerType): Boolean = type == HttpHandlerType.CURL override fun execute( project: Project, content: String, variablesName: Array, variableTable: MutableMap, ): String? { val processVariables: Map = variableTable.mapValues { it.value.toString() } var filledShell: String = content val client = OkHttpClient() var restClientRequest: RestClientRequest? = null val request = ApplicationManager.getApplication().executeOnPooledThread { runReadAction { val scope = getSearchScope(project) val envName = ShireEnvReader.getAllEnvironments(project, scope).firstOrNull() ?: ShireEnvReader.DEFAULT_ENV_NAME val envObject = ShireEnvReader.getEnvObject(envName, scope, project) val enVariables: List> = ShireEnvReader.fetchEnvironmentVariables(envName, scope) filledShell = ShireEnvVariableFiller.fillVariables(content, enVariables, envObject, processVariables) restClientRequest = CurlParser().parseToRestClientRequest(filledShell) CUrlConverter.convert(restClientRequest!!) } }.get() if (restClientRequest == null || request == null) { return null } restClientRequest?.let { showLogInConsole(project, filledShell, it) } val response = client.newCall(request).execute() return response.body?.string() } private fun showLogInConsole(project: Project, content: String, request: RestClientRequest) { val contentManager = RunContentManager.getInstance(project) val console = contentManager.selectedContent?.executionConsole as? ConsoleViewWrapperBase ?: return ///----- console.print("--------------------\n", ConsoleViewContentType.LOG_INFO_OUTPUT) /// original content console.print(content, ConsoleViewContentType.LOG_INFO_OUTPUT) /// new line console.print("\n", ConsoleViewContentType.LOG_INFO_OUTPUT) /// converted content console.print(request.httpMethod + " " + request.url, ConsoleViewContentType.LOG_INFO_OUTPUT) /// headers request.headers.forEach { console.print("\n${it.key}: ${it.value}", ConsoleViewContentType.LOG_INFO_OUTPUT) } /// request.body console.print("\n" + request.textToSend.toString(), ConsoleViewContentType.LOG_INFO_OUTPUT) console.print("\n--------------------\n", ConsoleViewContentType.LOG_INFO_OUTPUT) } private fun getSearchScope(project: Project, contextFile: PsiFile? = null): GlobalSearchScope { val projectScope = ProjectScope.getContentScope(project) if (contextFile == null) return projectScope val context = PsiUtilCore.getVirtualFile(contextFile) val whatsNewFile = HttpClientWhatsNewContentService.getInstance().getWhatsNewFileIfCreated() if (contextFile.virtualFile == whatsNewFile) { HttpRequestCollectionProvider.getCollectionFolder()?.let { folder -> return GlobalSearchScopesCore.directoryScope(project, folder, true) } } if (context != null && !ScratchUtil.isScratch(context) && !projectScope.contains(context)) { contextFile.parent?.let { parent -> return GlobalSearchScopesCore.directoryScope(parent, true) } } if (ScratchUtil.isScratch(context)) { return projectScope.uniteWith(ScratchesSearchScope.getScratchesScope(project)) } return projectScope } } ================================================ FILE: toolsets/httpclient/src/main/resources/com.phodal.shire.httpclient.xml ================================================ ================================================ FILE: toolsets/httpclient/src/test/kotlin/com/phodal/shire/httpclient/converter/CUrlConverterTest.kt ================================================ package com.phodal.shire.httpclient.converter import com.intellij.httpClient.converters.curl.parser.CurlParser import com.intellij.testFramework.fixtures.BasePlatformTestCase import com.phodal.shire.json.ShireEnvReader import com.phodal.shire.json.ShireEnvVariableFiller class CUrlConverterTest : BasePlatformTestCase() { fun testShouldConvertCurlToRestClientRequest() { // Given val content = "curl -X POST http://example.com/api/resource -d 'data'" val request = CurlParser().parseToRestClientRequest(content) // When val restClientRequest = CUrlConverter.convert(request = request) // Then assertEquals("http://example.com/api/resource", restClientRequest.url.toString()) } fun testShouldCallHttpClientWithConvertedRequest() { // Given val content = "curl --location 'https://open.bigmodel.cn/api/paas/v4/chat/completions' \\\n" + "--header 'Authorization: Bearer \$YourKey' \\\n" + "--header 'Content-Type: application/json' \\\n" + "--data '{\n" + " \"model\": \"glm-4\",\n" + " \"messages\": [\n" + " {\n" + " \"role\": \"user\",\n" + " \"content\": \"你好\"\n" + " }\n" + " ]\n" + "}'" val req = CurlParser().parseToRestClientRequest(content) // When val request = CUrlConverter.convert(request = req) // Then assertEquals("https://open.bigmodel.cn/api/paas/v4/chat/completions", request.url.toString()) } fun testShouldHandleForVariable() { val jsonEnv = """ { "development": { "name": "Phodal" } } """.trimIndent() val envPsiFile = myFixture.addFileToProject("demo.shireEnv.json", jsonEnv) val variables = listOf(setOf("development")) val obj = ShireEnvReader.getEnvObject("development", envPsiFile) // Given val messageBody = "Hello \${name}, my name is \${myName}!" // When val result = ShireEnvVariableFiller.fillVariables(messageBody, variables, obj, mapOf("myName" to "Shire")) // Then assertEquals("Hello Phodal, my name is Shire!", result) } } ================================================ FILE: toolsets/httpclient/src/test/kotlin/com/phodal/shirecore/agent/agenttool/browse/BrowseToolTest.kt ================================================ package com.phodal.shirecore.agent.agenttool.browse import junit.framework.TestCase.assertNotNull import org.junit.Test class BrowseToolTest { @Test fun should_parseHtml_correctly() { // given val url = "https://shire.phodal.com" // when val result = BrowseTool.parse(url).body // then println(result) assertNotNull(result) } } ================================================ FILE: toolsets/httpclient/src/test/resources/META-INF/plugin.xml ================================================ com.phodal.shire Shire - AI Coding Agent Language Phodal Huang com.intellij.modules.platform messages.ShireMainBundle ================================================ FILE: toolsets/mermaid/src/main/kotlin/com/phodal/shire/mermaid/provider/MermaidSketchProvider.kt ================================================ package com.phodal.shire.mermaid.provider import com.intellij.lang.Language import com.intellij.openapi.fileEditor.FileEditorProvider import com.intellij.openapi.fileEditor.TextEditorWithPreview import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.readText import com.intellij.testFramework.LightVirtualFile import com.intellij.ui.dsl.builder.Align import com.intellij.ui.dsl.builder.panel import com.intellij.util.ui.JBUI import com.phodal.shirecore.provider.sketch.ExtensionLangSketch import com.phodal.shirecore.provider.sketch.LanguageSketchProvider import javax.swing.JComponent import javax.swing.JPanel class MermaidSketchProvider : LanguageSketchProvider { override fun isSupported(lang: String): Boolean { return lang == "mermaid" || lang == "mmd" } override fun create(project: Project, content: String): ExtensionLangSketch { val file = LightVirtualFile("mermaid.mermaid", content) return MermaidSketch(project, file) } } class MermaidSketch(project: Project, private val virtualFile: VirtualFile) : ExtensionLangSketch { private var mainPanel: JPanel init { val editor = getEditorProvider().createEditor(project, virtualFile) as TextEditorWithPreview mainPanel = panel { row { cell(editor.component).align(Align.FILL) } }.apply { border = JBUI.Borders.empty(0, 10) } } private fun getEditorProvider(): FileEditorProvider = FileEditorProvider.EP_FILE_EDITOR_PROVIDER.extensionList.firstOrNull { it.javaClass.simpleName == "MermaidEditorWithPreviewProvider" } ?: TextEditorProvider.getInstance() override fun getExtensionName(): String = "mermaid" override fun getViewText(): String = virtualFile.readText() override fun updateViewText(text: String) {} override fun getComponent(): JComponent = mainPanel override fun updateLanguage(language: Language?, originLanguage: String?) {} override fun dispose() {} } ================================================ FILE: toolsets/mermaid/src/main/resources/com.phodal.shire.mermaid.xml ================================================ ================================================ FILE: toolsets/mock/README.md ================================================ # WireMock Start WireMock with IntelliJ IDEA: ```bash "/Users/phodal/Applications/IntelliJ IDEA Ultimate.app/Contents/jbr/Contents/Home/bin/java" -jar "/Users/phodal/Library/Application Support/JetBrains/IntelliJIdea2024.2/plugins/wiremock/server/wiremock-standalone-rt.jar" --port 8080 --root-dir /var/folders/rt/gw2rs2td209ck8nlqdlt_v8m0000gn/T/wiremock-run-4031184994358890732 --disable-banner The WireMock server is started ..... version: 3.5.2 port: 8080 enable-browser-proxying: false disable-banner: true no-request-journal: false verbose: false extensions: response-template,webhook ``` ================================================ FILE: toolsets/mock/src/main/kotlin/com/phodal/shire/mock/provider/WiremockFunction.kt ================================================ package com.phodal.shire.mock.provider enum class WiremockFunction(val funName: String) { Mock("mock") ; companion object { fun fromString(value: String): WiremockFunction? { return entries.firstOrNull { it.funName == value } } } } ================================================ FILE: toolsets/mock/src/main/kotlin/com/phodal/shire/mock/provider/WiremockFunctionProvider.kt ================================================ package com.phodal.shire.mock.provider import com.intellij.execution.RunManager import com.intellij.execution.actions.ConfigurationContext import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.phodal.shirecore.provider.function.ToolchainFunctionProvider import com.phodal.shirecore.runner.ConfigurationRunner class WiremockFunctionProvider : ToolchainFunctionProvider, ConfigurationRunner { override fun isApplicable(project: Project, funcName: String): Boolean { return WiremockFunction.values().any { it.funName == funcName } } override fun execute(project: Project, funcName: String, args: List, allVariables: Map): Any { val wiremockFunction = WiremockFunction.fromString(funcName) ?: throw IllegalArgumentException("Shire[Wiremock]: Invalid Wiremock function name") return when (wiremockFunction) { WiremockFunction.Mock -> { if (args.isEmpty()) { return "ShireError[Wiremock]: No args found" } val mockFilepath = args.first() val mockFile = project.baseDir.findFileByRelativePath(mockFilepath.toString()) ?: throw IllegalArgumentException("ShireError[Wiremock]: No file found: $mockFilepath") val jsonFile = runReadAction { PsiManager.getInstance(project).findFile(mockFile) } ?: throw IllegalArgumentException("ShireError[Wiremock]: No JsonFile found: $mockFilepath") runMock(project, jsonFile) } } } private fun runMock(project: Project, configFile: PsiFile): Any { val configurationSettings = runReadAction { ConfigurationContext(configFile).configurationsFromContext?.firstOrNull()?.configurationSettings } ?: throw IllegalArgumentException("ShireError[Wiremock]: Please install Wiremock plugin") if (!configurationSettings.name.startsWith("WireMock")) { throw IllegalArgumentException("ShireError[Wiremock]: No a valid WireMock configure found") } val runManager = RunManager.getInstance(project) // java.lang.Throwable: WireMock.WireMock mock_v0-stubs.json must be added before selecting // at com.intellij.openapi.diagnostic.Logger.error(Logger.java:376) // at com.intellij.execution.impl.RunManagerImpl.setSelectedConfiguration(Run runManager.addConfiguration(configurationSettings) runManager.selectedConfiguration = configurationSettings configurationSettings.isActivateToolWindowBeforeRun = true configurationSettings.isFocusToolWindowBeforeRun = true configurationSettings.isTemporary = true val runContext = createRunContext() executeRunConfigurations(null, configurationSettings, runContext, null, null) return "Done" } } ================================================ FILE: toolsets/mock/src/main/resources/com.phodal.shire.mock.xml ================================================ ================================================ FILE: toolsets/openrewrite/README.md ================================================ # OpenRewrite Module Since JetBrains don't provide the OpenRewrite API, we use reflection to access the OpenRewrite API. This means that the OpenRewrite module is not guaranteed to work with all versions of OpenRewrite. - [com.intellij.openRewrite.OpenRewriteFileService] - [com.intellij.openRewrite.run.OpenRewriteRunConfiguration] - [com.intellij.openRewrite.run.OpenRewriteRunConfigurationType] - [com.intellij.openRewrite.recipe.OpenRewriteRecipeService] ================================================ FILE: toolsets/openrewrite/src/main/kotlin/com/phodal/shire/openrewrite/OpenRewriteFileRunService.kt ================================================ package com.phodal.shire.openrewrite import com.intellij.execution.ExecutionManager import com.intellij.execution.RunManager import com.intellij.execution.configurations.ConfigurationTypeUtil import com.intellij.execution.configurations.RunConfiguration import com.intellij.execution.configurations.RunProfile import com.intellij.execution.executors.DefaultRunExecutor import com.intellij.execution.runners.ExecutionEnvironmentBuilder import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.phodal.shirecore.provider.shire.FileRunService class OpenRewriteFileRunService : FileRunService { override fun isApplicable(project: Project, file: VirtualFile): Boolean { return file.extension == "yml" && isOpenWriteFile(project, file) } private fun isOpenWriteFile(project: Project, file: VirtualFile): Boolean { try { val clazz = Class.forName("com.intellij.openRewrite.OpenRewriteFileService") val getInstanceMethod = clazz.getDeclaredMethod("getInstance") val isRecipeMethod = clazz.getDeclaredMethod("isRecipe", PsiFile::class.java) val fileService = getInstanceMethod.invoke(null) val psiFile = runReadAction { PsiManager.getInstance(project).findFile(file) } ?: return false isRecipeMethod.isAccessible = true val result = isRecipeMethod.invoke(fileService, psiFile) as Boolean return result } catch (e: Exception) { return false } } override fun runConfigurationClass(project: Project): Class? = null override fun runFile(project: Project, virtualFile: VirtualFile, psiElement: PsiElement?): String? { if (!isOpenWriteFile(project, virtualFile)) { return "" } val runManager = RunManager.getInstance(project) val allSettings = runManager.allSettings val workingPath = virtualFile.parent.path var settings = allSettings.firstOrNull { try { val config = it.configuration val configClass = config::class.java if (configClass.name == "com.intellij.openRewrite.run.OpenRewriteRunConfiguration") { val getExpandedWorkingDirectoryMethod = configClass.getMethod("getExpandedWorkingDirectory") val workingDirectory = getExpandedWorkingDirectoryMethod.invoke(config) as? String workingDirectory == workingPath } else { false } } catch (e: Exception) { false } } val list = try { getRecipeDescriptors(project, virtualFile) ?: return null } catch (e: Exception) { return null } if (list.isEmpty()) return null if (settings == null) { try { val configurationType = ConfigurationTypeUtil.findConfigurationType("OpenRewriteRunConfigurationType")!! settings = runManager.createConfiguration("", configurationType.configurationFactories[0]) val configuration = settings.configuration if (configuration.javaClass.name == "com.intellij.openRewrite.run.OpenRewriteRunConfiguration") { configureOpenRewrite(configuration, project, virtualFile, list) } runManager.setUniqueNameIfNeeded(settings) runManager.setTemporaryConfiguration(settings) } catch (e: Exception) { return null } } val builder = ExecutionEnvironmentBuilder.createOrNull(DefaultRunExecutor.getRunExecutorInstance(), settings) builder?.let { ExecutionManager.getInstance(project).restartRunProfile(it.build()) } return "" } private fun configureOpenRewrite( configuration: RunConfiguration, project: Project, virtualFile: VirtualFile, list: java.util.LinkedHashMap<*, *>, ) { val directoryMethod = configuration::class.java.getMethod("setWorkingDirectory", String::class.java) val projectRoot = project.basePath ?: "" directoryMethod.invoke(configuration, projectRoot) val setConfigLocationMethod = configuration::class.java.getMethod("setConfigLocation", String::class.java) setConfigLocationMethod.invoke(configuration, virtualFile.path) val setActiveRecipesMethod = configuration::class.java.getMethod("setActiveRecipes", String::class.java) setActiveRecipesMethod.invoke(configuration, list.keys.first()) val nameMethod = configuration::class.java.getMethod("setGeneratedName") nameMethod.invoke(configuration) } private fun getRecipeDescriptors(project: Project, virtualFile: VirtualFile): LinkedHashMap<*, *>? { val clazz = Class.forName("com.intellij.openRewrite.recipe.OpenRewriteRecipeService") val getInstanceMethod = clazz.getDeclaredMethod("getInstance", Project::class.java) val recipeService = getInstanceMethod.invoke(null, project) val OpenRewriteType = Class.forName("com.intellij.openRewrite.recipe.OpenRewriteType").getEnumConstants()[0] val method = clazz.getDeclaredMethod("getLocalDescriptors", PsiFile::class.java, OpenRewriteType::class.java) method.isAccessible = true val psiFile = runReadAction { PsiManager.getInstance(project).findFile(virtualFile) } ?: return null val list = runReadAction { val type = OpenRewriteType::class.java.enumConstants[0] method.invoke(recipeService, psiFile, type) as LinkedHashMap<*, *> } return list } } ================================================ FILE: toolsets/openrewrite/src/main/resources/com.phodal.shire.openrewrite.xml ================================================ ================================================ FILE: toolsets/plantuml/README.md ================================================ # PlantUml Module Todos: - [ ] Export images to a specific directory ================================================ FILE: toolsets/plantuml/src/main/kotlin/com/phodal/shire/plantuml/PlantUmlSketchProvider.kt ================================================ package com.phodal.shire.plantuml import com.intellij.lang.Language import com.intellij.openapi.fileEditor.TextEditor import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.testFramework.LightVirtualFile import com.intellij.ui.dsl.builder.panel import com.intellij.util.ui.JBUI import com.phodal.shirecore.provider.sketch.ExtensionLangSketch import com.phodal.shirecore.provider.sketch.LanguageSketchProvider import org.plantuml.idea.preview.PlantUmlPreviewPanel import org.plantuml.idea.preview.editor.PlantUmlPreviewEditor import org.plantuml.idea.preview.editor.PlantUmlSplitEditor import org.plantuml.idea.preview.editor.SplitFileEditor import org.plantuml.idea.rendering.LazyApplicationPoolExecutor import org.plantuml.idea.rendering.RenderCommand import org.plantuml.idea.settings.PlantUmlSettings import javax.swing.JComponent import javax.swing.JPanel class PlantUmlSketchProvider : LanguageSketchProvider { override fun isSupported(lang: String): Boolean { return lang == "plantuml" || lang == "puml" || lang == "uml" } override fun create(project: Project, content: String): ExtensionLangSketch { val virtualFile = LightVirtualFile("plantuml.puml", content) return PlantUmlSketch(project, virtualFile) } } class PlantUmlSketch(private val project: Project, private val virtualFile: VirtualFile) : ExtensionLangSketch { private var mainPanel: JPanel private var umlPreviewEditor: PlantUmlPreviewEditor init { val editor = TextEditorProvider.getInstance().createEditor(project, virtualFile) as TextEditor umlPreviewEditor = PlantUmlPreviewEditor(virtualFile, project) umlPreviewEditor.editor = editor.editor val splitEditor = PlantUmlSplitEditor(editor, umlPreviewEditor) splitEditor.component.preferredSize = null mainPanel = panel { row { cell(editor.component) } row { cell(splitEditor.component) } }.apply { border = JBUI.Borders.empty(0, 10) } PlantUmlSettings.getInstance().previewSettings.splitEditorLayout = SplitFileEditor.SplitEditorLayout.SECOND } override fun doneUpdateText(text: String) { (umlPreviewEditor.component as PlantUmlPreviewPanel).processRequest(LazyApplicationPoolExecutor.Delay.NOW, RenderCommand.Reason.FILE_SWITCHED) } override fun getExtensionName(): String { return "plantuml" } override fun getViewText(): String { return virtualFile.inputStream.bufferedReader().use { it.readText() } } override fun updateViewText(text: String) { virtualFile.setBinaryContent(text.toByteArray()) } override fun getComponent(): JComponent { return mainPanel } override fun updateLanguage(language: Language?, originLanguage: String?) { } override fun dispose() { } } ================================================ FILE: toolsets/plantuml/src/main/kotlin/com/phodal/shire/plantuml/PlantUmlToolchainProvider.kt ================================================ package com.phodal.shire.plantuml import com.intellij.openapi.project.Project import com.phodal.shirecore.provider.context.LanguageToolchainProvider import com.phodal.shirecore.provider.context.ToolchainContextItem import com.phodal.shirecore.provider.context.ToolchainPrepareContext import org.plantuml.idea.lang.PlantUmlLanguage class PlantUmlToolchainProvider : LanguageToolchainProvider { override fun isApplicable(project: Project, context: ToolchainPrepareContext): Boolean { return context.sourceFile?.language == PlantUmlLanguage.INSTANCE } override suspend fun collect(project: Project, context: ToolchainPrepareContext): List { return emptyList() } } ================================================ FILE: toolsets/plantuml/src/main/resources/com.phodal.shire.plantuml.xml ================================================ ================================================ FILE: toolsets/sonarqube/README.md ================================================ # Sonarqube Module Provide variable of Sonarqube, see in [SonarqubeVariable] - `$sonarIssue` - `$sonarResults` ================================================ FILE: toolsets/sonarqube/src/main/kotlin/com/phodal/shire/sonarqube/SonarLintProvider.kt ================================================ package com.phodal.shire.sonarqube import com.intellij.openapi.application.ReadAction import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.editor.Document import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.project.Project import com.intellij.openapi.util.TextRange import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiManager import org.sonarlint.intellij.analysis.Analysis import org.sonarlint.intellij.analysis.AnalysisCallback import org.sonarlint.intellij.analysis.AnalysisResult import org.sonarlint.intellij.analysis.AnalysisSubmitter import org.sonarlint.intellij.tasks.startBackgroundableModalTask import org.sonarlint.intellij.trigger.TriggerType import java.util.concurrent.CompletableFuture import java.util.stream.Stream object SonarLintProvider { fun analysisFile(project: Project, file: VirtualFile): String? { return analysis(file, project) { ReadAction.compute { val result = StringBuilder() it.findings.issuesPerFile.forEach { (file, issues) -> result.append("File: $file\n") issues.forEach { issue -> result.append(" - ${issue.userSeverity}: ${issue.message}\n") } } result.toString() } } } fun analysisResults(project: Project, file: VirtualFile): String? { return analysis(file, project) { ReadAction.compute { val psiFile = PsiManager.getInstance(project).findFile(file) ?: return@compute "" val document = PsiDocumentManager.getInstance(project).getDocument(psiFile) ?: return@compute "" val result = StringBuilder() it.findings.securityHotspotsPerFile.forEach { (file, issues) -> result.append("File: $file\n") issues.forEach { issue -> val range = issue.validTextRange val lineContent: String = if (range != null) { val (startLine, endLine) = textRangeToLineNumbers(document, range) "($startLine-$endLine) ${document.getText(range)}" } else { "" } result.append(" - ${lineContent} has issue : ${issue.message}\n") if (issue.quickFixes().isNotEmpty()) { result.append(" Quick Fixes suggestion: ") issue.quickFixes().forEach { quickFix -> result.append(" - ${quickFix.message}\n") } } } } result.toString() } } } private fun textRangeToLineNumbers(document: Document, textRange: TextRange): Pair { val startLine = document.getLineNumber(textRange.startOffset) + 1 val endLine = document.getLineNumber(textRange.endOffset) + 1 return startLine to endLine } private fun analysis( file: VirtualFile, project: Project, callback: (AnalysisResult) -> String, ): String? { val hasProject = Stream.of(file).anyMatch { f: VirtualFile -> f.path == project.basePath } if (hasProject) return null logger().info("Analysis file: ${file.path}") val future = CompletableFuture() val analysis = Analysis(project, listOf(file), TriggerType.CURRENT_FILE_ACTION, object : AnalysisCallback { override fun onSuccess(analysisResult: AnalysisResult) { future.complete(callback(analysisResult)) } override fun onError(throwable: Throwable) { future.completeExceptionally(throwable) } }) startBackgroundableModalTask(project, AnalysisSubmitter.ANALYSIS_TASK_TITLE) { indicator: ProgressIndicator? -> if (indicator != null) { analysis.run(indicator) } } logger().info("Analysis file: ${file.path} finished") return future.get() } } ================================================ FILE: toolsets/sonarqube/src/main/kotlin/com/phodal/shire/sonarqube/SonarLintVariableProvider.kt ================================================ package com.phodal.shire.sonarqube import com.intellij.openapi.editor.Editor import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement import com.phodal.shirecore.provider.variable.ToolchainVariableProvider import com.phodal.shirecore.provider.variable.model.toolchain.SonarqubeVariable import com.phodal.shirecore.provider.variable.model.ToolchainVariable class SonarLintVariableProvider : ToolchainVariableProvider { override fun isResolvable(variable: ToolchainVariable, psiElement: PsiElement?, project: Project): Boolean { return variable is SonarqubeVariable } override fun resolve(variable: ToolchainVariable, project: Project, editor: Editor, psiElement: PsiElement?): Any { return when (variable) { SonarqubeVariable.Issue -> { val file: VirtualFile = FileDocumentManager.getInstance().getFile(editor.document) ?: throw IllegalStateException("No file found for editor") SonarLintProvider.analysisFile(project, file) ?: "" } SonarqubeVariable.Results -> { val file: VirtualFile = FileDocumentManager.getInstance().getFile(editor.document) ?: throw IllegalStateException("No file found for editor") SonarLintProvider.analysisResults(project, file) ?: "" } else -> "" } } } ================================================ FILE: toolsets/sonarqube/src/main/resources/com.phodal.shire.sonarqube.xml ================================================ ================================================ FILE: toolsets/terminal/src/main/kotlin/com/phodal/shire/terminal/ShireTerminalExecutor.kt ================================================ package com.phodal.shire.terminal import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.PlatformCoreDataKeys import com.intellij.openapi.project.Project import com.intellij.openapi.wm.ToolWindowManager import com.intellij.ui.content.Content import com.phodal.shirecore.provider.action.TerminalLocationExecutor import com.phodal.shirecore.provider.action.terminal.TerminalHandler import org.jetbrains.plugins.terminal.TerminalToolWindowFactory import org.jetbrains.plugins.terminal.TerminalToolWindowManager import java.awt.Component class ShireTerminalExecutor : TerminalLocationExecutor { override fun getComponent(e: AnActionEvent): Component? { return e.getData(PlatformCoreDataKeys.CONTEXT_COMPONENT) } override fun bundler(project: Project, userInput: String): TerminalHandler? { val content = getContent(project) ?: return null return trySendMsgInOld(project, userInput, content) } private fun trySendMsgInOld(project: Project, userInput: String, content: Content): TerminalHandler? { val widget = TerminalToolWindowManager.getWidgetByContent(content) ?: return null return TerminalHandler( userInput, project, onChunk = { string -> widget.terminalStarter?.sendString(string, true) }, onFinish = {}) } private fun getContent(project: Project): Content? { val toolWindow = ToolWindowManager.getInstance(project).getToolWindow(TerminalToolWindowFactory.TOOL_WINDOW_ID) return toolWindow?.contentManager?.selectedContent } } ================================================ FILE: toolsets/terminal/src/main/kotlin/com/phodal/shire/terminal/TerminalToolchainVariableProvider.kt ================================================ package com.phodal.shire.terminal import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.psi.PsiElement import com.phodal.shirecore.provider.variable.ToolchainVariableProvider import com.phodal.shirecore.provider.variable.model.toolchain.TerminalToolchainVariable import com.phodal.shirecore.provider.variable.model.ToolchainVariable import org.jetbrains.plugins.terminal.TerminalProjectOptionsProvider class TerminalToolchainVariableProvider : ToolchainVariableProvider { override fun isResolvable(variable: ToolchainVariable, psiElement: PsiElement?, project: Project): Boolean { return variable is TerminalToolchainVariable } override fun resolve(variable: ToolchainVariable, project: Project, editor: Editor, psiElement : PsiElement?): Any { val options = TerminalProjectOptionsProvider.getInstance(project) return when (variable) { TerminalToolchainVariable.SHELL_PATH -> options.shellPath TerminalToolchainVariable.PWD -> { options.startingDirectory ?: project.guessProjectDir()?.path ?: System.getProperty("user.home") } else -> "" } ?: "" } } ================================================ FILE: toolsets/terminal/src/main/kotlin/com/phodal/shire/terminal/sketch/ShellUtil.kt ================================================ // Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.phodal.shire.terminal.sketch import com.intellij.execution.configuration.EnvironmentVariablesData import com.intellij.execution.configurations.PathEnvironmentVariableUtil import com.intellij.execution.wsl.WSLDistribution import com.intellij.execution.wsl.WslDistributionManager import com.intellij.openapi.util.SystemInfo import com.intellij.openapi.util.text.StringUtil import com.intellij.util.EnvironmentUtil import java.io.File import java.nio.file.Files import java.nio.file.Path object ShellUtil { private fun listWslShell() : List? { if (WSLDistribution.findWslExe() == null) return listOf() val distributions = WslDistributionManager.getInstance().installedDistributions return distributions.mapNotNull { it.shellPath } } fun detectShells(): List { val shells: MutableList = ArrayList() if (SystemInfo.isUnix) { addIfExists(shells, "/bin/bash") addIfExists(shells, "/usr/bin/bash") addIfExists(shells, "/usr/local/bin/bash") addIfExists(shells, "/opt/homebrew/bin/bash") addIfExists(shells, "/bin/zsh") addIfExists(shells, "/usr/bin/zsh") addIfExists(shells, "/usr/local/bin/zsh") addIfExists(shells, "/opt/homebrew/bin/zsh") addIfExists(shells, "/bin/fish") addIfExists(shells, "/usr/bin/fish") addIfExists(shells, "/usr/local/bin/fish") addIfExists(shells, "/opt/homebrew/bin/fish") addIfExists(shells, "/opt/homebrew/bin/pwsh") } else if (SystemInfo.isWindows) { val powershell = PathEnvironmentVariableUtil.findInPath("powershell.exe") if (powershell != null && StringUtil.startsWithIgnoreCase(powershell.absolutePath, "C:\\Windows\\System32\\WindowsPowerShell\\") ) { shells.add(powershell.absolutePath) } val cmd = PathEnvironmentVariableUtil.findInPath("cmd.exe") if (cmd != null && StringUtil.startsWithIgnoreCase(cmd.absolutePath, "C:\\Windows\\System32\\")) { shells.add(cmd.absolutePath) } val pwsh = PathEnvironmentVariableUtil.findInPath("pwsh.exe") if (pwsh != null && StringUtil.startsWithIgnoreCase(pwsh.absolutePath, "C:\\Program Files\\PowerShell\\")) { shells.add(pwsh.absolutePath) } val gitBash = File("C:\\Program Files\\Git\\bin\\bash.exe") if (gitBash.isFile) { shells.add(gitBash.absolutePath) } var cmderRoot = EnvironmentUtil.getValue("CMDER_ROOT") if (cmderRoot == null) { cmderRoot = EnvironmentVariablesData.DEFAULT.envs["CMDER_ROOT"] } if (cmderRoot != null && cmd != null && StringUtil.startsWithIgnoreCase(cmd.absolutePath, "C:\\Windows\\System32\\") ) { shells.add("cmd.exe /k \"%CMDER_ROOT%\\vendor\\init.bat\"") } } return shells } private fun addIfExists(shells: MutableList, filePath: String) { if (Files.exists(Path.of(filePath))) { shells.add(filePath) } } } ================================================ FILE: toolsets/terminal/src/main/kotlin/com/phodal/shire/terminal/sketch/TerminalLangSketchProvider.kt ================================================ package com.phodal.shire.terminal.sketch import com.intellij.execution.configurations.GeneralCommandLine import com.intellij.execution.configurations.PtyCommandLine import com.intellij.execution.process.KillableProcessHandler import com.intellij.execution.process.ProcessAdapter import com.intellij.execution.process.ProcessEvent import com.intellij.icons.AllIcons import com.intellij.lang.Language import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.ui.popup.JBPopup import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.openapi.ui.popup.util.MinimizeButton import com.intellij.terminal.JBTerminalWidget import com.intellij.util.ui.JBUI import com.phodal.shirecore.ShirelangNotifications import com.phodal.shirecore.provider.sketch.ExtensionLangSketch import com.phodal.shirecore.provider.sketch.LanguageSketchProvider import org.jetbrains.plugins.terminal.LocalTerminalDirectRunner import java.awt.BorderLayout import java.awt.Dimension import java.awt.event.MouseAdapter import java.awt.event.MouseEvent import javax.swing.JButton import javax.swing.JComponent import javax.swing.JLabel import javax.swing.JPanel class TerminalLangSketchProvider : LanguageSketchProvider { override fun isSupported(lang: String): Boolean = lang == "bash" override fun create(project: Project, content: String): ExtensionLangSketch { var content = content return object : ExtensionLangSketch { var terminalWidget: JBTerminalWidget? = null var panelLayout: JPanel? = null init { val projectDir = project.guessProjectDir()?.path val terminalRunner = LocalTerminalDirectRunner.createTerminalRunner(project) terminalWidget = terminalRunner.createTerminalWidget(this, projectDir, true).also { it.preferredSize = Dimension(it.preferredSize.width, 120) } panelLayout = object : JPanel(BorderLayout()) { init { add(JLabel("Terminal").also { it.border = JBUI.Borders.empty(5, 0) }, BorderLayout.NORTH) add(terminalWidget!!.component, BorderLayout.CENTER) val buttonPanel = JPanel(BorderLayout()) val runButton = JButton(AllIcons.Toolwindows.ToolWindowRun) .apply { addMouseListener(executeShellScriptOnClick(project, content, terminalWidget)) } val popupButton = JButton("Pop up Terminal").apply { addMouseListener(executePopup(terminalWidget, project)) } buttonPanel.add(runButton, BorderLayout.WEST) buttonPanel.add(popupButton, BorderLayout.EAST) add(buttonPanel, BorderLayout.SOUTH) } } panelLayout!!.border = JBUI.Borders.compound( JBUI.Borders.empty(5, 10), ) } private fun executePopup(terminalWidget: JBTerminalWidget?, project: Project): MouseAdapter = object : MouseAdapter() { override fun mouseClicked(e: MouseEvent?) { var popup: JBPopup? = null popup = JBPopupFactory.getInstance() .createComponentPopupBuilder(terminalWidget!!.component, null) .setProject(project) .setResizable(true) .setMovable(true) .setTitle("Terminal") .setCancelButton(MinimizeButton("Hide")) .setCancelCallback { popup?.cancel() panelLayout!!.remove(terminalWidget.component) panelLayout!!.add(terminalWidget.component) panelLayout!!.revalidate() panelLayout!!.repaint() true } .setFocusable(true) .setRequestFocus(true) .createPopup() val editor = FileEditorManager.getInstance(project).selectedTextEditor if (editor != null) { popup.showInBestPositionFor(editor) } else { popup.showInFocusCenter() } } } override fun getExtensionName(): String = "Terminal" override fun getViewText(): String = content override fun updateViewText(text: String) { content = text } override fun doneUpdateText(text: String) { ApplicationManager.getApplication().invokeLater { Thread.sleep(1000) // todo: change to when terminal ready terminalWidget!!.terminalStarter?.sendString(content, false) } } override fun getComponent(): JComponent = panelLayout!! override fun updateLanguage(language: Language?, originLanguage: String?) {} override fun dispose() {} } } fun executeShellScriptOnClick( project: Project, content: String, terminalWidget: JBTerminalWidget?, ): MouseAdapter = object : MouseAdapter() { override fun mouseClicked(e: MouseEvent?) { val commandLine = createCommandLineForScript(project, content) val processBuilder = commandLine.toProcessBuilder() val process = processBuilder.start() val processHandler = KillableProcessHandler(process, commandLine.commandLineString) processHandler.startNotify() processHandler.addProcessListener(object : ProcessAdapter() { override fun processTerminated(event: ProcessEvent) { ShirelangNotifications.warn( project, "Process terminated with exit code ${event.exitCode}, ${event.text}" ) processHandler.destroyProcess() } }) } } fun createCommandLineForScript(project: Project, scriptText: String): GeneralCommandLine { val workingDirectory = project.basePath val commandLine = PtyCommandLine() commandLine.withConsoleMode(false) commandLine.withInitialColumns(120) commandLine.withParentEnvironmentType(GeneralCommandLine.ParentEnvironmentType.CONSOLE) commandLine.setWorkDirectory(workingDirectory!!) commandLine.withExePath(ShellUtil.detectShells().first()) commandLine.withParameters("-c") commandLine.withParameters(scriptText) return commandLine } } ================================================ FILE: toolsets/terminal/src/main/resources/com.phodal.shire.terminal.xml ================================================ ================================================ FILE: toolsets/uitest/src/main/kotlin/com/phodal/shire/uitest/provider/PlaywrightFileRunService.kt ================================================ package com.phodal.shire.uitest.provider import com.intellij.aqua.runners.playwright.js.PlaywrightRunConfigurationProducer import com.intellij.execution.RunManager import com.intellij.execution.actions.ConfigurationContext import com.intellij.execution.actions.RunConfigurationProducer import com.intellij.execution.configurations.RunConfiguration import com.intellij.execution.configurations.RunProfile import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiManager import com.phodal.shirecore.provider.shire.FileRunService class PlaywrightFileRunService : FileRunService { override fun isApplicable(project: Project, file: VirtualFile): Boolean { val isSpecFile = file.name.endsWith(".spec.ts") || file.name.endsWith(".spec.js") if (isSpecFile) { return true } val content = file.inputStream.bufferedReader().use { it.readText() } val hasPlaywright = content.contains("playwright") return hasPlaywright } override fun runConfigurationClass(project: Project): Class? { return PlaywrightRunConfiguration::class.java return null } override fun createConfiguration(project: Project, virtualFile: VirtualFile): RunConfiguration? { val configurationSetting = runReadAction { val psiFile = PsiManager.getInstance(project).findFile(virtualFile) ?: return@runReadAction null val runManager = RunManager.getInstance(project) val configProducer = RunConfigurationProducer.getInstance( PlaywrightRunConfigurationProducer::class.java ) val configurationType = configProducer.findOrCreateConfigurationFromContext( ConfigurationContext(psiFile) )?.configurationType?.javaClass ?: return@runReadAction null val configuration = runManager.createConfiguration("Playwright", configurationType) runManager.addConfiguration(configuration) configuration } return configurationSetting?.configuration } } ================================================ FILE: toolsets/uitest/src/main/resources/com.phodal.shire.uitest.xml ================================================