Skip to content

Commit 862b8a2

Browse files
committed
Merge remote-tracking branch 'origin/dev' into write-steps
2 parents 2a3f3d9 + 952fc53 commit 862b8a2

17 files changed

Lines changed: 249 additions & 16 deletions

File tree

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/haz3lcore/derived/Measured.re

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -335,8 +335,12 @@ let of_segment_inner =
335335
~col=new_indent - origin.col,
336336
);
337337
// add seg to map and reset seg
338-
//TODO: decide if should actually add linebreak here
339-
let map = add_piece_row(origin.row, seg, map);
338+
let map =
339+
add_piece_row(
340+
origin.row,
341+
seg @ [Piece.Secondary(Secondary.mk_newline(Id.mk()))], /* NOTE: These linebreaks don't actually occur in the surface syntax */
342+
map,
343+
);
340344
let map =
341345
size.row == 0 ? map : add_n_empty_piece_rows(size.row - 1, map);
342346
([], new_indent, size, map);
@@ -354,7 +358,11 @@ let of_segment_inner =
354358
? {
355359
let g = DeferredLinebreaks.of_secondary();
356360
add_n_rows(origin, indent, g, map)
357-
|> add_piece_row(origin.row, seg, _)
361+
|> add_piece_row(
362+
origin.row,
363+
seg @ [Piece.Secondary(Secondary.mk_newline(Id.mk()))], /* NOTE: These linebreaks don't actually occur in the surface syntax */
364+
_,
365+
)
358366
|> add_n_empty_piece_rows(g - 1);
359367
}
360368
: map;

src/haz3lcore/lang/Token.re

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ let of_list = Unicode.of_list;
4141
/* Token Recognition Predicates */
4242

4343
/* A. Secondary Notation (Comments, Whitespace, etc.) */
44+
let empty = ""; /* This is invalid for view */
4445
let space = " ";
4546
let linebreak = "\n";
4647
let comment_regexp = regexp("^#[^#\n]*#$"); /* Multiline comments not supported */

src/haz3lcore/tiles/Secondary.re

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ open Language.Secondary;
44
[@deriving (show({with_path: false}), sexp, yojson, eq)]
55
type t = Language.Secondary.t;
66

