Skip to content
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

Programming exercises: Change the solution entry and testwise coverage editors to Monaco #9173

Merged
merged 21 commits into from
Aug 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ <h4 class="modal-title" jhiTranslate="artemisApp.programmingExerciseSolutionEntr
<div class="form-group">
<label class="label-narrow" for="field_path" jhiTranslate="artemisApp.programmingExerciseSolutionEntry.file"></label>
@if (solutionRepositoryFileNames?.length) {
<select class="form-select" required name="path" [(ngModel)]="this.solutionEntry.filePath" id="field_path">
<select class="form-select" required name="path" [(ngModel)]="this.solutionEntry.filePath" (change)="onUpdateFilePath()" id="field_path">
@for (fileName of solutionRepositoryFileNames; track fileName) {
<option [ngValue]="fileName">{{ fileName }}</option>
}
</select>
}
</div>
<div class="d-flex flex-row">
<jhi-solution-entry class="flex-grow-1" [solutionEntry]="solutionEntry" [enableEditing]="true" />
<div class="d-flex flex-column align-items-end gap-3">
<jhi-solution-entry #solutionEntryComponent class="w-100" [solutionEntry]="solutionEntry" [enableEditing]="true" />
<div class="ps-2 flex-grow-0">
<button
type="button"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { Component, EventEmitter, OnDestroy, OnInit } from '@angular/core';
import { Component, EventEmitter, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { CodeHintService } from 'app/exercises/shared/exercise-hint/services/code-hint.service';
import { Subject } from 'rxjs';
import { ProgrammingExerciseTestCase } from 'app/entities/programming-exercise-test-case.model';
import { ProgrammingExerciseSolutionEntry } from 'app/entities/hestia/programming-exercise-solution-entry.model';
import { ProgrammingExerciseService } from 'app/exercises/programming/manage/services/programming-exercise.service';
import { CodeHint } from 'app/entities/hestia/code-hint-model';
import { ProgrammingExerciseSolutionEntryService } from 'app/exercises/shared/exercise-hint/services/programming-exercise-solution-entry.service';
import { SolutionEntryComponent } from 'app/exercises/shared/exercise-hint/shared/solution-entry.component';

@Component({
selector: 'jhi-manual-solution-entry-creation-modal',
templateUrl: './manual-solution-entry-creation-modal.component.html',
})
export class ManualSolutionEntryCreationModalComponent implements OnInit, OnDestroy {
@ViewChild('solutionEntryComponent', { static: false }) solutionEntryComponent: SolutionEntryComponent;

solutionEntry = new ProgrammingExerciseSolutionEntry();

exerciseId: number;
Expand All @@ -27,7 +29,6 @@ export class ManualSolutionEntryCreationModalComponent implements OnInit, OnDest

constructor(
private activeModal: NgbActiveModal,
private service: CodeHintService,
private exerciseService: ProgrammingExerciseService,
private solutionEntryService: ProgrammingExerciseSolutionEntryService,
) {
Expand Down Expand Up @@ -58,6 +59,14 @@ export class ManualSolutionEntryCreationModalComponent implements OnInit, OnDest
});
}

/**
* Updates the solution entry editor to refer to the correct file path.
* In particular, this updates the syntax highlighting of the editor.
*/
onUpdateFilePath(): void {
this.solutionEntryComponent.setupEditor();
}

