Project

General

Profile

Feature #11741

implement a fully featured Language Server Protocol (LSP) server for 4GL code

Added by Greg Shah about 1 month ago. Updated 1 day ago.

Status:
WIP
Priority:
Normal
Target version:
-
Start date:
Due date:
% Done:

10%

billable:
No
vendor_id:
GCD
case_num:
version_reported:
version_resolved:
reviewer:
production:
No
env_name:
topics:

protocol_traffic.png (104 KB) Paula Păstrăguș, 09/11/2026 09:19 AM

Screencast from 09-14-2026 03_54_43 PM.webm (13.7 MB) Paula Păstrăguș, 09/14/2026 09:02 AM

Screencast from 09-16-2026 05_32_36 PM.webm (11.9 MB) Paula Păstrăguș, 09/16/2026 11:02 AM

case2.png (12.4 KB) Paula Păstrăguș, 09/18/2026 10:16 AM

case3.png (8.75 KB) Paula Păstrăguș, 09/18/2026 10:16 AM

case1.png (20.4 KB) Paula Păstrăguș, 09/18/2026 10:16 AM

Screencast from 09-21-2026 06_12_01 PM.webm (8.28 MB) Paula Păstrăguș, 09/21/2026 11:26 AM


Related issues

Related to Conversion Tools - Feature #3882: changes to allow the front-end to be used for 4GL syntax checking New
Related to Conversion Tools - Feature #3883: eclipse plug for developing 4GL code using FWD including editing, syntax checks, running conversion Pending
Related to Conversion Tools - Feature #11355: VSCode extension New
Related to Conversion Tools - Feature #11742: implement a fully featured Debug Adapter Protocol (DAP) server for 4GL code (in FWD) New
Related to Conversion Tools - Support #9919: discuss and define the MVP for the first pass at IDE support WIP

History

#1 Updated by Greg Shah about 1 month ago

  • Related to Feature #3882: changes to allow the front-end to be used for 4GL syntax checking added

#2 Updated by Greg Shah about 1 month ago

  • Related to Feature #3883: eclipse plug for developing 4GL code using FWD including editing, syntax checks, running conversion added

#3 Updated by Greg Shah about 1 month ago

#4 Updated by Greg Shah about 1 month ago

The Language Server Protocol (LSP) is a platform independent protocol for creating language support that can work across various IDEs (VSCode, Eclipse IDE, Eclipse Che, vim...).

It is an open standard, originally created by Microsoft for their internal use and then made more generic through work with Eclipse, IBM and Red Hat.

Why You Should Know the Language Server Protocol

VSCode only works with LSP. Building the LSP is the first step in implementing the full extension in #11355.

#5 Updated by Greg Shah about 1 month ago

  • Subject changed from implement a fully featured LSP for 4GL code to implement a fully featured Language Server Protocol (LSP) server for 4GL code

#6 Updated by Greg Shah about 1 month ago

  • Related to Feature #11742: implement a fully featured Debug Adapter Protocol (DAP) server for 4GL code (in FWD) added

#8 Updated by Greg Shah 12 days ago

  • Assignee set to Paula Păstrăguș

#9 Updated by Greg Shah 12 days ago

  • Related to Support #9919: discuss and define the MVP for the first pass at IDE support added

#10 Updated by Greg Shah 12 days ago

A good first step on this task would be to analyis technical approaches, particularly whether or not we should use something like LSP4J as a framework to reduce the effort on writing the LSP. One alternative is to write all the support ourselves, though my instict says that there should be some real value in a framework that handles the guts of the protocol.

  • The project needs to be open source and licensed in a manner that we can safely combine with our GPLv3 project. Permissive licenses like MIT, BSD or Apache 2.0 are all OK.
  • Is the project active?
  • How well supported is the project?
  • How mature is the project? (has it been stable for multiple years, is it used in well known projects)
  • Are there any critical technical limitations when implementing using this framework?

That last item is the biggest long term concern. If we can't achieve our objectives using it or if it will cause us to do a lot of unnatural implementation to get around its limits, then it isn't a good choice.

Post the analysis here and we can discuss.

#11 Updated by Greg Shah 12 days ago

Once we have that decision, the next step is to plan the implementation of the LSP itself.

#12 Updated by Paula Păstrăguș 12 days ago

