~hristoast/company-openmw-lua

6b8b59148d5a8019cc423b41bf31fd1ef46da701 — Hristos N. Triantafillou 3 days ago 84c70db
Smarter completions

Only offer completions that are valid for the given package, or offer
top-level package names as candidates if there's no package found.

This makes some assumptions about what package names will be stored
as, but that's okay I think.
3 files changed, 201 insertions(+), 96 deletions(-)

M company-openmw-lua-keywords.el
M company-openmw-lua.el
M generate-keywords.py
M company-openmw-lua-keywords.el => company-openmw-lua-keywords.el +61 -19
@@ 1,30 1,34 @@
;;; company-openmw-lua.el --- A company back-end for OpenMW-Lua
;;; company-openmw-lua-keywords.el --- A company back-end for OpenMW-Lua

;;; Commentary:
;; Generated automatically - don't modify this file manually.

;;; Code:

(defconst company-openmw-lua-keywords
(defconst company-openmw-lua-package-name-keywords
  '(
    ("async" "`openmw.async` contains timers and coroutine utils. All functions require")
    ("core" "`openmw.core` defines functions and types that are available in both local")
    ("nearby" "`openmw.nearby` provides read-only access to the nearest area of the game world.")
    ("query" "`openmw.query` constructs queries that can be used in `world.selectObjects` and `nearby.selectObjects`.")
    ("self" "`openmw.self` provides full access to the object the script is attached to.")
    ("ui" "`openmw.ui` controls user interface.")
    ("util" "`openmw.util` defines utility functions and classes like 3D vectors, that don't depend on the game world.")
    ("clamp" "Limits given value to the interval [`from`, `to`]")
    ("normalizeAngle" "Adds `2pi*k` and puts the angle in range `[-pi, pi]`")
    ("vector2" "Creates a new 2D vector. Vectors are immutable and can not be changed after creation.")
    ("length" "Length of the vector")
    ("length2" "Square of the length of the vector")
    ("normalize" "Returns two values: normalized vector and the length of the original vector. It doesn't change the original vector. ")
    ("rotate" "Rotates 2D vector clockwise.")
    ("dot" "Dot product.")
    ("vector3" "Creates a new 3D vector. Vectors are immutable and can not be changed after creation.")
    ("cross" "Cross product.")
    ("async" "`openmw.async` contains timers and coroutine utils. All functions requirethe package itself as a first argument.")
    ("world" "`openmw.world` is an interface to the game world for global scripts.")
    ("aux_util" "`openmw_aux.util` defines utility functions that are implemented in Lua rather than in C++.")
))

(defconst company-openmw-lua-async-keywords
  '(
    ("registerTimerCallback" "Register a function as a timer callback.")
    ("newTimerInSeconds" "Calls callback(arg) in `delay` seconds. Callback must be registered in advance.")
    ("newTimerInHours" "Calls callback(arg) in `delay` game hours. Callback must be registered in advance.")
    ("newUnsavableTimerInSeconds" "Calls `func()` in `delay` seconds. The timer will be lost if the game is saved and loaded.")
    ("newUnsavableTimerInHours" "Calls `func()` in `delay` game hours. The timer will be lost if the game is saved and loaded.")
    ("core" "`openmw.core` defines functions and types that are available in both localand global scripts.")
))

(defconst company-openmw-lua-core-keywords
  '(
    ("sendGlobalEvent" "Send an event to global scripts.")
    ("getGameTimeInSeconds" "Game time in seconds. The number of seconds in the game world, passed from starting a new game.")
    ("getGameTimeInHours" "Current time of the game world in hours. Note that the number of game seconds in a game hour is not guaranteed to be fixed.")


@@ 56,9 60,15 @@
    ("getProbes" "Get all probes.")
    ("getRepairKits" "Get all repair kits.")
    ("getWeapons" "Get all weapon.")
    ("nearby" "`openmw.nearby` provides read-only access to the nearest area of the game world.Can be used only from local scripts.")
))

(defconst company-openmw-lua-nearby-keywords
  '(
    ("selectObjects" "Evaluates a Query.")
    ("query" "`openmw.query` constructs queries that can be used in `world.selectObjects` and `nearby.selectObjects`.")
))

