Skip to content

Commit ad859c8

Browse files
committed
Snow CLI v0.7.27
- Compatible with .agents/skills/ directory - Expose all request scheme configuration to StatusLine - Optimize ace-file-outline tool VSCode Extension v0.4.24 - Support code completion and Tab&Tab AI cross-file suggestions JetBrains Plugin v0.4.22 - Support code completion and Tab&Tab AI cross-file suggestions
1 parent d6ab650 commit ad859c8

51 files changed

Lines changed: 7531 additions & 225 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build_jetbrains.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ jobs:
7979
8080
### What's New
8181
82-
- Add AI-generated git commit message feature
82+
- Supports code completion and Tab&Tab AI cross-file suggestions
8383
8484
### Usage
8585

.github/workflows/build_vsix.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ jobs:
6565
6666
### What's New
6767
68-
- Fix the problem of not being able to trigger the system ringtone and add relevant settings
68+
- Supports code completion and Tab&Tab AI cross-file suggestions
6969
7070
### Installation
7171

.github/workflows/publish.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,9 @@ jobs:
5656
5757
### What's New
5858
59-
- Add the /simple command to quickly switch to the simple theme
60-
- Add search engine plugins to support more custom search engines
59+
- Compatible with the .agents/skills/ directory
60+
- Expose all request scheme thinking configurations to StatusLine
61+
- Optimize the ace-file-outline tool
6162
6263
### Installation
6364
```bash

JetBrains/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ plugins {
55
}
66

77
group = "com.snow"
8-
version = "0.4.21"
8+
version = "0.4.22"
99

1010
repositories {
1111
mavenCentral()

JetBrains/src/main/kotlin/com/snow/plugin/SnowPluginLifecycle.kt

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ package com.snow.plugin
22

33
import com.intellij.ide.AppLifecycleListener
44
import com.intellij.openapi.application.ApplicationManager
5+
import com.intellij.openapi.diagnostic.Logger
6+
import com.intellij.openapi.fileEditor.FileEditorManager
7+
import com.intellij.openapi.fileEditor.FileEditorManagerListener
8+
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
59
import com.intellij.openapi.project.Project
610
import com.intellij.openapi.project.ProjectManager
711
import com.intellij.openapi.project.ProjectManagerListener
12+
import com.snow.plugin.completion.SnowCompletionService
13+
import com.snow.plugin.nextEdit.SnowNextEditEngine
814

9-
/**
10-
* Plugin lifecycle listener
11-
*/
1215
class SnowPluginLifecycle : AppLifecycleListener {
1316
private val wsManager = SnowWebSocketManager.instance
1417

@@ -22,6 +25,13 @@ class SnowPluginLifecycle : AppLifecycleListener {
2225
}
2326
})
2427

28+
// Eagerly initialize completion service so EditorFactoryListener is registered
29+
try {
30+
SnowCompletionService.getInstance()
31+
} catch (e: Exception) {
32+
logger.warn("Failed to initialize completion service", e)
33+
}
34+
2535
for (project in ProjectManager.getInstance().openProjects) {
2636
setupProject(project)
2737
}
@@ -32,6 +42,7 @@ class SnowPluginLifecycle : AppLifecycleListener {
3242
}
3343

3444
companion object {
45+
private val logger = Logger.getInstance(SnowPluginLifecycle::class.java)
3546
private val trackers = mutableMapOf<Project, SnowEditorContextTracker>()
3647
private val handlers = mutableMapOf<Project, SnowMessageHandler>()
3748

@@ -53,12 +64,44 @@ class SnowPluginLifecycle : AppLifecycleListener {
5364
}
5465
}
5566
}
67+
68+
initNextEditForProject(project)
5669
}
5770

5871
fun cleanupProject(project: Project) {
5972
SnowWebSocketManager.instance.cleanupPortInfoForProject(project)
6073
trackers.remove(project)
6174
handlers.remove(project)
6275
}
76+
77+
private fun initNextEditForProject(project: Project) {
78+
try {
79+
val engine = SnowNextEditEngine.getInstance(project)
80+
val editorManager = FileEditorManager.getInstance(project)
81+
82+
// Register all currently open editors
83+
for (file in editorManager.openFiles) {
84+
val editors = editorManager.getEditors(file)
85+
for (fileEditor in editors) {
86+
val textEditor = fileEditor as? com.intellij.openapi.fileEditor.TextEditor ?: continue
87+
engine.registerDocument(textEditor.editor)
88+
}
89+
}
90+
91+
// Register future editors on selection change
92+
project.messageBus.connect().subscribe(
93+
FileEditorManagerListener.FILE_EDITOR_MANAGER,
94+
object : FileEditorManagerListener {
95+
override fun selectionChanged(event: FileEditorManagerEvent) {
96+
val editor = FileEditorManager.getInstance(project).selectedTextEditor
97+
if (editor != null) engine.registerDocument(editor)
98+
}
99+
},
100+
)
101+
} catch (e: Exception) {
102+
logger.warn("Failed to init Next Edit for project", e)
103+
}
104+
}
105+
63106
}
64107
}

JetBrains/src/main/kotlin/com/snow/plugin/actions/GenerateCommitMessageAction.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.intellij.openapi.project.DumbAwareAction
77
import com.intellij.openapi.ui.Messages
88
import com.intellij.openapi.vcs.VcsDataKeys
99
import com.snow.plugin.commit.SnowCommitMessageGenerationService
10+
import com.snow.plugin.config.SnowBundle
1011
import icons.SnowPluginIcons
1112
import java.awt.event.InputEvent
1213

@@ -29,8 +30,8 @@ class GenerateCommitMessageAction : DumbAwareAction() {
2930
val additionalRequirements = if (shouldAskForRequirements) {
3031
val input = Messages.showInputDialog(
3132
project,
32-
"Add optional requirements for the generated commit message.",
33-
"Snow CLI: Commit Message Requirements",
33+
SnowBundle.message("commitMessage.requirementsPrompt"),
34+
SnowBundle.message("commitMessage.requirementsTitle"),
3435
Messages.getQuestionIcon(),
3536
) ?: return
3637
input.trim().ifEmpty { null }
@@ -59,14 +60,14 @@ class GenerateCommitMessageAction : DumbAwareAction() {
5960

6061
e.presentation.isEnabledAndVisible = project != null && hasCommitMessageTarget
6162
e.presentation.text = if (isGenerating) {
62-
"Cancel Commit Message Generation"
63+
SnowBundle.message("commitMessage.cancel")
6364
} else {
64-
"Generate Commit Message"
65+
SnowBundle.message("commitMessage.generate")
6566
}
6667
e.presentation.description = if (isGenerating) {
67-
"Cancel Snow CLI commit message generation"
68+
SnowBundle.message("commitMessage.cancelDescription")
6869
} else {
69-
"Generate a commit message with Snow CLI AI. Alt/Option-click to add requirements."
70+
SnowBundle.message("commitMessage.generateDescription")
7071
}
7172
}
7273

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package com.snow.plugin.actions
2+
3+
import com.intellij.notification.NotificationGroupManager
4+
import com.intellij.notification.NotificationType
5+
import com.intellij.openapi.actionSystem.ActionUpdateThread
6+
import com.intellij.openapi.actionSystem.AnActionEvent
7+
import com.intellij.openapi.actionSystem.CommonDataKeys
8+
import com.intellij.openapi.application.ApplicationManager
9+
import com.intellij.openapi.progress.ProgressIndicator
10+
import com.intellij.openapi.progress.ProgressManager
11+
import com.intellij.openapi.progress.Task
12+
import com.intellij.openapi.project.DumbAwareAction
13+
import com.intellij.openapi.ui.popup.JBPopupFactory
14+
import com.snow.plugin.completion.SnowCompletionConfig
15+
import com.snow.plugin.completion.SnowCompletionService
16+
import com.snow.plugin.completion.SnowModelFetcher
17+
import com.snow.plugin.config.SnowBundle
18+
19+
class SnowCompletionToggleAction : DumbAwareAction() {
20+
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
21+
22+
override fun actionPerformed(e: AnActionEvent) {
23+
val config = SnowCompletionConfig.getInstance()
24+
config.state.enabled = !config.state.enabled
25+
val project = e.project
26+
if (project != null) {
27+
val msg = if (config.enabled)
28+
SnowBundle.message("notification.completion.enabled")
29+
else
30+
SnowBundle.message("notification.completion.disabled")
31+
NotificationGroupManager.getInstance()
32+
.getNotificationGroup("Snow CLI")
33+
.createNotification(msg, NotificationType.INFORMATION)
34+
.notify(project)
35+
}
36+
}
37+
38+
override fun update(e: AnActionEvent) {
39+
val config = SnowCompletionConfig.getInstance()
40+
e.presentation.text = if (config.enabled)
41+
SnowBundle.message("notification.completion.disabled").removeSuffix(".")
42+
else
43+
SnowBundle.message("notification.completion.enabled").removeSuffix(".")
44+
}
45+
}
46+
47+
class SnowCompletionTriggerAction : DumbAwareAction() {
48+
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
49+
50+
override fun actionPerformed(e: AnActionEvent) {
51+
val editor = e.getData(CommonDataKeys.EDITOR) ?: return
52+
val config = SnowCompletionConfig.getInstance()
53+
if (!config.enabled) {
54+
e.project?.let {
55+
NotificationGroupManager.getInstance()
56+
.getNotificationGroup("Snow CLI")
57+
.createNotification(SnowBundle.message("notification.completion.disabledHint"), NotificationType.INFORMATION)
58+
.notify(it)
59+
}
60+
return
61+
}
62+
SnowCompletionService.getInstance().triggerManual(editor)
63+
}
64+
65+
override fun update(e: AnActionEvent) {
66+
e.presentation.isEnabled = e.getData(CommonDataKeys.EDITOR) != null
67+
}
68+
}
69+
70+
class SnowCompletionSelectModelAction : DumbAwareAction() {
71+
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
72+
73+
override fun actionPerformed(e: AnActionEvent) {
74+
val project = e.project ?: return
75+
val config = SnowCompletionConfig.getInstance()
76+
77+
if (config.apiKey.isBlank()) {
78+
NotificationGroupManager.getInstance()
79+
.getNotificationGroup("Snow CLI")
80+
.createNotification(SnowBundle.message("notification.completion.apiKeyMissing"), NotificationType.WARNING)
81+
.notify(project)
82+
return
83+
}
84+
85+
ProgressManager.getInstance().run(object : Task.Backgroundable(project, SnowBundle.message("modelSelect.fetching"), true) {
86+
override fun run(indicator: ProgressIndicator) {
87+
try {
88+
val models = SnowModelFetcher.fetchModels(config)
89+
ApplicationManager.getApplication().invokeLater {
90+
if (project.isDisposed) return@invokeLater
91+
val items = models.map { it.id }
92+
if (items.isEmpty()) {
93+
NotificationGroupManager.getInstance()
94+
.getNotificationGroup("Snow CLI")
95+
.createNotification(SnowBundle.message("notification.completion.noModels"), NotificationType.WARNING)
96+
.notify(project)
97+
return@invokeLater
98+
}
99+
100+
JBPopupFactory.getInstance()
101+
.createPopupChooserBuilder(items)
102+
.setTitle(SnowBundle.message("modelSelect.title", config.provider))
103+
.setItemChosenCallback { chosen ->
104+
config.state.model = chosen
105+
NotificationGroupManager.getInstance()
106+
.getNotificationGroup("Snow CLI")
107+
.createNotification(SnowBundle.message("notification.completion.modelSet", chosen), NotificationType.INFORMATION)
108+
.notify(project)
109+
}
110+
.createPopup()
111+
.showCenteredInCurrentWindow(project)
112+
}
113+
} catch (ex: Exception) {
114+
ApplicationManager.getApplication().invokeLater {
115+
NotificationGroupManager.getInstance()
116+
.getNotificationGroup("Snow CLI")
117+
.createNotification(SnowBundle.message("notification.completion.modelFetchFailed", ex.message ?: ""), NotificationType.ERROR)
118+
.notify(project)
119+
}
120+
}
121+
}
122+
})
123+
}
124+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.snow.plugin.actions
2+
3+
import com.intellij.openapi.actionSystem.DataContext
4+
import com.intellij.openapi.editor.Caret
5+
import com.intellij.openapi.editor.Editor
6+
import com.intellij.openapi.editor.actionSystem.EditorAction
7+
import com.intellij.openapi.editor.actionSystem.EditorActionHandler
8+
import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler
9+
import com.intellij.openapi.project.DumbAware
10+
import com.snow.plugin.completion.SnowCompletionService
11+
import com.snow.plugin.nextEdit.SnowNextEditEngine
12+
13+
/**
14+
* Snow CLI: Accept inline suggestion via TAB.
15+
*
16+
* Uses [EditorAction] + [isEnabledForCaret] so that when there is no inline
17+
* completion / next edit session active, this action will report itself as
18+
* disabled and IntelliJ will fall back to the default TAB behavior (smart
19+
* indent / dedent / live template / lookup) automatically. This avoids the
20+
* previous problem where a globally-replaced Tab handler caused the IDE's
21+
* auto-indent to overwrite the just-inserted completion text.
22+
*/
23+
class SnowAcceptInlineAction : EditorAction(AcceptHandler()), DumbAware {
24+
25+
private class AcceptHandler : EditorWriteActionHandler() {
26+
27+
override fun executeWriteAction(editor: Editor, caret: Caret?, dataContext: DataContext?) {
28+
// Inline completion has priority over next edit prediction
29+
val completion = SnowCompletionService.getInstance()
30+
if (completion.tryAccept(editor)) return
31+
32+
val project = editor.project ?: return
33+
SnowNextEditEngine.getInstance(project).tryAccept(editor)
34+
}
35+
36+
override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
37+
val completion = SnowCompletionService.getInstance()
38+
if (completion.hasCompletion(editor)) return true
39+
val project = editor.project ?: return false
40+
return SnowNextEditEngine.getInstance(project).hasSessionForEditor(editor)
41+
}
42+
}
43+
}
44+
45+
/**
46+
* Snow CLI: Dismiss inline suggestion via ESCAPE.
47+
*
48+
* Same rationale as [SnowAcceptInlineAction] — only intercept when we actually
49+
* have something to dismiss, otherwise let the IDE handle ESCAPE normally.
50+
*/
51+
class SnowDismissInlineAction : EditorAction(DismissHandler()), DumbAware {
52+
53+
private class DismissHandler : EditorActionHandler() {
54+
55+
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext) {
56+
val completion = SnowCompletionService.getInstance()
57+
if (completion.tryDismiss(editor)) return
58+
59+
val project = editor.project ?: return
60+
SnowNextEditEngine.getInstance(project).tryDismiss(editor)
61+
}
62+
63+
override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
64+
val completion = SnowCompletionService.getInstance()
65+
if (completion.hasCompletion(editor)) return true
66+
val project = editor.project ?: return false
67+
return SnowNextEditEngine.getInstance(project).hasSessionForEditor(editor)
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)