Here is the framework analysis for #11741-10.

What we would be choosing is a binding: a library that handles the protocol layer, the JSON-RPC messages the editor and our server exchange over a pipe, plus the framing, request/response matching and cancellation that go with them, and gives us the protocol's ~300 message types as Java classes. It handles none of the 4GL. That part is ours either way.

The important result of the survey is that there is no real field to compare. Every JVM option is either LSP4J itself or something built on top of LSP4J:

Candidate License Status Assessment
LSP4J EPL-2.0 OR BSD-3-Clause v1.0.0 released 2026-02-10; last commit 2026-07-09 The only real candidate. Covers LSP 3.18.0. Used by the Eclipse JDT Language Server — the server behind Red Hat's Java extension for VS Code, which is the closest existing analogue to what we are building — and by Scala Metals, Apache Camel LS and Eclipse LemMinX
ls-api None declared Archived since 2016 The only binding that was ever an independent alternative. Its repository description is now literally "MOVED TO" and points at LSP4J: it did not lose to LSP4J, it became LSP4J
Xtext (from #9919-2) EPL-2.0 only Active Fails the license test. Also expects the language to be re-expressed as an Xtext grammar, and its LSP support is built on LSP4J anyway
MagpieBridge EPL-2.0 only Last commit 2025-07-14 Fails the license test. Built for surfacing an existing static analysis's findings in an editor, which is close to our diagnostics case, but it pins LSP4J 0.14.0 from 2022 and pulls in WALA, JavaParser and JGit
Generic JSON-RPC libraries, e.g. jsonrpc4j MIT Active Gives the JSON-RPC envelope but not LSP's own framing and none of the protocol's types. More work than LSP4J for a worse result
Writing it ourselves n/a n/a No license risk, no capability gained, and a protocol implementation to maintain forever

There are good bindings outside the JVM, Microsoft's own in TypeScript, pygls in Python, tower-lsp in Rust, and they are all ruled out by the same thing: our server has to call the conversion front end in-process. Using a non-JVM binding would mean either reimplementing the front end or inventing a second protocol between the binding and our Java, for no gain.

The license is what decides it. LSP4J is dual-licensed, and the second arm is a BSD-3-clause license, which is compatible with GPLv3, so we simply take it under that arm. This matters more than it first appears: EPL-2.0 on its own is not GPL-compatible, which is exactly why Xtext and MagpieBridge are out. LSP4J is the only option in the group that offers a permissive arm at all.

On the rest of the criteria it is clean. Its only runtime dependency is Gson, and FWD already ships the version it wants, so adopting it adds nothing new to our dependency set. It needs Java 11; we are on 17. And the same library implements the Debug Adapter Protocol, the companion protocol for debugging, so it will also serve #11742 instead of forcing a second decision later.

One genuine limitation, from LSP4J's own changelog: breaking API changes between versions are common. The cause is structural, the LSP spec is written in TypeScript, and its types do not map cleanly onto Java, so the binding periodically has to change a boolean to a Boolean, or wrap a plain type once the spec allows an alternative for it. This is an upgrade-time cost rather than a design constraint, and the answer is the one we already use for every FWD dependency: pin a version and upgrade deliberately. It does not force us into unnatural implementation, which was the real concern in #11741-10.

Recommendation

Use LSP4J, pinned at 1.0.0. It is the only candidate that passes the license gate, it is active and mature, its reference consumer is a Java language server driving a VS Code extension, it adds no dependencies, and it covers DAP for #11742 as well.

Worth noting for completeness: the difficulties we will actually hit are in our own front end, not in this library, and they would be identical under any of the alternatives. So this is a smaller decision than it looks, and the interesting work is in the implementation plan.

Two things to confirm and then I will move on to #11741-11: agreement on LSP4J, and on pinning 1.0.0.

#13 Updated by Paula Păstrăguș 11 days ago

I've moved a bit ahead and experimented with the initial skeleton for the 4GL Language Server and have a working Phase 1 foundation up and running, using claude.

Current Progress: We have the TypeScript VS Code extension successfully spawning the Java com.goldencode.p2j.lsp server process. Communication is happening cleanly over JSON-RPC via stdin/stdout, and the server accurately tracks document states.

As shown in the screenshot below, the output panel is actively capturing protocol traffic. I've been running tests on my end, and the server is successfully receiving and logging didOpen, didChange,didClose and didSave events for OpenEdge ABL files (e.g., start.p in the dataset/abl directory, as well as include files like check-date.i).

#14 Updated by Greg Shah 11 days ago

Two things to confirm and then I will move on to #11741-11: agreement on LSP4J, and on pinning 1.0.0.

Agreed on both.

#15 Updated by Paula Păstrăguș 8 days ago

  • Status changed from New to WIP

Here's the progress from today: (I'll attach a video soon)

  • Syntax highlighting: keywords, strings, comments, preprocessor directives and macro references. The keyword list is generated from FWD's own lexer table, so the colouring matches what FWD actually accepts, and abbreviations are honoured: DISP colours the same as DISPLAY.
  • Error badge: a file that does not parse gets the red mark next to its name in the Explorer, a red tab, and an entry in the Problems panel. It appears while you type, before saving, and clears as soon as the code is valid again.
  • Schema-aware: checking uses the project's schema, so table and field references resolve properly. FOR EACH guest: DISPLAY guest.first-name. is accepted; a table that does not exist is reported.
  • Unsaved edits are what gets checked, both in the file itself and in any include file you have open and edited. Note what this does not cover: editing an include to introduce an error, even saving it, does not put a mark on the programs that include it. They are only re-checked when they are themselves edited. That matches OE, which does not propagate either. Should this scenario be improved, if yes, how?
  • Code folding on real 4GL structure: procedures, functions, methods, classes, and DO / FOR / CASE blocks, paired with their matching END. (by default the code folding depends on indentation)
  • Editor basics: comment toggling, bracket matching and auto-closing quotes.
  • Checking is debounced, so typing a statement runs one check when you pause, and not one per keystroke.
  • Nothing is written into the project while checking: no cache files, no ASTs, no hints. A check cannot interfere with a conversion's output.
  • The project is found automatically by looking for cfg/p2j.cfg.xml, so there is nothing to configure. A file outside a project is not checked and the reason is reported, though highlighting and folding still work on it.

