x
`; + expect(errors(doc)).toEqual([]); + expect(tokens(doc)).toContain('"\${fn:escapeXml(item.title)} #{a ? b : not empty c.d[0] and x.size() gt 2}
`; + expect(errors(doc)).toEqual([]); + expect(tokens(doc)).toEqual([ + "p:tag", + "${:el", + "fn:ns", + "escapeXml:call", + "item:var", + "title:prop", + "}:el", + "#{:el", + "a:var", + "b:var", + "not:opkw", + "empty:opkw", + "c:var", + "d:prop", + "0:num", + "and:opkw", + "x:var", + "size:method", + "gt:opkw", + "2:num", + "}:el", + "p:tag", + ]); + }); + + it("ends an EL expression at its own brace, past nested ones and strings", () => { + const doc = `\${ {'k': '}'}.k == true }`; + expect(errors(doc)).toEqual([]); + expect(tokens(doc)).toEqual(["${:el", "'k':str", "'}':str", "k:prop", "true:bool", "}:el", "b:tag"]); + }); + + it("parses markup with elements in attribute values and custom tags", () => { + const doc = + `\\\${a} <\\% b %>
`)).toEqual(["p:tag", "p:tag"]); + }); + + it("comments with JSP comments in the markup and Java ones in scriptlets", () => { + const doc = `x
<% int a; %>`; + const state = EditorState.create({ doc, extensions: jsp() }); + stateTree(state); + const at = (pos: number) => + state.languageDataAt<{ line?: string; block?: { open: string } }>("commentTokens", pos)[0]; + expect(at(4)?.block?.open).toBe("<%--"); + expect(at(doc.indexOf("int") + 1)?.line).toBe("//"); + }); + + it("folds JSP comments and scriptlets", () => { + const doc = `<%--\n a\n--%>\n<%\n int a;\n%>`; + const state = EditorState.create({ doc, extensions: jsp() }); + stateTree(state); + expect(foldable(state, 0, 4)).toEqual({ from: 4, to: doc.indexOf("--%>") }); + const line = state.doc.line(4); + expect(foldable(state, line.from, line.to)).toEqual({ from: line.to, to: doc.lastIndexOf("%>") }); + }); + + it("highlights like a fresh parse after resuming and editing", () => { + const doc = page(); + // Opening parses the first 3000 characters; the rest resumes from that tree. + let state = EditorState.create({ doc, extensions: jsp() }); + expect(tokens(doc, stateTree(state))).toEqual(tokens(doc)); + const at = (text: string, n = 1) => { + let pos = -1; + for (let i = 0; i < n; i++) pos = state.doc.toString().indexOf(text, pos + 1); + return pos; + }; + const edits = [ + () => ({ from: at("rows12.size()") + 4, insert: "x" }), + () => ({ from: at(".size() %>", 15) + 7, insert: "+1" }), + () => ({ from: at("<% } %>", 20), insert: "<% int k = 0; %>\n" }), + () => ({ from: at("<%-- row 7"), to: at("<%-- row 7") + 4 }), + () => ({ from: at("names3"), to: at("names3") + 6, insert: "a.b[0]" }), + () => ({ from: 0, insert: "${top}\n" }), + () => ({ from: at("