*&---------------------------------------------------------------------* *& Include ZABAPGIT_PAGE_DIFF *&---------------------------------------------------------------------* CLASS lcl_gui_page_diff DEFINITION FINAL INHERITING FROM lcl_gui_page. PUBLIC SECTION. CONSTANTS: BEGIN OF c_fstate, local TYPE char1 VALUE 'L', remote TYPE char1 VALUE 'R', both TYPE char1 VALUE 'B', END OF c_fstate. TYPES: BEGIN OF ty_file_diff, filename TYPE string, lstate TYPE char1, rstate TYPE char1, fstate TYPE char1, " FILE state - Abstraction for shorter ifs o_diff TYPE REF TO lcl_diff, END OF ty_file_diff, tt_file_diff TYPE STANDARD TABLE OF ty_file_diff. METHODS: constructor IMPORTING iv_key TYPE lcl_persistence_repo=>ty_repo-key is_file TYPE ty_file OPTIONAL is_object TYPE ty_item OPTIONAL RAISING lcx_exception, lif_gui_page~on_event REDEFINITION. PROTECTED SECTION. METHODS render_content REDEFINITION. PRIVATE SECTION. CONSTANTS: BEGIN OF c_actions, toggle_unified TYPE string VALUE 'toggle_unified', END OF c_actions. DATA: mt_diff_files TYPE tt_file_diff, mt_delayed_lines TYPE lcl_diff=>ty_diffs_tt, mv_unified TYPE abap_bool VALUE abap_true. METHODS render_diff IMPORTING is_diff TYPE ty_file_diff RETURNING VALUE(ro_html) TYPE REF TO lcl_html. METHODS render_diff_head IMPORTING is_diff TYPE ty_file_diff RETURNING VALUE(ro_html) TYPE REF TO lcl_html. METHODS render_table_head RETURNING VALUE(ro_html) TYPE REF TO lcl_html. METHODS render_lines IMPORTING is_diff TYPE ty_file_diff RETURNING VALUE(ro_html) TYPE REF TO lcl_html. METHODS render_beacon IMPORTING is_diff_line TYPE lcl_diff=>ty_diff is_diff TYPE ty_file_diff RETURNING VALUE(ro_html) TYPE REF TO lcl_html. METHODS render_line_split IMPORTING is_diff_line TYPE lcl_diff=>ty_diff iv_fstate TYPE char1 RETURNING VALUE(ro_html) TYPE REF TO lcl_html. METHODS render_line_unified IMPORTING is_diff_line TYPE lcl_diff=>ty_diff OPTIONAL RETURNING VALUE(ro_html) TYPE REF TO lcl_html. METHODS append_diff IMPORTING it_remote TYPE ty_files_tt it_local TYPE ty_files_item_tt is_status TYPE ty_result RAISING lcx_exception. METHODS build_menu RETURNING VALUE(ro_menu) TYPE REF TO lcl_html_toolbar. ENDCLASS. "lcl_gui_page_diff CLASS lcl_gui_page_diff IMPLEMENTATION. METHOD constructor. DATA: lt_remote TYPE ty_files_tt, lt_local TYPE ty_files_item_tt, lt_status TYPE ty_results_tt, lo_repo TYPE REF TO lcl_repo_online. FIELD-SYMBOLS: LIKE LINE OF lt_status. super->constructor( ). ms_control-page_title = 'DIFF'. ms_control-page_menu = build_menu( ). mv_unified = lcl_app=>user( )->get_diff_unified( ). ASSERT is_file IS INITIAL OR is_object IS INITIAL. " just one passed lo_repo ?= lcl_app=>repo_srv( )->get( iv_key ). lt_remote = lo_repo->get_files_remote( ). lt_local = lo_repo->get_files_local( ). lt_status = lo_repo->status( ). IF is_file IS NOT INITIAL. " Diff for one file READ TABLE lt_status ASSIGNING WITH KEY path = is_file-path filename = is_file-filename. append_diff( it_remote = lt_remote it_local = lt_local is_status = ). ELSEIF is_object IS NOT INITIAL. " Diff for whole object LOOP AT lt_status ASSIGNING WHERE obj_type = is_object-obj_type AND obj_name = is_object-obj_name AND match IS INITIAL. append_diff( it_remote = lt_remote it_local = lt_local is_status = ). ENDLOOP. ELSE. " Diff for the whole repo LOOP AT lt_status ASSIGNING WHERE match IS INITIAL. append_diff( it_remote = lt_remote it_local = lt_local is_status = ). ENDLOOP. ENDIF. IF lines( mt_diff_files ) = 0. lcx_exception=>raise( 'PAGE_DIFF ERROR: No diff files found' ). ENDIF. ENDMETHOD. METHOD append_diff. DATA: ls_r_dummy LIKE LINE OF it_remote ##NEEDED, ls_l_dummy LIKE LINE OF it_local ##NEEDED. FIELD-SYMBOLS: LIKE LINE OF it_remote, LIKE LINE OF it_local, LIKE LINE OF mt_diff_files. READ TABLE it_remote ASSIGNING WITH KEY filename = is_status-filename path = is_status-path. IF sy-subrc <> 0. ASSIGN ls_r_dummy TO . ENDIF. READ TABLE it_local ASSIGNING WITH KEY file-filename = is_status-filename file-path = is_status-path. IF sy-subrc <> 0. ASSIGN ls_l_dummy TO . ENDIF. IF IS INITIAL AND IS INITIAL. lcx_exception=>raise( |DIFF: file not found { is_status-filename }| ). ENDIF. APPEND INITIAL LINE TO mt_diff_files ASSIGNING . -filename = is_status-filename. -lstate = is_status-lstate. -rstate = is_status-rstate. IF -lstate IS NOT INITIAL AND -rstate IS NOT INITIAL. -fstate = c_fstate-both. ELSEIF -lstate IS NOT INITIAL. -fstate = c_fstate-local. ELSE. "rstate IS NOT INITIAL, lstate = empty. -fstate = c_fstate-remote. ENDIF. IF -fstate = c_fstate-remote. " Remote file leading changes CREATE OBJECT -o_diff EXPORTING iv_new = -data iv_old = -file-data. ELSE. " Local leading changes or both were modified CREATE OBJECT -o_diff EXPORTING iv_new = -file-data iv_old = -data. ENDIF. ENDMETHOD. "append_diff METHOD build_menu. CREATE OBJECT ro_menu. ro_menu->add( iv_txt = 'Split/Unified view' iv_act = c_actions-toggle_unified ) ##NO_TEXT. ENDMETHOD. " build_menu. ********************************************************************** * EVENT HANDLING ********************************************************************** METHOD lif_gui_page~on_event. CASE iv_action. WHEN c_actions-toggle_unified. " Toggle file diplay mv_unified = lcl_app=>user( )->toggle_diff_unified( ). ev_state = gc_event_state-re_render. ENDCASE. ENDMETHOD. "lif_gui_page~on_event ********************************************************************** * RENDER LOGIC ********************************************************************** METHOD render_content. DATA ls_diff_file LIKE LINE OF mt_diff_files. CREATE OBJECT ro_html. LOOP AT mt_diff_files INTO ls_diff_file. lcl_progress=>show( iv_key = 'Diff' iv_current = sy-tabix iv_total = lines( mt_diff_files ) iv_text = |Render Diff - { ls_diff_file-filename }| ). ro_html->add( render_diff( ls_diff_file ) ). ENDLOOP. ENDMETHOD. "render_content METHOD render_diff. CREATE OBJECT ro_html. ro_html->add( '
' ). "#EC NOTEXT ro_html->add( render_diff_head( is_diff ) ). " Content ro_html->add( '
' ). "#EC NOTEXT ro_html->add( '' ). "#EC NOTEXT ro_html->add( render_table_head( ) ). ro_html->add( render_lines( is_diff ) ). ro_html->add( '
' ). "#EC NOTEXT ro_html->add( '
' ). "#EC NOTEXT ro_html->add( '
' ). "#EC NOTEXT ENDMETHOD. " render_diff ********************************************************************** * CHUNKS ********************************************************************** METHOD render_diff_head. DATA: ls_stats TYPE lcl_diff=>ty_count. CREATE OBJECT ro_html. ls_stats = is_diff-o_diff->stats( ). IF is_diff-fstate = c_fstate-both. " Merge stats into 'update' if both were changed ls_stats-update = ls_stats-update + ls_stats-insert + ls_stats-delete. CLEAR: ls_stats-insert, ls_stats-delete. ENDIF. ro_html->add( '
' ). "#EC NOTEXT ro_html->add( |+ { ls_stats-insert }| ). ro_html->add( |- { ls_stats-delete }| ). ro_html->add( |~ { ls_stats-update }| ). ro_html->add( |{ is_diff-filename }| ). "#EC NOTEXT ro_html->add( lcl_gui_chunk_lib=>render_item_state( iv1 = is_diff-lstate iv2 = is_diff-rstate ) ). IF is_diff-fstate = c_fstate-both AND mv_unified = abap_true. ro_html->add( 'Attention: Unified mode' && ' highlighting for MM assumes local file is newer ! ' ). "#EC NOTEXT ENDIF. ro_html->add( '
' ). "#EC NOTEXT ENDMETHOD. METHOD render_table_head. CREATE OBJECT ro_html. IF mv_unified = abap_true. ro_html->add( '' ). "#EC NOTEXT ro_html->add( '' ). "#EC NOTEXT ro_html->add( 'old' ). "#EC NOTEXT ro_html->add( 'new' ). "#EC NOTEXT ro_html->add( 'code' ). "#EC NOTEXT ro_html->add( '' ). "#EC NOTEXT ro_html->add( '' ). "#EC NOTEXT ELSE. ro_html->add( '' ). "#EC NOTEXT ro_html->add( '' ). "#EC NOTEXT ro_html->add( '' ). "#EC NOTEXT ro_html->add( 'LOCAL' ). "#EC NOTEXT ro_html->add( '' ). "#EC NOTEXT ro_html->add( 'REMOTE' ). "#EC NOTEXT ro_html->add( '' ). "#EC NOTEXT ro_html->add( '' ). "#EC NOTEXT ENDIF. ENDMETHOD. " render_table_head. METHOD render_beacon. DATA: lv_beacon TYPE string. CREATE OBJECT ro_html. IF is_diff_line-beacon > 0. READ TABLE is_diff-o_diff->mt_beacons INTO lv_beacon INDEX is_diff_line-beacon. ELSE. lv_beacon = '---'. ENDIF. ro_html->add( '' ). ro_html->add( '' ). IF mv_unified = abap_true. ro_html->add( '' ). ro_html->add( '' ). ro_html->add( |@@ { is_diff_line-new_num } @@ { lv_beacon }| ). ELSE. ro_html->add( '' ). ro_html->add( |@@ { is_diff_line-new_num } @@ { lv_beacon }| ). ENDIF. ro_html->add( '' ). ro_html->add( '' ). ENDMETHOD. " render_beacon. METHOD render_lines. DATA: lo_highlighter TYPE REF TO lcl_syntax_highlighter, lt_diffs TYPE lcl_diff=>ty_diffs_tt, lv_insert_nav TYPE abap_bool. FIELD-SYMBOLS LIKE LINE OF lt_diffs. lo_highlighter = lcl_syntax_highlighter=>create( is_diff-filename ). CREATE OBJECT ro_html. lt_diffs = is_diff-o_diff->get( ). LOOP AT lt_diffs ASSIGNING . IF -short = abap_false. lv_insert_nav = abap_true. CONTINUE. ENDIF. IF lv_insert_nav = abap_true. " Insert separator line with navigation ro_html->add( render_beacon( is_diff_line = is_diff = is_diff ) ). lv_insert_nav = abap_false. ENDIF. IF lo_highlighter IS BOUND. -new = lo_highlighter->process_line( -new ). -old = lo_highlighter->process_line( -old ). ELSE. -new = escape( val = -new format = cl_abap_format=>e_html_attr ). -old = escape( val = -old format = cl_abap_format=>e_html_attr ). ENDIF. CONDENSE -new_num. "get rid of leading spaces CONDENSE -old_num. IF mv_unified = abap_true. ro_html->add( render_line_unified( is_diff_line = ) ). ELSE. ro_html->add( render_line_split( is_diff_line = iv_fstate = is_diff-fstate ) ). ENDIF. ENDLOOP. IF mv_unified = abap_true. ro_html->add( render_line_unified( ) ). " Release delayed lines ENDIF. ENDMETHOD. "render_lines METHOD render_line_split. DATA: lv_new TYPE string, lv_old TYPE string, lv_mark TYPE string, lv_bg TYPE string. CREATE OBJECT ro_html. " New line lv_mark = ` `. IF iv_fstate = c_fstate-both OR is_diff_line-result = lcl_diff=>c_diff-update. lv_bg = ' diff_upd'. lv_mark = `~`. ELSEIF is_diff_line-result = lcl_diff=>c_diff-insert. lv_bg = ' diff_ins'. lv_mark = `+`. ENDIF. lv_new = || && |{ lv_mark }{ is_diff_line-new }|. " Old line CLEAR lv_bg. lv_mark = ` `. IF iv_fstate = c_fstate-both OR is_diff_line-result = lcl_diff=>c_diff-update. lv_bg = ' diff_upd'. lv_mark = `~`. ELSEIF is_diff_line-result = lcl_diff=>c_diff-delete. lv_bg = ' diff_del'. lv_mark = `-`. ENDIF. lv_old = || && |{ lv_mark }{ is_diff_line-old }|. " render line, inverse sides if remote is newer ro_html->add( '' ). "#EC NOTEXT IF iv_fstate = c_fstate-remote. " Remote file leading changes ro_html->add( lv_old ). " local ro_html->add( lv_new ). " remote ELSE. " Local leading changes or both were modified ro_html->add( lv_new ). " local ro_html->add( lv_old ). " remote ENDIF. ro_html->add( '' ). "#EC NOTEXT ENDMETHOD. "render_line_split METHOD render_line_unified. DATA lv_line TYPE string. FIELD-SYMBOLS LIKE LINE OF mt_delayed_lines. CREATE OBJECT ro_html. " Release delayed subsequent update lines IF is_diff_line-result <> lcl_diff=>c_diff-update. LOOP AT mt_delayed_lines ASSIGNING . ro_html->add( '' ). "#EC NOTEXT ro_html->add( || && || && |-{ -old }| ). ro_html->add( '' ). "#EC NOTEXT ENDLOOP. LOOP AT mt_delayed_lines ASSIGNING . ro_html->add( '' ). "#EC NOTEXT ro_html->add( || && || && |+{ -new }| ). ro_html->add( '' ). "#EC NOTEXT ENDLOOP. CLEAR mt_delayed_lines. ENDIF. ro_html->add( '' ). "#EC NOTEXT CASE is_diff_line-result. WHEN lcl_diff=>c_diff-update. APPEND is_diff_line TO mt_delayed_lines. " Delay output of subsequent updates WHEN lcl_diff=>c_diff-insert. ro_html->add( || && || && |+{ is_diff_line-new }| ). WHEN lcl_diff=>c_diff-delete. ro_html->add( || && || && |-{ is_diff_line-old }| ). WHEN OTHERS. "none ro_html->add( || && || && | { is_diff_line-old }| ). ENDCASE. ro_html->add( '' ). "#EC NOTEXT ENDMETHOD. "render_line_unified ENDCLASS. "lcl_gui_page_diff