That last point deserves a decision. A developer who writes a scratch .p on the Desktop would reasonably expect to be told whether it parses? today they are told nothing. The obstacle is that the front end cannot start without a project.

#16 Updated by Paula Păstrăguș 8 days ago

I attached a video showing the syntax highlighting, code folding, and the filename displayed in red when an error is reported.

Also, as in OE, if an include file contains an error, the super file (the file that includes it) is not marked as incorrectly parsed (i.e., in red) until it is edited. As I mentioned before, if a different behavior is desired, please let me know.

#17 Updated by Greg Shah 7 days ago

This is very cool!

Syntax highlighting: keywords, strings, comments, preprocessor directives and macro references.

Please make sure all literals are highlighted. We will want to expand from there to syntax recognized by the parsr, not just the lexer. For example:

  • resource names (variables, streams, widgets, frames, ... colored by resource type, linked to their definition)
  • program structure (names/references of methods, procedures, functions...)
  • data types
  • punctuation and block structure
  • language statements

The OE devstudio took some short cuts and we probably have a richer AST to leverage here.

The project is found automatically by looking for cfg/p2j.cfg.xml, so there is nothing to configure. A file outside a project is not checked and the reason is reported, though highlighting and folding still work on it.

For now, this is OK. Later, we can put in a "default project cfg" that can work with non-project files. But don't worry about this now.

Also, as in OE, if an include file contains an error, the super file (the file that includes it) is not marked as incorrectly parsed (i.e., in red) until it is edited. As I mentioned before, if a different behavior is desired, please let me know.

Yes, I think this should be different. Every open (in the editor) procedure or class that depends upon a broken include file should report the error, instead of just reporting it at the include.

#18 Updated by Paula Păstrăguș 7 days ago

The colors for highlighting should be the same as in OE? At least for data types I would not consider red, because it makes you think that you just got an error, I would go with green. Also, I don't have access to a dark theme for Open Edge to check the colors due to my old version. Maybe newer versions of OE have this feature?

#19 Updated by Paula Păstrăguș 7 days ago