clear() {
this.activeModal.close();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ <h4 class="modal-title" jhiTranslate="artemisApp.programmingExerciseSolutionEntr
>Note: Changes apply to the fragments in existing code hints that contain this fragment.</span
>
</div>
<div class="d-flex flex-row">
<jhi-solution-entry class="flex-grow-1" [solutionEntry]="solutionEntry" [enableEditing]="isEditable" />
<div class="d-flex flex-column align-items-end gap-3">
<jhi-solution-entry class="w-100" [solutionEntry]="solutionEntry" [enableEditing]="isEditable" />
@if (isEditable) {
<div class="ps-2 flex-grow-0">
<button type="button" class="btn btn-primary" jhiTranslate="entity.action.save" (click)="saveSolutionEntry()"></button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-expansion-panel-header class="me-2">
<mat-panel-title>
{{ fileName }}
</mat-panel-title>
<mat-panel-description> {{ (proportionCoveredLines * 100).toFixed(1) + ' %' }} </mat-panel-description>
<mat-panel-description class="text-nowrap flex-grow-0"> {{ proportionString }} </mat-panel-description>
pzdr7 marked this conversation as resolved.
Show resolved Hide resolved
</mat-expansion-panel-header>
<jhi-ace-editor #editor [mode]="'java'" [readOnly]="true" [autoUpdateContent]="true" [hidden]="false" class="code-editor-ace__content__editor" />
<div [style.height.px]="editorHeight">
<jhi-monaco-editor #editor [shrinkToFit]="false" [readOnly]="true" (contentHeightChanged)="editorHeight = $event" />
</div>
</mat-expansion-panel>
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

.mat-expansion-panel .mat-expansion-panel-body {
padding: 0 !important;
text-align: center;
}

.mat-expansion-panel-header-description {
Expand All @@ -15,3 +14,7 @@
margin-right: 2px !important;
margin-left: 2px !important;
}

.covered-line-highlight {
background-color: var(--monaco-editor-test-coverage-highlight);
}
pzdr7 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Component, Input, OnChanges, OnInit, SimpleChanges, ViewChild } from '@angular/core';
import { AceEditorComponent } from 'app/shared/markdown-editor/ace-editor/ace-editor.component';
import ace from 'brace';
import { Component, Input, OnChanges, OnInit, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core';
import { CoverageFileReport } from 'app/entities/hestia/coverage-file-report.model';
import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component';

@Component({
selector: 'jhi-testwise-coverage-file',
templateUrl: './testwise-coverage-file.component.html',
styleUrls: ['./testwise-coverage-file.component.scss'],
encapsulation: ViewEncapsulation.None,
})
export class TestwiseCoverageFileComponent implements OnInit, OnChanges {
@Input()
Expand All @@ -19,13 +19,17 @@ export class TestwiseCoverageFileComponent implements OnInit, OnChanges {
fileReport: CoverageFileReport;

@ViewChild('editor', { static: true })
editor: AceEditorComponent;
editor: MonacoEditorComponent;

proportionCoveredLines: number;
proportionString: string;
editorHeight: number = 20;

pzdr7 marked this conversation as resolved.
Show resolved Hide resolved
static readonly COVERED_LINE_HIGHLIGHT_CLASS = 'covered-line-highlight';

ngOnInit(): void {
this.setupEditor();
this.renderFile();
this.editorHeight = this.editor.getContentHeight();
}

ngOnChanges(changes: SimpleChanges): void {
Expand All @@ -48,6 +52,7 @@ export class TestwiseCoverageFileComponent implements OnInit, OnChanges {

// set the covered line ratio accordingly
this.proportionCoveredLines = orderedLines.length / this.fileReport!.lineCount!;
this.proportionString = `${(this.proportionCoveredLines * 100).toFixed(1)} %`;

let index = 0;
while (index < orderedLines.length) {
Expand Down Expand Up @@ -76,25 +81,11 @@ export class TestwiseCoverageFileComponent implements OnInit, OnChanges {
return [...Array(lineCount).keys()].map((i) => i + startLine - 1);
}

private setupEditor(): void {
this.editor.getEditor().setOptions({
animatedScroll: true,
maxLines: Infinity,
highlightActiveLine: false,
showPrintMargin: false,
});
ace.Range = ace.acequire('ace/range').Range;
}

private renderFile() {
const session = this.editor.getEditor().getSession();
session.setValue(this.fileContent ?? '');

Object.entries(session.getMarkers() ?? {}).forEach(([, v]) => session.removeMarker((v as any).id));

this.editor.changeModel(this.fileName, this.fileContent ?? '');
this.editor.disposeLineHighlights();
this.aggregateCoveredLinesBlocks(this.fileReport).forEach((blockLength, lineNumber) => {
const range = new ace.Range(lineNumber, 0, lineNumber + blockLength - 1, 1);
session.addMarker(range, 'ace_highlight-marker', 'fullLine');
this.editor.highlightLines(lineNumber + 1, lineNumber + blockLength, 'covered-line-highlight', 'covered-line-highlight');
});
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { NgModule } from '@angular/core';
import { ArtemisSharedModule } from 'app/shared/shared.module';
import { AceEditorModule } from 'app/shared/markdown-editor/ace-editor/ace-editor.module';
import { TestwiseCoverageReportModalComponent } from 'app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report-modal.component';
import { TestwiseCoverageReportComponent } from 'app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report.component';
import { TestwiseCoverageFileComponent } from 'app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-file.component';
import { MatExpansionModule } from '@angular/material/expansion';
import { MonacoEditorModule } from 'app/shared/monaco-editor/monaco-editor.module';

@NgModule({
imports: [ArtemisSharedModule, AceEditorModule, MatExpansionModule],
imports: [ArtemisSharedModule, MatExpansionModule, MonacoEditorModule],
declarations: [TestwiseCoverageFileComponent, TestwiseCoverageReportComponent, TestwiseCoverageReportModalComponent],
exports: [TestwiseCoverageFileComponent, TestwiseCoverageReportModalComponent, TestwiseCoverageReportComponent],
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
@for (solutionEntry of sortedSolutionEntries; track solutionEntry) {
<div>
<div class="d-flex flex-row mb-3">
<jhi-solution-entry class="flex-grow-1" [solutionEntry]="solutionEntry" [enableEditing]="enableEditing" (onRemoveEntry)="removeEntryFromCodeHint(solutionEntry.id!)" />
<div class="d-flex flex-column gap-1 mb-4">
<jhi-solution-entry [solutionEntry]="solutionEntry" [enableEditing]="enableEditing" (onRemoveEntry)="removeEntryFromCodeHint(solutionEntry.id!)" />
@if (enableEditing) {
<div class="ps-2 flex-grow-0">
<div>
<button
jhiDeleteButton
[entityTitle]="solutionEntry.id!.toString()"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { NgModule } from '@angular/core';
import { ArtemisSharedModule } from 'app/shared/shared.module';
import { CastToCodeHintPipe } from 'app/exercises/shared/exercise-hint/services/code-hint-cast.pipe';
import { SolutionEntryComponent } from 'app/exercises/shared/exercise-hint/shared/solution-entry.component';
import { AceEditorModule } from 'app/shared/markdown-editor/ace-editor/ace-editor.module';
import { CodeHintContainerComponent } from 'app/exercises/shared/exercise-hint/shared/code-hint-container.component';
import { ArtemisMarkdownModule } from 'app/shared/markdown.module';
import { MonacoEditorModule } from 'app/shared/monaco-editor/monaco-editor.module';

@NgModule({
imports: [ArtemisSharedModule, AceEditorModule, ArtemisMarkdownModule],
imports: [ArtemisSharedModule, ArtemisMarkdownModule, MonacoEditorModule],
declarations: [SolutionEntryComponent, CodeHintContainerComponent, CastToCodeHintPipe],
exports: [SolutionEntryComponent, CodeHintContainerComponent, CastToCodeHintPipe],
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<div class="col-6">
<h6>{{ solutionEntry.filePath }}</h6>
</div>
<jhi-ace-editor
#editor
[mode]="'java'"
[readOnly]="!enableEditing"
[hidden]="false"
[autoUpdateContent]="true"
(textChange)="onEditorContentChange($event)"
class="jhi-solution-entry-editor"
/>
<div class="solution-entry-editor" [style.height.px]="editorHeight">
<jhi-monaco-editor
#editor
[readOnly]="!enableEditing"
[shrinkToFit]="false"
(contentHeightChanged)="onContentSizeChange($event)"
(textChanged)="onEditorContentChange($event)"
/>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.solution-entry-editor {
border: 1px solid var(--border-color);
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

import { AceEditorComponent } from 'app/shared/markdown-editor/ace-editor/ace-editor.component';
import { faTimes } from '@fortawesome/free-solid-svg-icons';
import { ProgrammingExerciseSolutionEntry } from 'app/entities/hestia/programming-exercise-solution-entry.model';
import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component';

@Component({
selector: 'jhi-solution-entry',
templateUrl: './solution-entry.component.html',
styleUrls: ['./solution-entry.component.scss'],
})
export class SolutionEntryComponent implements OnInit {
@ViewChild('editor', { static: true })
editor: AceEditorComponent;
editor: MonacoEditorComponent;

@Input()
solutionEntry: ProgrammingExerciseSolutionEntry;
Expand All @@ -21,40 +20,28 @@ export class SolutionEntryComponent implements OnInit {
@Output()
onRemoveEntry: EventEmitter<void> = new EventEmitter();

faTimes = faTimes;
editorHeight = 20;

constructor(protected route: ActivatedRoute) {}
protected readonly faTimes = faTimes;

ngOnInit() {
ngOnInit(): void {
this.setupEditor();
}

emitRemovalEvent() {
this.onRemoveEntry.emit();
onEditorContentChange(value: string): void {
this.solutionEntry.code = value;
}

onEditorContentChange(value: any) {
this.solutionEntry.code = value;
onContentSizeChange(contentHeight: number): void {
this.editorHeight = contentHeight;
}

private setupEditor() {
const line = this.solutionEntry.line;

this.editor.getEditor().setOptions({
animatedScroll: true,
maxLines: Infinity,
showPrintMargin: false,
});
// Ensure that the line counter is according to the solution entry
this.editor.getEditor().session.gutterRenderer = {
getWidth(session: any, lastLineNumber: number, config: any) {
return this.getText(session, lastLineNumber).toString().length * config.characterWidth;
},
getText(session: any, row: number): string | number {
return !line ? '' : row + line;
},
};
this.editor.getEditor().getSession().setValue(this.solutionEntry.code);
this.editor.getEditor().resize();
setupEditor(): void {
const startLine = this.solutionEntry.line ?? 1;
this.editor.setStartLineNumber(startLine);
this.editor.changeModel(this.solutionEntry.filePath ?? 'file', this.solutionEntry.code ?? '');
this.editor.layout();
// We manually fetch the initial content height, as the editor does not provide it immediately
this.editorHeight = this.editor.getContentHeight();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ export class MonacoEditorComponent implements OnInit, OnDestroy {
return this._editor.getValue();
}

getContentHeight(): number {
return this._editor.getContentHeight() + this._editor.getOption(monaco.editor.EditorOption.lineHeight);
}

setText(text: string): void {
if (this.getText() !== text) {
this._editor.setValue(text);
Expand Down Expand Up @@ -379,6 +383,16 @@ export class MonacoEditorComponent implements OnInit, OnDestroy {
});
}

/**
* Sets the line number from which the editor should start counting.
* @param startLineNumber The line number to start counting from (starting at 1).
*/
setStartLineNumber(startLineNumber: number): void {
this._editor.updateOptions({
lineNumbers: (number) => `${startLineNumber + number - 1}`,
});
}
pzdr7 marked this conversation as resolved.
Show resolved Hide resolved

/**
* Enables a text field mode for the editor. This will make the editor look more like a text field and less like a code editor.
* In particular, line numbers, margins, and highlights will be disabled.
Expand Down
1 change: 1 addition & 0 deletions src/main/webapp/content/scss/themes/_dark-variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ $monaco-editor-build-annotation-outdated-background: scale-color($gray-500, $alp
$monaco-editor-build-annotation-outdated-glyph: $gray-500;
$monaco-editor-diff-highlight-green: $success;
$monaco-editor-diff-line-highlight: scale-color($monaco-editor-diff-highlight-green, $alpha: -80%, $lightness: -20%);
$monaco-editor-test-coverage-highlight: scale-color($warning, $alpha: -80%);
$monaco-editor-add-feedback-button-background: $gray-600;
$monaco-editor-add-feedback-button-hover-background: lighten($gray-600, 10%);
$monaco-editor-add-feedback-button-text: $gray-300;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ $monaco-editor-build-annotation-outdated-background: scale-color($gray-500, $alp
$monaco-editor-build-annotation-outdated-glyph: $gray-500;
$monaco-editor-diff-highlight-green: rgb(63, 185, 80);
$monaco-editor-diff-line-highlight: scale-color($monaco-editor-diff-highlight-green, $alpha: -60%);
$monaco-editor-test-coverage-highlight: scale-color($warning, $alpha: -80%);
$monaco-editor-add-feedback-button-background: $gray-400;
$monaco-editor-add-feedback-button-hover-background: $gray-500;
$monaco-editor-add-feedback-button-text: $black;
Expand Down
Loading
Loading