MopeSWTP-SS21 / MopeSWTP

MIT License
1 stars 0 forks source link

As Modelica programmer, I can receive simple code completions while typing in an Editor with LSP-plugin support, so I can avoid typos and quickly recall the structure of a package or class without opening it #21

Open CSchoel opened 3 years ago

CSchoel commented 3 years ago

Code completion should be possible in the following scenarios:

The following points are optional, but would make code completion much more effective:

CSchoel commented 3 years ago

Since I cannot find a separate issue for code-completion of class names as sub-issue of #39, I think this is the best place to post my ideas for the "filename-to-class-list" algorithm.

Since I want to leave the implementation to you, I will just briefly draft my idea as pseudocode. The algorithm is based on the following observations/assumptions:

  1. There are two mechanisms to load packages in the OpenModelica Scripting API
    • loadModel() loads models based on a mapping between class names and file names. A class A.B.C can be defined in a file A/B/C.mo, A/B.mo/A/B/package.mo, or A.mo/A/package.mo, depending on how much of the structure of the package is embedded within the package.mo files and if the enclosing structure is a package (usually defined in a package.mo) or another kind of class like a model (usually defined in a file ClassName.mo).
    • loadFile() foregoes the whole mapping idea and instead assumes that the given file name includes an arbitrary number of toplevel class definitions. It is, for example, not possible to load a file A/B/C.mo, which is part of a larger package structure, with this function, because it is unaware of the fact that this file might require definitions in A/package.mo or any other classes within the whole package structure.
  2. We can assume that Mo|E will be mostly used for the development of Modelica packages, so loadModel() is more important. The loadFile() structure should also be supported somewhere along the line, but can be considered optional for now.
  3. I assume that we do not know what the "main folder" of the project is. We could add this information in a project configuration file, which would make things a little easier. But for now, I want to provide an Algorithm that works without that information.

And here is my proposed algorithm:

algorithm fileNameToClassList($filename)
    return OMC.getClassNames(fileNameToClassName($filename))

algorithm fileNameToClassName($filename)
    // remove prefix from $filename that is not part of the project folder
    // this is not necessary, if we know that $filename is relative to the main project folder
    $relevant := basename($filename)
    $remaining := dirname($filename)
    while file "package.mo" exists in directory $remaining do
        remove basename($remaining) from $remaining and add it to $relevant

    // remove trailing [package].mo
    if endsWith($relevant, "package.mo") then
        $relevant := dirname($relevant)
    else
        $relevant := $relevant.substring(0, $relevant.length - 3)

    return replace file separators in $relevant with '.'

If you want to be more fancy, you can also parse the within definition in the file, which must exist if it is part of a larger package structure:

algorithm fileNameToClassName($filename)
    $within = first match of regex /^\s*within\s+(.*);/ in content of $filename
    $prefix = ""
    if $within is a match then
        $prefix = content of capture group 1 of match $within (w/o trailing whitespace)

    if basename($filename) = "package.mo" then
        $classnname = basename(dirname($filename))
    else
        $classname = basename($filename)
        $classname = $classname.substring(0, $classname.length - 3)

    return $prefix + "." + $classname

A third option could be to parse the MODELICAPATH and remove the longest matching prefix between an entry in the MODELICAPATH and the (absolute) filename in question. I think there is not much of a trade-off between any of these solutions. As a first instinct, I would go with the first one (look for "package.mo" files), as it seems easy to implement and should be quite robust.

CSchoel commented 3 years ago

I have now also updated MopeSWTP-SS21/vs-code-client and MopeSWTP-SS21/LSP4J-test-CS so that you have an example how code completions work with LSP4J. The server should signal its completion capabilities like this:

CompletionOptions comp = new CompletionOptions();
comp.setTriggerCharacters(List.of("."));
comp.setResolveProvider(false);
cap.setCompletionProvider(comp);

With this setting, the VS Code client sends completion requests both for any changes in a Modelica file and especially when the user types a dot. On the server side, you can distinguish between the two types by calling getContext().getTriggerKind() on the CompletionParams parameter in your implementation of completion(). On the client side, completions are only shown when the trigger character (dot) has been typed.

I am not perfectly sure how you can get the text before the cursor. Currently, I suspect that you have to keep track of changes in the document yourself by listening to document/didChange. Maybe you can find a better way.