diff --git a/app/src/main/java/com/ubisam/ubgitea/feature/codebrowser/CodeViewerScreen.kt b/app/src/main/java/com/ubisam/ubgitea/feature/codebrowser/CodeViewerScreen.kt index acbd407..f27f54a 100644 --- a/app/src/main/java/com/ubisam/ubgitea/feature/codebrowser/CodeViewerScreen.kt +++ b/app/src/main/java/com/ubisam/ubgitea/feature/codebrowser/CodeViewerScreen.kt @@ -202,6 +202,8 @@ fun CodeViewerScreen( // The share of the width the left pane of a header/source pair takes. var splitFraction by rememberSaveable { mutableFloatStateOf(DEFAULT_SPLIT) } 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(null) } // The other half of a header/source pair, once a wide window has asked for it and it has // arrived as text. @@ -265,6 +267,27 @@ fun CodeViewerScreen( val sidePaneIsShown = layout == ViewerLayout.WIDE && !showsPair 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( preferences = preferences, searching = searching, @@ -310,6 +333,7 @@ fun CodeViewerScreen( onOpenEntry = onOpenEntry, onToggleFolder = onToggleFolder, outlineFirst = outlineFirst, + jump = jump, onSwap = swapPanes, onSymbolSelected = { scope.launch { drawerState.close() } @@ -384,6 +408,7 @@ fun CodeViewerScreen( onOpenEntry = onOpenEntry, onToggleFolder = onToggleFolder, outlineFirst = outlineFirst, + jump = jump, onSwap = swapPanes, onSymbolSelected = { goToSymbol(it) }, modifier = Modifier.width(sidePaneWidth).fillMaxHeight(), @@ -421,6 +446,7 @@ fun CodeViewerScreen( if (outlineOpen) { OutlineSheet( document = document, + jump = jumpForSheet, currentLine = firstVisibleLine, collapsed = collapsed, 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( val preferences: CodeViewerPreferences, val searching: Boolean, @@ -997,13 +1026,14 @@ private fun SidePane( onSymbolSelected: (CodeSymbol) -> Unit, outlineFirst: Boolean, onSwap: () -> Unit, + jump: SymbolJump?, modifier: Modifier = Modifier, ) { val outline = remember(document.symbols, collapsed) { outlineTree(document.symbols, collapsed) } val rows = remember(state.tree) { state.tree.rows() } val folders: @Composable (Modifier) -> Unit = { FolderCard(state, rows, onOpenFile, onToggleFolder, onOpenEntry, it) } 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)) { val half = Modifier.weight(1f).fillMaxWidth() @@ -1065,6 +1095,7 @@ private fun SymbolCard( collapsed: Set, onToggleCollapsed: (CodeSymbol) -> Unit, onSymbolSelected: (CodeSymbol) -> Unit, + jump: SymbolJump?, modifier: Modifier, ) { val current = currentIn(outline, document.symbols, currentLine) @@ -1092,6 +1123,7 @@ private fun SymbolCard( isCollapsed = symbol.line in collapsed, methodCount = methodCountOf(document.symbols, symbol), onToggle = { onToggleCollapsed(symbol) }, + jump = jump, ) { onSymbolSelected(symbol) } } } @@ -1246,6 +1278,7 @@ private fun SymbolRow( isCollapsed: Boolean, methodCount: Int, onToggle: () -> Unit, + jump: SymbolJump?, onClick: () -> Unit, ) { val colors = rememberCodeSyntaxColors() @@ -1311,6 +1344,22 @@ private fun SymbolRow( maxLines = 1, 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) { Text( stringResource(R.string.codeviewer_outline_line, symbol.line + 1), @@ -1326,6 +1375,7 @@ private fun SymbolRow( @Composable private fun OutlineSheet( document: CodeDocument, + jump: SymbolJump?, currentLine: Int, collapsed: Set, onToggleCollapsed: (CodeSymbol) -> Unit, @@ -1400,6 +1450,7 @@ private fun OutlineSheet( isCollapsed = symbol.line in collapsed, methodCount = methodCountOf(document.symbols, symbol), onToggle = { onToggleCollapsed(symbol) }, + jump = jump, ) { onSymbolSelected(symbol) } } } diff --git a/docs/UBGitea_기술소개.pptx b/docs/UBGitea_기술소개.pptx index 6eef4fc..005fa20 100644 Binary files a/docs/UBGitea_기술소개.pptx and b/docs/UBGitea_기술소개.pptx differ diff --git a/docs/development-log.md b/docs/development-log.md index d2fd6d9..f9c35f6 100644 --- a/docs/development-log.md +++ b/docs/development-log.md @@ -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 the owner's files. - 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. diff --git a/tasks/059-jump-from-declaration-to-definition.md b/tasks/059-jump-from-declaration-to-definition.md new file mode 100644 index 0000000..4fea9d1 --- /dev/null +++ b/tasks/059-jump-from-declaration-to-definition.md @@ -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.