diff --git a/lua/treewalker/nodes.lua b/lua/treewalker/nodes.lua index 512493c..a0476b8 100644 --- a/lua/treewalker/nodes.lua +++ b/lua/treewalker/nodes.lua @@ -7,10 +7,14 @@ local TARGET_BLACKLIST_TYPE_MATCHERS = { } local HIGHLIGHT_BLACKLIST_TYPE_MATCHERS = { - "chunk", - "body", - "block", - "program", + "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 } @@ -52,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 @@ -109,12 +121,13 @@ function M.get_descendants(node) return descendants end --- Get farthest ancestor (or self) at the same starting coordinates +-- Get farthest ancestor (or self) at the same starting row ---@param node TSNode ---@return TSNode -function M.get_farthest_ancestor_with_same_srow(node) +function M.get_highest_coincident(node) local parent = node:parent() - while parent and M.have_same_start(node, parent) do + -- 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 diff --git a/lua/treewalker/ops.lua b/lua/treewalker/ops.lua index 7d25706..abd5fe2 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 function M.highlight(range) @@ -63,7 +56,7 @@ function M.jump(row, node) vim.api.nvim_win_set_cursor(0, { row, 0 }) vim.cmd('normal! ^') if require("treewalker").opts.highlight then - node = nodes.get_farthest_ancestor_with_same_srow(node) + node = nodes.get_highest_coincident(node) M.highlight(nodes.range(node)) end 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/treewalker/highlight_spec.lua b/tests/treewalker/highlight_spec.lua new file mode 100644 index 0000000..63550ef --- /dev/null +++ b/tests/treewalker/highlight_spec.lua @@ -0,0 +1,88 @@ +local util = require "treewalker.util" +local load_fixture = require "tests.load_fixture" +local stub = require 'luassert.stub' +local assert = require "luassert" +local treewalker = require 'treewalker' +local ops = require 'treewalker.ops' + +describe("Treewalker highlighting", function() + local highlight_stub = stub(ops, "highlight") + + -- 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_stub.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_stub = stub(ops, "highlight") + end) + + it("respects highlight config option", function() + 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.equal(4, #highlight_stub.calls) + + highlight_stub = stub(ops, "highlight") + treewalker.setup({ highlight = false }) + vim.fn.cursor(23, 5) + treewalker.move_out() + treewalker.move_down() + treewalker.move_up() + treewalker.move_in() + assert.equal(0, #highlight_stub.calls) + + highlight_stub = stub(ops, "highlight") + treewalker.setup({ highlight = true }) + vim.fn.cursor(23, 5) + treewalker.move_out() + treewalker.move_down() + treewalker.move_up() + treewalker.move_in() + assert.equal(4, #highlight_stub.calls) + 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 74% rename from tests/treewalker/acceptance_spec.lua rename to tests/treewalker/movement_spec.lua index 6dfe34a..b8f7a19 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,35 +77,6 @@ describe("Treewalker", function() treewalker.move_out() assert_cursor_at(143, 1, "function") end) - - it("respects highlight config option", function() - local highlight_stub = stub(ops, "highlight") - 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.equal(4, #highlight_stub.calls) - - highlight_stub = stub(ops, "highlight") - treewalker.setup({ highlight = false }) - vim.fn.cursor(23, 5) - treewalker.move_out() - treewalker.move_down() - treewalker.move_up() - treewalker.move_in() - assert.equal(0, #highlight_stub.calls) - - highlight_stub = stub(ops, "highlight") - treewalker.setup({ highlight = true }) - vim.fn.cursor(23, 5) - treewalker.move_out() - treewalker.move_down() - treewalker.move_up() - treewalker.move_in() - assert.equal(4, #highlight_stub.calls) - end) end) describe("lua spec file: ", function()