Skip to content

Commit b705a4d

Browse files
Merge pull request #15 from opengrep/feature-go-to-line-on-click
feat: add scroll to line on scan result card and inspect rule click
2 parents e3ca2dc + f5e3b1d commit b705a4d

File tree

9 files changed

+118
-27
lines changed

9 files changed

+118
-27
lines changed

package-lock.json

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
"keywords": [],
3434
"license": "MIT",
3535
"dependencies": {
36+
"@fortawesome/fontawesome-svg-core": "^6.7.2",
37+
"@fortawesome/free-regular-svg-icons": "^6.7.2",
38+
"@fortawesome/vue-fontawesome": "^3.0.8",
3639
"@guolao/vue-monaco-editor": "^1.5.4",
3740
"electron-squirrel-startup": "^1.0.1",
3841
"js-yaml": "^4.1.0",

src/App.vue

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,14 @@
2525

2626
<!-- Results Viewer -->
2727
<div class="column-view resizable" style="flex-grow: 1;">
28-
<RuleResults style="flex: 1; display: 'grid'; gap: '12px'" @showDataFlows="handleShowDataFlows" />
28+
<RuleResults style="flex: 1; display: 'grid'; gap: '12px'" @showDataFlows="handleShowDataFlows"
29+
@scrollToCodeSnippet="handleScrollToCodeSnippet" />
2930
<div class="resize-handle" @mousedown="startResize($event, 2)"></div>
3031
</div>
3132
</div>
3233
<!-- Debug Rule Area -->
3334
<div class="meta-section">
34-
<DebugSection style="flex: 3;" class="scroll-container"/>
35+
<DebugSection style="flex: 3;" class="scroll-container" @scrollToCodeSnippet="handleScrollToCodeSnippet" />
3536
</div>
3637
</div>
3738
</template>
@@ -43,8 +44,6 @@ import RuleResults from './components/RuleResults.vue';
4344
import DebugSection from './components/DebugSection.vue';
4445
import RuleEditor from './components/RuleEditor.vue';
4546
import { store } from './store';
46-
import { getLanguage } from './language.mapper';
47-
4847
4948
const getRootDir = inject('$getRootDir');
5049
const getSafeDir = inject('$getSafeDir');
@@ -117,6 +116,12 @@ function stopResize() {
117116
document.removeEventListener('mouseup', stopResize);
118117
resizingColumn.value = null;
119118
}
119+
120+
function handleScrollToCodeSnippet(lineNumber) {
121+
if (codeEditor.value) {
122+
codeEditor.value.scrollToCodeSnippet(lineNumber)
123+
}
124+
}
120125
</script>
121126
122127
<style lang="scss">
@@ -222,13 +227,13 @@ $secondary-color: #2ecc71;
222227
}
223228
224229
.empty-state {
225-
font-family: monospace;
226-
display: flex;
227-
justify-content: center;
228-
align-items: center;
229-
height: 100%;
230-
color: #7f8c8d;
231-
font-size: 14px;
232-
font-style: italic;
230+
font-family: monospace;
231+
display: flex;
232+
justify-content: center;
233+
align-items: center;
234+
height: 100%;
235+
color: #7f8c8d;
236+
font-size: 14px;
237+
font-style: italic;
233238
}
234239
</style>