Also, claude just built a working Outline view for Procedures and Functions, but it relies on a pretty strange regex. It's definitely fast, but I'd rather change it to an AST traversal since our AST already has all the rich context we need. What's your input on this? Should the Outline view be changed in order to gather info from the AST itself, or should we keep using regex's?

#20 Updated by Paula Păstrăguș 7 days ago

Aaand, I'm just exploring some ideas and wanted to also ask, is there any possibility of using the call graph or our reports to gather the Outline view data more easily? Let me know if you think this is an approach worth exploring..

Also, I recall you mentioning a button for incremental project conversion that would display the 4GL and Java views side-by-side. Should we include this feature in the scope of this current task? If so, could you please provide a few more details on how it should look?

#21 Updated by Greg Shah 7 days ago

The color scheme should be our own, we don't have to follow OE. Let's make it better.

Also, claude just built a working Outline view for Procedures and Functions, but it relies on a pretty strange regex. It's definitely fast, but I'd rather change it to an AST traversal since our AST already has all the rich context we need. What's your input on this? Should the Outline view be changed in order to gather info from the AST itself, or should we keep using regex's?

Do NOT use regexes for anything. We drive it all via AST. That is the point to all of this work. We can do better because we implement a richer AST.

Aaand, I'm just exploring some ideas and wanted to also ask, is there any possibility of using the call graph or our reports to gather the Outline view data more easily? Let me know if you think this is an approach worth exploring..

Not in an initial implementation. We will integrate both facilities though call graph needs configuration hints that will have to be build before it is useful.

Also, I recall you mentioning a button for incremental project conversion that would display the 4GL and Java views side-by-side. Should we include this feature in the scope of this current task? If so, could you please provide a few more details on how it should look?

Not exactly a button. The idea is that every procedure or class will have the ability to have a companion view in Java of its converted code. These will be cross linked so that moving either direction is easy. This isn't just about incremental conversion, but about conversion in general.

But for now, don't focus on this yet.

#22 Updated by Paula Păstrăguș 7 days ago

Greg Shah wrote:

Do NOT use regexes for anything. We drive it all via AST. That is the point to all of this work. We can do better because we

Unfortunately, we can't avoid regexes for syntax highlighting, because VS Code depends on TextMate grammars to apply colors the millisecond a file is opened. TextMate evaluates regular expressions line-by-line, completely independent of our Language Server.
This is actually why syntax highlighting works even if you open a random scratch file that isn't tied to a project.

#23 Updated by Paula Păstrăguș 6 days ago

The initial implementation and today's changes were committed as 16755 and 16756 / 11741a.

I attached a video with the progress.

#24 Updated by Paula Păstrăguș 5 days ago

