stevearc / aerial.nvim

Neovim plugin for a code outline window
MIT License
1.78k stars 88 forks source link

feature request: Kotlin support #427

Open bqv opened 1 week ago

bqv commented 1 week ago

Language: Kotlin

package org.javacs.kt

import java.io.PrintWriter
import java.io.StringWriter
import java.util.*
import java.util.logging.Formatter
import java.util.logging.LogRecord
import java.util.logging.Handler
import java.util.logging.Level
import java.time.Instant

internal val LOG = object : Logger() {
  override fun toString(): String = "logger"
}

protected class Demo<T> private constructor() : Collection<T> {
  init {
    LOG.toString()
  }

  private inline fun <reified S> Iterable<T>.something(callback: S.() -> Unit) {
    TODO()
    throw Exception("unreachable")
  }
}

enum class LogLevel(val value: Int) {
  NONE(100),
  ERROR(2),
  WARN(1),
  INFO(0),
  DEBUG(-1),
  TRACE(-2),
  DEEP_TRACE(-3),
  ALL(-100)
}

class Logger {
  class LogMessage(
    val level: LogLevel,
    val message: String
  ) {
    val formatted: String
      get() = "[$level] $message"
  }

  private var outBackend: ((LogMessage) -> Unit)? = null
  private var errBackend: ((LogMessage) -> Unit)? = null
  private val outQueue: Queue<LogMessage> = ArrayDeque()
  private val errQueue: Queue<LogMessage> = ArrayDeque()

  private val newline = System.lineSeparator()
  val logTime = false
  var level = LogLevel.INFO

  private fun outputError(msg: LogMessage) {
    if (errBackend == null) {
      errQueue.offer(msg)
    } else {
      errBackend?.invoke(msg)
    }
  }

  private fun output(msg: LogMessage) {
    if (outBackend == null) {
      outQueue.offer(msg)
    } else {
      outBackend?.invoke(msg)
    }
  }

  private fun log(msgLevel: LogLevel, msg: String, placeholders: Array<out Any?>) {
    if (level.value <= msgLevel.value) {
      output(LogMessage(msgLevel, format(insertPlaceholders(msg, placeholders))))
    }
  }

  suspend fun error(msg: String, vararg placeholders: Any?) = log(LogLevel.ERROR, msg, placeholders)

  suspend fun warn(msg: String, vararg placeholders: Any?) = log(LogLevel.WARN, msg, placeholders)

  suspend fun info(msg: String, vararg placeholders: Any?) = log(LogLevel.INFO, msg, placeholders)

  suspend fun debug(msg: String, vararg placeholders: Any?) = log(LogLevel.DEBUG, msg, placeholders)

  suspend fun trace(msg: String, vararg placeholders: Any?) = log(LogLevel.TRACE, msg, placeholders)

  suspend fun deepTrace(msg: String, vararg placeholders: Any?) = log(LogLevel.DEEP_TRACE, msg, placeholders)

  fun connectJULFrontend() {
    class JULRedirector(private val downstream: Logger): Handler() {
      override fun publish(record: LogRecord) {
        when (record.level) {
          Level.SEVERE -> downstream.error(record.message)
          Level.WARNING -> downstream.warn(record.message)
          Level.INFO -> downstream.info(record.message)
          Level.CONFIG -> downstream.debug(record.message)
          Level.FINE -> downstream.trace(record.message)
          else -> downstream.deepTrace(record.message)
        }
      }

      override fun flush() {}

      override fun close() {}
    }

    val rootLogger = java.util.logging.Logger.getLogger("").also {
      it.addHandler(JULRedirector(this))
    }
  }

  fun connectOutputBackend(outBackend: (LogMessage) -> Unit) {
    this.outBackend = outBackend
    flushOutQueue()
  }

  fun connectErrorBackend(errBackend: (LogMessage) -> Unit) {
    this.errBackend = errBackend
    flushErrQueue()
  }

  fun connectStdioBackend() {
    connectOutputBackend { println(it.formatted) }
    connectOutputBackend { System.err.println(it.formatted) }
  }

  private fun insertPlaceholders(msg: String, placeholders: Array<out Any?>): String {
    val msgLength = msg.length
    val lastIndex = msgLength - 1
    var charIndex = 0
    var placeholderIndex = 0
    var result = StringBuilder()

    while (charIndex < msgLength) {
      val currentChar = msg.get(charIndex)
      val nextChar = if (charIndex != lastIndex) msg.get(charIndex + 1) else '?'
      if ((currentChar == '{') && (nextChar == '}')) {
        if (placeholderIndex >= placeholders.size) {
          return "ERROR: Tried to log more '{}' placeholders than there are values"
        }
        result.append(placeholders[placeholderIndex] ?: "null")
        placeholderIndex += 1
        charIndex += 2
      } else {
        result.append(currentChar)
        charIndex += 1
      }
    }

    return result.toString()
  }

  private fun flushOutQueue() {
    while (outQueue.isNotEmpty()) {
      outBackend?.invoke(outQueue.poll())
    }
  }

  private fun flushErrQueue() {
    while (errQueue.isNotEmpty()) {
      errBackend?.invoke(errQueue.poll())
    }
  }

  private fun format(msg: String): String {
    val time = if (logTime) "${Instant.now()} " else ""
    var thread = Thread.currentThread().name

    return time + shortenOrPad(thread, 10) + msg
  }

  private fun shortenOrPad(str: String, length: Int): String =
    if (str.length <= length) {
      str.padEnd(length, ' ')
    } else {
      ".." + str.substring(str.length - length + 2)
    }
}
bqv commented 17 hours ago

My attempt:

(source_file
  (#set! "kind" "File")) @symbol

; Package
(package_header
  (identifier) @name
  (#set! "kind" "Package")) @symbol

; Enums
(class_declaration
  (type_identifier) @name
  (enum_class_body)
  (#set! "kind" "Enum")) @symbol
(enum_entry
  (simple_identifier) @name
(#set! "kind" "EnumMember")) @symbol

; Classes
(class_declaration
  (type_identifier) @name
  (#set! "kind" "Class")) @symbol

(object_declaration
  (type_identifier) @name
  (#set! "kind" "Object")) @symbol

; Functions
(function_declaration
  (simple_identifier) @name
  (#set! "kind" "Function")) @symbol
(anonymous_initializer
  (#offset! @name 0 0 0 4)
  (#set! "kind" "Constructor")) @symbol
(secondary_constructor
  (#offset! @name 0 1 0 -1)
  (#set! "kind" "Constructor")) @symbol

; Properties
(property_declaration
  (variable_declaration
    (simple_identifier) @name
  )
  (#set! "kind" "Property")) @symbol
(getter
  (#offset! @name 0 1 0 -1)
  (#set! "kind" "Method")) @symbol
(setter
  (#offset! @name 0 1 0 -1)
  (#set! "kind" "Method")) @symbol

; Blocks
(call_expression
  (call_expression
    (simple_identifier) @name
  )
  (call_suffix
    (annotated_lambda)
  )
  (#set! "kind" "Namespace")) @symbol
(call_expression
  (simple_identifier) @name
  (call_suffix
    (annotated_lambda)
  )
  (#set! "kind" "Namespace")) @symbol
(call_expression
  (navigation_expression
    (navigation_suffix
      (simple_identifier) @name
    )
  )
  (call_suffix
    (annotated_lambda)
  )
  (#set! "kind" "Namespace")) @symbol

(boolean_literal
  (#set! "kind" "Boolean")) @symbol
(string_literal
  (#set! "kind" "String")) @symbol
(integer_literal
  (#set! "kind" "Number")) @symbol