src/components/CodeEditor.vue

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ function determineHighlightLinesFromTestResult(rawTestResults) {
109109
if (!rawTestResults || !Object.entries(rawTestResults.results).length) return;
110110
111111
let newDecorations = [];
112-
store.jsonResult.parsedTestResults = [];
112+
store.jsonResult.parsedTestResults = [];
113113
let expectedSet = null;
114114
let reportedSet = null;
115115
@@ -324,8 +324,14 @@ function highlightDebugLocationCode(locations) {
324324
componentState.debugLocationCodeDecorations = editorRef.value.deltaDecorations(componentState.debugLocationCodeDecorations, newDecorations);
325325
}
326326
327+
function scrollToCodeSnippet(lineNumber) {
328+
if (!editorRef.value) return;
329+
editorRef.value.revealLineInCenter(lineNumber,0);
330+
editorRef.value.setPosition({ lineNumber, column: 1 });
331+
}
332+
327333
// Expose the determineCodeFlowInformation function to the parent component
328-
defineExpose({ determineCodeFlowInformation });
334+
defineExpose({ determineCodeFlowInformation, scrollToCodeSnippet });
329335
330336
</script>
331337
<style>

src/components/DebugSection.vue

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<div class="inspect-rule">
33
<h3 class="title">Inspect Rule</h3>
44
<div v-if="!parsedData" class="empty-state">No debugging information available.</div>
5-
<TreeView v-else :data="parsedData" @node-hover="handleHover" />
5+
<TreeView v-else :data="parsedData" @node-hover="handleHover" @scrollToCodeSnippet="handleScrollToCodeSnippet" />
66
</div>
77
</template>
88

@@ -12,6 +12,7 @@ import TreeView from "./TreeView.vue";
1212
import { store } from '../store'
1313
1414
const parsedData = ref(null);
15+
const emit = defineEmits(['scrollToCodeSnippet']);
1516
1617
watch(() => store.jsonResult.scanResults, (scanResults) => {
1718
generateDebuggingInfo(scanResults);
@@ -20,7 +21,7 @@ watch(() => store.jsonResult.scanResults, (scanResults) => {
2021
2122
async function generateDebuggingInfo(scanResults) {
2223
if (!scanResults) return;
23-
24+
2425
const explanations = scanResults.explanations || [];
2526
parsedData.value = parseExplanation(explanations);
2627
}
@@ -102,6 +103,10 @@ function handleHover(debugCodeLocation) {
102103
store.codeEditorDebugLocation = debugCodeLocation;
103104
}
104105
106+
function handleScrollToCodeSnippet(event, lineNumber) {
107+
emit('scrollToCodeSnippet', lineNumber);
108+
}
109+
105110
</script>
106111

107112
<style lang="scss" scoped>

src/components/RuleEditor.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ function getLanguageDetails(yamlContent) {
7272
return null
7373
}
7474
75-
if(languages.length > 1){
75+
if (!getLanguage(languages[0]) && languages.length > 1) {
7676
return getLanguage('generic');
7777
}
7878

src/components/RuleResults.vue

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,18 @@
2626
<div class="result-body">
2727
<p>{{ result.extra.message }}</p>
2828
<div class="location-card">
29-
<p><strong>Snippet on line {{ result.start.line }}:</strong><br><br>{{
30-
result.extra.lines.trim() }} </p>
29+
<p><strong>Snippet on line {{ result.start.line
30+
}}:</strong><br><br>{{result.extra.lines.trim() }} </p>
3131
</div>
3232
</div>
33-
<div class="result-footer" v-if="result.extra.dataflow_trace">
34-
<button class="small" @click="handleShowDataFlows(result.extra.dataflow_trace)"
33+
<div class="result-footer">
34+
<button v-if="result.extra.dataflow_trace" class="small"
35+
@click="handleShowDataFlows(result.extra.dataflow_trace)"
3536
style="align-self: center;">Show
3637
dataflows</button>
38+
<button class="small" @click="scrollToCodeSnippet(result.start.line)">
39+
Go to code snippet
40+
</button>
3741
</div>
3842
</div>
3943
</div>
@@ -74,9 +78,9 @@
7478

7579
<script setup>
7680
import { inject, defineEmits, ref, onMounted } from 'vue';
77-
import { store } from '../store'
81+
import { store } from '../store';
7882
79-
const emit = defineEmits(['showDataFlows']);
83+
const emit = defineEmits(['showDataFlows', 'scrollToCodeSnippet']);
8084
8185
const joinPath = inject('$joinPath');
8286
const runBinary = inject('$runBinary');
@@ -211,7 +215,11 @@ function getMatchSatusText(result) {
211215
return 'Untested match on';
212216
}
213217
return result.mustMatch ? 'Must match' : 'Must not match';
214-
}
218+
}
219+
220+
function scrollToCodeSnippet(lineNumber) {
221+
emit('scrollToCodeSnippet', lineNumber);
222+
}
215223
</script>
216224
217225
<style scoped>
@@ -221,6 +229,11 @@ function getMatchSatusText(result) {
221229
align-items: center;
222230
}
223231
232+
.result-footer {
233+
display: flex;
234+
gap: 16px;
235+
}
236+
224237
.results-container {
225238
display: flex;
226239
flex-direction: column;

src/components/TreeView.vue

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@
22
<div class="tree-node">
33
<div v-if="data" class="node-header" @click="toggle" @mouseover="handleMouseOver" @mouseleave="handleMouseLeave">
44
<span v-if="hasChildren" class="toggle-icon">{{ isOpen ? "▼" : "▶" }}</span>
5-
<span class="node-name">{{ data?.name }}</span>
5+
<div style="display: flex; align-items: center; gap: 8px;">
6+
<span class="node-name">{{ data?.name }}</span>
7+
<font-awesome-icon :icon="['far', 'file-code']" size="xl"
8+
@click="scrollToCodeSnippet($event, data?.matches[0]?.start.line)" />
9+
</div>
610
</div>
711
<div v-if="isOpen" class="node-children">
8-
<TreeView v-for="(child, index) in data?.children" :key="index" :data="child" @node-hover="handleNodeHover" />
12+
<TreeView v-for="(child, index) in data?.children" :key="index" :data="child"
13+
@scrollToCodeSnippet="scrollToCodeSnippet($event, child?.matches[0]?.start.line)"
14+
@node-hover="handleNodeHover" />
915
</div>
1016
</div>
1117
</template>
@@ -17,7 +23,7 @@ const props = defineProps({
1723
data: Object,
1824
});
1925
20-
const emit = defineEmits(["node-hover"]);
26+
const emit = defineEmits(["node-hover", 'scrollToCodeSnippet']);
2127
2228
const isOpen = ref(true);
2329
const hasChildren = computed(() => props.data?.children && props.data.children.length > 0);
@@ -56,6 +62,12 @@ const handleNodeHover = (location) => {
5662
hoveredChildData.value = location; // Store child node location
5763
emit("node-hover", hoveredChildData.value);
5864
};
65+
66+
const scrollToCodeSnippet = (event, lineNumber) => {
67+
event.stopPropagation();
68+
emit('scrollToCodeSnippet', event, lineNumber);
69+
70+
}
5971
</script>
6072
6173
<style lang="scss" scoped>

src/renderer.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@ import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker"
99
import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker"
1010
import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker"
1111
import YamlWorker from './yaml.worker?worker';
12+
import { library } from '@fortawesome/fontawesome-svg-core'
13+
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
14+
import { far } from '@fortawesome/free-regular-svg-icons';
1215

1316
const app = createApp(App);
17+
library.add(far);
18+
app.component('font-awesome-icon', FontAwesomeIcon);
1419

1520
app.provide('$electronAPI', window.electronAPI);
1621
app.provide('$getPlatform', window.electronAPI.getPlatform);

0 commit comments

Comments
 (0)