stevearc / aerial.nvim

Neovim plugin for a code outline window
MIT License
1.55k stars 76 forks source link

Nim support #300

Open levinotik opened 8 months ago

levinotik commented 8 months ago

Language: Nim

# Importing a standard library module
import strutils

# Constants and Variables
const
  pi = 3.14159
var
  radius: float = 5.0

# Procedures (Functions)
proc greet(name: string): string =
  return "Hello, " & name & "!"

# Control Flow
if radius > 0.0:
  echo "Valid radius."
elif radius == 0.0:
  echo "Zero radius."
else:
  echo "Invalid radius."

# Loops
for i in 1..5:
  echo "Iteration ", i

# Sequences
let fruits = @["apple", "banana", "cherry"]

# Iterating over a sequence
for fruit in fruits:
  echo "Fruit: ", fruit

# Record (Custom Data Type)
type
  Person = object
    name: string
    age: int

let alice = Person(name: "Alice", age: 30)

# Displaying Record Fields
echo "Name: ", alice.name
echo "Age: ", alice.age

# Exception Handling
try
  let result = 10 / 0
except DivisionByZero:
  echo "Division by zero!"

# Modules
import math

# Using a module function
let sqrtValue = sqrt(16.0)

echo "Square root of 16: ", sqrtValue

# Templates (Generics)
proc add[T](a, b: T): T =
  return a + b

let sumInt = add(5, 3)
let sumFloat = add(2.5, 3.7)

# Custom Types
type
  ShapeKind = enum
    Circle, Rectangle, Triangle

type
  Shape = object
    kind: ShapeKind
    case kind
    of Circle:
      radius: float
    of Rectangle:
      width, height: float
    of Triangle:
      side1, side2, side3: float

let circle = Shape(kind: Circle, radius: 4.0)
let rectangle = Shape(kind: Rectangle, width: 5.0, height: 3.0)

echo "Circle radius: ", circle.radius
echo "Rectangle area: ", rectangle.width * rectangle.height

Mappings for each of the Nim language constructs to their corresponding SymbolKind values:

  1. Constants and Variables:

    • Nim: Constants and variables don't have a specific SymbolKind, but they can be categorized as Variable or Constant depending on their context.
  2. Procedures (Functions):

    • Nim: Procedures are typically mapped to Function in LSP's SymbolKind.
  3. Control Flow:

    • Nim: Control flow constructs like if, elif, and else are not usually considered symbols in LSP, so they don't have a direct SymbolKind.
  4. Loops:

    • Nim: Like control flow, loops such as for are not typically categorized as symbols in LSP.
  5. Sequences:

    • Nim: Sequences like arrays and lists are often categorized as Array, Object, or Variable in LSP, depending on context.
  6. Record (Custom Data Type):

    • Nim: Custom data types like records are often mapped to Class or TypeParameter in LSP.
  7. Exception Handling:

    • Nim: Exception handling constructs like try and except are not usually categorized as symbols in LSP.
  8. Modules:

    • Nim: Modules are usually mapped to Module in LSP.
  9. Templates (Generics):

    • Nim: Template/generic constructs can be categorized as TypeParameter in LSP.
  10. Custom Types:

    • Nim: Custom types like enums and objects can be categorized based on their context. Enums are often mapped to Enum, while objects may be mapped to Object.
  11. Case Blocks:

    • Nim: Case blocks do not have a direct mapping to SymbolKind but may be categorized as Object, Enum, or Class depending on the context.

I've made some guesses here since the categorization is not always straightforward; Nim is a flexible language with various constructs that may not align perfectly with LSP's SymbolKind categories.

I imagine looking at https://github.com/PMunch/nimlsp and/or may help.

stevearc commented 8 months ago

I will put this on my backlog, but I would review a PR faster than I will get to doing it myself. #273 is a pretty good template for what needs to be done to add support for a new language.

levinotik commented 8 months ago