(defconst company-openmw-lua-query-keywords
  '(
    ("where" "Add condition.")
    ("limit" "Limit result size.")
    ("eq" "Equal")


@@ 67,20 77,52 @@
    ("lte" "Lesser or equal")
    ("gt" "Greater")
    ("gte" "Greater or equal")
    ("self" "`openmw.self` provides full access to the object the script is attached to.Can be used only from local scripts. All fields and function of `GameObject` are also available for `openmw.self`.")
))

(defconst company-openmw-lua-self-keywords
  '(
    ("isActive" "Returns true if the script isActive (the object it is attached to is in an active cell). If it is not active, then `openmw.nearby` can not be used.")
    ("setDirectControl" "Enables or disables direct movement control (disabled by default).")
    ("enableAI" "Enables or disables standart AI (enabled by default).")
    ("getCombatTarget" "Returns current target or nil if not in combat")
    ("stopCombat" "Remove all combat packages from the actor.")
    ("startCombat" "Attack `target`.")
    ("ui" "`openmw.ui` controls user interface.Can be used only by local scripts, that are attached to a player.")
))

(defconst company-openmw-lua-ui-keywords
  '(
    ("showMessage" "Shows given message at the bottom of the screen.")
    ("world" "`openmw.world` is an interface to the game world for global scripts.Can not be used from local scripts.")
))

(defconst company-openmw-lua-util-keywords
  '(
    ("clamp" "Limits given value to the interval [`from`, `to`]")
    ("normalizeAngle" "Adds `2pi*k` and puts the angle in range `[-pi, pi]`")
    ("vector2" "Creates a new 2D vector. Vectors are immutable and can not be changed after creation.")
    ("length" "Length of the vector")
    ("length2" "Square of the length of the vector")
    ("normalize" "Returns two values: normalized vector and the length of the original vector. It doesn't change the original vector. ")
    ("rotate" "Rotates 2D vector clockwise.")
    ("dot" "Dot product.")
    ("vector3" "Creates a new 3D vector. Vectors are immutable and can not be changed after creation.")
    ("cross" "Cross product.")
))

(defconst company-openmw-lua-world-keywords
  '(
    ("selectObjects" "Evaluates a Query.")
    ("getCellByName" "Loads a named cell")
    ("getExteriorCell" "Loads an exterior cell by grid indices")
))

(defconst company-openmw-lua-aux_util-keywords
  '(
    ("findNearestTo" "Ignores cells, uses only coordinates. Returns the nearest object, and the distance to it. If objectList is empty, returns nil.")
    ("runEveryNSeconds" "Note that loading a save stops the evaluation. If it should work always, call it in 2 places -- when a script starts and in the engine handler `onLoad`.")
    ("runEveryNHours" "Note that loading a save stops the evaluation. If it should work always, call it in 2 places -- when a script starts and in the engine handler `onLoad`.")
))


