~technomancy/fennel-lang.org

02a093247f521aa2889fc8f4d24d09d12c07345c — Oliver Vartiainen 1 year, 9 months ago 5b2e668
Highlight Fennel code on /see
4 files changed, 224 insertions(+), 16 deletions(-)

M fennel.css
A highlight.lua
M see.html
M see.lua
M fennel.css => fennel.css +52 -4
@@ 15,7 15,7 @@ h1 img { max-width: 1.5em; height: auto; }
a { color: #0d98ba; }
a:hover { color: #0c7090; }

tt, pre, code, kbd { font-family: "Fira Mono", Inconsolata, monospace; }
tt, pre, code, kbd, textarea { font-family: "Fira Mono", Inconsolata, monospace; }

#where li { display: inline; margin: 2em; }



@@ 105,14 105,62 @@ tt, pre, code, kbd { font-family: "Fira Mono", Inconsolata, monospace; }
#sample .comment {/* font-lock-comment-face */ color: #b22222;}

/* see fennel */
#see-fennel textarea {
#see-fennel .editor {
    display: grid; /* This combined with `grid-column: 1` and `grid-row: 1` gives overlapped elements */
    float: left;
    width: 48%;
    height: 25em;
    width: calc(50% - 1em);
    height: 20em;
    margin: 0.5em;
}

#see-fennel .editor textarea,
#see-fennel .editor pre {
    box-sizing: border-box;
    font-size: 13px;
    grid-column: 1;
    grid-row: 1;
    height: 100%;
    line-height: 1.5;
    margin: 0;
    overflow-y: auto;
    padding: 0.4em;
    white-space: pre-wrap;
    width: 100%;
}

#see-fennel .editor textarea {
    border: 1px solid transparent; /* To make dimensions match with pre */
    background: transparent;
    caret-color: #444; /* TODO: Somehow inherit the color from body */
    color: transparent;
    outline: none; /* No focus glow */
    resize: none;
    z-index: 1;
}

#see-fennel .editor textarea::placeholder {
    opacity: 1;
    color: #94b996; /* Same color as comment */
}

#see-fennel .editor pre {
    border-radius: 3px;
    border: 1px solid #ddd;
    z-index: 0;
}

/* Syntax highlight theme */
#see-fennel .editor pre { background-color: #f9fef5; color: #181a19; }
#see-fennel .editor pre .comment { color: #94b996; font-style: italic; }
#see-fennel .editor pre .keyword,
#see-fennel .editor pre .string,
#see-fennel .editor pre .number,
#see-fennel .editor pre .nil,
#see-fennel .editor pre .boolean { color: #8e9144; }
#see-fennel .editor pre .symbol.special-symbol,
#see-fennel .editor pre .symbol.macro-symbol,
#see-fennel .editor pre .symbol.global-symbol { color: #448f22; font-weight: bold; }

#see-fennel button, #see-fennel select {
    margin: 0.5em;
    width: 48%;

A highlight.lua => highlight.lua +91 -0
@@ 0,0 1,91 @@
local function scan(text)
  local current_index = 1
  local last_matching_at = 0
  local text_length = string.len(text)
  local function at_eof_3f()
    return (current_index > text_length)
  end
  local function yield_buffered_nonmatching()
    if (0 < (current_index - last_matching_at)) then
      return coroutine.yield({kind = nil, value = string.sub(text, (last_matching_at + 1), (current_index - 1))})
    else
      return nil
    end
  end
  local function yield(kind, matched)
    yield_buffered_nonmatching()
    coroutine.yield({kind = kind, value = matched})
    current_index = (current_index + string.len(matched))
    last_matching_at = (current_index - 1)
    return nil
  end
  local function attempt_match(kind, pattern)
    local _2_ = string.match(text, pattern, current_index)
    if (nil ~= _2_) then
      local matched = _2_
      yield(kind, matched)
      return true
    else
      return nil
    end
  end
  local function increment_current_index()
    current_index = (current_index + 1)
    return nil
  end
  local symbol_char = "!%$&#%*%+%-%./:<=>%?%^_%w"
  while not at_eof_3f() do
    do local _ = (attempt_match("comment", "^(;[^\n]*[\n])") or attempt_match("string", "^(\"[^\"]*\")") or attempt_match("keyword", ("^(:[" .. symbol_char .. "]+)")) or attempt_match("number", "^([%+%-]?%d+[xX]?%d*%.?%d?)") or attempt_match("nil", ("^(nil)[^" .. symbol_char .. "]")) or attempt_match("boolean", ("^(true)[^" .. symbol_char .. "]")) or attempt_match("boolean", ("^(false)[^" .. symbol_char .. "]")) or attempt_match("symbol", ("^([" .. symbol_char .. "]+)")) or attempt_match("bracket", "^([%(%)%[%]{}])") or increment_current_index()) end
  end
  return yield_buffered_nonmatching()
