TASK-059: jump from a function in the outline to its counterpart file

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
최민영 2026-09-28 16:17:22 +09:00
parent 3c1bd1adb2
commit aa23f356d9
4 changed files with 116 additions and 1 deletions

View File

@ -202,6 +202,8 @@ fun CodeViewerScreen(
// The share of the width the left pane of a header/source pair takes. // The share of the width the left pane of a header/source pair takes.
var splitFraction by rememberSaveable { mutableFloatStateOf(DEFAULT_SPLIT) } var splitFraction by rememberSaveable { mutableFloatStateOf(DEFAULT_SPLIT) }
val drawerState = rememberDrawerState(DrawerValue.Closed) val drawerState = rememberDrawerState(DrawerValue.Closed)
// The sheet is shown from outside the layout below, where the jump is worked out.
var jumpForSheet by remember { mutableStateOf<SymbolJump?>(null) }
// The other half of a header/source pair, once a wide window has asked for it and it has // The other half of a header/source pair, once a wide window has asked for it and it has
// arrived as text. // arrived as text.
@ -265,6 +267,27 @@ fun CodeViewerScreen(
val sidePaneIsShown = layout == ViewerLayout.WIDE && !showsPair val sidePaneIsShown = layout == ViewerLayout.WIDE && !showsPair
val drawerWidth = (maxWidth * DRAWER_WIDTH_FRACTION).coerceAtMost(MaxDrawerWidth) val drawerWidth = (maxWidth * DRAWER_WIDTH_FRACTION).coerceAtMost(MaxDrawerWidth)
// From a function in the outline to the same function in the other file of the pair: from
// a declaration in the header to its definition in the source, and back. Beside each other
// they already move together, so there it is not offered.
val jump = if (counterpartPath == null || showsPair) {
null
} else {
SymbolJump(
label = "." + counterpartPath.value.substringAfterLast('.'),
description = stringResource(
if (cppRoleOf(counterpartPath) == CppFileRole.HEADER) R.string.codeviewer_jump_to_header else R.string.codeviewer_jump_to_source,
counterpartPath.value.substringAfterLast('/'),
),
onJump = { symbol ->
outlineOpen = false
scope.launch { drawerState.close() }
onOpenSibling(counterpartPath, symbol.name)
},
)
}
jumpForSheet = jump
val tools = ViewerTools( val tools = ViewerTools(
preferences = preferences, preferences = preferences,
searching = searching, searching = searching,
@ -310,6 +333,7 @@ fun CodeViewerScreen(
onOpenEntry = onOpenEntry, onOpenEntry = onOpenEntry,
onToggleFolder = onToggleFolder, onToggleFolder = onToggleFolder,
outlineFirst = outlineFirst, outlineFirst = outlineFirst,
jump = jump,
onSwap = swapPanes, onSwap = swapPanes,
onSymbolSelected = { onSymbolSelected = {
scope.launch { drawerState.close() } scope.launch { drawerState.close() }
@ -384,6 +408,7 @@ fun CodeViewerScreen(
onOpenEntry = onOpenEntry, onOpenEntry = onOpenEntry,
onToggleFolder = onToggleFolder, onToggleFolder = onToggleFolder,
outlineFirst = outlineFirst, outlineFirst = outlineFirst,
jump = jump,
onSwap = swapPanes, onSwap = swapPanes,
onSymbolSelected = { goToSymbol(it) }, onSymbolSelected = { goToSymbol(it) },
modifier = Modifier.width(sidePaneWidth).fillMaxHeight(), modifier = Modifier.width(sidePaneWidth).fillMaxHeight(),
@ -421,6 +446,7 @@ fun CodeViewerScreen(
if (outlineOpen) { if (outlineOpen) {
OutlineSheet( OutlineSheet(
document = document, document = document,
jump = jumpForSheet,
currentLine = firstVisibleLine, currentLine = firstVisibleLine,
collapsed = collapsed, collapsed = collapsed,
onToggleCollapsed = toggleCollapsed, onToggleCollapsed = toggleCollapsed,
@ -437,6 +463,9 @@ fun CodeViewerScreen(
} }
} }
/** Goes from a function to the function of the same name in the other file of a header/source pair. */
private class SymbolJump(val label: String, val description: String, val onJump: (CodeSymbol) -> Unit)
private class ViewerTools( private class ViewerTools(
val preferences: CodeViewerPreferences, val preferences: CodeViewerPreferences,
val searching: Boolean, val searching: Boolean,
@ -997,13 +1026,14 @@ private fun SidePane(
onSymbolSelected: (CodeSymbol) -> Unit, onSymbolSelected: (CodeSymbol) -> Unit,
outlineFirst: Boolean, outlineFirst: Boolean,
onSwap: () -> Unit, onSwap: () -> Unit,
jump: SymbolJump?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val outline = remember(document.symbols, collapsed) { outlineTree(document.symbols, collapsed) } val outline = remember(document.symbols, collapsed) { outlineTree(document.symbols, collapsed) }
val rows = remember(state.tree) { state.tree.rows() } val rows = remember(state.tree) { state.tree.rows() }
val folders: @Composable (Modifier) -> Unit = { FolderCard(state, rows, onOpenFile, onToggleFolder, onOpenEntry, it) } val folders: @Composable (Modifier) -> Unit = { FolderCard(state, rows, onOpenFile, onToggleFolder, onOpenEntry, it) }
val symbols: @Composable (Modifier) -> Unit = { val symbols: @Composable (Modifier) -> Unit = {
SymbolCard(document, outline, currentLine, collapsed, onToggleCollapsed, onSymbolSelected, it) SymbolCard(document, outline, currentLine, collapsed, onToggleCollapsed, onSymbolSelected, jump, it)
} }
Column(modifier.background(MaterialTheme.colorScheme.background).padding(horizontal = 10.dp, vertical = 10.dp)) { Column(modifier.background(MaterialTheme.colorScheme.background).padding(horizontal = 10.dp, vertical = 10.dp)) {
val half = Modifier.weight(1f).fillMaxWidth() val half = Modifier.weight(1f).fillMaxWidth()
@ -1065,6 +1095,7 @@ private fun SymbolCard(
collapsed: Set<Int>, collapsed: Set<Int>,
onToggleCollapsed: (CodeSymbol) -> Unit, onToggleCollapsed: (CodeSymbol) -> Unit,
onSymbolSelected: (CodeSymbol) -> Unit, onSymbolSelected: (CodeSymbol) -> Unit,
jump: SymbolJump?,
modifier: Modifier, modifier: Modifier,
) { ) {
val current = currentIn(outline, document.symbols, currentLine) val current = currentIn(outline, document.symbols, currentLine)
@ -1092,6 +1123,7 @@ private fun SymbolCard(
isCollapsed = symbol.line in collapsed, isCollapsed = symbol.line in collapsed,
methodCount = methodCountOf(document.symbols, symbol), methodCount = methodCountOf(document.symbols, symbol),
onToggle = { onToggleCollapsed(symbol) }, onToggle = { onToggleCollapsed(symbol) },
jump = jump,
) { onSymbolSelected(symbol) } ) { onSymbolSelected(symbol) }
} }
} }
@ -1246,6 +1278,7 @@ private fun SymbolRow(
isCollapsed: Boolean, isCollapsed: Boolean,
methodCount: Int, methodCount: Int,
onToggle: () -> Unit, onToggle: () -> Unit,
jump: SymbolJump?,
onClick: () -> Unit, onClick: () -> Unit,
) { ) {
val colors = rememberCodeSyntaxColors() val colors = rememberCodeSyntaxColors()
@ -1311,6 +1344,22 @@ private fun SymbolRow(
maxLines = 1, maxLines = 1,
modifier = Modifier.padding(end = if (compact) 8.dp else 0.dp), modifier = Modifier.padding(end = if (compact) 8.dp else 0.dp),
) )
} else if (jump != null && symbol.kind == SymbolKind.METHOD) {
Surface(
onClick = { jump.onJump(symbol) },
shape = RoundedCornerShape(8.dp),
color = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
modifier = Modifier.semantics { contentDescription = jump.description },
) {
Row(
Modifier.padding(start = 8.dp, end = 4.dp, top = 5.dp, bottom = 5.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(jump.label, fontFamily = FontFamily.Monospace, fontSize = 12.sp, fontWeight = FontWeight.Bold, maxLines = 1)
UBIcon(R.drawable.ic_ub_chevron_right, null, size = 14.dp)
}
}
} else if (!compact) { } else if (!compact) {
Text( Text(
stringResource(R.string.codeviewer_outline_line, symbol.line + 1), stringResource(R.string.codeviewer_outline_line, symbol.line + 1),
@ -1326,6 +1375,7 @@ private fun SymbolRow(
@Composable @Composable
private fun OutlineSheet( private fun OutlineSheet(
document: CodeDocument, document: CodeDocument,
jump: SymbolJump?,
currentLine: Int, currentLine: Int,
collapsed: Set<Int>, collapsed: Set<Int>,
onToggleCollapsed: (CodeSymbol) -> Unit, onToggleCollapsed: (CodeSymbol) -> Unit,
@ -1400,6 +1450,7 @@ private fun OutlineSheet(
isCollapsed = symbol.line in collapsed, isCollapsed = symbol.line in collapsed,
methodCount = methodCountOf(document.symbols, symbol), methodCount = methodCountOf(document.symbols, symbol),
onToggle = { onToggleCollapsed(symbol) }, onToggle = { onToggleCollapsed(symbol) },
jump = jump,
) { onSymbolSelected(symbol) } ) { onSymbolSelected(symbol) }
} }
} }

Binary file not shown.

View File

@ -1392,3 +1392,12 @@ This is the append-only implementation and handoff record shared by Codex and Cl
- Live verification: installed on the SM-F966N; reproduced and fixed in unit tests only, not on - Live verification: installed on the SM-F966N; reproduced and fixed in unit tests only, not on
the owner's files. the owner's files.
- Next owner: the product owner, to confirm on the files they were reading. - Next owner: the product owner, to confirm on the files they were reading.
## 2026-09-28 (continued) — TASK-059: from a declaration to its definition
- Request: the human product owner confirmed the C++ outline now appears, and asked to go from a
function in a `.h` to where it is written in the `.cpp`.
- What changed: see `tasks/059-jump-from-declaration-to-definition.md`.
- Checks and results: the required checks pass; 435 unit tests, 0 failures; lint 0 errors.
- Live verification: installed on the SM-F966N; not seen on a screen by the implementer.
- Next owner: the product owner, to confirm on the device.

View File

@ -0,0 +1,55 @@
# TASK-059: From a function in the header to its definition in the source
Status: implemented; required checks pass; installed on the product owner's Z Fold7, awaiting their confirmation
Owner: Claude
Reviewer: human product owner
## Outcome
The human product owner confirmed the outline now appears for their C++ files (TASK-058) and asked
whether choosing a function in a `.h` could go to where it is written in the `.cpp`.
## Scope
- Included: in the outline (the side pane and the outline sheet), every function of a file that
has a counterpart carries a button named after the counterpart's extension (`.cpp ›` in a
header, `.h ›` in a source file). It opens the counterpart at the function of the same name.
Choosing the row itself still goes to the function in the file being read.
- Excluded:
- Tapping a function's name in the code itself.
- Side by side: the two panes already move to the same function together, so no button there.
- Telling overloads apart: the first function of that name is the one opened.
## Writable paths
- `app/src/main/java/com/ubisam/ubgitea/feature/codebrowser/CodeViewerScreen.kt`
- `tasks/`, `docs/`
## Acceptance criteria
- [x] The button appears only on functions, and only when the file has a counterpart that is not
already beside it.
- [x] The drawer or sheet closes and the counterpart opens at the function.
- [x] A function with no definition in the counterpart (inline, pure virtual) opens the counterpart
at its top rather than failing.
## Security and privacy
No additional concerns.
## Required checks
```text
./gradlew compileDebugKotlin compileDebugUnitTestKotlin testDebugUnitTest assembleDebug lintDebug
```
## Evidence
- Required checks pass: 435 unit tests, 0 failures; lint 0 errors. No test was added: the change
is wiring in the screen, and the lookup it uses (`findSymbolNamed`, `openSibling` with a symbol)
was already covered.
- Installed on the SM-F966N over wireless `adb`. Not seen working on a screen by the implementer.
## Review findings
None yet.