Open CSchoel opened 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:
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.loadModel()
is more important. The loadFile()
structure should also be supported somewhere along the line, but can be considered optional for now.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.
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.
Code completion should be possible in the following scenarios:
Modelica.Electrical.An...
, a list of classes within that name space that have the same prefix should be suggested. In this example,Modelica.Electrical.Analog
should be the only recommendation remaining.import Modelica.Electrical;
, typingElec
should yield the suggestionElectrical
.import Modelica.*;
, typingMa
should yield the suggestionsMagnetic
andMath
.The following points are optional, but would make code completion much more effective:
VIN
within the model file forModelica.Electrical.Analog.Examples.NandGate
, the suggestionsVIN1
andVIN2
should be made, but when I typeVIN1.st
, the suggestion should beVIN1.startTime
instead.|
) is in the following positionModelica.Electrical.Analog.Basic.Resistor r(us|)
, the suggestion should beuseHeatPort
.