end
local function with_symbol_subkind(syntax, _4_)
  local _arg_5_ = _4_
  local token = _arg_5_
  local kind = _arg_5_["kind"]
  local value = _arg_5_["value"]
  if ("symbol" == kind) then
    local _6_ = syntax[value]
    if ((_G.type(_6_) == "table") and ((_6_)["special?"] == true)) then
      token.subkind = "special-symbol"
    elseif ((_G.type(_6_) == "table") and ((_6_)["macro?"] == true)) then
      token.subkind = "macro-symbol"
    elseif ((_G.type(_6_) == "table") and ((_6_)["global?"] == true)) then
      token.subkind = "global-symbol"
    else
      token.subkind = nil
    end
  else
  end
  return token
end
local function token__3ehtml(_9_)
  local _arg_10_ = _9_
  local kind = _arg_10_["kind"]
  local subkind = _arg_10_["subkind"]
  local value = _arg_10_["value"]
  local class = ((kind or "nonmatching") .. " " .. (subkind or ""))
  local escaped_value = string.gsub(string.gsub(value, "&", "&amp;"), "<", "&lt;")
  return ("<span class=\"" .. class .. "\">" .. escaped_value .. "</span>")
end
local function for_html(syntax, code)
  local tokens
  do
    local tbl_17_auto = {}
    local i_18_auto = #tbl_17_auto
    local function _11_()
      return scan((code .. "\n"))
    end
    for token in coroutine.wrap(_11_) do
      local val_19_auto = token__3ehtml(with_symbol_subkind(syntax, token))
      if (nil ~= val_19_auto) then
        i_18_auto = (i_18_auto + 1)
        do end (tbl_17_auto)[i_18_auto] = val_19_auto
      else
      end
    end
    tokens = tbl_17_auto
  end
  return table.concat(tokens)
end
return {["for-html"] = for_html, for_html = for_html}

M see.html => see.html +11 -2
@@ 11,8 11,17 @@
  <body>
    <div id="see-fennel">
      <h1>See Fennel</h1>
      <textarea id="fennel-source" placeholder="Enter some Fennel code to see it compiled to Lua!"></textarea>
      <textarea id="lua-source" placeholder="Or enter some Lua to see it compiled to Fennel..."></textarea>

      <div class="editor">
        <textarea id="fennel-source" placeholder="Enter some Fennel code to see it compiled to Lua!"></textarea>
        <pre aria-hidden="true" id="fennel-highlighted"></pre>
      </div>

      <div class="editor">
        <textarea id="lua-source" placeholder="Or enter some Lua to see it compiled to Fennel..."></textarea>
        <pre aria-hidden="true" id="lua-highlighted"></pre>
      </div>

      <button id="compile-fennel">→</button>
      <button id="compile-lua">←</button>
      <select id="sample-fennel"><option>Fennel examples...</option></select>

M see.lua => see.lua +70 -10
@@ 1,6 1,9 @@
package.path = "./?.lua"
local js = require("js")

local fennel = require("fennel/fennel")
local highlight = require("highlight")

-- minimal shims just to allow the compilers to load in Fengari
package.loaded.ffi = {typeof=function() end}
os = {getenv=function() end}


@@ 17,7 20,10 @@ local compile_lua = document:getElementById("compile-lua")
local out = document:getElementById("out")

local fennel_source = document:getElementById("fennel-source")
local fennel_highlighted = document:getElementById("fennel-highlighted")

local lua_source = document:getElementById("lua-source")
local lua_highlighted = document:getElementById("lua-highlighted")

local status = function(msg, success)
   out.innerHTML = msg