Here are some thoughts / features we might implement (it also contains ideas from yesterday's meeting):

  • Synchronized Cursor Navigation: When a developer switches tabs between the main source file (.p / .w) and the preprocessed cache file, the cursor should automatically jump to the equivalent location in the target file.
  • Convert on Save: A project setting that, when enabled, physically regenerates and saves the cache file to disk every time the main program is saved.
  • Stale Cache Warning / No Artifacts: If the "Convert on Save" setting is set to false, we shouldn't clutter the project with generated files. However, if the developer opens an outdated cache file, the IDE should display a clear warning badge indicating that the source has been modified and the cache is out of sync.
  • Smart Include Refresh Prompt: When saving an include (.i) file, the IDE should detect open parent programs that depend on it and prompt the user: "You have open programs that depend on this include file. Do you want to refresh them?"
  • Include-Aware Error Reporting: If a compilation or syntax error originates inside an include file, the error marker (squiggly line) should be placed on the {include.i} reference in the parent file. Since an include can be called multiple times in the same file (sometimes with different parameters), the system should flag all occurrences where that include is called and breaks the build.
  • Go to Preprocessor Definition: Ctrl+Click on a {&NAME} reference should navigate directly to its corresponding &SCOPED-DEFINE or &GLOBAL-DEFINE.
  • Resolved Preprocessor Peek (Hover): Hovering over a preprocessor reference (like {&WINDOW-NAME}) should display a tooltip showing its final resolved value, saving the developer from having to search for the definition manually.
  • Read-Only Cache View with Source Redirection: The preprocessed view should ideally reject direct edits (since they would be overwritten on the next compile). If a developer attempts to type in the cache file, the IDE could offer a quick action to jump to the actual source line that generated that code.
  • Peek Expansion (Inline View): Alt+Clicking an include reference should open the expanded text inline (in a peek window) without leaving the file. This provides quick context without forcing the developer to actually open the include file in another TAB.
  • Expansion Weight Lens: A small CodeLens annotation above each include reference reporting what it brings in (e.g., "adm2/support.i · 412 lines · 3 nested includes"). This helps developers instantly gauge how much scaffolding a file relies on.
  • Origin Ribbon: In the preprocessed view, a colored stripe in the gutter should mark which include each run of lines came from (with darker shades for deeper nesting). The developer's own code would remain unshaded, making it instantly stand out.
  • Fold to Source: A command in the preprocessed view that collapses every include-derived region. The file then reads exactly like the original source, allowing the developer to expand only the specific include they are currently investigating.
  • Unresolved Include Explained: When an include cannot be found, the error shouldn't just say "File not found." It should explicitly list the PROPATH directories that were searched, turning a dead-end error into an actionable configuration fix.
  • Include Path Completion: Typing { should trigger autocomplete suggestions for files based on the PROPATH, filtering the list as the user types to prevent hard-to-spot typos.
  • Preprocessor Name Completion: Inside a {& block, the IDE should offer auto-completion for the names actually defined and in-scope at that exact point, along with their values.
  • Include Rename and Move Refactoring: Renaming or moving an .i file should trigger a refactoring tool that updates every {...} reference to it across the entire project (this can also be applied for .w / .p files / variable names, procedures and more).
  • Circular and Duplicate Include Warnings: The IDE should flag warnings if an include file pulls itself in (circular dependency) or if it is pulled in twice along the same chain, preventing redundant outlines and unexpected compilation errors.

#25 Updated by Greg Shah 4 days ago

I love all the new ideas! Let's GO!

Are any of these present in OEPDS?

#26 Updated by Paula Păstrăguș 4 days ago

Yes, two of these features are already present in OpenEdge: hovering over a preprocessor reference displays its resolved value, and auto-completion is supported for {&.

#27 Updated by Paula Păstrăguș 4 days ago

I have attached three test cases demonstrating the new preprocessor reference hover behavior. Unlike the native OpenEdge hover, which only resolves local defines and completely ignores typos and inherited files (aka defines from includes), this implementation covers those gaps.
  • Case 1: Undefined or Misspelled References

    Hovering over an unresolved reference explicitly warns the user that the value is not defined anywhere the preprocessor looked.
  • Case 2: Definitions from Include Files

    Hovering over a reference defined externally reveals its resolved value and explicitly states that it originates from an include file. As a future enhancement, we could improve this further by explicitly displaying the include filename in the hover tooltip.
  • Case 3: Local Definitions

    Hovering over a locally defined reference displays its value alongside the exact line number where the &SCOPED-DEFINE or &GLOBAL-DEFINE is located. For this scenario, Ctrl+Click takes you straight to the definition.

#28 Updated by Greg Shah 4 days ago

As a future enhancement, we could improve this further by explicitly displaying the include filename in the hover tooltip.

Yes, we absolutely should do that. Showing exactly where the definition exists is a very helpful feature.

For this scenario, Ctrl+Click takes you straight to the definition.

We could implement this for the non-local defintion too. That seems like a good idea to me.

#29 Updated by Paula Păstrăguș 4 days ago

Absolutely, both would be very useful for a developer.

#30 Updated by Paula Păstrăguș 1 day ago

Committed today's work as rev 16759. It contains the followings:

  • Added re-checking of the programs which use an include when that include is modified, controlled by a setting: always, ask first, or never. An error inside an include is now reported at every {include} reference, in the programs and in the include itself.
  • Errors inside an include are now shown on every place the include is used.
  • Added an error for an include which is not found on the PROPATH.
  • Added completion of include paths after { and of preprocessor names after {&.
  • Added a read-only view of a program's preprocessed source, opened beside it on request. The view shows the conversion's cache file, and its tab is green when the file is up to date and yellow when it is stale.
  • Added a "write on save" setting, on by default, which writes the cache file and the preprocessor hints when a program is saved.

I have also attached a video to illustrate each newly added feature.

Also available in: Atom PDF