1(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[64606],{5733:function(s,e,r){(window.__NEXT_P=window.__NEXT_P||[]).push(["/blog/showing-inline-errors-codemirror",function(){return r(92495)}])},92495:function(s,e,r){"use strict";r.r(e),r.d(e,{__N_SSG:function(){return t},default:function(){return h},metadata:function(){return i}});var n=r(85893),a=r(91644),l=r(47185),t=!0;let i={slug:"showing-inline-errors-codemirror",date:"2024-06-08",metaTitle:"Showing inline errors in codemirror",description:"We are using CodeMirror for our code editor, and we want to show inline errors when the execution engine throws an error. Here is how we do it.",keywords:"codemirror, error, inline, devtoolsdaily"},o=function(s){let{children:e}=s;return(0,n.jsx)(l.Z,{title:i.metaTitle,date:i.date,children:e})};function c(s){let e={a:"a",code:"code",h2:"h2",h3:"h3",img:"img",li:"li",p:"p",pre:"pre",span:"span",strong:"strong",ul:"ul",...(0,a.a)(),...s.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(e.h2,{children:"Problem"}),"\n",(0,n.jsx)(e.p,{children:"Our website has hundreds of different pages, and many of them have a split-page layout:"}),"\n",(0,n.jsxs)(e.ul,{children:["\n",(0,n.jsxs)(e.li,{children:["Left side is a code editor (we use ",(0,n.jsx)(e.strong,{children:"CodeMirror"})," for that)"]}),"\n",(0,n.jsxs)(e.li,{children:["Right side is some execution result (e.g., SQL table output, Graphviz/Mermaid graph, regex/JQ/JSONPath result, etc.)\n",(0,n.jsx)(e.img,{src:"/blog-img/codemirror-error/split-page-layout.png",alt:"split page layout"})]}),"\n"]}),"\n",(0,n.jsxs)(e.p,{children:["When an issue occurs on the right side, it's critical to point the user to the exact location in the code editor so they can fix it.\nFor example, this is bad experience - we just show an error that underlying engine throws.\n",(0,n.jsx)(e.img,{src:"/blog-img/codemirror-error/bad-error.png",alt:"bad error display"})]}),"\n",(0,n.jsx)(e.h2,{children:"Solution"}),"\n",(0,n.jsx)(e.p,{children:"There are 2 possible solutions:"}),"\n",(0,n.jsx)(e.h3,{children:(0,n.jsx)(e.strong,{children:"CodeMirror linter with matching rules to the execution engine"})}),"\n",(0,n.jsxs)(e.p,{children:["For example, this works well for us on the ",(0,n.jsx)(e.a,{href:"https://www.devtoolsdaily.com/graphviz/",children:"Graphviz Playground"}),", where we have a Graphviz linter + Graphviz rendering. Independent execution usually matches errors, so it feels like when render fails, we show the correct error."]}),"\n",(0,n.jsx)(e.p,{children:"Here is how it's done in the Graphviz code editor (CodeMirror):"}),"\n",(0,n.jsx)(e.pre,{children:(0,n.jsxs)(e.code,{className:"hljs language-js",children:[(0,n.jsx)(e.span,{className:"hljs-keyword",children:"const"})," syntaxLinter = ",(0,n.jsx)(e.span,{className:"hljs-title function_",children:"linter"}),"(",(0,n.jsxs)(e.span,{className:"hljs-function",children:["(",(0,n.jsx)(e.span,{className:"hljs-params",children:"view"}),") =>"]})," {\n ",(0,n.jsx)(e.span,{className:"hljs-keyword",children:"let"})," ",(0,n.jsx)(e.span,{className:"hljs-attr",children:"diagnostics"}),": ",(0,n.jsx)(e.span,{className:"hljs-title class_",children:"Diagnostic"}),"[] = [];\n ",(0,n.jsx)(e.span,{className:"hljs-keyword",children:"let"})," ",(0,n.jsx)(e.span,{className:"hljs-attr",children:"graphtype"}),": string | ",(0,n.jsx)(e.span,{className:"hljs-literal",children:"null"})," = ",(0,n.jsx)(e.span,{className:"hljs-literal",children:"null"}),";\n ...\n ",(0,n.jsx)(e.span,{className:"hljs-keyword",children:"return"})," diagnostics;\n});\n\n",(0,n.jsx)(e.span,{className:"hljs-keyword",children:"let"})," state = ",(0,n.jsx)(e.span,{className:"hljs-title class_",children:"EditorState"}),".",(0,n.jsx)(e.span,{className:"hljs-title function_",children:"create"}),"({\n ",(0,n.jsx)(e.span,{className:"hljs-attr",children:"doc"}),": defaultValue,\n ",(0,n.jsx)(e.span,{className:"hljs-attr",children:"extensions"}),": [\n basicSetup,\n ...\n syntaxLinter,\n theme,\n ]\n});\n"]})}),"\n",(0,n.jsx)(e.p,{children:(0,n.jsx)(e.img,{src:"/blog-img/codemirror-error/graphviz-error-display.png",alt:"graphviz error display"})}),"\n",(0,n.jsx)(e.h3,{children:"Manually show execution engine errors in CodeMirror"}),"\n",(0,n.jsxs)(e.p,{children:["We just started this approach with our ",(0,n.jsx)(e.a,{href:"https://devtoolsdaily.com/sql/ddl-to-diagram/",children:"DDL to DBML/grpahviz conversion page"}),"."]}),"\n",(0,n.jsx)(e.p,{children:"We are using DBML-core library to parse DDL, it throws an error with line number and column number when parsing failed.\nWe are catching this error and convert
1ing that to a codemirror diagnostics object."}),"\n",(0,n.jsx)(e.h3,{children:"Construct correct codemirror offset for diagnostics error"}),"\n",(0,n.jsx)(e.p,{children:"First we convert the error to our internal type."}),"\n",(0,n.jsx)(e.pre,{children:(0,n.jsxs)(e.code,{className:"hljs language-js",children:[(0,n.jsx)(e.span,{className:"hljs-keyword",children:"try"})," {\n databaseObj = ",(0,n.jsx)(e.span,{className:"hljs-title class_",children:"Parser"}),".",(0,n.jsx)(e.span,{className:"hljs-title function_",children:"parse"}),"(source, ",(0,n.jsx)(e.span,{className:"hljs-string",children:"'mysql'"}),");\n} ",(0,n.jsx)(e.span,{className:"hljs-keyword",children:"catch"})," (e) {\n ",(0,n.jsx)(e.span,{className:"hljs-keyword",children:"if"})," (e ",(0,n.jsx)(e.span,{className:"hljs-keyword",children:"instanceof"})," ",(0,n.jsx)(e.span,{className:"hljs-title class_",children:"CompilerError"}),") {\n ",(0,n.jsx)(e.span,{className:"hljs-keyword",children:"const"})," compilerError = e ",(0,n.jsx)(e.span,{className:"hljs-keyword",children:"as"})," ",(0,n.jsx)(e.span,{className:"hljs-title class_",children:"CompilerError"}),";\n ",(0,n.jsx)(e.span,{className:"hljs-keyword",children:"const"})," ",(0,n.jsx)(e.span,{className:"hljs-attr",children:"diag"}),": ",(0,n.jsx)(e.span,{className:"hljs-title class_",children:"CompilerDiagnostic"})," = compilerError.",(0,n.jsx)(e.span,{className:"hljs-property",children:"diags"}),"[",(0,n.jsx)(e.span,{className:"hljs-number",children:"0"}),"];\n ",(0,n.jsx)(e.span,{className:"hljs-keyword",children:"const"})," ",(0,n.jsx)(e.span,{className:"hljs-attr",children:"editorError"}),": ",(0,n.jsx)(e.span,{className:"hljs-title class_",children:"EditorError"})," = {\n ",(0,n.jsx)(e.span,{className:"hljs-attr",children:"name"}),": diag.",(0,n.jsx)(e.span,{className:"hljs-property",children:"type"})," || ",(0,n.jsx)(e.span,{className:"hljs-string",children:"'Error'"}),",\n ",(0,n.jsx)(e.span,{className:"hljs-attr",children:"message"}),": diag.",(0,n.jsx)(e.span,{className:"hljs-property",children:"message"}),".",(0,n.jsx)(e.span,{className:"hljs-title function_",children:"replace"}),"(",(0,n.jsx)(e.span,{className:"hljs-regexp",children:"/\\\\n/g"}),", ",(0,n.jsx)(e.span,{className:"hljs-string",children:"'\\n'"}),"),\n ",(0,n.jsx)(e.span,{className:"hljs-attr",children:"location"}),": {\n ",(0,n.jsx)(e.span,{className:"hljs-attr",children:"start"}),": diag.",(0,n.jsx)(e.span,{className:"hljs-property",children:"location"}),".",(0,n.jsx)(e.span,{className:"hljs-property",children:"start"}),",\n ",(0,n.jsx)(e.span,{className:"hljs-attr",children:"end"}),": diag.",(0,n.jsx)(e.span,{className:"hljs-property",children:"location"}),".",(0,n.jsx)(e.span,{className:"hljs-property",children:"end"}),"\n }\n }\n ",(0,n.jsx)(e.span,{className:"hljs-title function_",children:"setEditorError"}),"(editorError);\n }\n} \n"]})}),"\n",(0,n.jsx)(e.p,{children:"then we convert that into CodeMirror diagnostics object"}),"\n",(0,n.jsx)(e.pre,{children:(0,n.jsxs)(e.code,{className:"hljs language-js",children:[(0,n.jsx)(e.span,{className:"hljs-keyword",children:"let"})," diagnostics = [\n {\n ",(0,n.jsx)(e.span,{className:"hljs-attr",children:"from"}),": startPosition,\n ",(0,n.jsx)(e.span,{className:"hljs-attr",children:"to"}),": endPosition,\n ",(0,n.jsx)(e.span,{className:"hljs-attr",children:"severity"}),": ",(0,n.jsx)(e.span,{className:"hljs-string",children:'"error"'})," ",(0,n.jsx)(e.span,{className:"hljs-keyword",children:"as"})," ",(0,n.jsx)(e.span,{className:"hljs-string",children:'"error"'}),",\n ",(0,n.jsx)(e.span,{className:"hljs-attr",children:"message"}),": error.",(0,n.jsx)(e.span,{className:"hljs-property",children:"message"}),"\n }\n];\n\n editorRef.",(0,n.jsx)(e.span,{className:"hljs-property",children:"current"}),".",(0,n.jsx)(e.span,{className:"hljs-property",children:"view"}),"?.",(0,n.jsx)(e.span,{className:"hljs-title function_",children:"dispatch"}),"(\n ",(0,n.jsx)(e.span,{className:"hljs-title function_",children:"setDiagnostics"}),"(editorRef.",(0,n.jsx)(e.span,{className:"hljs-property",children:"current"}),".",(0,n.jsx)(e.span,{className:"hljs-property",children:"state"}),"!, diagnostics)\n);\n"]})}),"\n",(0,n.jsxs)(e.p,{children:["The result looks like this\n",(0,n.jsx)(e.img,{src:"/blog-img/codemirror-error/good-error.png",alt:"result error inline"})]}),"\n",(0,n.jsx)(e.p,{children:"Now we plan to apply the same idea to other pages like SQL playground, MermaidJS playground, Regex tester, JQ playground etc."})]})}function h(){let s=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return(0,n.jsx)(o,{...s,children:(0,n.jsx)(c,{...s})})}},47185:function(s,e,r){"use strict";r.d(e,{Z:function(){return c}});var n=r(85893),a=r(43078),l=r(9008),t=r.n(l),i=r(11163);r(48097);let o="https://www.devtoolsdaily.com";function c(s){let{date:e,children:r,title:l,useContainer:c=!0,articleSchema:h}=s,d=(0,i.useRouter)(),p=((null==d?void 0:d.asPath)||"").split("?")[0].split("#")[0],j="".concat(o).concat(p),m=h?JSON.stringify({"@context":"https://schema.org","@type":h.type||"TechArticle",headline:h.title,...h.description?{description:h.description}:{},...h.keywords?{keywords:h.keywords}:{},...h.date?{datePublished:h.date,dateModified:h.date}:{},mainEntityOfPage:{"@type":"WebPage","@id":j},author:{"@type":"Organization",name:"DevTools Daily",url:o},publisher:{"@type":"Organization",name:"DevTools Daily",url:o}}).replace(/</g,"\\u003c"):null,x=p.startsWith("/blog/")&&"/blog/"!==p&&"/blog"!==p?JSON.stringify({"@context":"https://schema.org","@type":"BreadcrumbList",itemListElement:[{"@type":"ListItem",position:1,name:"DevTools Daily",item:"".concat(o,"/")},{"@type":"ListItem",position:2,name:"Blog",item:"".concat(o,"/blog/")},{"@type":"ListItem",position:3,name:l,item:j}]}).replace(/</g,"\\u003c"):null,u=c?(0,n.jsxs)(a.W,{className:"blog-article",children:[(0,n.jsx)("h1",{children:l}),(0,n.jsx)("div",{className:"text-sm italic date pb-1 pt-1",children:e}),r]}):(0,n.jsxs)("div",{className:"blog-article",children:[(0,n.jsx)("h1",{children:l}),r]});return(0,n.jsxs)(n.Fragment,{children:[(m||x)&&(0,n.jsxs)(t(),{children:[m&&(0,n.jsx)("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:m}}),x&&(0,n.jsx)("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:x}})]}),u]})}},91644:function(s,e,r){"use strict";function n(s){return{...s}}r.d(e,{a:function(){return n}})},48097:function(){},43078:function(s,e,r){"use strict";r.d(e,{W:function(){return j}});var n=r(85893);r(67294);var a=r(69429),l=r(13637),t=r(90987),i=r(46976),o=r(33583),c=r(9535),h={root:"m_7485cace"};let d={},p=(0,l.Z)((s,{size:e,fluid:r})=>({root:{"--container-size":r?void 0:(0,a.ap)(e,"container-size")}})),j=(0,c.d5)((s,e)=>{let r=(0,t.w)("Container",d,s),{classNames:a,className:l,style:c,styles:j,unstyled:m,vars:x,fluid:u,mod:g,...N}=r,y=(0,i.y)({name:"Container",classes:h,props:r,className:l,style:c,classNames:a,styles:j,unstyled:m,vars:x,varsResolver:p});return(0,n.jsx)(o.x,{ref:e,mod:[{fluid:u},g],...y("root"),...N})});j.classes=h,j.displayName="@mantine/core/Container"}},function(s){s.O(0,[92888,49774,40179],function(){return s(s.s=5733)}),_N_E=s.O()}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.