@@ 41,6 47,60 @@ lua_source.onkeydown = function(_,e)
   end
end

-- Syntax highlighting is adapted from this guide: https://css-tricks.com/creating-an-editable-textarea-that-supports-syntax-highlighted-code/
--
-- TL;DR: To highlight syntax in a user-editable textarea, we
-- 1. Copy the contents of the textarea on every edit
-- 2. Run the contents through a highlighting function to get styled HTML
-- 3. Set the HTML into a pre element that's just behind the textarea
-- 4. Make the textarea completely transparent except for the input caret
--    and ensure the input textarea and output pre have the same dimensions
--    using a bunch of CSS

local syntax = fennel.syntax()
function highlight_fennel()
   fennel_highlighted.innerHTML = highlight.for_html(syntax, fennel_source.value)
end

function highlight_lua()
   -- TODO: Implement :-)
   lua_highlighted.innerHTML = lua_source.value
end

-- Initial run
highlight_fennel()
highlight_lua()

fennel_source.onscroll = function()
   fennel_highlighted.scrollTop = fennel_source.scrollTop
   fennel_highlighted.scrollLeft = fennel_source.scrollLeft
end

lua_source.onscroll = function()
   lua_highlighted.scrollTop = lua_source.scrollTop
   lua_highlighted.scrollLeft = lua_source.scrollLeft
end

fennel_source.oninput = function()
   highlight_fennel()
   fennel_source.onscroll()
end

lua_source.oninput = function()
   highlight_lua()
   lua_source.onscroll()
end

function update_fennel_source(new_value)
   fennel_source.value = new_value
   highlight_fennel()
end

function update_lua_source(new_value)
   lua_source.value = new_value
   highlight_lua()
end

local anti_msg = "Compiled Lua to Fennel.\n\n"..
   "Note that compiling Lua to Fennel can result in some" ..
   " strange-looking code when\nusing constructs that Fennel" ..


@@ 66,10 126,10 @@ local init_worker = function(auto_click)
         -- because we can't send tables as events, we encode the type of the
         -- message in the last character of the string.
         if event.data:match(" $") then
            lua_source.value = event.data
            update_lua_source(event.data)
            status("Compiled Fennel to Lua.", true)
         elseif event.data:match("\t$") then
            fennel_source.value = event.data
            update_fennel_source(event.data)
            status(anti_msg, true)
         else
            status(event.data, false)


@@ 85,7 145,7 @@ local load_direct = function(auto_click)
   compile_fennel.onclick = function()
      local ok, code = pcall(fennel.compileString, fennel_source.value)
      if ok then
         lua_source.value = code
         update_lua_source(code)
         status("Compiled Fennel to Lua.", true)
      else
         status(tostring(code), false)


@@ 99,7 159,7 @@ local load_direct = function(auto_click)

      local ok, code = pcall(antifennel, lua_source.value)
      if ok then
         fennel_source.value = code
         update_fennel_source(code)
         status(anti_msg, true)
      else
         status(tostring(code), false)


@@ 224,7 284,7 @@ end",
   end\
end"}

local init_samples = function(id, samples, target)
local init_samples = function(id, samples, update_source)
   local select = document:getElementById(id)
   for name, sample in pairs(samples) do
      local option = document:createElement("option")


@@ 235,12 295,12 @@ local init_samples = function(id, samples, target)
   select.onchange = function(self, e)
      init()
      local code = samples[self.value]
      if code then target.value = code end
      if code then update_source(code) end
   end
end

init_samples("sample-fennel", fennel_samples, fennel_source)
init_samples("sample-lua", lua_samples, lua_source)
init_samples("sample-fennel", fennel_samples, update_fennel_source)
init_samples("sample-lua", lua_samples, update_lua_source)

if js.global.URLSearchParams then
   local params = js.new(js.global.URLSearchParams, document.location.search)


@@ 248,10 308,10 @@ if js.global.URLSearchParams then
   local lua_param = params:get("lua")

   if tostring(fennel_param) ~= "null" then
      fennel_source.value = fennel_param
      update_fennel_source(fennel_param)
      init(compile_fennel)
   elseif tostring(lua_param) ~= "null" then
      lua_source.value = lua_param
      update_lua_source(lua_param)
      init(compile_lua)
   end
end