@@ -13,60 +13,19 @@ import { Wheatley } from "../wheatley.js";
1313import { MINUTE } from "../common.js" ;
1414import { MessageContextMenuInteractionBuilder } from "../command-abstractions/context-menu.js" ;
1515import { async_exec_file } from "../utils/filesystem.js" ;
16+ import { markdown_node , MarkdownParser , CodeBlockRule , InlineCodeRule , TextRule } from "dismark" ;
1617import { Mutex } from "../utils/containers.js" ;
1718
1819const color = 0x7e78fe ; //0xA931FF;
1920
21+ const code_only_parser = new MarkdownParser ( [ new CodeBlockRule ( ) , new InlineCodeRule ( ) , new TextRule ( ) ] ) ;
22+
2023const clang_format_path = "/usr/bin/clang-format" ;
2124
2225const max_attachment_size = 1024 * 10 ;
2326
24- // highlight js accepts all
25- // prettier-ignore
26- const languages = [
27- "1c" , "4d" , "abnf" , "accesslog" , "actionscript" , "ada" , "adoc" , "alan" , "angelscript" , "apache" , "apacheconf" ,
28- "applescript" , "arcade" , "arduino" , "arm" , "armasm" , "as" , "asc" , "asciidoc" , "aspectj" , "atom" , "autohotkey" ,
29- "autoit" , "avrasm" , "awk" , "axapta" , "bash" , "basic" , "bat" , "bbcode" , "bf" , "bind" , "blade" , "bnf" , "brainfuck" ,
30- "c" , "c++" , "cal" , "capnp" , "capnproto" , "cc" , "chaos" , "chapel" , "chpl" , "cisco" , "clj" , "clojure" , "cls" ,
31- "cmake.in" , "cmake" , "cmd" , "coffee" , "coffeescript" , "console" , "coq" , "cos" , "cpc" , "cpp" , "cr" , "craftcms" ,
32- "crm" , "crmsh" , "crystal" , "cs" , "csharp" , "cshtml" , "cson" , "csp" , "css" , "cxx" , "cypher" , "d" , "dart" , "delphi" ,
33- "dfm" , "diff" , "django" , "dns" , "docker" , "dockerfile" , "dos" , "dpr" , "dsconfig" , "dst" , "dts" , "dust" , "dylan" ,
34- "ebnf" , "elixir" , "elm" , "erl" , "erlang" , "excel" , "extempore" , "f90" , "f95" , "fix" , "fortran" , "freepascal" , "fs" ,
35- "fsharp" , "gams" , "gauss" , "gawk" , "gcode" , "gdscript" , "gemspec" , "gf" , "gherkin" , "glimmer" , "glsl" , "gms" , "gn" ,
36- "gni" , "go" , "godot" , "golang" , "golo" , "gololang" , "gradle" , "graph" , "groovy" , "gss" , "gyp" , "h" , "h++" , "haml" ,
37- "handlebars" , "haskell" , "haxe" , "hbs" , "hbs" , "hcl" , "hh" , "hlsl" , "hpp" , "hs" , "html.handlebars" ,
38- "html.handlebars" , "html.hbs" , "html.hbs" , "html" , "htmlbars" , "http" , "https" , "hx" , "hxx" , "hy" , "hylang" , "i" ,
39- "i7" , "iced" , "iecst" , "inform7" , "ini" , "ino" , "instances" , "iol" , "irb" , "irpf90" , "java" , "javascript" , "jinja" ,
40- "jolie" , "js" , "json" , "jsp" , "jsx" , "julia-repl" , "julia" , "k" , "kaos" , "kdb" , "kotlin" , "kt" , "lasso" ,
41- "lassoscript" , "lazarus" , "ldif" , "leaf" , "lean" , "less" , "lfm" , "lisp" , "livecodeserver" , "livescript" , "ln" ,
42- "lpr" , "ls" , "ls" , "lua" , "mak" , "make" , "makefile" , "markdown" , "mathematica" , "matlab" , "mawk" , "maxima" , "md" ,
43- "mel" , "mercury" , "mirc" , "mizar" , "mk" , "mkd" , "mkdown" , "ml" , "ml" , "mm" , "mma" , "mojolicious" , "monkey" , "moon" ,
44- "moonscript" , "mrc" , "n1ql" , "nawk" , "nc" , "never" , "nginx" , "nginxconf" , "nim" , "nimrod" , "nix" , "nsis" , "obj-c" ,
45- "obj-c++" , "objc" , "objective-c++" , "objectivec" , "ocaml" , "ocl" , "ol" , "openscad" , "osascript" , "oxygene" , "p21" ,
46- "papyrus" , "parser3" , "pas" , "pascal" , "patch" , "pcmk" , "perl" , "pf.conf" , "pf" , "pgsql" , "php" , "php3" , "php4" ,
47- "php5" , "php6" , "php7" , "php8" , "pl" , "plaintext" , "plist" , "pm" , "podspec" , "pony" , "postgres" , "postgresql" ,
48- "powershell" , "pp" , "processing" , "profile" , "prolog" , "properties" , "protobuf" , "ps" , "ps1" , "psc" , "puppet" , "py" ,
49- "pycon" , "python-repl" , "python" , "qml" , "qsharp" , "r" , "razor-cshtml" , "razor" , "rb" , "re" , "reasonml" , "rebol" ,
50- "red-system" , "red" , "redbol" , "rf" , "rib" , "risc" , "riscript" , "robot" , "rpm-spec" , "rpm-specfile" , "rpm" , "rs" ,
51- "rsl" , "rss" , "ruby" , "ruleslanguage" , "rust" , "sas" , "SAS" , "sc" , "scad" , "scala" , "scheme" , "sci" , "scilab" ,
52- "scl" , "scss" , "sh" , "shell" , "shexc" , "smali" , "smalltalk" , "sml" , "sol" , "solidity" , "spec" , "specfile" , "spl" ,
53- "sql" , "st" , "stan" , "stanfuncs" , "stata" , "step" , "stl" , "stp" , "structured-text" , "styl" , "stylus" , "subunit" ,
54- "supercollider" , "svelte" , "svg" , "swift" , "tao" , "tap" , "tcl" , "terraform" , "tex" , "text" , "tf" , "thor" , "thrift" ,
55- "tk" , "toml" , "tp" , "ts" , "tsql" , "twig" , "txt" , "typescript" , "unicorn-rails-log" , "v" , "vala" , "vb" , "vba" ,
56- "vbnet" , "vbs" , "vbscript" , "verilog" , "vhdl" , "vim" , "wl" , "x++" , "x86asm" , "xhtml" , "xjb" , "xl" , "xls" , "xlsx" ,
57- "xml" , "xpath" , "xq" , "xquery" , "xsd" , "xsl" , "xtlang" , "xtm" , "yaml" , "yml" , "zenscript" , "zep" , "zephir" , "zone" ,
58- "zs" , "zsh"
59- ] ;
60-
6127const c_cpp_language_codes = new Set ( [ "c" , "h" , "cpp" , "hpp" , "cc" , "hh" , "cxx" , "cxx" , "c++" , "h++" ] ) ;
6228
63- const languages_re = new RegExp (
64- languages
65- . sort ( ( a , b ) => b . length - a . length )
66- . map ( x => x . replaceAll ( "+" , "\\+" ) )
67- . join ( "|" ) ,
68- ) ;
69-
7029const code_begin = [
7130 "//" ,
7231 "/\\*" ,
@@ -109,8 +68,6 @@ function word_boundary(regex: string) {
10968
11069const code_begin_re = new RegExp ( `(?:${ code_begin . map ( word_boundary ) . join ( "|" ) } )` ) ;
11170
112- const code_block_re = new RegExp ( `(\`\`\`(?:${ languages_re . source } \b)?)(.*?)\`\`\`` , "gims" ) ;
113-
11471const default_clang_format_language = "cpp" ;
11572
11673const ignore_prefixes = [ ";compile" , ";asm" ] ;
@@ -148,48 +105,55 @@ export async function clang_format_general(text: string) {
148105 return await clang_format ( text , [ `-style={${ clang_format_style . join ( ", " ) } }` ] ) ;
149106}
150107
151- // https://stackoverflow.com/questions/12568097/how-can-i-replace-a-string-by-range
152- function replace_range ( s : string , start : number , end : number , substitute : string ) {
153- return s . substring ( 0 , start ) + substitute + s . substring ( end ) ;
154- }
155-
156- async function format ( replying_to : Discord . Message ) {
157- let content = replying_to . content ;
158- // does the message have code blocks?
159- const code_blocks : { language : string ; content : string } [ ] = [ ] ;
160- content = content . replaceAll ( code_block_re , ( _ , starter : string , block : string ) => {
161- const language = starter . length > 3 ? starter . substring ( 3 ) : "cpp" ;
162- code_blocks . push ( { language, content : block } ) ;
163- return `<[<[<[<[${ code_blocks . length - 1 } ]>]>]>]>` ;
164- } ) ;
165- // else ...
166- if ( code_blocks . length == 0 ) {
167- const start = content . search ( code_begin_re ) ;
168- if ( start > - 1 ) {
169- const end = Math . max ( ...[ ...";}" ] . map ( c => content . lastIndexOf ( c ) ) ) ;
170- if ( end > start ) {
171- code_blocks . push ( {
172- language : default_clang_format_language ,
173- content : content . substring ( start , end + 1 ) ,
174- } ) ;
175- content = replace_range ( content , start , end + 1 , `<[<[<[<[${ code_blocks . length - 1 } ]>]>]>]>` ) ;
108+ async function format_message_content ( ast : markdown_node ) : Promise < { content : string ; found_code : boolean } > {
109+ let found_code = false ;
110+
111+ const format_node = async ( node : markdown_node ) : Promise < string > => {
112+ switch ( node . type ) {
113+ case "doc" :
114+ return ( await Promise . all ( node . content . map ( format_node ) ) ) . join ( "" ) ;
115+ case "code_block" : {
116+ found_code = true ;
117+ const language = node . language ?? default_clang_format_language ;
118+ if ( c_cpp_language_codes . has ( language ) ) {
119+ const formatted = await clang_format_general ( node . content ) ;
120+ return `\`\`\`${ node . language ?? "" } \n${ formatted } \n\`\`\`` ;
121+ } else {
122+ return `\`\`\`${ node . language ?? "" } \n${ node . content } \n\`\`\`` ;
123+ }
176124 }
125+ case "inline_code" :
126+ return `\`${ node . content } \`` ;
127+ case "plain" : {
128+ const code_start = node . content . search ( code_begin_re ) ;
129+ if ( code_start > - 1 ) {
130+ const end = Math . max ( ...[ ...";}" ] . map ( c => node . content . lastIndexOf ( c ) ) ) ;
131+ if ( end > code_start ) {
132+ found_code = true ;
133+ const code_content = node . content . substring ( code_start , end + 1 ) ;
134+ const formatted = await clang_format_general ( code_content ) ;
135+ return (
136+ node . content . substring ( 0 , code_start ) +
137+ `\`\`\`${ default_clang_format_language } \n${ formatted } \n\`\`\`` +
138+ node . content . substring ( end + 1 )
139+ ) ;
140+ }
141+ }
142+ return node . content ;
143+ }
144+ default :
145+ throw new Error ( `Unknown ast node ${ ( node as markdown_node ) . type } ` ) ;
177146 }
178- }
147+ } ;
179148
180- for ( const [ i , block ] of code_blocks . entries ( ) ) {
181- if ( c_cpp_language_codes . has ( block . language ) ) {
182- content = content . replace (
183- `<[<[<[<[${ i } ]>]>]>]>` ,
184- `\`\`\`${ block . language } \n${ await clang_format_general ( block . content ) } \n\`\`\`` ,
185- ) ;
186- } else {
187- // don't format, just put it back
188- content = content . replace ( `<[<[<[<[${ i } ]>]>]>]>` , `\`\`\`${ block . language } \n${ block . content } \n\`\`\`` ) ;
189- }
190- }
149+ const content = await format_node ( ast ) ;
150+ return { content, found_code } ;
151+ }
152+
153+ async function format ( replying_to : Discord . Message ) {
154+ const ast = code_only_parser . parse ( replying_to . content ) ;
155+ const { content, found_code } = await format_message_content ( ast ) ;
191156
192- // does the message have attachments?
193157 const attachments = await Promise . all (
194158 [ ...replying_to . attachments . values ( ) ]
195159 . filter ( attachment => attachment . contentType ?. startsWith ( "text/" ) ?? false )
@@ -208,7 +172,11 @@ async function format(replying_to: Discord.Message) {
208172 } ) ,
209173 ) ;
210174
211- return { content, attachments, found_code_blocks : code_blocks . length > 0 } ;
175+ return {
176+ content,
177+ attachments,
178+ found_code_blocks : found_code ,
179+ } ;
212180}
213181
214182function should_replace_original ( replying_to : Discord . Message , request_timestamp : Date ) {
0 commit comments