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:
parent
3c1bd1adb2
commit
aa23f356d9
@ -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.
@ -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.
|
||||||
|
|||||||
55
tasks/059-jump-from-declaration-to-definition.md
Normal file
55
tasks/059-jump-from-declaration-to-definition.md
Normal 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.
|
||||||
Loading…
x
Reference in New Issue
Block a user