7+
let mk_empty = id => {
8+
content: Whitespace(Token.empty),
9+
id,
10+
};
11+
712
let mk_space = id => {
813
content: Whitespace(Token.space),
914
id,

src/web/Settings.re

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ module Model = {
1414
explainThis: ExplainThisModel.Settings.t,
1515
assistant: AssistantSettings.t,
1616
sidebar: SidebarModel.Settings.t,
17+
line_numbers: bool,
18+
relative_line_numbers: bool,
1719
};
1820

1921
let init = {
@@ -62,6 +64,8 @@ module Model = {
6264
panel: LanguageDocumentation,
6365
show: true,
6466
},
67+
line_numbers: false,
68+
relative_line_numbers: false,
6569
};
6670

6771
let fix_instructor_mode = settings =>
@@ -124,7 +128,9 @@ module Update = {
124128
| Sidebar(SidebarModel.Settings.action)
125129
| ExplainThis(ExplainThisModel.Settings.action)
126130
| Assistant(AssistantSettings.action)
127-
| FlipAnimations;
131+
| FlipAnimations
132+
| ToggleLineNumbers
133+
| ToggleRelativeLineNumbers;
128134

129135
let can_undo = (action: t) => {
130136
switch (action) {
@@ -352,6 +358,15 @@ module Update = {
352358
...settings, //TODO[Matt]: Make sure instructor mode actually makes prelude read-only
353359
instructor_mode: !settings.instructor_mode,
354360
}
361+
362+
| ToggleLineNumbers => {
363+
...settings,
364+
line_numbers: !settings.line_numbers,
365+
}
366+
| ToggleRelativeLineNumbers => {
367+
...settings,
368+
relative_line_numbers: !settings.relative_line_numbers,
369+
}
355370
}
356371
)
357372
|> Updated.return(~scroll_active=false);

src/web/app/editors/Editors.re

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ module View = {
367367
editors: Model.t,
368368
) =>
369369
switch (editors) {
370+
// Add in the line numbering for Scratch editor
370371
| Scratch(m) =>
371372
ScratchMode.View.view(
372373
~signal=
@@ -381,6 +382,7 @@ module View = {
381382
~inject=a => Update.Scratch(a) |> inject,
382383
m,
383384
)
385+
// Add in the line numbering for Documentation editor
384386
| Documentation(m) =>
385387
ScratchMode.View.view(
386388
~signal=

src/web/app/editors/cell/CellEditor.re

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ module View = {
199199
~caption: option(Node.t)=?,
200200
~result_kind=?,
201201
~locked=false,
202+
~lines=false,
202203
model: Model.t,
203204
) => {
204205
let (footer, overlays) =
@@ -249,6 +250,7 @@ module View = {
249250
: (action => inject(MainEditor(action))),
250251
~selected=selected == Some(MainEditor) ? Yes : No,
251252
~overlays=overlays(model.editor.editor),
253+
~lines,
252254
~dynamics=EvalResult.Model.dynamics(model.result),
253255
model.editor,
254256
),

src/web/app/editors/code/CodeEditable.re

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ module View = {
275275
~inject: Update.t => Ui_effect.t(unit),
276276
~selected: selected,
277277
~overlays: list(Node.t)=[],
278+
~lines: bool=false,
278279
~dynamics: Language.Dynamics.Map.t,
279280
~expand_selection=false,
280281
model: Model.t,
@@ -459,10 +460,14 @@ module View = {
459460
};
460461
};
461462

463+
let display_line_numbers: bool = lines && globals.settings.line_numbers;
464+
462465
Node.div(
463466
~attrs=[
464467
Attr.classes(
465-
["cell-item", "code-editor"] @ (selected != No ? ["selected"] : []),
468+
["cell-item", "code-editor"]
469+
@ (selected != No ? ["selected"] : [])
470+
@ (display_line_numbers ? ["has-line-numbers"] : []),
466471
),
467472
Attr.on_contextmenu(evt =>
468473
switch (Pointer.Event.mk(evt)) {
@@ -480,7 +485,14 @@ module View = {
480485
Attr.on_mousemove(evt => drag_select(Pointer.Event.mk(evt))),
481486
Attr.on_wheel(evt => drag_select(Pointer.Event.mk(evt))),
482487
],
483-
[code_view],
488+
display_line_numbers
489+
? LineNumbers.View.view(
490+
model,
491+
globals.settings.relative_line_numbers,
492+
selected == Yes,
493+
)
494+
@ [code_view]
495+
: [code_view],
484496
);
485497
};
486498
};
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
open Haz3lcore;
2+
//open Virtual_dom.Vdom;
3+
open Util;
4+
open WebUtil;
5+
6+
/*
7+
Used to display line numbering alongside cells
8+
*/
9+
module Model = CodeWithStatics.Model;
10+
11+
module View = {
12+
let view = (model: Model.t, show_relative_numbers: bool, selected: bool) => {
13+
let {editor: {syntax: {measured, _}, state: {zipper, _}, _}, _}: Model.t = model;
14+
let num_rows = List.length(measured.piece_rows);
15+
let empty_row = row => {
16+
let result = List.nth_opt(List.rev(measured.piece_rows), row);
17+
switch (result) {
18+
| Some(value) =>
19+
switch (value) {
20+
| [] => true
21+
| _ => false
22+
}
23+
| None => true // The row doesn't actually exist, hence it's empty
24+
};
25+
};
26+
let Point.{row, _} = Zipper.Caret.point(measured, zipper);
27+
let cursor_row_index = row;
28+
/*
29+
Recursively builds a list of line numbers for display, skipping empty rows.
30+
31+
Parameters:
32+
- row_index: Current row being processed (0-indexed)
33+
- line_count: The line number to assign to the current non-empty row
34+
35+
Returns: (line_numbers, cursor_line_number) where:
36+
- line_numbers: List where 0 indicates skip this row, non-zero is the display line number
37+
- cursor_line_number: The line number where the cursor is located (0 if not found yet)
38+
*/
39+
let rec processed_line_numbers =
40+
(row_index: int, line_count: int): (list(int), int) =>
41+
if (row_index == num_rows) {
42+
([], 0);
43+
} else {
44+
let is_row_empty = empty_row(row_index);
45+
let (returned_processed_list, returned_cursor_line_number) =
46+
is_row_empty
47+
? processed_line_numbers(row_index + 1, line_count)
48+
: processed_line_numbers(row_index + 1, line_count + 1);
49+
let current_line_number = is_row_empty ? 0 : line_count;
50+
let cursor_line_number =
51+
if (returned_cursor_line_number == 0 && row_index == cursor_row_index) {
52+
line_count;
53+
} else {
54+
returned_cursor_line_number;
55+
};
56+
(
57+
[current_line_number] @ returned_processed_list,
58+
cursor_line_number,
59+
);
60+
};
61+
let (processed_list, cursor_line_number) = processed_line_numbers(0, 1);
62+
/*
63+
Converts a row index to its display text.
64+
Returns "\n" for empty rows, or the line number (absolute or relative) with newline.
65+
*/
66+
let index_to_text = (i): string => {
67+
let line_number = List.nth(processed_list, i);
68+
line_number == 0
69+
? "\n" // if this is a line we want to skip
70+
: {
71+
(
72+
if (show_relative_numbers && selected) {
73+
string_of_int(
74+
abs(line_number - cursor_line_number) == 0
75+
? line_number : abs(line_number - cursor_line_number),
76+
);
77+
} else {
78+
string_of_int(line_number);
79+
}
80+
)
81+
++ (i == num_rows ? "" : "\n"); // Add a line break if this is not the last row
82+
};
83+
};
84+
let index_to_span = (i): Node.t => {
85+
Node.span(
86+
~attrs=
87+
i == row && selected ? [Attr.classes(["line-numbers-bold"])] : [],
88+
[Text(index_to_text(i))],
89+
);
90+
};
91+
[
92+
Node.div(
93+
~attrs=[
94+
Attr.classes([
95+
"code",
96+
"line-numbers",
97+
selected ? "line-numbers-selected" : "",
98+
]),
99+
],
100+
[
101+
Node.span(
102+
~attrs=[Attr.classes(["code-text", "line-numbers-text"])],
103+
List.init(num_rows, (i): Node.t => {index_to_span(i)}),
104+
),
105+
],
106+
),
107+
];
108+
};
109+
};

src/web/view/ExerciseMode.re

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,7 @@ module View = {
651651
)
652652
| _ => CellCommon.caption(caption, ~rest=?subcaption)
653653
},
654+
~lines=true,
654655
cell,
655656
);
656657
};

0 commit comments

Comments
 (0)