11import { Plugin , PluginKey , type EditorState } from "@tiptap/pm/state" ;
22import { Decoration , DecorationSet } from "@tiptap/pm/view" ;
33import { isSupportedLanguage , type DcLanguageId } from "$lib/highlighter/catalog" ;
4+ import { highlightedLineIndexes } from "$lib/highlighter/highlight-lines" ;
45
56export type EditorCodeTokenKind = "comment" | "string" | "keyword" | "number" | "function" ;
67
@@ -10,6 +11,21 @@ export type EditorCodeToken = {
1011 kind : EditorCodeTokenKind ;
1112} ;
1213
14+ export type EditorCodeLineDecorationKind = "highlight" | "addition" | "deletion" ;
15+
16+ export type EditorCodeLineDecoration = {
17+ from : number ;
18+ to : number ;
19+ kind : EditorCodeLineDecorationKind ;
20+ empty ?: boolean ;
21+ } ;
22+
23+ type CodeLineDecorationAttrs = {
24+ highlightLines ?: unknown ;
25+ additionLines ?: unknown ;
26+ deletionLines ?: unknown ;
27+ } ;
28+
1329const keywordSets : Partial < Record < DcLanguageId , readonly string [ ] > > = {
1430 c : [
1531 "auto" ,
@@ -355,6 +371,35 @@ const keywordSets: Partial<Record<DcLanguageId, readonly string[]>> = {
355371 json : [ "false" , "null" , "true" ] ,
356372 yaml : [ "false" , "null" , "off" , "on" , "true" , "yes" , "no" ] ,
357373 toml : [ "false" , "true" ] ,
374+ mermaid : [
375+ "activate" ,
376+ "alt" ,
377+ "and" ,
378+ "class" ,
379+ "classDef" ,
380+ "click" ,
381+ "deactivate" ,
382+ "else" ,
383+ "end" ,
384+ "erDiagram" ,
385+ "flowchart" ,
386+ "gantt" ,
387+ "gitGraph" ,
388+ "graph" ,
389+ "journey" ,
390+ "loop" ,
391+ "mindmap" ,
392+ "note" ,
393+ "opt" ,
394+ "participant" ,
395+ "pie" ,
396+ "quadrantChart" ,
397+ "sequenceDiagram" ,
398+ "stateDiagram" ,
399+ "stateDiagram-v2" ,
400+ "subgraph" ,
401+ "timeline" ,
402+ ] ,
358403 sql : [
359404 "add" ,
360405 "alter" ,
@@ -541,6 +586,10 @@ function lineCommentPrefixes(language: DcLanguageId): readonly string[] {
541586 return [ "--" ] ;
542587 }
543588
589+ if ( language === "mermaid" ) {
590+ return [ "%%" ] ;
591+ }
592+
544593 if ( cFamilyLanguages . has ( language ) ) {
545594 return [ "//" ] ;
546595 }
@@ -680,6 +729,38 @@ function scanRegexTokens(
680729 }
681730}
682731
732+ function scanMarkdownTokens (
733+ line : string ,
734+ lineOffset : number ,
735+ protectedRanges : readonly ProtectedRange [ ] ,
736+ tokens : EditorCodeToken [ ] ,
737+ ) : void {
738+ scanRegexTokens (
739+ line ,
740+ lineOffset ,
741+ protectedRanges ,
742+ / ^ \s { 0 , 3 } # { 1 , 6 } (? = \s | $ ) | ^ \s { 0 , 3 } (?: [ - * + ] | \d + \. ) \s | ^ \s { 0 , 3 } > + \s ? / g,
743+ "keyword" ,
744+ tokens ,
745+ ) ;
746+ scanRegexTokens (
747+ line ,
748+ lineOffset ,
749+ protectedRanges ,
750+ / ` ` ` + [ ^ ` ] * | ~ ~ ~ + [ ^ ~ ] * / g,
751+ "keyword" ,
752+ tokens ,
753+ ) ;
754+ scanRegexTokens (
755+ line ,
756+ lineOffset ,
757+ protectedRanges ,
758+ / \[ [ ^ \] ] + ] \( [ ^ ) ] + \) / g,
759+ "string" ,
760+ tokens ,
761+ ) ;
762+ }
763+
683764function scanFunctionTokens (
684765 line : string ,
685766 lineOffset : number ,
@@ -722,6 +803,10 @@ export function highlightCodeTokens(code: string, language: unknown): EditorCode
722803 const protectedRanges : ProtectedRange [ ] = [ ] ;
723804 scanProtectedTokens ( line , offset , normalizedLanguage , tokens , protectedRanges ) ;
724805
806+ if ( normalizedLanguage === "markdown" ) {
807+ scanMarkdownTokens ( line , offset , protectedRanges , tokens ) ;
808+ }
809+
725810 const keywordRegex = keywordRegexFor ( normalizedLanguage ) ;
726811 if ( keywordRegex ) {
727812 scanRegexTokens ( line , offset , protectedRanges , keywordRegex , "keyword" , tokens ) ;
@@ -745,6 +830,53 @@ export function highlightCodeTokens(code: string, language: unknown): EditorCode
745830 return cloneTokens ( sortedTokens ) ;
746831}
747832
833+ export function codeLineDecorations (
834+ code : string ,
835+ attrs : CodeLineDecorationAttrs ,
836+ ) : EditorCodeLineDecoration [ ] {
837+ const lines = code . split ( "\n" ) ;
838+ const highlightIndexes = highlightedLineIndexes ( attrs . highlightLines , lines . length ) ;
839+ const additionIndexes = highlightedLineIndexes ( attrs . additionLines , lines . length ) ;
840+ const deletionIndexes = highlightedLineIndexes ( attrs . deletionLines , lines . length ) ;
841+
842+ if ( highlightIndexes . size === 0 && additionIndexes . size === 0 && deletionIndexes . size === 0 ) {
843+ return [ ] ;
844+ }
845+
846+ const decorations : EditorCodeLineDecoration [ ] = [ ] ;
847+ let offset = 0 ;
848+
849+ lines . forEach ( ( line , index ) => {
850+ const kind : EditorCodeLineDecorationKind | undefined = deletionIndexes . has ( index )
851+ ? "deletion"
852+ : additionIndexes . has ( index )
853+ ? "addition"
854+ : highlightIndexes . has ( index )
855+ ? "highlight"
856+ : undefined ;
857+
858+ if ( kind ) {
859+ const lineEnd = offset + line . length ;
860+ decorations . push (
861+ lineEnd > offset
862+ ? { from : offset , to : lineEnd , kind }
863+ : { from : offset , to : offset , kind, empty : true } ,
864+ ) ;
865+ }
866+
867+ offset += line . length + 1 ;
868+ } ) ;
869+
870+ return decorations ;
871+ }
872+
873+ function codeLineDecorationElement ( kind : EditorCodeLineDecorationKind ) : HTMLElement {
874+ const marker = document . createElement ( "span" ) ;
875+ marker . className = `dc-code-line dc-code-line-${ kind } ` ;
876+ marker . setAttribute ( "aria-hidden" , "true" ) ;
877+ return marker ;
878+ }
879+
748880function codeBlockDecorations ( state : EditorState ) : DecorationSet {
749881 const decorations : Decoration [ ] = [ ] ;
750882
@@ -753,6 +885,16 @@ function codeBlockDecorations(state: EditorState): DecorationSet {
753885 return ;
754886 }
755887
888+ for ( const lineDecoration of codeLineDecorations ( node . textContent , node . attrs ) ) {
889+ decorations . push (
890+ Decoration . widget (
891+ pos + 1 + lineDecoration . from ,
892+ ( ) => codeLineDecorationElement ( lineDecoration . kind ) ,
893+ { side : - 1 } ,
894+ ) ,
895+ ) ;
896+ }
897+
756898 for ( const token of highlightCodeTokens ( node . textContent , node . attrs . language ) ) {
757899 decorations . push (
758900 Decoration . inline ( pos + 1 + token . from , pos + 1 + token . to , {
0 commit comments