diff --git a/web/src/api/models/pair.ts b/web/src/api/models/pair.ts index 9ec356dd2..e1437faf3 100644 --- a/web/src/api/models/pair.ts +++ b/web/src/api/models/pair.ts @@ -1,4 +1,5 @@ import { File, Fragment } from "@/api/models"; +import { Region } from "@dodona/dolos-core"; export interface Pair { id: number; @@ -10,4 +11,6 @@ export interface Pair { leftCovered: number; rightCovered: number; fragments: Fragment[] | null; + leftIgnoredKgrams: Region[]; + rightIgnoredKgrams: Region[]; } diff --git a/web/src/api/stores/pair.store.ts b/web/src/api/stores/pair.store.ts index f1e497a2e..ceac8e752 100644 --- a/web/src/api/stores/pair.store.ts +++ b/web/src/api/stores/pair.store.ts @@ -88,6 +88,8 @@ export const usePairStore = defineStore("pairs", () => { fragments: null, leftCovered, rightCovered, + leftIgnoredKgrams: [], + rightIgnoredKgrams: [], }; } return pairs; diff --git a/web/src/api/workers/data.worker.ts b/web/src/api/workers/data.worker.ts index 76e1a30c9..feb9a6455 100644 --- a/web/src/api/workers/data.worker.ts +++ b/web/src/api/workers/data.worker.ts @@ -8,7 +8,7 @@ import { PairedOccurrence, Hash, } from "@/api/models"; -import { Fragment as DolosFragment, FingerprintIndex } from "@dodona/dolos-core"; +import { Fragment as DolosFragment, FingerprintIndex, TokenizedFile, Pair as DolosPair } from "@dodona/dolos-core"; // Parse a list of Dolos fragments into a list of fragment models. export function parseFragments( @@ -37,6 +37,30 @@ export function parseFragments( }); } +function getIgnoredKgrams(reportPair: DolosPair, leftFile: TokenizedFile, rightFile: TokenizedFile) { + const leftIgnoredKgrams = []; + const rightIgnoredKgrams = []; + + for (const ignoredKgram of reportPair.leftEntry.ignored) { + const occurrences = ignoredKgram.occurrencesOf(leftFile); + if (occurrences.length > 0) { + leftIgnoredKgrams.push(occurrences[0].side.location); + } + } + + for (const ignoredKgram of reportPair.rightEntry.ignored) { + const occurrences = ignoredKgram.occurrencesOf(rightFile); + if (occurrences.length > 0) { + rightIgnoredKgrams.push(occurrences[0].side.location); + } + } + + return { + leftIgnoredKgrams, + rightIgnoredKgrams + }; +} + // Populate the fragments for a given pair. export function populateFragments( pair: Pair, @@ -64,7 +88,10 @@ export function populateFragments( const kmer = kmers[kmerKey]; kmersMap.set(kmer.hash, kmer); } - pair.fragments = parseFragments(reportPair.buildFragments(), kmersMap); + const ignoredKgramsMap = getIgnoredKgrams(reportPair, leftFile, rightFile); + pair.fragments = parseFragments(reportPair.buildFragments(), kmersMap); + pair.leftIgnoredKgrams = ignoredKgramsMap.leftIgnoredKgrams; + pair.rightIgnoredKgrams = ignoredKgramsMap.rightIgnoredKgrams; return pair; } diff --git a/web/src/components/pair/PairCodeMatchEditor.vue b/web/src/components/pair/PairCodeMatchEditor.vue index b774ecc9f..f264c0341 100644 --- a/web/src/components/pair/PairCodeMatchEditor.vue +++ b/web/src/components/pair/PairCodeMatchEditor.vue @@ -28,7 +28,7 @@ interface Props { } interface Selection { - match: Fragment | null; + match: Fragment | null | string; range: monaco.IRange | null; isWholeLine: boolean; } @@ -44,11 +44,13 @@ const colors = { match: "rgba(60, 115, 168, 0.2)", matchHovering: "rgba(60, 115, 168, 0.3)", matchSelected: "rgba(26, 188, 156, 0.5)", + matchIgnored: "rgba(220, 220, 220, 1)", }; // File to display // Based on the pair & the given side. const file = computed(() => props.side === "left" ? props.pair.leftFile : props.pair.rightFile); +const ignoredKgrams = computed(() => props.side === "left" ? props.pair.leftIgnoredKgrams : props.pair.rightIgnoredKgrams); // List of matches, sorted on the start line & column. const matches = computed(() => { @@ -83,7 +85,6 @@ const getMatchAtPosition = (row: number, col: number): Fragment | null => { for (const match of matches.value) { const side = match[props.side]; - // If the row/col is within the match row range. const inRowRange = side.startRow + 1 <= row && row <= side.endRow + 1; // If the row/col is within the match col range. @@ -178,6 +179,20 @@ const initializeSelections = (): void => { isWholeLine: true, }); } + + // Convert the ignored kgrams into selections + for (const ignored of ignoredKgrams.value) { + selections.value.push({ + match: "ignored", + range: { + startLineNumber: ignored.startRow + 1, + startColumn: ignored.startCol + 1, + endLineNumber: ignored.endRow + 1, + endColumn: ignored.endCol + 1, + }, + isWholeLine: false, + }); + } }; const areMatchesEqual = (match1: Fragment | null, match2: Fragment | null) => { @@ -196,15 +211,20 @@ const initializeDecorations = (): void => { decorations.value, selections.value.map((selection) => { const match = selection.match; - let classname = "highlight-match"; - if (areMatchesEqual(match, selectedMatch?.value)) classname += " highlight-match--selected"; - else if (areMatchesEqual(match, hoveringMatch?.value)) classname += " highlight-match--hovering"; - + if (typeof match !== "string") { + if (areMatchesEqual(match, selectedMatch?.value)) classname += " highlight-match--selected"; + else if (areMatchesEqual(match, hoveringMatch?.value)) classname += " highlight-match--hovering"; + } + else if (match === "ignored") { + classname += " highlight-match--ignored"; + } let color = colors.match; - if (areMatchesEqual(match, selectedMatch?.value)) color = colors.matchSelected; - else if (areMatchesEqual(match, hoveringMatch?.value)) color = colors.matchHovering; + if (typeof match !== "string") { + if (areMatchesEqual(match, selectedMatch?.value)) color = colors.matchSelected; + else if (areMatchesEqual(match, hoveringMatch?.value)) color = colors.matchHovering; + } return { range: selection.range, @@ -367,6 +387,10 @@ watch( &--hovering { background-color: v-bind("colors.matchHovering"); } + + &--ignored { + background-color: v-bind("colors.matchIgnored"); + } } }