mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 19:11:03 +08:00
feat(jetbrains): hint worktree usage on empty session
Show a branch-aware tip under the logo on the empty session screen. On a plain checkout it nudges toward running the task in a worktree, with "run it in a worktree" as an inline link that opens the New Worktree flow. In a worktree it confirms the work is isolated instead. The tip falls back to the generic welcome whenever a claim would be wrong: before branch status resolves, when git is missing, and on a detached HEAD. Read-only surfaces show no tip and skip the fetch. Branch status now feeds the empty panel as well as the branch dock, so worktree editor tabs get it too even though they render no dock. Translate the new strings into all 18 locales. The tip keys carry placeholders and therefore go through MessageFormat, where a lone apostrophe silently swallows surrounding text, so KiloBundleLocaleTest formats every locale for real and checks apostrophe escaping.
This commit is contained in:
+1
@@ -101,6 +101,7 @@ abstract class SessionHost(
|
||||
activity = { activity() },
|
||||
titles = { titles() },
|
||||
timers = timers,
|
||||
newWorktree = if (supportsNewWorktree) ({ newWorktree() }) else null,
|
||||
)
|
||||
|
||||
@RequiresEdt
|
||||
|
||||
+1
@@ -52,6 +52,7 @@ interface SessionManager {
|
||||
history = { showHistory() },
|
||||
activity = { activity() },
|
||||
titles = { titles() },
|
||||
newWorktree = if (supportsNewWorktree) ({ newWorktree() }) else null,
|
||||
)
|
||||
|
||||
fun openSession(session: SessionDto) {
|
||||
|
||||
+15
-3
@@ -72,6 +72,7 @@ import ai.kilocode.client.util.UiTimerSource
|
||||
import ai.kilocode.client.util.UiTimers
|
||||
import ai.kilocode.client.vfs.KiloVfsManager
|
||||
import ai.kilocode.log.ChatLogSummary
|
||||
import ai.kilocode.rpc.dto.BranchStatusDto
|
||||
import ai.kilocode.rpc.dto.ModelLimitDto
|
||||
import ai.kilocode.rpc.dto.DiffFileDto
|
||||
import ai.kilocode.rpc.dto.PromptDto
|
||||
@@ -213,6 +214,12 @@ class SessionUi(
|
||||
private lateinit var load: LoadingPanel
|
||||
private lateinit var migrationWizard: MigrationWizardPanel
|
||||
private var empty: EmptySessionPanel? = null
|
||||
|
||||
/**
|
||||
* Last observed branch/worktree status. Retained so an empty panel created after the fetch shows
|
||||
* its tip immediately instead of waiting for the next refresh.
|
||||
*/
|
||||
private var branch: BranchStatusDto? = null
|
||||
private var modalFocus: (() -> JComponent)? = null
|
||||
private var style = SessionEditorStyle.current()
|
||||
private val selection = SessionSelection()
|
||||
@@ -247,8 +254,8 @@ class SessionUi(
|
||||
dock?.let {
|
||||
syncDock()
|
||||
refreshBranchChanges()
|
||||
refreshBranch()
|
||||
}
|
||||
refreshBranch()
|
||||
loaded?.let(::finishOpen)
|
||||
}
|
||||
|
||||
@@ -613,6 +620,7 @@ class SessionUi(
|
||||
val panel = manager?.emptyPanel(this, controller)
|
||||
?: EmptySessionPanel(this, controller, controller.recents(), timers = timers)
|
||||
empty = panel
|
||||
panel.setBranch(branch)
|
||||
scroll.show(panel.view)
|
||||
}
|
||||
|
||||
@@ -1023,7 +1031,9 @@ class SessionUi(
|
||||
* split mode — so the PR always matches the branch checked out in this session's directory.
|
||||
*/
|
||||
private fun refreshBranch() {
|
||||
val dock = dock ?: return
|
||||
// Also feeds the empty panel's branch/worktree tip, so this runs even on surfaces without a
|
||||
// dock (worktree editor tabs). Read-only surfaces show no tip and get no fetch.
|
||||
if (readonly) return
|
||||
branchJob?.cancel()
|
||||
branchJob = cs.launch {
|
||||
val status = runCatching { service<KiloWorktreeService>().branchStatus(workspace.directory) }
|
||||
@@ -1034,7 +1044,9 @@ class SessionUi(
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
if (disposed || project.isDisposed) return@withContext
|
||||
dock.setBranch(status)
|
||||
branch = status
|
||||
dock?.setBranch(status)
|
||||
empty?.setBranch(status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+114
-9
@@ -14,12 +14,16 @@ import ai.kilocode.client.ui.layout.VAlign
|
||||
import ai.kilocode.client.ui.layout.align
|
||||
import ai.kilocode.client.util.UiTimerSource
|
||||
import ai.kilocode.client.util.UiTimers
|
||||
import ai.kilocode.rpc.dto.BranchStatusDto
|
||||
import ai.kilocode.rpc.dto.GhAvailability
|
||||
import ai.kilocode.rpc.dto.SessionDto
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.ide.BrowserUtil
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.IconLoader
|
||||
import com.intellij.openapi.util.text.HtmlChunk
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.util.ui.Centerizer
|
||||
@@ -39,6 +43,8 @@ import java.awt.event.MouseEvent
|
||||
import javax.swing.JButton
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.SwingUtilities
|
||||
import javax.swing.event.HyperlinkEvent
|
||||
import javax.swing.event.HyperlinkListener
|
||||
|
||||
/**
|
||||
* Empty-session panel.
|
||||
@@ -56,6 +62,7 @@ class EmptySessionPanel(
|
||||
private val browse: (String) -> Unit = BrowserUtil::browse,
|
||||
private val timers: UiTimerSource = UiTimers,
|
||||
private val minimal: Boolean = false,
|
||||
private val newWorktree: (() -> Unit)? = null,
|
||||
) : BorderLayoutPanel(), Disposable, SessionEditorStyleTarget {
|
||||
private var style = SessionEditorStyle.current()
|
||||
val view: Align = align(
|
||||
@@ -71,6 +78,12 @@ class EmptySessionPanel(
|
||||
addActionListener { history() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Branch/worktree status behind the tip under the logo. Null until [setBranch] delivers it, so
|
||||
* the first paint falls back to the generic welcome rather than flashing a wrong claim.
|
||||
*/
|
||||
private var branch: BranchStatusDto? = null
|
||||
|
||||
private val feedback = EmptySessionFeedback(browse)
|
||||
|
||||
private val logo = JBLabel(
|
||||
@@ -79,10 +92,21 @@ class EmptySessionPanel(
|
||||
horizontalAlignment = JBLabel.CENTER
|
||||
}
|
||||
|
||||
private val welcomeLabel = JBLabel(welcomeHtml()).apply {
|
||||
/**
|
||||
* Text is set by [syncTip], which picks the generic welcome or a branch/worktree tip. Copyable
|
||||
* mode swaps the label's internals for an HTML pane, which is what makes the inline worktree
|
||||
* link clickable; auto-wrapping must be set first so that pane's CSS allows line breaks.
|
||||
*/
|
||||
private val welcomeLabel = object : JBLabel() {
|
||||
override fun createHyperlinkListener() = HyperlinkListener { e ->
|
||||
if (e.eventType != HyperlinkEvent.EventType.ACTIVATED) return@HyperlinkListener
|
||||
if (e.description == WORKTREE_HREF) newWorktree?.invoke()
|
||||
}
|
||||
}.apply {
|
||||
foreground = SessionUiStyle.Text.Secondary.foreground()
|
||||
horizontalAlignment = JBLabel.CENTER
|
||||
setAllowAutoWrapping(true)
|
||||
setCopyable(true)
|
||||
}
|
||||
|
||||
private val description = object : BorderLayoutPanel() {
|
||||
@@ -101,6 +125,13 @@ class EmptySessionPanel(
|
||||
add(welcomeLabel, BorderLayout.CENTER)
|
||||
}
|
||||
|
||||
private val descriptionSlot = description.align(HAlign.CENTER, VAlign.CENTER)
|
||||
|
||||
private val header = BorderLayoutPanel(0, UiStyle.Gap.pad()).apply {
|
||||
isOpaque = false
|
||||
add(logo, BorderLayout.NORTH)
|
||||
}
|
||||
|
||||
init {
|
||||
Disposer.register(parent, this)
|
||||
Disposer.register(this, feedback)
|
||||
@@ -119,12 +150,6 @@ class EmptySessionPanel(
|
||||
val gap = UiStyle.Gap.pad()
|
||||
layout = BorderLayout(0, gap)
|
||||
|
||||
val header = BorderLayoutPanel(0, gap).apply {
|
||||
isOpaque = false
|
||||
add(logo, BorderLayout.NORTH)
|
||||
if (!minimal) add(description.align(HAlign.CENTER, VAlign.CENTER), BorderLayout.CENTER)
|
||||
}
|
||||
|
||||
val actions = Stack.vertical(gap = UiStyle.Gap.lg())
|
||||
if (!minimal) actions.next(Centerizer(historyButton, Centerizer.TYPE.HORIZONTAL))
|
||||
actions.next(Centerizer(feedback.button, Centerizer.TYPE.HORIZONTAL))
|
||||
@@ -136,6 +161,68 @@ class EmptySessionPanel(
|
||||
add(header, BorderLayout.NORTH)
|
||||
if (!minimal && recent.hasSessions()) add(recent, BorderLayout.CENTER)
|
||||
add(south, BorderLayout.SOUTH)
|
||||
syncTip()
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the branch/worktree status behind the tip under the logo. Called again whenever the
|
||||
* status is refreshed, so it must stay a no-op when nothing changed.
|
||||
*/
|
||||
@RequiresEdt
|
||||
fun setBranch(status: BranchStatusDto?) {
|
||||
if (branch == status) return
|
||||
branch = status
|
||||
syncTip()
|
||||
}
|
||||
|
||||
/**
|
||||
* The tip under the logo, as an HTML fragment: an isolation reminder on a worktree, a nudge
|
||||
* towards one on a plain checkout, where "run it in a worktree" is an inline link. Null when the
|
||||
* status is unknown, git is missing, or no branch is checked out — the generic welcome covers
|
||||
* those rather than asserting something wrong.
|
||||
*/
|
||||
private fun tip(): String? {
|
||||
val status = branch ?: return null
|
||||
if (status.availability == GhAvailability.GIT_MISSING) return null
|
||||
val name = name()?.let { XmlStringUtil.escapeString(it) }
|
||||
if (status.worktree) {
|
||||
return name?.let { KiloBundle.message("session.empty.worktree", it) }
|
||||
?: KiloBundle.message("session.empty.worktree.unknown")
|
||||
}
|
||||
if (name == null) return null
|
||||
return KiloBundle.message("session.empty.branch", name, worktreePhrase())
|
||||
}
|
||||
|
||||
/**
|
||||
* "run it in a worktree" as a link, or as plain text on surfaces that cannot open the flow, so
|
||||
* the sentence reads the same either way.
|
||||
*/
|
||||
private fun worktreePhrase(): String {
|
||||
val phrase = KiloBundle.message("session.empty.branch.link")
|
||||
if (newWorktree == null) return XmlStringUtil.escapeString(phrase)
|
||||
return HtmlChunk.link(WORKTREE_HREF, phrase).toString()
|
||||
}
|
||||
|
||||
/** Branch name trimmed to fit the fixed-width description, or null when there is no branch. */
|
||||
private fun name(): String? {
|
||||
val value = branch?.branch?.trim().orEmpty()
|
||||
if (value.isEmpty() || value == DETACHED) return null
|
||||
return StringUtil.shortenTextWithEllipsis(value, BRANCH_MAX, 0, true)
|
||||
}
|
||||
|
||||
@RequiresEdt
|
||||
private fun syncTip() {
|
||||
val tip = tip()
|
||||
welcomeLabel.text = centeredHtml(
|
||||
tip ?: XmlStringUtil.escapeString(KiloBundle.message("session.empty.welcome")),
|
||||
)
|
||||
// Minimal surfaces (worktree/subagent editor tabs) skip the generic blurb but still want a
|
||||
// state-specific tip, so the slot is attached on demand instead of once at construction.
|
||||
val described = tip != null || !minimal
|
||||
if (described && descriptionSlot.parent == null) header.add(descriptionSlot, BorderLayout.CENTER)
|
||||
if (!described && descriptionSlot.parent != null) header.remove(descriptionSlot)
|
||||
revalidate()
|
||||
repaint()
|
||||
}
|
||||
|
||||
internal fun recentCount() = recent.count()
|
||||
@@ -184,6 +271,16 @@ class EmptySessionPanel(
|
||||
|
||||
internal fun explanationText() = KiloBundle.message("session.empty.welcome")
|
||||
|
||||
/** The tip as the user reads it, with the inline link's markup stripped. */
|
||||
internal fun tipText() = tip()?.let { StringUtil.removeHtmlTags(it) }
|
||||
|
||||
/** The plain text currently under the logo: the state-specific tip, or the generic welcome. */
|
||||
internal fun descriptionText() = tipText() ?: KiloBundle.message("session.empty.welcome")
|
||||
|
||||
internal fun worktreeLinked() = tip()?.contains("href=\"$WORKTREE_HREF\"") == true
|
||||
|
||||
internal fun worktreeHref() = WORKTREE_HREF
|
||||
|
||||
internal fun welcomeLabelAlignment() = welcomeLabel.horizontalAlignment
|
||||
|
||||
internal fun descriptionPreferredSize() = description.preferredSize
|
||||
@@ -272,11 +369,19 @@ class EmptySessionPanel(
|
||||
repaint()
|
||||
}
|
||||
|
||||
private fun welcomeHtml() = XmlStringUtil.wrapInHtml(
|
||||
"<div style='text-align:center'>${XmlStringUtil.escapeString(KiloBundle.message("session.empty.welcome"))}</div>"
|
||||
/** [body] must already be escaped or generated HTML — this only wraps and centers it. */
|
||||
private fun centeredHtml(body: String) = XmlStringUtil.wrapInHtml(
|
||||
"<div style='text-align:center'>$body</div>"
|
||||
)
|
||||
|
||||
private companion object {
|
||||
const val ACTIVITY_MS = 3_000
|
||||
|
||||
/** Keeps a long branch name from wrapping the fixed-width description into a wall of text. */
|
||||
const val BRANCH_MAX = 28
|
||||
const val DETACHED = "(detached)"
|
||||
|
||||
/** Href of the inline worktree link; matched in the label's hyperlink listener. */
|
||||
const val WORKTREE_HREF = "worktree"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,10 @@ session.connection.warning.config=Configuration warnings
|
||||
notification.group.kilo=Kilo Code
|
||||
|
||||
session.empty.welcome=Kilo Code is an AI coding assistant. Ask it to build features, fix bugs, or explain your codebase.
|
||||
session.empty.branch=You''re working directly on {0}. Start a task, or {1} to keep changes isolated.
|
||||
session.empty.branch.link=run it in a worktree
|
||||
session.empty.worktree=You''re in an isolated worktree on {0}. Work freely — your main checkout stays untouched.
|
||||
session.empty.worktree.unknown=You're in an isolated worktree. Work freely — your main checkout stays untouched.
|
||||
session.account.balance=Balance: {0}
|
||||
session.account.switcher=Switch account
|
||||
session.empty.loading=Loading...
|
||||
|
||||
@@ -17,6 +17,10 @@ session.connection.unsupported.options=الخيار 1: افتح المشروع
|
||||
session.connection.warning.config=تحذيرات التكوين
|
||||
|
||||
session.empty.welcome=Kilo Code هو مساعد برمجة بالذكاء الاصطناعي. اطلب منه بناء ميزات أو إصلاح أخطاء أو شرح قاعدة الكود.
|
||||
session.empty.branch=تعمل مباشرةً على {0}. ابدأ مهمة، أو {1} لإبقاء التغييرات معزولة.
|
||||
session.empty.branch.link=شغّلها في worktree
|
||||
session.empty.worktree=أنت في worktree معزول على {0}. اعمل بحرية — تبقى نسختك الرئيسية دون تغيير.
|
||||
session.empty.worktree.unknown=أنت في worktree معزول. اعمل بحرية — تبقى نسختك الرئيسية دون تغيير.
|
||||
session.empty.loading=جاري التحميل…
|
||||
session.empty.recent=الحديثة
|
||||
session.showHistory=عرض السجل
|
||||
@@ -163,6 +167,8 @@ action.Kilo.Settings.text=الإعدادات
|
||||
action.Kilo.Settings.description=إعدادات Kilo Code
|
||||
action.Kilo.NewSession.text=جلسة جديدة
|
||||
action.Kilo.NewSession.description=بدء جلسة Kilo جديدة
|
||||
action.Kilo.NewSession.toolbar=جلسة
|
||||
action.Kilo.NewWorktree.toolbar=Worktree
|
||||
action.Kilo.History.text=السجل
|
||||
action.Kilo.History.description=عرض سجل الجلسات
|
||||
action.Kilo.SendPrompt.text=إرسال الطلب
|
||||
|
||||
@@ -17,6 +17,10 @@ session.connection.unsupported.options=Opcija 1: Otvorite projekat u kontejneru
|
||||
session.connection.warning.config=Upozorenja konfiguracije
|
||||
|
||||
session.empty.welcome=Kilo Code je AI asistent za kodiranje. Zatražite od njega da gradi funkcije, ispravlja greške ili objašnjava vašu bazu koda.
|
||||
session.empty.branch=Radite direktno na {0}. Započnite zadatak ili {1} da izmjene ostanu izolovane.
|
||||
session.empty.branch.link=pokrenite ga u worktree-u
|
||||
session.empty.worktree=Nalazite se u izolovanom worktree-u na {0}. Radite slobodno — vaš glavni checkout ostaje nepromijenjen.
|
||||
session.empty.worktree.unknown=Nalazite se u izolovanom worktree-u. Radite slobodno — vaš glavni checkout ostaje nepromijenjen.
|
||||
session.empty.loading=Učitavanje…
|
||||
session.empty.recent=NEDAVNO
|
||||
session.showHistory=Prikaži historiju
|
||||
@@ -163,6 +167,8 @@ action.Kilo.Settings.text=Postavke
|
||||
action.Kilo.Settings.description=Postavke Kilo Code
|
||||
action.Kilo.NewSession.text=Nova sesija
|
||||
action.Kilo.NewSession.description=Pokrenite novu Kilo sesiju
|
||||
action.Kilo.NewSession.toolbar=Sesija
|
||||
action.Kilo.NewWorktree.toolbar=Worktree
|
||||
action.Kilo.History.text=Historija
|
||||
action.Kilo.History.description=Prikaži historiju sesija
|
||||
action.Kilo.SendPrompt.text=Pošalji upit
|
||||
|
||||
@@ -17,6 +17,10 @@ session.connection.unsupported.options=Mulighed 1: Åbn projektet i containeren
|
||||
session.connection.warning.config=Konfigurationsadvarsler
|
||||
|
||||
session.empty.welcome=Kilo Code er en AI-kodningsassistent. Bed den om at bygge funktioner, rette fejl eller forklare din kodebase.
|
||||
session.empty.branch=Du arbejder direkte på {0}. Start en opgave, eller {1} for at holde ændringer isoleret.
|
||||
session.empty.branch.link=kør den i et worktree
|
||||
session.empty.worktree=Du er i et isoleret worktree på {0}. Arbejd frit — dit primære checkout forbliver urørt.
|
||||
session.empty.worktree.unknown=Du er i et isoleret worktree. Arbejd frit — dit primære checkout forbliver urørt.
|
||||
session.empty.loading=Indlæser…
|
||||
session.empty.recent=SENESTE
|
||||
session.showHistory=Vis historik
|
||||
@@ -163,6 +167,8 @@ action.Kilo.Settings.text=Indstillinger
|
||||
action.Kilo.Settings.description=Kilo Code-indstillinger
|
||||
action.Kilo.NewSession.text=Ny session
|
||||
action.Kilo.NewSession.description=Start en ny Kilo-session
|
||||
action.Kilo.NewSession.toolbar=Session
|
||||
action.Kilo.NewWorktree.toolbar=Worktree
|
||||
action.Kilo.History.text=Historik
|
||||
action.Kilo.History.description=Vis sessionshistorik
|
||||
action.Kilo.SendPrompt.text=Send prompt
|
||||
|
||||
@@ -17,6 +17,10 @@ session.connection.unsupported.options=Option 1: Öffnen Sie das Projekt im Cont
|
||||
session.connection.warning.config=Konfigurationswarnungen
|
||||
|
||||
session.empty.welcome=Kilo Code ist ein KI-Coding-Assistent. Bitten Sie ihn, Funktionen zu erstellen, Fehler zu beheben oder Ihre Codebasis zu erklären.
|
||||
session.empty.branch=Sie arbeiten direkt auf {0}. Starten Sie eine Aufgabe oder {1}, um Änderungen isoliert zu halten.
|
||||
session.empty.branch.link=führen Sie sie in einem Worktree aus
|
||||
session.empty.worktree=Sie sind in einem isolierten Worktree auf {0}. Arbeiten Sie frei — Ihr Haupt-Checkout bleibt unberührt.
|
||||
session.empty.worktree.unknown=Sie sind in einem isolierten Worktree. Arbeiten Sie frei — Ihr Haupt-Checkout bleibt unberührt.
|
||||
session.empty.loading=Wird geladen…
|
||||
session.empty.recent=ZULETZT
|
||||
session.showHistory=Verlauf anzeigen
|
||||
@@ -163,6 +167,8 @@ action.Kilo.Settings.text=Einstellungen
|
||||
action.Kilo.Settings.description=Kilo Code Einstellungen
|
||||
action.Kilo.NewSession.text=Neue Sitzung
|
||||
action.Kilo.NewSession.description=Neue Kilo-Sitzung starten
|
||||
action.Kilo.NewSession.toolbar=Sitzung
|
||||
action.Kilo.NewWorktree.toolbar=Worktree
|
||||
action.Kilo.History.text=Verlauf
|
||||
action.Kilo.History.description=Sitzungsverlauf anzeigen
|
||||
action.Kilo.SendPrompt.text=Anfrage senden
|
||||
|
||||
@@ -17,6 +17,10 @@ session.connection.unsupported.options=Opción 1: Abre el proyecto en el contene
|
||||
session.connection.warning.config=Advertencias de configuración
|
||||
|
||||
session.empty.welcome=Kilo Code es un asistente de codificación con IA. Pida que construya funciones, corrija errores o explique su base de código.
|
||||
session.empty.branch=Estás trabajando directamente en {0}. Inicia una tarea o {1} para mantener los cambios aislados.
|
||||
session.empty.branch.link=ejecútala en un worktree
|
||||
session.empty.worktree=Estás en un worktree aislado en {0}. Trabaja con libertad — tu checkout principal no se toca.
|
||||
session.empty.worktree.unknown=Estás en un worktree aislado. Trabaja con libertad — tu checkout principal no se toca.
|
||||
session.empty.loading=Cargando…
|
||||
session.empty.recent=RECIENTE
|
||||
session.showHistory=Mostrar historial
|
||||
@@ -163,6 +167,8 @@ action.Kilo.Settings.text=Configuración
|
||||
action.Kilo.Settings.description=Configuración de Kilo Code
|
||||
action.Kilo.NewSession.text=Nueva sesión
|
||||
action.Kilo.NewSession.description=Iniciar una nueva sesión de Kilo
|
||||
action.Kilo.NewSession.toolbar=Sesión
|
||||
action.Kilo.NewWorktree.toolbar=Worktree
|
||||
action.Kilo.History.text=Historial
|
||||
action.Kilo.History.description=Mostrar el historial de sesiones
|
||||
action.Kilo.SendPrompt.text=Enviar mensaje
|
||||
|
||||
@@ -17,6 +17,10 @@ session.connection.unsupported.options=Option 1 : ouvrez le projet dans le conte
|
||||
session.connection.warning.config=Avertissements de configuration
|
||||
|
||||
session.empty.welcome=Kilo Code est un assistant de codage IA. Demandez-lui de créer des fonctionnalités, corriger des bugs ou expliquer votre base de code.
|
||||
session.empty.branch=Vous travaillez directement sur {0}. Lancez une tâche ou {1} pour isoler vos modifications.
|
||||
session.empty.branch.link=exécutez-la dans un worktree
|
||||
session.empty.worktree=Vous êtes dans un worktree isolé sur {0}. Travaillez librement — votre checkout principal reste intact.
|
||||
session.empty.worktree.unknown=Vous êtes dans un worktree isolé. Travaillez librement — votre checkout principal reste intact.
|
||||
session.empty.loading=Chargement…
|
||||
session.empty.recent=RÉCENT
|
||||
session.showHistory=Afficher l'historique
|
||||
@@ -163,6 +167,8 @@ action.Kilo.Settings.text=Paramètres
|
||||
action.Kilo.Settings.description=Paramètres de Kilo Code
|
||||
action.Kilo.NewSession.text=Nouvelle session
|
||||
action.Kilo.NewSession.description=Démarrer une nouvelle session Kilo
|
||||
action.Kilo.NewSession.toolbar=Session
|
||||
action.Kilo.NewWorktree.toolbar=Worktree
|
||||
action.Kilo.History.text=Historique
|
||||
action.Kilo.History.description=Afficher l'historique des sessions
|
||||
action.Kilo.SendPrompt.text=Envoyer l'invite
|
||||
|
||||
@@ -17,6 +17,10 @@ session.connection.unsupported.options=オプション1: JetBrains Gateway を
|
||||
session.connection.warning.config=設定の警告
|
||||
|
||||
session.empty.welcome=Kilo CodeはAIコーディングアシスタントです。機能の作成、バグの修正、またはコードベースの説明を依頼できます。
|
||||
session.empty.branch={0} で直接作業しています。タスクを開始するか、{1}すると変更を隔離できます。
|
||||
session.empty.branch.link=worktree で実行
|
||||
session.empty.worktree={0} の独立した worktree にいます。メインのチェックアウトには影響しないので自由に作業できます。
|
||||
session.empty.worktree.unknown=独立した worktree にいます。メインのチェックアウトには影響しないので自由に作業できます。
|
||||
session.empty.loading=読み込み中…
|
||||
session.empty.recent=最近
|
||||
session.showHistory=履歴を表示
|
||||
@@ -163,6 +167,8 @@ action.Kilo.Settings.text=設定
|
||||
action.Kilo.Settings.description=Kilo Codeの設定
|
||||
action.Kilo.NewSession.text=新しいセッション
|
||||
action.Kilo.NewSession.description=新しいKiloセッションを開始
|
||||
action.Kilo.NewSession.toolbar=セッション
|
||||
action.Kilo.NewWorktree.toolbar=Worktree
|
||||
action.Kilo.History.text=履歴
|
||||
action.Kilo.History.description=セッション履歴を表示
|
||||
action.Kilo.SendPrompt.text=プロンプトを送信
|
||||
|
||||
@@ -17,6 +17,10 @@ session.connection.unsupported.options=옵션 1: JetBrains Gateway로 컨테이
|
||||
session.connection.warning.config=구성 경고
|
||||
|
||||
session.empty.welcome=Kilo Code는 AI 코딩 어시스턴트입니다. 기능 구축, 버그 수정, 코드베이스 설명을 요청하세요.
|
||||
session.empty.branch={0}에서 직접 작업하고 있습니다. 작업을 시작하거나 {1}하여 변경 사항을 분리하세요.
|
||||
session.empty.branch.link=worktree에서 실행
|
||||
session.empty.worktree={0}의 격리된 worktree에 있습니다. 메인 체크아웃은 영향을 받지 않으니 자유롭게 작업하세요.
|
||||
session.empty.worktree.unknown=격리된 worktree에 있습니다. 메인 체크아웃은 영향을 받지 않으니 자유롭게 작업하세요.
|
||||
session.empty.loading=로딩 중…
|
||||
session.empty.recent=최근
|
||||
session.showHistory=기록 보기
|
||||
@@ -163,6 +167,8 @@ action.Kilo.Settings.text=설정
|
||||
action.Kilo.Settings.description=Kilo Code 설정
|
||||
action.Kilo.NewSession.text=새 세션
|
||||
action.Kilo.NewSession.description=새 Kilo 세션 시작
|
||||
action.Kilo.NewSession.toolbar=세션
|
||||
action.Kilo.NewWorktree.toolbar=Worktree
|
||||
action.Kilo.History.text=기록
|
||||
action.Kilo.History.description=세션 기록 표시
|
||||
action.Kilo.SendPrompt.text=프롬프트 전송
|
||||
|
||||
@@ -17,6 +17,10 @@ session.connection.unsupported.options=Optie 1: Open het project in de container
|
||||
session.connection.warning.config=Configuratiewaarschuwingen
|
||||
|
||||
session.empty.welcome=Kilo Code is een AI-codeerassistent. Vraag het om functies te bouwen, bugs te repareren of uw codebase uit te leggen.
|
||||
session.empty.branch=Je werkt direct op {0}. Start een taak of {1} om wijzigingen geïsoleerd te houden.
|
||||
session.empty.branch.link=voer hem uit in een worktree
|
||||
session.empty.worktree=Je zit in een geïsoleerde worktree op {0}. Werk vrij — je hoofdcheckout blijft ongemoeid.
|
||||
session.empty.worktree.unknown=Je zit in een geïsoleerde worktree. Werk vrij — je hoofdcheckout blijft ongemoeid.
|
||||
session.empty.loading=Laden…
|
||||
session.empty.recent=RECENT
|
||||
session.showHistory=Geschiedenis weergeven
|
||||
@@ -163,6 +167,8 @@ action.Kilo.Settings.text=Instellingen
|
||||
action.Kilo.Settings.description=Kilo Code-instellingen
|
||||
action.Kilo.NewSession.text=Nieuwe sessie
|
||||
action.Kilo.NewSession.description=Een nieuwe Kilo-sessie starten
|
||||
action.Kilo.NewSession.toolbar=Sessie
|
||||
action.Kilo.NewWorktree.toolbar=Worktree
|
||||
action.Kilo.History.text=Geschiedenis
|
||||
action.Kilo.History.description=Sessiegeschiedenis weergeven
|
||||
action.Kilo.SendPrompt.text=Prompt verzenden
|
||||
|
||||
@@ -17,6 +17,10 @@ session.connection.unsupported.options=Alternativ 1: Åpne prosjektet i containe
|
||||
session.connection.warning.config=Konfigurasjonsadvarsler
|
||||
|
||||
session.empty.welcome=Kilo Code er en AI-kodingsassistent. Be den om å bygge funksjoner, fikse feil eller forklare kodebasen din.
|
||||
session.empty.branch=Du jobber direkte på {0}. Start en oppgave, eller {1} for å holde endringene isolert.
|
||||
session.empty.branch.link=kjør den i et worktree
|
||||
session.empty.worktree=Du er i et isolert worktree på {0}. Jobb fritt — hovedutsjekkingen din er urørt.
|
||||
session.empty.worktree.unknown=Du er i et isolert worktree. Jobb fritt — hovedutsjekkingen din er urørt.
|
||||
session.empty.loading=Laster…
|
||||
session.empty.recent=NYLIGE
|
||||
session.showHistory=Vis historikk
|
||||
@@ -168,6 +172,8 @@ action.Kilo.Settings.text=Innstillinger
|
||||
action.Kilo.Settings.description=Kilo Code-innstillinger
|
||||
action.Kilo.NewSession.text=Ny økt
|
||||
action.Kilo.NewSession.description=Start en ny Kilo-økt
|
||||
action.Kilo.NewSession.toolbar=Økt
|
||||
action.Kilo.NewWorktree.toolbar=Worktree
|
||||
action.Kilo.History.text=Historikk
|
||||
action.Kilo.History.description=Vis økthistorikk
|
||||
action.Kilo.SendPrompt.text=Send forespørsel
|
||||
|
||||
@@ -17,6 +17,10 @@ session.connection.unsupported.options=Opcja 1: Otwórz projekt w kontenerze lub
|
||||
session.connection.warning.config=Ostrzeżenia konfiguracji
|
||||
|
||||
session.empty.welcome=Kilo Code to asystent kodowania AI. Poproś go o tworzenie funkcji, naprawianie błędów lub wyjaśnianie bazy kodu.
|
||||
session.empty.branch=Pracujesz bezpośrednio na {0}. Rozpocznij zadanie lub {1}, aby odizolować zmiany.
|
||||
session.empty.branch.link=uruchom je w worktree
|
||||
session.empty.worktree=Jesteś w odizolowanym worktree na {0}. Pracuj swobodnie — twój główny checkout pozostaje nietknięty.
|
||||
session.empty.worktree.unknown=Jesteś w odizolowanym worktree. Pracuj swobodnie — twój główny checkout pozostaje nietknięty.
|
||||
session.empty.loading=Ładowanie…
|
||||
session.empty.recent=OSTATNIE
|
||||
session.showHistory=Pokaż historię
|
||||
@@ -168,6 +172,8 @@ action.Kilo.Settings.text=Ustawienia
|
||||
action.Kilo.Settings.description=Ustawienia Kilo Code
|
||||
action.Kilo.NewSession.text=Nowa sesja
|
||||
action.Kilo.NewSession.description=Rozpocznij nową sesję Kilo
|
||||
action.Kilo.NewSession.toolbar=Sesja
|
||||
action.Kilo.NewWorktree.toolbar=Worktree
|
||||
action.Kilo.History.text=Historia
|
||||
action.Kilo.History.description=Pokaż historię sesji
|
||||
action.Kilo.SendPrompt.text=Wyślij monit
|
||||
|
||||
+6
@@ -17,6 +17,10 @@ session.connection.unsupported.options=Opção 1: Abra o projeto no contêiner o
|
||||
session.connection.warning.config=Avisos de configuração
|
||||
|
||||
session.empty.welcome=Kilo Code é um assistente de codificação com IA. Peça que construa funcionalidades, corrija bugs ou explique sua base de código.
|
||||
session.empty.branch=Você está trabalhando diretamente em {0}. Inicie uma tarefa ou {1} para manter as alterações isoladas.
|
||||
session.empty.branch.link=execute-a em um worktree
|
||||
session.empty.worktree=Você está em um worktree isolado em {0}. Trabalhe livremente — seu checkout principal permanece intacto.
|
||||
session.empty.worktree.unknown=Você está em um worktree isolado. Trabalhe livremente — seu checkout principal permanece intacto.
|
||||
session.empty.loading=Carregando…
|
||||
session.empty.recent=RECENTE
|
||||
session.showHistory=Mostrar histórico
|
||||
@@ -168,6 +172,8 @@ action.Kilo.Settings.text=Configurações
|
||||
action.Kilo.Settings.description=Configurações do Kilo Code
|
||||
action.Kilo.NewSession.text=Nova sessão
|
||||
action.Kilo.NewSession.description=Iniciar uma nova sessão do Kilo
|
||||
action.Kilo.NewSession.toolbar=Sessão
|
||||
action.Kilo.NewWorktree.toolbar=Worktree
|
||||
action.Kilo.History.text=Histórico
|
||||
action.Kilo.History.description=Exibir histórico de sessões
|
||||
action.Kilo.SendPrompt.text=Enviar prompt
|
||||
|
||||
@@ -17,6 +17,10 @@ session.connection.unsupported.options=Вариант 1: откройте про
|
||||
session.connection.warning.config=Предупреждения конфигурации
|
||||
|
||||
session.empty.welcome=Kilo Code — это AI-ассистент по программированию. Попросите его разработать функции, исправить ошибки или объяснить кодовую базу.
|
||||
session.empty.branch=Вы работаете напрямую в {0}. Начните задачу или {1}, чтобы изолировать изменения.
|
||||
session.empty.branch.link=запустите её в worktree
|
||||
session.empty.worktree=Вы в изолированном worktree на {0}. Работайте свободно — основная рабочая копия не затрагивается.
|
||||
session.empty.worktree.unknown=Вы в изолированном worktree. Работайте свободно — основная рабочая копия не затрагивается.
|
||||
session.empty.loading=Загрузка…
|
||||
session.empty.recent=НЕДАВНИЕ
|
||||
session.showHistory=Показать историю
|
||||
@@ -168,6 +172,8 @@ action.Kilo.Settings.text=Настройки
|
||||
action.Kilo.Settings.description=Настройки Kilo Code
|
||||
action.Kilo.NewSession.text=Новая сессия
|
||||
action.Kilo.NewSession.description=Запустить новую сессию Kilo
|
||||
action.Kilo.NewSession.toolbar=Сессия
|
||||
action.Kilo.NewWorktree.toolbar=Worktree
|
||||
action.Kilo.History.text=История
|
||||
action.Kilo.History.description=Показать историю сессий
|
||||
action.Kilo.SendPrompt.text=Отправить запрос
|
||||
|
||||
@@ -17,6 +17,10 @@ session.connection.unsupported.options=ตัวเลือกที่ 1: เ
|
||||
session.connection.warning.config=คำเตือนการกำหนดค่า
|
||||
|
||||
session.empty.welcome=Kilo Code คือผู้ช่วยเขียนโค้ด AI ขอให้สร้างฟีเจอร์แก้สันข้อผิดพลาด หรืออธิบายโค้ดเบสของคุณ
|
||||
session.empty.branch=คุณกำลังทำงานบน {0} โดยตรง เริ่มงานใหม่ หรือ {1} เพื่อแยกการเปลี่ยนแปลงออกจากกัน
|
||||
session.empty.branch.link=รันใน worktree
|
||||
session.empty.worktree=คุณอยู่ใน worktree ที่แยกอิสระบน {0} ทำงานได้อย่างอิสระ — เช็คเอาต์หลักของคุณไม่ถูกแตะต้อง
|
||||
session.empty.worktree.unknown=คุณอยู่ใน worktree ที่แยกอิสระ ทำงานได้อย่างอิสระ — เช็คเอาต์หลักของคุณไม่ถูกแตะต้อง
|
||||
session.empty.loading=กำลังโหลด…
|
||||
session.empty.recent=ล่าสุด
|
||||
session.showHistory=แสดงประวัติ
|
||||
@@ -168,6 +172,8 @@ action.Kilo.Settings.text=การตั้งค่า
|
||||
action.Kilo.Settings.description=การตั้งค่า Kilo Code
|
||||
action.Kilo.NewSession.text=เซสชันใหม่
|
||||
action.Kilo.NewSession.description=เริ่มเซสชัน Kilo ใหม่
|
||||
action.Kilo.NewSession.toolbar=เซสชัน
|
||||
action.Kilo.NewWorktree.toolbar=Worktree
|
||||
action.Kilo.History.text=ประวัติ
|
||||
action.Kilo.History.description=แสดงประวัติเซสชัน
|
||||
action.Kilo.SendPrompt.text=ส่งคำขอ
|
||||
|
||||
@@ -17,6 +17,10 @@ session.connection.unsupported.options=Seçenek 1: Projeyi JetBrains Gateway ile
|
||||
session.connection.warning.config=Yapılandırma uyarıları
|
||||
|
||||
session.empty.welcome=Kilo Code, bir yapay zeka kodlama asistanıdır. Özellik oluşturmasını, hata düzeltirlmesi veya kod tabanınızı açıklamasını isteyin.
|
||||
session.empty.branch=Doğrudan {0} üzerinde çalışıyorsunuz. Bir görev başlatın ya da değişiklikleri izole tutmak için {1}.
|
||||
session.empty.branch.link=bir worktree içinde çalıştırın
|
||||
session.empty.worktree={0} üzerinde izole bir worktree içindesiniz. Rahatça çalışın — ana checkout''unuz olduğu gibi kalır.
|
||||
session.empty.worktree.unknown=İzole bir worktree içindesiniz. Rahatça çalışın — ana checkout'unuz olduğu gibi kalır.
|
||||
session.empty.loading=Yükleniyor…
|
||||
session.empty.recent=SON
|
||||
session.showHistory=Geçmişi göster
|
||||
@@ -168,6 +172,8 @@ action.Kilo.Settings.text=Ayarlar
|
||||
action.Kilo.Settings.description=Kilo Code ayarları
|
||||
action.Kilo.NewSession.text=Yeni oturum
|
||||
action.Kilo.NewSession.description=Yeni bir Kilo oturumu başlat
|
||||
action.Kilo.NewSession.toolbar=Oturum
|
||||
action.Kilo.NewWorktree.toolbar=Worktree
|
||||
action.Kilo.History.text=Geçmiş
|
||||
action.Kilo.History.description=Oturum geçmişini göster
|
||||
action.Kilo.SendPrompt.text=İstem gönder
|
||||
|
||||
@@ -17,6 +17,10 @@ session.connection.unsupported.options=Варіант 1: відкрийте пр
|
||||
session.connection.warning.config=Попередження конфігурації
|
||||
|
||||
session.empty.welcome=Kilo Code — це AI-асистент для програмування. Попросіть його створити функції, виправити помилки або пояснити ваш код.
|
||||
session.empty.branch=Ви працюєте безпосередньо в {0}. Почніть завдання або {1}, щоб ізолювати зміни.
|
||||
session.empty.branch.link=запустіть його в worktree
|
||||
session.empty.worktree=Ви в ізольованому worktree на {0}. Працюйте вільно — основна робоча копія залишається незмінною.
|
||||
session.empty.worktree.unknown=Ви в ізольованому worktree. Працюйте вільно — основна робоча копія залишається незмінною.
|
||||
session.empty.loading=Завантаження…
|
||||
session.empty.recent=НЕДАВНІ
|
||||
session.showHistory=Показати історію
|
||||
@@ -163,6 +167,8 @@ action.Kilo.Settings.text=Налаштування
|
||||
action.Kilo.Settings.description=Налаштування Kilo Code
|
||||
action.Kilo.NewSession.text=Нова сесія
|
||||
action.Kilo.NewSession.description=Розпочати нову сесію Kilo
|
||||
action.Kilo.NewSession.toolbar=Сесія
|
||||
action.Kilo.NewWorktree.toolbar=Worktree
|
||||
action.Kilo.History.text=Історія
|
||||
action.Kilo.History.description=Показати історію сесій
|
||||
action.Kilo.SendPrompt.text=Надіслати запит
|
||||
|
||||
+6
@@ -17,6 +17,10 @@ session.connection.unsupported.options=选项 1:使用 JetBrains Gateway 在
|
||||
session.connection.warning.config=配置警告
|
||||
|
||||
session.empty.welcome=Kilo Code 是一个 AI 编程助手。可请它构建功能、修复错误或解释您的代码库。
|
||||
session.empty.branch=你正在 {0} 上直接工作。开始一个任务,或{1}以隔离改动。
|
||||
session.empty.branch.link=在 worktree 中运行
|
||||
session.empty.worktree=你在 {0} 的独立 worktree 中。可以放心工作 — 主工作副本不会受到影响。
|
||||
session.empty.worktree.unknown=你在独立的 worktree 中。可以放心工作 — 主工作副本不会受到影响。
|
||||
session.empty.loading=加载中…
|
||||
session.empty.recent=最近
|
||||
session.showHistory=显示历史
|
||||
@@ -163,6 +167,8 @@ action.Kilo.Settings.text=设置
|
||||
action.Kilo.Settings.description=Kilo Code 设置
|
||||
action.Kilo.NewSession.text=新建会话
|
||||
action.Kilo.NewSession.description=开始新的 Kilo 会话
|
||||
action.Kilo.NewSession.toolbar=会话
|
||||
action.Kilo.NewWorktree.toolbar=Worktree
|
||||
action.Kilo.History.text=历史
|
||||
action.Kilo.History.description=显示会话历史
|
||||
action.Kilo.SendPrompt.text=发送提示
|
||||
|
||||
+6
@@ -17,6 +17,10 @@ session.connection.unsupported.options=選項 1:使用 JetBrains Gateway 在
|
||||
session.connection.warning.config=設定警告
|
||||
|
||||
session.empty.welcome=Kilo Code 是 AI 程式輔助。可請它建置功能、修復錯誤或解釋您的程式程式庫。
|
||||
session.empty.branch=你正在 {0} 上直接工作。開始一個任務,或{1}以隔離變更。
|
||||
session.empty.branch.link=在 worktree 中執行
|
||||
session.empty.worktree=你在 {0} 的獨立 worktree 中。可以放心工作 — 主工作副本不會受到影響。
|
||||
session.empty.worktree.unknown=你在獨立的 worktree 中。可以放心工作 — 主工作副本不會受到影響。
|
||||
session.empty.loading=載入中…
|
||||
session.empty.recent=最近
|
||||
session.showHistory=顯示歷史
|
||||
@@ -163,6 +167,8 @@ action.Kilo.Settings.text=設定
|
||||
action.Kilo.Settings.description=Kilo Code 設定
|
||||
action.Kilo.NewSession.text=新建工作階段
|
||||
action.Kilo.NewSession.description=開始新的 Kilo 工作階段
|
||||
action.Kilo.NewSession.toolbar=會話
|
||||
action.Kilo.NewWorktree.toolbar=Worktree
|
||||
action.Kilo.History.text=歷史
|
||||
action.Kilo.History.description=顯示工作階段歷史
|
||||
action.Kilo.SendPrompt.text=發送提示
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package ai.kilocode.client.plugin
|
||||
|
||||
import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
||||
import java.io.InputStreamReader
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.text.MessageFormat
|
||||
import java.util.Locale
|
||||
import java.util.Properties
|
||||
|
||||
/**
|
||||
* Guards the localized empty-session tip and toolbar labels.
|
||||
*
|
||||
* The tip keys carry `{0}`/`{1}`, so they go through [MessageFormat], where a lone apostrophe
|
||||
* silently swallows the surrounding text and `''` collapses to one. Translations are easy to get
|
||||
* wrong here, so every locale is formatted for real rather than compared as a raw string.
|
||||
*/
|
||||
class KiloBundleLocaleTest : BasePlatformTestCase() {
|
||||
fun `test parameterized tips format cleanly in every locale`() {
|
||||
for (locale in LOCALES) {
|
||||
val props = load(locale)
|
||||
|
||||
val branch = props.getProperty("session.empty.branch")
|
||||
assertNotNull("$locale: missing session.empty.branch", branch)
|
||||
assertEscaped(locale, "session.empty.branch", branch!!)
|
||||
val rendered = format(branch, "main", "LINK_PHRASE")
|
||||
assertTrue("$locale: branch tip dropped the branch name -> $rendered", rendered.contains("main"))
|
||||
assertTrue("$locale: branch tip dropped the link -> $rendered", rendered.contains("LINK_PHRASE"))
|
||||
assertClean(locale, "session.empty.branch", rendered)
|
||||
|
||||
val worktree = props.getProperty("session.empty.worktree")
|
||||
assertNotNull("$locale: missing session.empty.worktree", worktree)
|
||||
assertEscaped(locale, "session.empty.worktree", worktree!!)
|
||||
val tree = format(worktree, "feature/x")
|
||||
assertTrue("$locale: worktree tip dropped the branch name -> $tree", tree.contains("feature/x"))
|
||||
assertClean(locale, "session.empty.worktree", tree)
|
||||
}
|
||||
}
|
||||
|
||||
fun `test plain keys are present and carry no placeholders`() {
|
||||
for (locale in LOCALES) {
|
||||
val props = load(locale)
|
||||
for (key in PLAIN) {
|
||||
val value = props.getProperty(key)
|
||||
assertNotNull("$locale: missing $key", value)
|
||||
assertTrue("$locale: $key is blank", value!!.isNotBlank())
|
||||
assertFalse("$locale: $key should not contain a placeholder -> $value", value.contains("{0}"))
|
||||
assertFalse(
|
||||
"$locale: $key has no placeholders so apostrophes must not be doubled -> $value",
|
||||
value.contains("''"),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun format(pattern: String, vararg args: String) =
|
||||
MessageFormat(pattern, Locale.ROOT).format(args)
|
||||
|
||||
/**
|
||||
* Every apostrophe in a MessageFormat pattern must be doubled. A lone one opens a quoted run
|
||||
* that silently eats itself (and any placeholder it spans), which formatting alone will not
|
||||
* always reveal — so the raw pattern is checked directly.
|
||||
*/
|
||||
private fun assertEscaped(locale: String, key: String, pattern: String) {
|
||||
for (run in Regex("'+").findAll(pattern)) {
|
||||
assertTrue(
|
||||
"$locale: $key has an unescaped apostrophe, double it -> $pattern",
|
||||
run.value.length % 2 == 0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** After formatting, MessageFormat has consumed its quoting — leftovers mean a bad pattern. */
|
||||
private fun assertClean(locale: String, key: String, rendered: String) {
|
||||
assertFalse("$locale: $key still has a doubled apostrophe -> $rendered", rendered.contains("''"))
|
||||
assertFalse("$locale: $key left an unformatted placeholder -> $rendered", rendered.contains("{"))
|
||||
}
|
||||
|
||||
private fun load(locale: String): Properties {
|
||||
val name = if (locale == "en") "/messages/KiloBundle.properties" else "/messages/KiloBundle_$locale.properties"
|
||||
val stream = javaClass.getResourceAsStream(name)
|
||||
assertNotNull("$locale: $name not on the classpath", stream)
|
||||
return Properties().apply {
|
||||
InputStreamReader(stream!!, StandardCharsets.UTF_8).use { load(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val LOCALES = listOf(
|
||||
"en", "ar", "bs", "da", "de", "es", "fr", "ja", "ko", "nl",
|
||||
"no", "pl", "pt_BR", "ru", "th", "tr", "uk", "zh_CN", "zh_TW",
|
||||
)
|
||||
|
||||
val PLAIN = listOf(
|
||||
"session.empty.branch.link",
|
||||
"session.empty.worktree.unknown",
|
||||
"action.Kilo.NewSession.toolbar",
|
||||
"action.Kilo.NewWorktree.toolbar",
|
||||
)
|
||||
}
|
||||
}
|
||||
+144
-1
@@ -15,6 +15,8 @@ import ai.kilocode.client.ui.FilledBadgeIcon
|
||||
import ai.kilocode.client.testing.FakeAppRpcApi
|
||||
import ai.kilocode.client.testing.FakeSessionRpcApi
|
||||
import ai.kilocode.client.testing.FakeWorkspaceRpcApi
|
||||
import ai.kilocode.rpc.dto.BranchStatusDto
|
||||
import ai.kilocode.rpc.dto.GhAvailability
|
||||
import ai.kilocode.rpc.dto.KiloAppStateDto
|
||||
import ai.kilocode.rpc.dto.KiloAppStatusDto
|
||||
import ai.kilocode.rpc.dto.KiloWorkspaceStateDto
|
||||
@@ -34,6 +36,8 @@ import kotlinx.coroutines.runBlocking
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Cursor
|
||||
import javax.swing.JButton
|
||||
import javax.swing.JEditorPane
|
||||
import javax.swing.event.HyperlinkEvent
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
class EmptySessionPanelTest : BasePlatformTestCase() {
|
||||
@@ -159,6 +163,135 @@ class EmptySessionPanelTest : BasePlatformTestCase() {
|
||||
)
|
||||
}
|
||||
|
||||
// ---- branch/worktree tip under the logo ----
|
||||
|
||||
fun `test no tip until branch status arrives`() {
|
||||
val panel = panel(newWorktree = {})
|
||||
|
||||
assertNull(panel.tipText())
|
||||
assertFalse(panel.worktreeLinked())
|
||||
assertEquals(panel.explanationText(), panel.descriptionText())
|
||||
}
|
||||
|
||||
fun `test branch promotes running the task in a worktree`() {
|
||||
val panel = panel(newWorktree = {})
|
||||
|
||||
panel.setBranch(BranchStatusDto(branch = "main", worktree = false))
|
||||
|
||||
assertEquals(
|
||||
"You're working directly on main. Start a task, or run it in a worktree to keep changes isolated.",
|
||||
panel.tipText(),
|
||||
)
|
||||
assertTrue(panel.worktreeLinked())
|
||||
}
|
||||
|
||||
fun `test worktree hints isolation and links nothing`() {
|
||||
val panel = panel(newWorktree = {})
|
||||
|
||||
panel.setBranch(BranchStatusDto(branch = "feature/x", worktree = true))
|
||||
|
||||
assertEquals(
|
||||
"You're in an isolated worktree on feature/x. Work freely — your main checkout stays untouched.",
|
||||
panel.tipText(),
|
||||
)
|
||||
assertFalse(panel.worktreeLinked())
|
||||
}
|
||||
|
||||
fun `test worktree without a branch name falls back to the generic worktree tip`() {
|
||||
val panel = panel()
|
||||
|
||||
panel.setBranch(BranchStatusDto(branch = "(detached)", worktree = true))
|
||||
|
||||
assertEquals(
|
||||
"You're in an isolated worktree. Work freely — your main checkout stays untouched.",
|
||||
panel.tipText(),
|
||||
)
|
||||
}
|
||||
|
||||
fun `test detached plain checkout keeps the generic welcome`() {
|
||||
val panel = panel(newWorktree = {})
|
||||
|
||||
panel.setBranch(BranchStatusDto(branch = "(detached)", worktree = false))
|
||||
|
||||
assertNull(panel.tipText())
|
||||
assertFalse(panel.worktreeLinked())
|
||||
assertEquals(panel.explanationText(), panel.descriptionText())
|
||||
}
|
||||
|
||||
fun `test missing git keeps the generic welcome`() {
|
||||
val panel = panel(newWorktree = {})
|
||||
|
||||
panel.setBranch(
|
||||
BranchStatusDto(branch = "main", worktree = false, availability = GhAvailability.GIT_MISSING),
|
||||
)
|
||||
|
||||
assertNull(panel.tipText())
|
||||
assertFalse(panel.worktreeLinked())
|
||||
}
|
||||
|
||||
fun `test long branch name is shortened`() {
|
||||
val panel = panel()
|
||||
|
||||
panel.setBranch(BranchStatusDto(branch = "feature/a-very-long-branch-name-that-keeps-going"))
|
||||
|
||||
val tip = panel.tipText().orEmpty()
|
||||
assertTrue(tip, tip.contains("…"))
|
||||
assertFalse(tip, tip.contains("keeps-going"))
|
||||
}
|
||||
|
||||
fun `test minimal surface shows a worktree tip but no generic welcome`() {
|
||||
val panel = panel(minimal = true)
|
||||
|
||||
assertFalse(panel.descriptionVisible())
|
||||
|
||||
panel.setBranch(BranchStatusDto(branch = "feature/x", worktree = true))
|
||||
|
||||
assertTrue(panel.descriptionVisible())
|
||||
}
|
||||
|
||||
fun `test phrase stays plain text without a callback`() {
|
||||
val panel = panel()
|
||||
|
||||
panel.setBranch(BranchStatusDto(branch = "main", worktree = false))
|
||||
|
||||
assertEquals(
|
||||
"You're working directly on main. Start a task, or run it in a worktree to keep changes isolated.",
|
||||
panel.tipText(),
|
||||
)
|
||||
assertFalse(panel.worktreeLinked())
|
||||
}
|
||||
|
||||
fun `test activating the inline link invokes the callback`() {
|
||||
var fired = 0
|
||||
val panel = panel(newWorktree = { fired++ })
|
||||
panel.setBranch(BranchStatusDto(branch = "main", worktree = false))
|
||||
|
||||
activateLink(panel)
|
||||
|
||||
assertEquals(1, fired)
|
||||
}
|
||||
|
||||
fun `test activating the inline link ignores other hrefs`() {
|
||||
var fired = 0
|
||||
val panel = panel(newWorktree = { fired++ })
|
||||
panel.setBranch(BranchStatusDto(branch = "main", worktree = false))
|
||||
|
||||
activateLink(panel, href = "https://example.test")
|
||||
|
||||
assertEquals(0, fired)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires the activation through the editor pane that `setCopyable(true)` installs, so the real
|
||||
* listener wiring is exercised rather than a stand-in.
|
||||
*/
|
||||
private fun activateLink(panel: EmptySessionPanel, href: String = panel.worktreeHref()) {
|
||||
val pane = UIUtil.uiTraverser(panel).filter(JEditorPane::class.java).first()
|
||||
assertNotNull(pane)
|
||||
val event = HyperlinkEvent(pane, HyperlinkEvent.EventType.ACTIVATED, null, href)
|
||||
pane!!.hyperlinkListeners.forEach { it.hyperlinkUpdate(event) }
|
||||
}
|
||||
|
||||
fun `test selecting recent session does not open it`() {
|
||||
val panel = panel(listOf(session("ses_1"), session("ses_2")))
|
||||
|
||||
@@ -373,7 +506,17 @@ class EmptySessionPanelTest : BasePlatformTestCase() {
|
||||
activity: () -> Map<String, SessionActivityKind> = { sessions.activitySnapshot() },
|
||||
titles: () -> Map<String, String> = { emptyMap() },
|
||||
minimal: Boolean = false,
|
||||
) = EmptySessionPanel(testRootDisposable, controller, recents, history, activity, titles, minimal = minimal)
|
||||
newWorktree: (() -> Unit)? = null,
|
||||
) = EmptySessionPanel(
|
||||
testRootDisposable,
|
||||
controller,
|
||||
recents,
|
||||
history,
|
||||
activity,
|
||||
titles,
|
||||
minimal = minimal,
|
||||
newWorktree = newWorktree,
|
||||
)
|
||||
|
||||
private fun flush() = runBlocking {
|
||||
delay(100)
|
||||
|
||||
Reference in New Issue
Block a user