diff --git a/README.md b/README.md
index e5ba574..a0705ad 100644
--- a/README.md
+++ b/README.md
@@ -1,26 +1,49 @@
# Banana.nvim
-This plugin seeks to turn neovim into a hypermedia client through its own custom markup, styling, and scripting interface for extensions. It will also try to use the hypermedia controls similar to what htmx uses to make building extensions easier.
-
-Banana is designed to make neovim uis absurdly easy by using html. For this, it has a custom html language offshoot and a custom css offshoot. The goal of banana is to have an lsp and also a devtools-esque debug window to make development easier.
+A blazingly fast html renderer for neovim
> [!CAUTION]
>
> This project is still in early development.
> Expect breaking changes and bugs, and please report any issues you encounter.
+## The problem
+
+I have made two large plugins for neovim: [pineapple](https://github.com/CWood-sdf/pineapple) and [spaceport](https://github.com/CWood-sdf/spaceport.nvim). For both of these plugins I ended up making my own specialized rendering frameworks. This was originally because I did not know about [nui](https://github.com/MunifTanjim/nui.nvim), but I did not switch my plugins over because there are a two major roadblocks with nui:
+
+- It's a whole new set of tools that I have to learn
+- It seems a little bit verbose
+
+## The solution
+
+Html!
+
+I don't think it is too bold an assumption to say that every developer knows at least a little bit of html. So, it would be really nice if this knowledge everyone has could be transferrable to writing neovim plugins. This makes it so that rather than having to solve the problems of rendering, core features, and ui design, plugin authors do not need to solve rendering because it is all html.
+
+So, the mission of banana is to make neovim uis absurdly easy by using html. For this, it has a custom html language offshoot and a custom css offshoot.
+
+In the future, I would also like to have a lot of "extra" features that usually come to browsers via third-party libraries (eg jquery.load) so that plugin authors can just add banana as a dependency and start coding.
+
The banana renderer takes a design architecture that allows it to be blazingly fast: rendering is done in _one_ pass. There will be no reflowing of any elements ever because later elements were rendered. This also means that complicated box trees and render trees (for example, I believe ladybird uses two trees to render) are _not_ allowed in the renderer. This allows the renderer to be very simple (and fast) while also supporting most of the features of modern html and css. This rendering architecture does make some things (particularly floats) more complicated, but it lets everything else be very fast. This simplification is required because banana is written in lua (slower than the c++ that modern browsers use).
+## Features
+
Partial or complete implementations exist for the following elements:
- div
- title
+- script
+- style
- meta
- ul
- ol
- li
- span
+Html character entity support does not currently exist
+
+Banana has added a very simple text templating system via the % character. Adding the text %asdf to an html element causes the renderer to replace %asdf with the value of the attribute 'asdf' in the first parent element that chas that attribute.
+
Partial or complete implementations exist for the following css properties (for the most uptodate list check lua/banana/ncss/validations.lua):
- hl-\* properties for highlighting (use the same properties as nvim_set_hl)
@@ -34,7 +57,7 @@ Partial or complete implementations exist for the following css properties (for
- list-style-type (very partial)
- width
- height
-- display (currently just flex, inherit, initial, and none)
+- display (currently just flex, inherit, initial, and none) (block, inline... are not planned to be supported because banana only formats the tree once)
- flex-basis
- flex-shrink
- flex-grow
@@ -78,12 +101,127 @@ The following units are implemented (for uptodate list see M.calcUnitNoMod in lu
- fr
- %
+## Structuring plugins
+
+Banana searches for all nml and ncss in the banana/ folder in the neovim require path (similar to the lua/ folder for plugins), thus your plugins can be structured like this:
+
+```
+∟ Plugin
+ ∟ banana/
+ ∟ pluginName/
+ ∟ markup.nml
+ ∟ lua/
+ ∟ pluginName/
+ ∟ plugin.lua
+```
+
+Requiring these markup files uses the slash seperator that links use (rather than the dot lua uses). The filenames index.nml and index.ncss are the "init.lua"s for banana. So to require the file in banana/plugin/index.nml, the require path would just be "plugin"; to require banana/plugin/other.nml, the require path would be "plugin/other".
+
+When using Instance:loadNmlTo(), adding "?..." allows parameter passing in the same way browsers use it. This feature is currently not spread accross the entire ecosystem, but it will happen eventually.
+
+## Executing banana plugins
+
+Opening a banana plugin is very simple:
+
+```lua
+-- the buffer name can be overriden using the
tag
+local document = require('banana.instance').newInstance("require path", "initial buffer name")
+
+-- opens the window
+document:open()
+```
+
+## Scripting
+
+There are two scripting methods available: seperate lua files and embedded lua.
+
+Of the two, seperate lua files has much better support (lsp and parameter passing).
+
+Loading a seperate lua file into an nml document is very simple:
+
+```html
+
+```
+
+this will work if the script returns either a function or a table with a key of \_\_banana_run that has a function.
+
+```lua
+--- Add types for luals
+---@param document Banana.Instance
+---@param opts table
+return function(document, opts)
+ -- any params passed from ?param=value
+ -- only supported on loadNmlTo currently
+ local params = opts.params
+ local element = document:getElementById("asdf")
+end
+```
+
+Embedded lua is also very simple, banana predefines the document variable for you:
+
+```html
+
+```
+
+## Api
+
+The api is modeled very closely after the browser api, some extra functions might come later that make it also possible to use similar to jquery. Currently most of banana's internal api functions are listed as public, but start with an underscore; thus there are two rules for the api:
+
+- _NEVER_ use a function that starts with an underscore
+- _ONLY_ use the api functions, never use a property. To be safe, only use : for field access
+
+### Extra api functions
+
+A banana nml ast node (dom element) has a function called attachRemap. This function enables two features:
+
+- remaps bound to a certain ast are cleaned up when that ast is removed
+- remaps that require hovering over the element are only called when that is the case
+
+The attachRemap function is almost the exact same as vim.keymap.set, but with an extra third parameter which is an array of restrictions. The array of restrictions is currently OR'd together
+
+```lua
+local ast = document:getElementById("foo")
+
+ast:attachRemap(
+ "n", "a", { 1, "hover" --[[ "line-hover" is also an option ]]},
+ function()
+ print("Either you prefixed this map with 1, or are hovering over the element")
+ end,
+ {
+ -- buffer is already set, a feature is needed to prevent auto setting
+ }
+)
+
+```
+
## NEEDED DOCUMENTATION
-- creating and opening instance
-- instance/ast api
-- scripting support
-- banana path
-- using remaps
- developing banana
- developing a banana plugin
+- specifics about all the apis and stuff
+
+## Contributing
+
+There is a lot of work still to be done. If you want to help out, the primary areas work is needed in are:
+
+- table rendering
+- grid rendering
+- gradients
+- documentation
+- tests
+- apis
+- extensibility
+
+## Final notes
+
+As you can probably tell, this plugin is nowhere close to being done. If you would like to contribute to this plugin, by all means please do. If you don't want to contribute, but still find this project interesting, then give it a star. If you want to follow the progress, then "watch" it on github.
+
+## Self promotion
+
+Follow me on [X](https://x.com/CWood_sdf)
+
+Try out my other two plugins: [spaceport](https://github.com/CWood-sdf/spaceport.nvim) and [pineapple](https://github.com/CWood-sdf/pineapple)
+
+Follow me on github
diff --git a/lua/banana/instance.lua b/lua/banana/instance.lua
index b86caab..f7be167 100644
--- a/lua/banana/instance.lua
+++ b/lua/banana/instance.lua
@@ -62,7 +62,7 @@ local Instance = {}
---@param width number
---@param height number
---@return Banana.Line[]
-function Instance:virtualRender(ast, width, height)
+function Instance:_virtualRender(ast, width, height)
flame.new("virtualRender")
---@type Banana.Line[]
local ret = {}
@@ -172,7 +172,7 @@ function Instance:useFile(filename)
self.scripts = scripts
self.styleRules = styleRules
self.ast = ast
- self:applyId(ast)
+ self:_applyId(ast)
end
---@param nml string
@@ -181,7 +181,7 @@ function Instance:useNml(nml)
self.scripts = scripts
self.styleRules = styleRules
self.ast = ast
- self:applyId(ast)
+ self:_applyId(ast)
end
function Instance:_attachAutocmds()
@@ -210,7 +210,7 @@ end
function Instance:open()
self.isVisible = true
- self:render()
+ self:_render()
end
---@return boolean
@@ -238,6 +238,11 @@ function Instance:useBuffer(bufnr)
self.bufnr = bufnr
end
+function Instance:getBufnr()
+ return self.bufnr
+end
+
+---@deprecated just use buffer =
---@param ev string|string[]
---@param opts vim.api.keyset.create_autocmd
---@return number
@@ -314,14 +319,14 @@ function Instance:_setRemap(mode, lhs, rhs, opts, dep)
end
---@param ast Banana.Ast
-function Instance:removeMapsFor(ast)
+function Instance:_removeMapsFor(ast)
for _, vals in ipairs(self.astMapDeps[ast] or {}) do
local map = vals[3]
map.disabled = true
end
for _, node in ipairs(ast.nodes) do
if type(node) ~= "string" then
- self:removeMapsFor(node)
+ self:_removeMapsFor(node)
end
end
if self.foreignStyles[ast] ~= nil then
@@ -349,7 +354,7 @@ function Instance:body()
end
---@param ast Banana.Ast
-function Instance:applyId(ast)
+function Instance:_applyId(ast)
if ast.instance == nil then
ast.instance = self.instanceId
end
@@ -358,30 +363,30 @@ function Instance:applyId(ast)
goto continue
end
---@diagnostic disable-next-line: param-type-mismatch
- self:applyId(ast.nodes[i])
+ self:_applyId(ast.nodes[i])
::continue::
end
end
---@param ast Banana.Ast
-function Instance:applyInlineStyles(ast)
+function Instance:_applyInlineStyles(ast)
ast:_applyInlineStyleDeclarations()
for _, v in ipairs(ast.nodes) do
if type(v) ~= "string" then
- self:applyInlineStyles(v)
+ self:_applyInlineStyles(v)
end
end
end
---@param ast Banana.Ast?
---@param rules Banana.Ncss.RuleSet[]
-function Instance:applyStyleDeclarations(ast, rules)
+function Instance:_applyStyleDeclarations(ast, rules)
if ast == nil then
log.assert(false,
"Ast is nil")
error("")
end
- self:applyInlineStyles(ast)
+ self:_applyInlineStyles(ast)
for _, v in ipairs(rules) do
if v.query == nil then
goto continue
@@ -397,7 +402,7 @@ end
---@param script string
---@param opts table
-function Instance:runScript(script, opts)
+function Instance:_runScript(script, opts)
---@type fun(opts: table)|nil
local f = nil
if #script > 0 and script:sub(1, 1) == "@" then
@@ -418,7 +423,7 @@ function Instance:runScript(script, opts)
end
---@return number, number
-function Instance:createWinAndBuf()
+function Instance:_createWinAndBuf()
local headQuery = require('banana.ncss.query').selectors.oneTag("head")
local headTag = headQuery:getMatches(self.ast)
if #headTag ~= 0 then
@@ -505,7 +510,7 @@ function Instance:_deferRender()
end
self.renderRequested = false
self.renderStart = vim.loop.hrtime()
- self:render()
+ self:_render()
self.renderRequested = false
self.rendering = false
end, 20)
@@ -522,13 +527,13 @@ end
function Instance:forceRerender()
self.renderRequested = false
- self:render()
+ self:_render()
end
local n = 0
local avg = 0
-function Instance:render()
+function Instance:_render()
if not self.isVisible then
return
end
@@ -544,19 +549,19 @@ function Instance:render()
local astTime = 0
local styleTime = 0
self.ast:_clearStyles()
- self:applyStyleDeclarations(self.ast, self.styleRules)
+ self:_applyStyleDeclarations(self.ast, self.styleRules)
for ast, rules in pairs(self.foreignStyles) do
- self:applyStyleDeclarations(ast, rules)
+ self:_applyStyleDeclarations(ast, rules)
end
self:body().relativeBoxes = {}
self:body().absoluteAsts = {}
styleTime = vim.loop.hrtime() - startTime
startTime = vim.loop.hrtime()
- local width, height = self:createWinAndBuf()
+ local width, height = self:_createWinAndBuf()
local winTime = vim.loop.hrtime() - startTime
startTime = vim.loop.hrtime()
-- self:body():resolveUnits(width, height, {})
- local stuffToRender = self:virtualRender(self.ast, width, height)
+ local stuffToRender = self:_virtualRender(self.ast, width, height)
local renderTime = vim.loop.hrtime() - startTime
local lines = {}
@@ -577,9 +582,9 @@ function Instance:render()
})
vim.api.nvim_buf_set_lines(self.bufnr, 0, -1, false, lines)
startTime = vim.loop.hrtime()
- self:highlight(stuffToRender, 0)
+ self:_highlight(stuffToRender, 0)
for _, script in ipairs(self.scripts) do
- self:runScript(script, {})
+ self:_runScript(script, {})
end
self.scripts = {}
local hlTime = vim.loop.hrtime() - startTime
@@ -642,7 +647,7 @@ end
---@param lines Banana.Line[]
---@param offset number?
-function Instance:highlight(lines, offset)
+function Instance:_highlight(lines, offset)
offset = offset or 0
vim.api.nvim_win_set_hl_ns(self.winid, self.highlightNs)
if self.highlightNs ~= nil then
@@ -735,7 +740,7 @@ function Instance:loadNmlTo(file, ast, preserve)
end
end
for _, script in ipairs(scripts) do
- self:runScript(script, {
+ self:_runScript(script, {
params = params
})
end
@@ -821,7 +826,7 @@ end
---@return Banana.Ast
function Instance:createElement(name)
local ast = require('banana.nml.ast').Ast:new(name, M.getNilAst())
- self:applyId(ast)
+ self:_applyId(ast)
return ast
end
diff --git a/lua/banana/nml/ast.lua b/lua/banana/nml/ast.lua
index e1c3591..c1e3619 100644
--- a/lua/banana/nml/ast.lua
+++ b/lua/banana/nml/ast.lua
@@ -774,7 +774,7 @@ function M.Ast:remove()
break
end
end
- require('banana.instance').getInstance(self.instance):removeMapsFor(self)
+ require('banana.instance').getInstance(self.instance):_removeMapsFor(self)
self._parent = nil
self:_requestRender()
end
@@ -796,7 +796,7 @@ function M.Ast:appendNode(node)
node._parent = self
table.insert(self.nodes, node)
if self.instance ~= nil then
- require('banana.instance').getInstance(self.instance):applyId(node)
+ require('banana.instance').getInstance(self.instance):_applyId(node)
end
self:_requestRender()
end