(provide 'company-openmw-lua-keywords)
;;; company-openmw-lua-keywords.el ends here


M company-openmw-lua.el => company-openmw-lua.el +58 -10
@@ 14,34 14,82 @@
(declare-function company-grab-symbol-cons "company.el")

(defun company-openmw-lua--make-candidate (candidate)
  "TODO CANDIDATE."
  "Prepare CANDIDATE for printing."
  (let ((text (car candidate))
        (meta (cadr candidate)))
    (propertize text 'meta meta)))


(defun company-openmw-lua--methods-for-pkg (pkg-name)
  "Return all methods for PKG-NAME, if any.  It's entirely possible this can be done in a more lispey, idiomatic way.."
  (if (string-equal pkg-name "async")
      company-openmw-lua-async-keywords
    (if (string-equal pkg-name "core")
        company-openmw-lua-core-keywords
      (if (string-equal pkg-name "nearby")
          company-openmw-lua-nearby-keywords
        (if (string-equal pkg-name "query")
            company-openmw-lua-query-keywords
          (if (string-equal pkg-name "self")
              company-openmw-lua-self-keywords
            (if (string-equal pkg-name "ui")
                company-openmw-lua-ui-keywords
              (if (string-equal pkg-name "util")
                  company-openmw-lua-util-keywords
                (if (string-equal pkg-name "world")
                    company-openmw-lua-world-keywords
                  (if (string-equal pkg-name "aux_util")
                      company-openmw-lua-aux_util-keywords))))))))))


(defun company-openmw-lua--candidates (prefix)
  "TODO PREFIX."
  (let (res)
    (dolist (item company-openmw-lua-keywords)
      (when (string-prefix-p prefix (car item))
        (push (company-openmw-lua--make-candidate item) res)))
  "Given PREFIX, return all valid completion candidates."
  (let ((res)
        (line (company-grab-line "^.*")))
    (if (string-match-p (regexp-quote ":") line)
        (progn
          ;; Match :
          (dolist (item (company-openmw-lua--methods-for-pkg
                         (car (last (butlast (split-string line ":"))))))
            (when (string-prefix-p prefix (car item))
              (push (company-openmw-lua--make-candidate item) res)))
          )
      (if (string-match-p (regexp-quote "\.") line)
          (progn
          ;; Match .
            (dolist (item (company-openmw-lua--methods-for-pkg
                           (car (last (butlast (split-string line "\\."))))))
              (when (string-prefix-p prefix (car item))
                (push (company-openmw-lua--make-candidate item) res))))

        (progn
          ;; Match nothin'; try to match packages/top-level
          (dolist (item company-openmw-lua-package-name-keywords)
            (when (string-prefix-p prefix (car item))
              (push (company-openmw-lua--make-candidate item) res))))))

    res))

(defun company-openmw-lua--meta (candidate)
  "TODO CANDIDATE."
  "Format string for CANDIDATE."
  (format "%s" (substring-no-properties candidate)))

(defun company-openmw-lua--annotation (candidate)
  "TODO (change this) CANDIDATE."
  "Format annotation string CANDIDATE."
  (format " %s" (get-text-property 0 'meta candidate)))

(defun company-openmw-lua--prefix ()
  "How to grab the prefix."
  (company-grab-symbol-cons "\\.\\|:" 2))


;;;###autoload
(defun company-openmw-lua (command &optional arg &rest ignored)
  "TODO COMMAND ARG IGNORED."
  "Given COMMAND, ARG, and IGNORED, do autocompletions for the OpenMW-Lua API."
  (interactive (list 'interactive))
  (cl-case command
    (interactive (company-begin-backend 'company-openmw-lua))
    (prefix (company-grab-symbol-cons "\\.\\|:" 2))
    (prefix (company-openmw-lua--prefix))
    (candidates (company-openmw-lua--candidates arg))
    (annotation (company-openmw-lua--annotation arg))
    (meta (company-openmw-lua--meta arg))))

M generate-keywords.py => generate-keywords.py +82 -67
@@ 2,86 2,101 @@
import os


OPENMW_LUA_API_FILES = (
    "/opt/build-openmw/src/openmw/files/builtin_scripts/openmw_aux/util.lua",
    "/opt/build-openmw/src/openmw/files/lua_api/openmw/async.lua",
    "/opt/build-openmw/src/openmw/files/lua_api/openmw/core.lua",
    "/opt/build-openmw/src/openmw/files/lua_api/openmw/nearby.lua",
    "/opt/build-openmw/src/openmw/files/lua_api/openmw/query.lua",
    "/opt/build-openmw/src/openmw/files/lua_api/openmw/self.lua",
    "/opt/build-openmw/src/openmw/files/lua_api/openmw/ui.lua",
    "/opt/build-openmw/src/openmw/files/lua_api/openmw/util.lua",
    "/opt/build-openmw/src/openmw/files/lua_api/openmw/world.lua",
)


if __name__ == "__main__":
    symbols = {}
    for f in OPENMW_LUA_API_FILES:
        package_docstring = ""
        package_name = f.split(os.path.sep)[-1].replace(".lua", "")
        package_symbols = {}

OPENMW_LUA_API_FILES = {
    "async": "/opt/build-openmw/src/openmw/files/lua_api/openmw/async.lua",
    "core": "/opt/build-openmw/src/openmw/files/lua_api/openmw/core.lua",
    "nearby": "/opt/build-openmw/src/openmw/files/lua_api/openmw/nearby.lua",
    "query": "/opt/build-openmw/src/openmw/files/lua_api/openmw/query.lua",
    "self": "/opt/build-openmw/src/openmw/files/lua_api/openmw/self.lua",
    "ui": "/opt/build-openmw/src/openmw/files/lua_api/openmw/ui.lua",
    "util": "/opt/build-openmw/src/openmw/files/lua_api/openmw/util.lua",
    "world": "/opt/build-openmw/src/openmw/files/lua_api/openmw/world.lua",
    "aux_util": "/opt/build-openmw/src/openmw/files/builtin_scripts/openmw_aux/util.lua",
}


def package_names_sexp() -> str:
    s = """(defconst company-openmw-lua-package-name-keywords
  '(
"""
    for package_name, f in OPENMW_LUA_API_FILES.items():
        if os.path.exists(f) and os.path.isfile(f):
            package_docstring = ""
            with open(f, "r") as of:
                lines = of.readlines()

                count = 0
                lines = of.readlines()
                for line in lines:
                    if count < 5:
                        if line.startswith("-- ") and not line.startswith("-- @"):
                        if (
                            line.startswith("-- ")
                            and not line.startswith("-- @")
                            and package_docstring == ""
                        ):
                            package_docstring += line.rstrip("\n").replace("-- ", "")
            s += '    ("{0}" "{1}")\n'.format(package_name, package_docstring)

                    l = line.rstrip("\n")
                    if l.startswith("-- @function "):
                        docscount = count - 1
                        docs = ""
                        if docscount < len(lines):
                            if l.startswith("-- "):
                                docs = lines[docscount].rstrip("\n").replace("-- ", "")
                                if docs.startswith("--") or docs.startswith(
                                    "@function "
                                ):
                                    docs = ""
                                if not lines[docscount - 1].startswith("----"):
                                    docs = (
                                        lines[docscount - 1]
                                        .rstrip("\n")
                                        .replace("-- ", "")
                                        + " "
                                        + docs
                                    )
                        package_symbols.update({l.split(" ")[-1]: docs})
                    count += 1
        else:
            # "OPENMW INSTALLATION IS MISSING FILES!!"
            return ""
    s += "))"
    return s

            symbols.update(
                {
                    package_name: {
                        "docstring": package_docstring,
                        "symbols": package_symbols,
                    }
                }
            )

        else:
            print("Missing file: " + f)
def methods_by_package(pkg: str) -> str:
    s = """(defconst company-openmw-lua-{}-keywords
  '(
""".format(
        pkg
    )

    symbols = {}
    with open(OPENMW_LUA_API_FILES[pkg], "r") as of:
        lines = of.readlines()
        count = 0
        for line in lines:
            l = line.rstrip("\n")
            if l.startswith("-- @function"):
                docscount = count - 1
                docs = ""
                if docscount < len(lines):
                    if l.startswith("-- "):
                        docs = lines[docscount].rstrip("\n").replace("-- ", "")
                        if docs.startswith("--") or docs.startswith("@function "):
                            docs = ""  # TODO: dont do this
                        if not lines[docscount - 1].startswith("----"):
                            docs = (
                                lines[docscount - 1].rstrip("\n").replace("-- ", "")
                                + " "
                                + docs
                            )
                symbols.update({l.split(" ")[-1]: docs})
            count += 1

    for sym, doc in symbols.items():
        s += '    ("{0}" "{1}")\n'.format(sym, doc)

    s += "))"
    return s

    lisp = """;;; company-openmw-lua.el --- A company back-end for OpenMW-Lua

if __name__ == "__main__":
    print(
        """;;; company-openmw-lua-keywords.el --- A company back-end for OpenMW-Lua

;;; Commentary:
;; Generated automatically - don't modify this file manually.

;;; Code:

(defconst company-openmw-lua-keywords
  '(
"""

    for pkg in symbols.keys():
        lisp += '    ("{0}" "{1}")\n'.format(pkg, symbols[pkg]["docstring"])
        for k, v in symbols[pkg]["symbols"].items():
            lisp += '    ("{0}" "{1}")\n'.format(k, v)

    lisp += "))\n\n(provide 'company-openmw-lua-keywords)\n;;; company-openmw-lua-keywords.el ends here"

    print(lisp)
    )
    print(package_names_sexp())
    print()
    for pkg in OPENMW_LUA_API_FILES.keys():
        print(methods_by_package(pkg))
        print()
    print(
        """
(provide 'company-openmw-lua-keywords)
;;; company-openmw-lua-keywords.el ends here
"""
    )