@stevearc For sure. I'll try my hand at it!

Slotos commented 7 months ago

@levinotik can you check if the following set of rules results in a reasonable outline?

after/queries/nim/aerial.scm:

;; extends

(const_section
  (variable_declaration
    (symbol_declaration_list
      (symbol_declaration
        name: (_) @name)
      )
    ) @symbol
  (#set! "kind" "Constant"))

(let_section
  (variable_declaration
    (symbol_declaration_list
      (symbol_declaration
        name: (_) @name
        )
      )
    ) @symbol
  (#set! "kind" "Variable"))

(var_section
  (variable_declaration
    (symbol_declaration_list
      (symbol_declaration
        name: (_) @name
        )
      )
    ) @symbol
  (#set! "kind" "Variable"))

((proc_declaration
   name: (_) @name) @symbol
 (#set! "kind" "Function"))

((template_declaration
   name: (_) @name) @symbol
 (#set! "kind" "Constructor"))

; A version that doesn't show type in the outline
; ((parameter_declaration
;    (symbol_declaration_list
;      (symbol_declaration
;        name: (_) @name
;        )
;      )
;    ) @symbol (#set! "kind" "Property"))

((parameter_declaration) @name @symbol (#set! "kind" "Property"))

(generic_parameter_list
  (parameter_declaration
    (symbol_declaration_list
      (symbol_declaration) @name @symbol)
    (#set! "kind" "TypeParameter"))
  )

(import_statement
  (expression_list
    (_) @name @symbol
    (#set! "kind" "Package"))
  )

(include_statement
  (expression_list
    (_) @name @symbol
    (#set! "kind" "Package"))
  )

((type_declaration
   (type_symbol_declaration
     name: (_) @name)
   (enum_declaration)
   ) @symbol
 (#set! "kind" "Enum"))

((type_declaration
   (type_symbol_declaration
     name: (_) @name)
   (object_declaration)
   ) @symbol
 (#set! "kind" "Class"))

(enum_field_declaration
  (symbol_declaration
    name: (_) @name) @symbol
  (#set! "kind" "EnumMember"))

((while
   condition: (_) @name
   (#gsub! @name "^" "while ")
   ) @symbol
 (#set! "kind" "Boolean"))

((for
   left: (_) @name
   (#gsub! @name "^" "for ")
   ) @symbol
 (#set! "kind" "Boolean"))

((when
   condition: (_) @name
   ) @symbol
 (#gsub! @name "^" "when ")
 (#set! "kind" "Boolean"))

((if
   condition: (_) @name
   ) @symbol
 (#gsub! @name "^" "if ")
 (#set! "kind" "Boolean"))

(_
  alternative: (elif_branch
                 condition: (_) @name
                 ) @symbol
  (#gsub! @name "^" "elif ")
  (#set! "kind" "Boolean"))

(_
  alternative: (else_branch) @name @symbol
  (#set! @name "text" "else")
  (#set! "kind" "Boolean"))

((case
   value: (_) @name
   (#gsub! @name "^" "case ")
   ) @symbol
 (#set! "kind" "Enum"))

(case
  (of_branch
    values: (_) @name
    ) @symbol
  (#set! "kind" "EnumMember")
  (#gsub! @name "^" "of "))

(case
  (else_branch) @symbol @name
  (#set! "kind" "EnumMember")
  (#set! @name "text" "else"))

((call
   function: (_) @name
   (argument_list
     (statement_list))
   ) @symbol
 (#set! "kind" "Interface"))

I've been testing it against nimlsp's source code. It's not truly honest — conditionals are not booleans, for example, — but if the goal is to have a (too) detailed code outline, it should do the job.

Overall, main nym's syntax tree is very easy to work with. Find the node you're interested in, descend, enjoy.

levinotik commented 7 months ago

Thanks very much @Slotos My apologies for the delayed response; I'm just seeing this message now. I'll give this a try as soon as I can and report back. Thanks so much for this.