Skip to content

[WIP] Editor mode #7369

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 26 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
03425fa
disable using completion powered hover when regular hover fails
zth Mar 27, 2025
c868b96
track all typed patterns and expressions
zth Mar 27, 2025
f264d0b
tweaks
zth Mar 28, 2025
a7855bf
use labels with locations in typedtree
zth Mar 28, 2025
0188340
add editor mode to compiler that lets you continue past certain type …
zth Mar 28, 2025
9cc244c
return multiple errors in editor mode
zth Mar 28, 2025
248b5be
handle ppat_record labels as tainted
zth Mar 28, 2025
6e9cbe6
new bare bones completion setup
zth Apr 3, 2025
5c11212
more pattern completion
zth Apr 4, 2025
6a8e2bc
handle locs in arg labels in a more robust way
zth Apr 15, 2025
069cc33
sketch out a new test setup for the analysis revamp
zth Apr 26, 2025
e2504aa
redundant
zth Apr 26, 2025
671e261
add a few more empty cases
zth Apr 27, 2025
59c6275
restore most of old setup so it can run in parallell
zth Apr 27, 2025
c9ae2c8
fix
zth Apr 27, 2025
84aecb9
use config to activate new engine
zth Apr 28, 2025
f61f73a
restore old behavior when config is not activated
zth Apr 28, 2025
8f09841
Better snapshot (#7409)
nojaf Apr 29, 2025
4d2b45b
some snapshot refactoring and tweaking
zth Apr 29, 2025
dd1cb30
add empty string case
zth Apr 29, 2025
b1d8a44
try modifying and reusing the existing Code_frame module for snapshot…
zth Apr 29, 2025
819ddb2
Inital dump of cmt (#7411)
nojaf Apr 30, 2025
46b69ba
error message
zth Apr 29, 2025
8fa6209
wip wrk command to dump cmt contents
zth Apr 30, 2025
8bf5c17
clean non-existing snapshots
zth Apr 30, 2025
b11f460
handle empty let assignments with constraints
zth Apr 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions analysis/bin/main.ml
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,18 @@ let main () =
| _ -> print_endline "\"ERR: Did not find root \"")
| [_; "completion"; path; line; col; currentFile] ->
printHeaderInfo path line col;
Commands.completion ~debug ~path
~pos:(int_of_string line, int_of_string col)
~currentFile
if !Cfg.useRevampedCompletion then
match
Commands.completionRevamped ~debug ~path
~pos:(int_of_string line, int_of_string col)
~currentFile
with
| None -> ()
| Some (_, completablesText) -> print_endline completablesText
else
Commands.completion ~debug:true ~path
~pos:(int_of_string line, int_of_string col)
~currentFile
| [_; "completionResolve"; path; modulePath] ->
Commands.completionResolve ~path ~modulePath
| [_; "definition"; path; line; col] ->
Expand Down Expand Up @@ -208,8 +217,16 @@ let main () =
(Json.escape (CreateInterface.command ~path ~cmiFile))
| [_; "format"; path] ->
Printf.printf "\"%s\"" (Json.escape (Commands.format ~path))
| [_; "test"; path] -> Commands.test ~path
| [_; "test"; path] -> Commands.test ~path ~debug
| [_; "test_revamped"; path; config_file_path] ->
Packages.overrideConfigFilePath := Some config_file_path;
Cfg.useRevampedCompletion := true;
Commands.test ~path ~debug
| args when List.mem "-h" args || List.mem "--help" args -> prerr_endline help
| [_; "cmt"; path] -> CmtViewer.dump path
| [_; "cmt"; line; col; path] ->
let cursor = (int_of_string line, int_of_string col) in
CmtViewer.dump ~filter:(Cursor cursor) path
| _ ->
prerr_endline help;
exit 1
Expand Down
16 changes: 8 additions & 8 deletions analysis/reanalyze/src/Arnold.ml
Original file line number Diff line number Diff line change
Expand Up @@ -582,9 +582,9 @@ module ExtendFunctionTable = struct
Texp_apply {funct = {exp_desc = Texp_ident (path, {loc}, _)}; args};
}
when kindOpt <> None ->
let checkArg ((argLabel : Asttypes.Noloc.arg_label), _argOpt) =
let checkArg ((argLabel : Asttypes.arg_label), _argOpt) =
match (argLabel, kindOpt) with
| (Labelled l | Optional l), Some kind ->
| (Labelled {txt = l} | Optional {txt = l}), Some kind ->
kind |> List.for_all (fun {Kind.label} -> label <> l)
| _ -> true
in
Expand Down Expand Up @@ -624,9 +624,9 @@ module ExtendFunctionTable = struct
when callee |> FunctionTable.isInFunctionInTable ~functionTable ->
let functionName = Path.name callee in
args
|> List.iter (fun ((argLabel : Asttypes.Noloc.arg_label), argOpt) ->
|> List.iter (fun ((argLabel : Asttypes.arg_label), argOpt) ->
match (argLabel, argOpt |> extractLabelledArgument) with
| Labelled label, Some (path, loc)
| Labelled {txt = label}, Some (path, loc)
when path |> FunctionTable.isInFunctionInTable ~functionTable
->
functionTable
Expand Down Expand Up @@ -672,11 +672,11 @@ module CheckExpressionWellFormed = struct
->
let functionName = Path.name functionPath in
args
|> List.iter (fun ((argLabel : Asttypes.Noloc.arg_label), argOpt) ->
|> List.iter (fun ((argLabel : Asttypes.arg_label), argOpt) ->
match argOpt |> ExtendFunctionTable.extractLabelledArgument with
| Some (path, loc) -> (
match argLabel with
| Labelled label -> (
| Labelled {txt = label} -> (
if
functionTable
|> FunctionTable.functionGetKindOfLabel ~functionName
Expand Down Expand Up @@ -761,7 +761,7 @@ module Compile = struct
let argsFromKind =
innerFunctionDefinition.kind
|> List.map (fun (entry : Kind.entry) ->
( Asttypes.Noloc.Labelled entry.label,
( Asttypes.Labelled (Location.mknoloc entry.label),
Some
{
expr with
Expand All @@ -785,7 +785,7 @@ module Compile = struct
args
|> List.find_opt (fun arg ->
match arg with
| Asttypes.Noloc.Labelled s, Some _ -> s = label
| Asttypes.Labelled {txt = s}, Some _ -> s = label
| _ -> false)
in
let argOpt =
Expand Down
2 changes: 1 addition & 1 deletion analysis/reanalyze/src/DeadValue.ml
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ let processOptionalArgs ~expType ~(locFrom : Location.t) ~locTo ~path args =
| None -> Some false
in
match lbl with
| Asttypes.Noloc.Optional s when not locFrom.loc_ghost ->
| Asttypes.Optional {txt = s} when not locFrom.loc_ghost ->
if argIsSupplied <> Some false then supplied := s :: !supplied;
if argIsSupplied = None then suppliedMaybe := s :: !suppliedMaybe
| _ -> ());
Expand Down
5 changes: 5 additions & 0 deletions analysis/src/Cfg.ml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@ let readProjectConfigCache =
| "true" -> true
| _ -> false
with _ -> false)

let useRevampedCompletion =
ref (Sys.getenv_opt "RESCRIPT_NEW_ANALYSIS_ENGINE" |> Option.is_some)

let isTestWorkmode = ref false
4 changes: 1 addition & 3 deletions analysis/src/Cmt.ml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ let fullFromUri ~uri =
else None
in
match incremental with
| Some cmtInfo ->
if Debug.verbose () then Printf.printf "[cmt] Found incremental cmt\n";
Some cmtInfo
| Some cmtInfo -> Some cmtInfo
| None -> (
match Hashtbl.find_opt package.pathsForModule moduleName with
| Some paths ->
Expand Down
93 changes: 93 additions & 0 deletions analysis/src/CmtViewer.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
let loc_to_string (loc : Warnings.loc) : string =
Format.sprintf "(%03d,%03d--%03d,%03d)" loc.loc_start.pos_lnum
(loc.loc_start.pos_cnum - loc.loc_start.pos_bol)
loc.loc_end.pos_lnum
(loc.loc_end.pos_cnum - loc.loc_end.pos_bol)

let filter_by_cursor cursor (loc : Warnings.loc) : bool =
match cursor with
| None -> true
| Some (line, col) ->
let start = loc.loc_start and end_ = loc.loc_end in
let line_in = start.pos_lnum <= line && line <= end_.pos_lnum in
let col_in =
if start.pos_lnum = end_.pos_lnum then
start.pos_cnum - start.pos_bol <= col
&& col <= end_.pos_cnum - end_.pos_bol
else if line = start.pos_lnum then col >= start.pos_cnum - start.pos_bol
else if line = end_.pos_lnum then col <= end_.pos_cnum - end_.pos_bol
else true
in
line_in && col_in

type filter = Cursor of (int * int) | Loc of Loc.t

let dump ?filter path =
match Cmt.loadFullCmtFromPath ~path with
| None -> failwith (Format.sprintf "Could not load cmt for %s" path)
| Some full ->
let open SharedTypes in
let open SharedTypes.Stamps in
let applyFilter =
match filter with
| None -> fun _ -> true
| Some (Cursor cursor) -> Loc.hasPos ~pos:cursor
| Some (Loc loc) -> Loc.isInside loc
in
(match filter with
| None -> ()
| Some (Cursor (line, col)) ->
Printf.printf "Filtering by cursor %d,%d\n" line col
| Some (Loc loc) -> Printf.printf "Filtering by loc %s\n" (Loc.toString loc));
let stamps =
full.file.stamps |> getEntries
|> List.filter (fun (_, stamp) -> applyFilter (locOfKind stamp))
in

let total_stamps = List.length stamps in
Printf.printf "Found %d stamps:\n%s" total_stamps
(if total_stamps > 0 then "\n" else "");

stamps
|> List.sort (fun (_, a) (_, b) ->
let aLoc = locOfKind a in
let bLoc = locOfKind b in
match compare aLoc.loc_start.pos_lnum bLoc.loc_start.pos_lnum with
| 0 -> compare aLoc.loc_start.pos_cnum bLoc.loc_start.pos_cnum
| c -> c)
|> List.iter (fun (stamp, kind) ->
match kind with
| KType t ->
Printf.printf "%d ktype %s\n" stamp
(loc_to_string t.extentLoc)
| KValue t ->
Printf.printf "%d kvalue %s\n" stamp
(loc_to_string t.extentLoc)
| KModule t ->
Printf.printf "%d kmodule %s\n" stamp
(loc_to_string t.extentLoc)
| KConstructor t ->
Printf.printf "%d kconstructor %s\n" stamp
(loc_to_string t.extentLoc));

(* Dump all locItems (typed nodes) *)
let locItems =
match full.extra with
| {locItems} ->
locItems |> List.filter (fun locItem -> applyFilter locItem.loc)
in

Printf.printf "\nFound %d locItems (typed nodes):\n\n"
(List.length locItems);

locItems
|> List.sort (fun a b ->
let aLoc = a.loc.Location.loc_start in
let bLoc = b.loc.Location.loc_start in
match compare aLoc.pos_lnum bLoc.pos_lnum with
| 0 -> compare aLoc.pos_cnum bLoc.pos_cnum
| c -> c)
|> List.iter (fun {loc; locType} ->
let locStr = loc_to_string loc in
let kindStr = SharedTypes.locTypeToString locType in
Printf.printf "%s %s\n" locStr kindStr)
125 changes: 125 additions & 0 deletions analysis/src/CodeFence.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
(* Define a type for a range with start and finish indices *)
type range = {start: int; finish: int}

(* --- Helper function to find the 0-based line index containing a given 0-based character index --- *)
let get_line_index_from_char_index code char_index =
let lines = String.split_on_char '\n' code in
let rec find_line_idx current_char_idx current_line_num remaining_lines =
match remaining_lines with
| [] ->
max 0 (current_line_num - 1)
(* If char_index is beyond the end, return last line index *)
| line :: tl ->
let line_length = String.length line in
(* Check if char_index is within the current line (including the newline char) *)
if
char_index >= current_char_idx
&& char_index <= current_char_idx + line_length
then current_line_num
else
(* Move to the next line, account for the newline character (+1) *)
find_line_idx
(current_char_idx + line_length + 1)
(current_line_num + 1) tl
in
find_line_idx 0 0 lines

(* --- Helper function to calculate the 0-based character index of the start of a given 0-based line index --- *)
let get_char_index_from_line_index code target_line_index =
let lines = String.split_on_char '\n' code in
let rec calculate_start_index_impl current_char_idx current_line_num
lines_to_process =
if current_line_num >= target_line_index then current_char_idx
else
match lines_to_process with
| [] -> current_char_idx (* Target line index is out of bounds *)
| line :: tl ->
(* Move past the current line and its newline character *)
calculate_start_index_impl
(current_char_idx + String.length line + 1)
(current_line_num + 1) tl
in
calculate_start_index_impl 0 0 lines

(* --- Main formatting function --- *)
let format_code_snippet_cropped code (underline_range : range option)
lines_around_annotation =
let lines = String.split_on_char '\n' code in
let total_lines = List.length lines in
let formatted_output = Buffer.create (String.length code) in
(* Initial capacity *)

(* Determine the central line index for cropping *)
let target_line_index =
match underline_range with
| Some {start; finish = _} -> get_line_index_from_char_index code start
| None -> 0 (* Default to first line if no annotations *)
in

(* Determine the cropping window (0-based line indices) *)
let start_line_index = max 0 (target_line_index - lines_around_annotation) in
let end_line_index =
min (total_lines - 1) (target_line_index + lines_around_annotation)
in

(* Keep track of the global character index corresponding to the start of the *current* line being iterated over *)
let current_char_index = ref 0 in

(* Iterate through all original lines to correctly track current_char_index *)
List.iteri
(fun original_line_idx line ->
let line_length = String.length line in
(* Check if the current original line is within our cropping window *)
if
original_line_idx >= start_line_index
&& original_line_idx <= end_line_index
then (
let original_line_number = original_line_idx + 1 in
(* 1-based for display *)
let line_number_prefix = Printf.sprintf "%d + " original_line_number in
let prefix_length = String.length line_number_prefix in

(* Add the code line *)
Buffer.add_string formatted_output line_number_prefix;
Buffer.add_string formatted_output line;
Buffer.add_char formatted_output '\n';

(* Prepare the annotation line buffer *)
let annotation_line_buffer =
Buffer.create (prefix_length + line_length)
in
Buffer.add_string annotation_line_buffer (String.make prefix_length ' ');

(* Initial padding *)
let has_annotation_on_this_line = ref false in

(* Check each character position within this line for annotations *)
for i = 0 to line_length - 1 do
let global_char_index = !current_char_index + i in
let annotation_char = ref ' ' in
(* Default to space *)

(* Check for underline using Option.iter *)
Option.iter
(fun {start; finish} ->
if global_char_index >= start && global_char_index < finish then (
annotation_char := '-' (* '¯' *);
(* Macron symbol *)
has_annotation_on_this_line := true))
underline_range;

Buffer.add_char annotation_line_buffer !annotation_char
done;

(* Add the annotation line to the main output if needed *)
if !has_annotation_on_this_line then (
Buffer.add_buffer formatted_output annotation_line_buffer;
Buffer.add_char formatted_output '\n'));

(* Update the global character index to the start of the next line *)
(* This happens regardless of whether the line was in the cropped window *)
current_char_index := !current_char_index + line_length + 1
(* +1 for the newline *))
lines;

Buffer.contents formatted_output
Loading
Loading