diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..e454ce8
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,39 @@
+name: Run Tests
+
+on:
+ push:
+ branches: [ "*" ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ test:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout Code
+ uses: actions/checkout@v3
+
+ - name: Install Make Dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install libtool-bin autoconf automake cmake g++ pkg-config unzip gettext curl -y
+
+ - name: Install Neovim
+ run: |
+ sudo snap install nvim --classic
+
+ - name: Install Plenary
+ run: |
+ git clone https://github.com/nvim-lua/plenary.nvim.git
+ mkdir -p .local/share/nvim/lazy/
+ mv plenary.nvim .local/share/nvim/lazy/
+
+ - name: Tests
+ env:
+ XDG_CONFIG_HOME: ${{ github.workspace }}/.config
+ XDG_DATA_HOME: ${{ github.workspace }}/.local/share
+ XDG_STATE_HOME: ${{ github.workspace }}/.local/state
+ XDG_CACHE_HOME: ${{ github.workspace }}/.cache
+ run: make test
diff --git a/Makefile b/Makefile
index fe134cd..3d8fa51 100644
--- a/Makefile
+++ b/Makefile
@@ -3,8 +3,6 @@ TESTS_DIR=tests
.PHONY: test
-# TODO I want test to contain a lua-language-server pass
-
test_nvim:
@nvim \
--headless \
@@ -13,7 +11,7 @@ test_nvim:
-c "PlenaryBustedDirectory ${TESTS_DIR} { minimal_init = '${TESTS_INIT}' }"
test:
- -$(MAKE) test_nvim || exit 1
+ $(MAKE) test_nvim
test-watch:
- nodemon -e lua -x "$(MAKE) test"
+ nodemon -e lua -x "$(MAKE) test || exit 1"
diff --git a/README.md b/README.md
index 81a10cb..9742d27 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+![build status](https://github.com/aaronik/treewalker.nvim/actions/workflows/test.yml/badge.svg)
@@ -30,7 +31,8 @@ Moving slowly, showing each command
{
"aaronik/treewalker.nvim",
opts = {
- highlight = true -- default is false; can also be a duration
+ highlight = true -- Whether to briefly highlight the node after jumping to it
+ -- Can be numeric to specify highlight duration instead
}
}
```
diff --git a/lua/treewalker/init.lua b/lua/treewalker/init.lua
index 8a2365a..be56fad 100644
--- a/lua/treewalker/init.lua
+++ b/lua/treewalker/init.lua
@@ -8,8 +8,11 @@ local Treewalker = {}
---@alias Opts { highlight: boolean | integer }
+-- Default setup() options
---@type Opts
-Treewalker.opts = {}
+Treewalker.opts = {
+ highlight = true
+}
---@param opts Opts | nil
function Treewalker.setup(opts)
diff --git a/lua/treewalker/nodes.lua b/lua/treewalker/nodes.lua
index 7fdc6db..a0476b8 100644
--- a/lua/treewalker/nodes.lua
+++ b/lua/treewalker/nodes.lua
@@ -1,36 +1,47 @@
local util = require "treewalker.util"
-local lines= require "treewalker.lines"
+local lines = require "treewalker.lines"
-local NON_TARGET_NODE_MATCHERS = {
- -- "chunk", -- lua
- "^.*comment.*$",
+-- These are regexes but just happen to be real simple so far
+local TARGET_BLACKLIST_TYPE_MATCHERS = {
+ "comment",
}
-local TARGET_DESCENDANT_TYPES = {
- "body_statement", -- lua, rb
- "block", -- lua
- "statement_block", -- lua
-
- -- "then", -- helps rb, hurts lua
- "do_block", -- rb
+local HIGHLIGHT_BLACKLIST_TYPE_MATCHERS = {
+ "module", -- python
+ "chunk", -- lua
+ "body", -- ruby
+ "block", -- ruby
+ "program", -- ruby
+ "haskell", -- guess which language starts their module tree with this node
+ "translation_unit", -- c module
+ "source_file", -- rust
}
+
local M = {}
---@param node TSNode
+---@param matchers string[]
---@return boolean
-function M.is_jump_target(node)
- for _, matcher in ipairs(NON_TARGET_NODE_MATCHERS) do
- -- If it's a banned type
+local function is_matched_in(node, matchers)
+ for _, matcher in ipairs(matchers) do
if node:type():match(matcher) then
- return false
+ return true
end
end
- return true
+ return false
+end
+
+---@param node TSNode
+---@return boolean
+function M.is_jump_target(node)
+ return not is_matched_in(node, TARGET_BLACKLIST_TYPE_MATCHERS)
end
-function M.is_descendant_jump_target(node)
- return util.contains(TARGET_DESCENDANT_TYPES, node:type())
+---@param node TSNode
+---@return boolean
+function M.is_highlight_target(node)
+ return not is_matched_in(node, HIGHLIGHT_BLACKLIST_TYPE_MATCHERS)
end
---Do the nodes have the same starting point
@@ -45,6 +56,14 @@ function M.have_same_start(node1, node2)
scol1 == scol2
end
+---Do the nodes have the same starting row
+---@param node1 TSNode
+---@param node2 TSNode
+---@return boolean
+function M.have_same_row(node1, node2)
+ return M.get_row(node1) == M.get_row(node2)
+end
+
---Do the nodes have the same level of indentation
---@param node1 TSNode
---@param node2 TSNode
@@ -83,41 +102,54 @@ end
---@param node TSNode
---@return TSNode[]
function M.get_descendants(node)
- local descendants = {}
-
- -- Helper function to recursively collect descendants
- local function collect_descendants(current_node)
- local child_count = current_node:child_count()
- for i = 0, child_count - 1 do
- local child = current_node:child(i)
- table.insert(descendants, child)
- -- Recursively collect descendants of the child
- collect_descendants(child)
- end
+ local descendants = {}
+
+ -- Helper function to recursively collect descendants
+ local function collect_descendants(current_node)
+ local child_count = current_node:child_count()
+ for i = 0, child_count - 1 do
+ local child = current_node:child(i)
+ table.insert(descendants, child)
+ -- Recursively collect descendants of the child
+ collect_descendants(child)
end
+ end
- -- Start the recursive collection with the given node
- collect_descendants(node)
+ -- Start the recursive collection with the given node
+ collect_descendants(node)
- return descendants
+ return descendants
+end
+
+-- Get farthest ancestor (or self) at the same starting row
+---@param node TSNode
+---@return TSNode
+function M.get_highest_coincident(node)
+ local parent = node:parent()
+ -- prefer row over start on account of lisps / S-expressions, which start with (identifier, ..)
+ while parent and M.have_same_row(node, parent) do
+ if M.is_highlight_target(parent) then node = parent end
+ parent = parent:parent()
+ end
+ return node
end
--- Take a list of nodes and unique them based on line start
---@param nodes TSNode[]
---@return TSNode[]
function M.unique_per_line(nodes)
- local unique_nodes = {}
- local seen_lines = {}
-
- for _, node in ipairs(nodes) do
- local line = node:start() -- Assuming node:start() returns the line number of the node
- if not seen_lines[line] then
- table.insert(unique_nodes, node)
- seen_lines[line] = true
- end
+ local unique_nodes = {}
+ local seen_lines = {}
+
+ for _, node in ipairs(nodes) do
+ local line = node:start() -- Assuming node:start() returns the line number of the node
+ if not seen_lines[line] then
+ table.insert(unique_nodes, node)
+ seen_lines[line] = true
end
+ end
- return unique_nodes
+ return unique_nodes
end
-- Easy conversion to table
diff --git a/lua/treewalker/ops.lua b/lua/treewalker/ops.lua
index 5331255..228893d 100644
--- a/lua/treewalker/ops.lua
+++ b/lua/treewalker/ops.lua
@@ -16,13 +16,6 @@ end
local M = {}
----set cursor without throwing error
----@param row integer
----@param col integer
-function M.safe_set_cursor(row, col)
- pcall(vim.api.nvim_win_set_cursor, 0, { row, col }) -- catch any errors in nvim_win_set_cursor
-end
-
---Flash a highlight over the given range
---@param range Range4
---@param duration integer
@@ -65,6 +58,7 @@ function M.jump(row, node)
vim.cmd('normal! ^')
local highlight = require("treewalker").opts.highlight
if highlight and highlight ~= 0 then
+ node = nodes.get_highest_coincident(node)
M.highlight(nodes.range(node), highlight ~= true and highlight or 250)
end
end
diff --git a/lua/treewalker/util.lua b/lua/treewalker/util.lua
index 5a9b25f..75110e9 100644
--- a/lua/treewalker/util.lua
+++ b/lua/treewalker/util.lua
@@ -98,12 +98,6 @@ M.guid = function()
end)
end
----@param env_key string
----@return boolean
-M.has_env_var = function(env_key)
- return type(os.getenv(env_key)) ~= type(nil)
-end
-
---reverse an array table
---@param t table
M.reverse = function (t)
diff --git a/plugin/init.lua b/plugin/init.lua
index fc1a3f5..388e7ef 100644
--- a/plugin/init.lua
+++ b/plugin/init.lua
@@ -1,6 +1,5 @@
-local util = require "treewalker.util"
-
local function tw()
+ -- local util = require "treewalker.util"
-- return util.R('treewalker')
return require('treewalker')
end
diff --git a/tests/fixtures/c.c b/tests/fixtures/c.c
new file mode 100644
index 0000000..ee05399
--- /dev/null
+++ b/tests/fixtures/c.c
@@ -0,0 +1,57 @@
+#include
+#include
+
+// Structure to represent an account
+typedef struct {
+ int accountNumber;
+ float balance;
+} Account;
+
+// Function to create a new account
+Account* createAccount(int accountNumber, float initialBalance) {
+ Account* newAccount = (Account*)malloc(sizeof(Account));
+ if (!newAccount) {
+ printf("Memory error\n");
+ return NULL;
+ }
+ newAccount->accountNumber = accountNumber;
+ newAccount->balance = initialBalance;
+ return newAccount;
+}
+
+// Function to deposit money into an account
+void deposit(Account* account, float amount) {
+ if (amount > 0.0f) {
+ account->balance += amount;
+ printf("Deposited $%.2f into account %d\n", amount, account->accountNumber);
+ } else {
+ printf("Invalid deposit amount: $%.2f\n", amount);
+ }
+}
+
+// Function to withdraw money from an account
+void withdraw(Account* account, float amount) {
+ if (amount > 0.0f && amount <= account->balance) {
+ account->balance -= amount;
+ printf("Withdrawn $%.2f from account %d\n", amount, account->accountNumber);
+ } else {
+ printf("Invalid withdrawal amount: $%.2f\n", amount);
+ }
+}
+
+// Function to display account information
+void printAccountInfo(Account* account) {
+ printf("Account Number: %d\nBalance: $%.2f\n", account->accountNumber, account->balance);
+}
+
+int main() {
+ Account* account = createAccount(12345, 1000.00f);
+
+ deposit(account, 500.00f);
+ withdraw(account, 200.00f);
+ printAccountInfo(account);
+
+ free(account);
+ return 0;
+}
+
diff --git a/tests/fixtures/haskell.hs b/tests/fixtures/haskell.hs
index 95681cc..1fa3ced 100644
--- a/tests/fixtures/haskell.hs
+++ b/tests/fixtures/haskell.hs
@@ -1,9 +1,46 @@
-import Data.List
+-- Import the necessary modules
+import Data.List (sort)
+import Control.Monad (replicateM)
-permutations :: [a] -> [[a]]
-permutations [] = [[]]
-permutations xs = do
- x <- xs
- xsRest <- permutations $ filter (/=x) xs
- return $ map (x:) xsRest
+-- Define a function to print out all even numbers in a list
+printEvens :: [Int] -> IO ()
+printEvens [] = return ()
+printEvens (x : xs)
+ | x `mod` 2 == 0 = putStrLn (show x) >> printEvens xs
+ | otherwise = printEvens xs
+-- Define a function to calculate the sum of all numbers in a list
+sumNumbers :: [Int] -> Int
+sumNumbers [] = 0
+sumNumbers (x : xs) = x + sumNumbers xs
+
+-- Define a function to generate a random list of numbers
+randomList :: IO [Int]
+randomList = do
+ n <- getLine
+ let n' = read n :: Int
+ replicateM n (getRandomR (-100, 100)) >>= return . sort
+
+-- Define a function to calculate the median of a list of numbers
+median :: [Double] -> Double
+median xs = median' (sort xs)
+ where
+ median' [] = error "Empty list"
+ median' [_] = error "List contains single element"
+ median' xs
+ | odd len = fromIntegral $ xs !! (len `div` 2)
+ | otherwise = mean
+ where
+ len = length xs
+ mean = (sum xs) / fromIntegral len
+
+-- Main function to run the program
+main :: IO ()
+main = do
+ printEvens [1, 3, 5, 7, 9]
+ print $ sumNumbers [-2, -4, 0, 10]
+ randomList >>= mapM_ putStrLn . map show
+ let xs = [-3.0, -1.0, 0.0, 1.0, 3.0]
+ ys = [5.5, 6.6]
+ print $ median xs
+ print $ sumNumbers ys
diff --git a/tests/fixtures/lua.lua b/tests/fixtures/lua.lua
index 034bdc9..02a2ce5 100644
--- a/tests/fixtures/lua.lua
+++ b/tests/fixtures/lua.lua
@@ -183,3 +183,6 @@ function M.get_node()
end
return M
+
+-- This is a lua fixture. I thought I was being smart when I got it
+-- from this plugin. But actually I was being dumb, and this is very confusing, l0lz
diff --git a/tests/fixtures/python.py b/tests/fixtures/python.py
index b865738..72d6738 100644
--- a/tests/fixtures/python.py
+++ b/tests/fixtures/python.py
@@ -1,3 +1,27 @@
+class Person:
+ def __init__(self, name):
+ self.name = name
+
+ def greet(self):
+ print(f"Hello, my name is {self.name}!")
+
+class Book:
+ def __init__(self, title, author):
+ self.title = title
+ self.author = author
+
+ def describe(self):
+ print(f"{self.title} by {self.author}")
+
+class Car:
+ def __init__(self, make, model, year):
+ self.make = make
+ self.model = model
+ self.year = year
+
+ def display_info(self):
+ print(f"Make: {self.make}, Model: {self.model}, Year: {self.year}")
+
def main():
"""
This function demonstrates a nested structure.
diff --git a/tests/fixtures/rust.rs b/tests/fixtures/rust.rs
index 5121029..e9ec49d 100644
--- a/tests/fixtures/rust.rs
+++ b/tests/fixtures/rust.rs
@@ -1,244 +1,65 @@
-use std::collections::HashMap;
-
use rand::{thread_rng, Rng};
use strum::IntoEnumIterator;
use strum_macros::{Display, EnumIter};
use serde::{Serialize, Deserialize};
-// Map of neuron id -> ..
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct NeuralNet {
- pub input_neurons: HashMap,
- pub inner_neurons: HashMap,
- pub output_neurons: HashMap,
-
- /// A mapping of neuron id -> neuron type, useful if you have a neuron id and want to find out
- /// what type of neuron it is.
- neuron_type_map: HashMap,
-}
-
-/// The brain of a lifeform. Has three neuron types: Input, Inner, and Output. Input has
-/// information from the world, Inner creates a recursive structure, and Output creates actions the
-/// lifeform takes on the world.
-impl NeuralNet {
- pub fn new(num_inner_neurons: usize) -> Self {
- let mut input_neurons = HashMap::new();
- let mut output_neurons = HashMap::new();
- let mut inner_neurons = HashMap::new();
- let mut neuron_type_map = HashMap::new();
-
- // -- Generate Neurons
-
- for (idx, neuron_member) in InputNeuronType::iter().enumerate() {
- // Assuming there'll never be more than 100 input neuron types, we'll do this
- // to assure a different id from the other neurons
- let id = idx + 100;
- let neuron = InputNeuron { id, value: 0.0 };
- input_neurons.insert(id, (neuron_member, neuron));
- neuron_type_map.insert(id, NeuronType::InputNeuron);
- }
-
- // Note: If the 200 here changes, it needs to be changed in add_inner_neuron as well
- for idx in 0..num_inner_neurons {
- let id = idx + 200;
- let neuron = InnerNeuron { id };
- inner_neurons.insert(id, neuron);
- neuron_type_map.insert(id, NeuronType::InnerNeuron);
- }
-
- for (idx, neuron_member) in OutputNeuronType::iter().enumerate() {
- let id = idx + 300;
- let neuron = OutputNeuron { id };
- output_neurons.insert(id, (neuron_member, neuron));
- neuron_type_map.insert(id, NeuronType::OutputNeuron);
- }
-
- // -- Generate Neuron Ids
-
- Self {
- input_neurons,
- output_neurons,
- inner_neurons,
- neuron_type_map,
- }
- }
-
- /// Returns a neuron id randomly chosen from input neurons unioned with inner neurons.
- /// This is all the places where a gene can start from.
- /// Takes an optional "not" value, which, if supplied, will prevent this from returning
- /// that value.
- pub fn random_from_neuron(&self, not_id: Option) -> usize {
- let num_neurons = self.input_neurons.len() + self.inner_neurons.len();
- let idx = thread_rng().gen_range(0..num_neurons);
-
- let id: usize;
-
- if idx < self.input_neurons.len() {
- let ids = &self.input_neurons.keys().map(|k| *k).collect();
- id = get_id_not_id(ids, idx, not_id);
- } else {
- let ids = &self.inner_neurons.keys().map(|k| *k).collect();
- let index = idx - self.input_neurons.len();
- id = get_id_not_id(ids, index, not_id);
- }
-
- id
- }
-
- /// Returns a neuron id randomly chosen from inner neurons unioned with output neurons.
- /// This is all the places where a gene can end, aka go to.
- /// Takes an optional "not" value, which, if supplied, will prevent this from returning
- /// that value.
- pub fn random_to_neuron(&self, not_id: Option) -> usize {
- let num_neurons = self.inner_neurons.len() + self.output_neurons.len();
- let idx = thread_rng().gen_range(0..num_neurons);
-
- let id: usize;
-
- if idx < self.inner_neurons.len() {
- let ids = &self.inner_neurons.keys().map(|k| *k).collect();
- id = get_id_not_id(ids, idx, not_id);
- } else {
- let ids = &self.output_neurons.keys().map(|k| *k).collect();
- let index = idx - self.inner_neurons.len();
- id = get_id_not_id(ids, index, not_id);
- }
-
- id
- }
-
- pub fn neuron_type(&self, neuron_id: &usize) -> &NeuronType {
- &self.neuron_type_map[neuron_id]
- }
-
- /// Add an inner neuron after the net is initially created. Initially made for mutation
- pub fn add_inner_neuron(&mut self) {
-
- let id;
-
- if self.inner_neurons.len() == 0 {
- id = 200;
- } else {
- id = self.inner_neurons.keys().max().unwrap() + &1;
- }
-
- self.inner_neurons.insert(id, InnerNeuron { id });
- self.neuron_type_map.insert(id, NeuronType::InnerNeuron);
- }
-
- /// Remove an inner neuron after the net is initially created. Initially made for mutation
- pub fn remove_inner_neuron(&mut self, id: usize) {
- self.inner_neurons.remove(&id);
- self.neuron_type_map.remove(&id);
- }
+// Define a trait that describes how to calculate the area of a shape
+trait Shape {
+ fn area(&self) -> f64;
}
-#[derive(Debug, EnumIter, Clone, Display, Serialize, Deserialize)]
-pub enum InputNeuronType {
- VisionDistanceLeft,
- VisionTypeLeft,
- VisionDistanceCenter,
- VisionTypeCenter,
- VisionDistanceRight,
- VisionTypeRight,
- PharamoneRedLeft,
- PharamoneRedRight,
- PharamoneYellowRight,
- PharamoneYellowLeft,
- PharamoneBlueRight,
- PharamoneBlueLeft,
- Health,
- Hunger,
- PopulationDensity,
- NeighborhoodDensity,
- Random,
- Oscillator,
+// Implement the Shape trait for a Circle
+struct Circle {
+ radius: f64,
}
-#[derive(Debug, Default, Clone, Serialize, Deserialize)]
-pub struct InputNeuron {
- pub id: usize,
- pub value: f32,
+impl Shape for Circle {
+ fn area(&self) -> f64 {
+ std::f64::consts::PI * self.radius.powi(2)
+ }
}
-#[derive(Debug, EnumIter, Clone, Display, Serialize, Deserialize)]
-pub enum OutputNeuronType {
- TurnLeft,
- TurnRight,
- MoveForward,
- Attack,
- ExcretePharamoneRed,
- ExcretePharamoneYellow,
- ExcretePharamoneBlue,
+// Implement the Shape trait for a Rectangle
+struct Rectangle {
+ width: f64,
+ height: f64,
}
-#[derive(Debug, Default, Clone, Serialize, Deserialize)]
-pub struct OutputNeuron {
- pub id: usize,
+impl Shape for Rectangle {
+ fn area(&self) -> f64 {
+ self.width * self.height
+ }
}
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct InnerNeuron {
- pub id: usize,
+fn calculate_area(shape: &dyn Shape) -> f64 {
+ shape.area()
}
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub enum NeuronType {
- InputNeuron,
- InnerNeuron,
- OutputNeuron,
+// Define a function that uses the Shape trait to print the area of an object
+fn print_shape_area(shape: &dyn Shape) {
+ println!("The area of this shape is: {}", calculate_area(shape));
}
-/// The basic issue is that we want to request a random neuron id, but we sometimes want
-/// to make sure that it's different from a given one, which in this case is called not_id.
-/// This is just a helper to abstract some of the repeated logic in random_{from,to}_neuron.
-fn get_id_not_id(ids: &Vec, mut idx: usize, not_id: Option) -> usize {
- let mut id = ids[idx];
+fn main() {
+ let circle = Circle { radius: 5.0 };
+ let rectangle = Rectangle { width: 4.0, height: 6.0 };
- if let Some(not_id) = not_id {
- if not_id == id {
- if idx > 0 {
- idx -= 1;
- } else {
- idx += 1;
- }
- }
- }
+ print_shape_area(&circle);
+ print_shape_area(&rectangle);
- // On some occasions we may have a length one vector.
- // In these cases, we'll just return the first id.
- if idx >= ids.len() {
- idx = 0;
+ // Example of a function that uses both the Shape and Display traits
+ fn display_area(shape: &T) {
+ println!("The area of this {} is: {}", shape, calculate_area(shape));
}
- id = ids[idx];
-
- id
-}
-
-#[cfg(test)]
-mod test {
-
- use super::*;
+ display_area(&circle);
+ display_area(&rectangle);
- #[test]
- fn add_inner_neuron() {
- let mut nn = NeuralNet::new(0);
- assert_eq!(nn.inner_neurons.len(), 0);
- nn.add_inner_neuron();
- assert_eq!(nn.inner_neurons.len(), 1);
+ // Example of a function that uses the Shape trait with generics
+ fn sum_areas(shapes: Vec<&T>) -> f64 {
+ shapes.iter().map(|shape| shape.area()).sum()
}
- #[test]
- fn remove_inner_neuron() {
- let mut nn = NeuralNet::new(1);
-
- assert_eq!(nn.inner_neurons.len(), 1);
-
- let id = nn.inner_neurons.keys().last().unwrap();
- nn.remove_inner_neuron(*id);
-
- assert_eq!(nn.inner_neurons.len(), 0);
- }
+ let shapes = vec![&circle, &rectangle];
+ println!("The total area is: {}", sum_areas(shapes));
}
-
diff --git a/tests/fixtures/scheme.scm b/tests/fixtures/scheme.scm
index c5e7d0b..03d7d7a 100644
--- a/tests/fixtures/scheme.scm
+++ b/tests/fixtures/scheme.scm
@@ -1,8 +1,49 @@
-(define (permutations lst)
- (if (null? lst)
- '(())
- (append-map (lambda (x)
- (map (lambda (y) (cons x y))
- (permutations (remove x lst))))
- lst)))
+(define (print-odd-numbers up-to)
+ (let ((count 1))
+ (lambda ()
+ (if (> count up-to)
+ null
+ (begin
+ (display count)
+ (newline)
+ (set! count (+ count 2))
+ (cons count (print-odd-numbers up-to)))))))
+(define (sum-a-list lst)
+ (cond ((null? lst) 0)
+ ((list? (cdr lst))
+ (+ (car lst) (sum-a-list (cdr lst))))
+ (else
+ (display "Invalid list")
+ (newline))))
+
+(define (is-prime? num)
+ (let ((divisors 2))
+ (cond ((> (* divisors divisors) num) true)
+ ((= (modulo num divisors) 0) false)
+ (else
+ (set! divisors (+ divisors 1))
+ (is-prime? num)))))
+
+(define (greet name)
+ (display "Hello, ")
+ (display name)
+ (newline))
+
+(module test racket
+ (require rackunit)
+
+ (define odd-numbers-upto-10 (print-odd-numbers 10))
+ (check-equal? (car (odd-numbers-upto-10)) 1)
+
+ (define sum-of-list '(1 2 3 4 5)
+ (sum-a-list sum-of-list) => 15)
+
+ (define is-prime 100
+ (is-prime? num) => #t))
+
+(module main racket
+ (require rackunit)
+
+ (let ((name "John"))
+ (greet name)))
diff --git a/tests/minimal_init.lua b/tests/minimal_init.lua
index 1117c70..c8be46f 100644
--- a/tests/minimal_init.lua
+++ b/tests/minimal_init.lua
@@ -2,13 +2,6 @@ local lazypath = vim.fn.stdpath("data") .. "/lazy"
vim.notify = print
vim.opt.rtp:append(".")
vim.opt.rtp:append(lazypath .. "/plenary.nvim")
-vim.opt.rtp:append(lazypath .. "/nui.nvim")
-vim.opt.rtp:append(lazypath .. "/telescope.nvim")
--- vim.opt.rtp:append(lazypath .. "/nvim-nio")
-
--- -- Get all our normal plugins into the test env
--- local suite = os.getenv("SUITE")
--- vim.opt.rtp:append(suite .. "nvim")
vim.opt.swapfile = false
diff --git a/tests/treewalker/highlight_spec.lua b/tests/treewalker/highlight_spec.lua
new file mode 100644
index 0000000..d3d9dee
--- /dev/null
+++ b/tests/treewalker/highlight_spec.lua
@@ -0,0 +1,134 @@
+local util = require "treewalker.util"
+local load_fixture = require "tests.load_fixture"
+local spy = require 'luassert.spy'
+local assert = require "luassert"
+local treewalker = require 'treewalker'
+local ops = require 'treewalker.ops'
+
+describe("Treewalker highlighting", function()
+ local highlight_spy = spy.on(ops, "highlight")
+ local bufclear_spy = spy.on(vim.api, "nvim_buf_clear_namespace")
+
+ -- use with rows as they're numbered in vim lines (1-indexed)
+ local function assert_highlighted(srow, scol, erow, ecol, desc)
+ assert.same(
+ { srow - 1, scol - 1, erow - 1, ecol },
+ highlight_spy.calls[1].refs[1],
+ "highlight wrong for: " .. desc
+ )
+ end
+
+ describe("regular lua file: ", function()
+ load_fixture("/lua.lua", "lua")
+
+ before_each(function()
+ treewalker.setup({ highlight = true })
+ highlight_spy = spy.on(ops, "highlight")
+ bufclear_spy = spy.on(vim.api, "nvim_buf_clear_namespace")
+ end)
+
+ it("respects highlight config option", function()
+ vim.wait(250 + 5) -- wait out potential "buf_clear" calls queue up from previous tests
+
+ -- 'nvim_buf_clear_namespace' should be called times
+ -- within a 10ms tolerance window after ms
+ local function assert_bufclears_after(timeout, calls)
+ bufclear_spy:clear()
+ vim.wait(timeout - 5)
+ assert.spy(bufclear_spy).was.not_called()
+ vim.wait(10)
+ assert.spy(bufclear_spy).was.called(calls)
+ end
+
+ highlight_spy:clear()
+ treewalker.setup() -- highlight defaults to true, doesn't blow up with empty setup
+ vim.fn.cursor(23, 5)
+ treewalker.move_out()
+ treewalker.move_down()
+ treewalker.move_up()
+ treewalker.move_in()
+ assert.spy(highlight_spy).was.called(4)
+ assert_bufclears_after(250, 4)
+
+ highlight_spy:clear()
+ treewalker.setup({ highlight = 0 })
+ vim.fn.cursor(23, 5)
+ treewalker.move_out()
+ treewalker.move_down()
+ treewalker.move_up()
+ treewalker.move_in()
+ assert.spy(highlight_spy).was.not_called()
+
+ highlight_spy:clear()
+ treewalker.setup({ highlight = false })
+ vim.fn.cursor(23, 5)
+ treewalker.move_out()
+ treewalker.move_down()
+ treewalker.move_up()
+ treewalker.move_in()
+ assert.spy(highlight_spy).was.not_called()
+
+ highlight_spy:clear()
+ treewalker.setup({ highlight = true })
+ vim.fn.cursor(23, 5)
+ treewalker.move_out()
+ treewalker.move_down()
+ treewalker.move_up()
+ treewalker.move_in()
+ assert.spy(highlight_spy).was.called(4)
+ assert_bufclears_after(250, 4)
+
+ highlight_spy:clear()
+ treewalker.setup({ highlight = 50 })
+ vim.fn.cursor(23, 5)
+ treewalker.move_out()
+ treewalker.move_down()
+ treewalker.move_up()
+ treewalker.move_in()
+ assert.spy(highlight_spy).was.called(4)
+ assert_bufclears_after(50, 4)
+
+ highlight_spy:clear()
+ treewalker.setup({ highlight = 500 })
+ vim.fn.cursor(23, 5)
+ treewalker.move_out()
+ treewalker.move_down()
+ treewalker.move_up()
+ treewalker.move_in()
+ assert.spy(highlight_spy).was.called(4)
+ assert_bufclears_after(500, 4)
+ end)
+
+ it("highlights whole functions", function()
+ vim.fn.cursor(10, 1)
+ treewalker.move_down()
+ assert_highlighted(21, 1, 28, 3, "is_jump_target function")
+ end)
+
+ it("highlights whole lines starting with identifiers", function()
+ vim.fn.cursor(134, 5)
+ treewalker.move_up()
+ assert_highlighted(133, 5, 133, 33, "table.insert call")
+ end)
+
+ it("highlights whole lines starting assignments", function()
+ vim.fn.cursor(133, 5)
+ treewalker.move_down()
+ assert_highlighted(134, 5, 134, 18, "child = iter()")
+ end)
+
+ -- Note this is highly language dependent, so this test is not so powerful
+ it("doesn't highlight the whole file", function()
+ vim.fn.cursor(3, 1)
+ treewalker.move_up()
+ assert_highlighted(1, 1, 1, 39, "first line")
+ end)
+
+ -- Also very language dependent
+ it("highlights only the first item in a block", function()
+ vim.fn.cursor(27, 3)
+ treewalker.move_up()
+ assert_highlighted(22, 3, 26, 5, "child = iter()")
+ end)
+ end)
+end)
diff --git a/tests/treewalker/acceptance_spec.lua b/tests/treewalker/movement_spec.lua
similarity index 59%
rename from tests/treewalker/acceptance_spec.lua
rename to tests/treewalker/movement_spec.lua
index 62feccd..39f7b69 100644
--- a/tests/treewalker/acceptance_spec.lua
+++ b/tests/treewalker/movement_spec.lua
@@ -18,7 +18,7 @@ local function assert_cursor_at(line, column, msg)
assert.are.same({ line, column }, { current_line, current_column }, msg)
end
-describe("Treewalker", function()
+describe("Treewalker movement", function()
describe("regular lua file: ", function()
load_fixture("/lua.lua", "lua")
@@ -63,13 +63,13 @@ describe("Treewalker", function()
end)
it("doesn't jump into a comment", function()
- vim.fn.cursor(177, 1) -- In a bigger function
+ vim.fn.cursor(177, 1)
treewalker.move_in()
assert_cursor_at(179, 3, "local")
end)
it("goes out of functions", function()
- vim.fn.cursor(149, 7) -- In a bigger function
+ vim.fn.cursor(149, 7)
treewalker.move_out()
assert_cursor_at(148, 5, "if")
treewalker.move_out()
@@ -77,72 +77,6 @@ describe("Treewalker", function()
treewalker.move_out()
assert_cursor_at(143, 1, "function")
end)
-
- it("respects highlight config option", function()
- spy.on(ops, "highlight")
- local bufclear = spy.on(vim.api, "nvim_buf_clear_namespace")
-
- -- 'nvim_buf_clear_namespace' should be called times
- -- within a 10ms tolerance window after ms
- local function assert_bufclears_after(timeout, calls)
- bufclear:clear()
- vim.wait(timeout - 5)
- assert.spy(bufclear).was.not_called()
- vim.wait(10)
- assert.spy(bufclear).was.called(calls)
- end
-
- treewalker.setup()
- vim.fn.cursor(23, 5)
- treewalker.move_out()
- treewalker.move_down()
- treewalker.move_up()
- treewalker.move_in()
- assert.spy(ops.highlight).was.not_called()
-
- treewalker.setup({ highlight = 0 })
- vim.fn.cursor(23, 5)
- treewalker.move_out()
- treewalker.move_down()
- treewalker.move_up()
- treewalker.move_in()
- assert.spy(ops.highlight).was.not_called()
-
- treewalker.setup({ highlight = false })
- vim.fn.cursor(23, 5)
- treewalker.move_out()
- treewalker.move_down()
- treewalker.move_up()
- treewalker.move_in()
- assert.spy(ops.highlight).was.not_called()
-
- treewalker.setup({ highlight = true })
- vim.fn.cursor(23, 5)
- treewalker.move_out()
- treewalker.move_down()
- treewalker.move_up()
- treewalker.move_in()
- assert.spy(ops.highlight).was.called(4)
- assert_bufclears_after(250, 4)
-
- treewalker.setup({ highlight = 50 })
- vim.fn.cursor(23, 5)
- treewalker.move_out()
- treewalker.move_down()
- treewalker.move_up()
- treewalker.move_in()
- assert.spy(ops.highlight).was.called(8)
- assert_bufclears_after(50, 4)
-
- treewalker.setup({ highlight = 500 })
- vim.fn.cursor(23, 5)
- treewalker.move_out()
- treewalker.move_down()
- treewalker.move_up()
- treewalker.move_in()
- assert.spy(ops.highlight).was.called(12)
- assert_bufclears_after(500, 4)
- end)
end)
describe("lua spec file: ", function()
diff --git a/tests/treewalker/util_spec.lua b/tests/treewalker/util_spec.lua
index 0e569ad..163941b 100644
--- a/tests/treewalker/util_spec.lua
+++ b/tests/treewalker/util_spec.lua
@@ -91,19 +91,6 @@ describe("util", function()
end)
end)
- describe("ensure_env_var", function()
- it("returns true", function()
- -- always set
- local res = util.has_env_var("SHELL")
- assert.is_true(res)
- end)
-
- it("returns false", function()
- local res = util.has_env_var("IM_SUPER_SURE_THIS_ENV_VAR_WONT_BE_SET_FR_FR")
- assert.is_false(res)
- end)
- end)
-
describe("reverse", function()
it("reverses an array table", function()
local t = { 1, 2, 3, 4, 5 }