/* * Copyright (c) 2021 joshua stein * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "amend.h" #include "browser.h" #include "committer.h" #include "diff.h" #include "focusable.h" #include "repo.h" #include "settings.h" #include "tetab.h" #include "util.h" #define LABEL_FONT geneva #define LABEL_FONT_SIZE 10 #define PADDING 10 /* needed by diffreg */ struct stat stb1, stb2; long diff_format, diff_context, status = 0; char *ifdefname, *diffargs, *label[2], *ignore_pats; struct committer *committer_diffing = NULL; bool committer_close(struct focusable *focusable); void committer_idle(struct focusable *focusable, EventRecord *event); void committer_update(struct focusable *focusable, EventRecord *event); void committer_suspend(struct focusable *focusable); void committer_resume(struct focusable *focusable); void committer_key_down(struct focusable *focusable, EventRecord *event); void committer_mouse_down(struct focusable *focusable, EventRecord *event); bool committer_handle_menu(struct focusable *focusable, short menu, short item); void committer_generate_diff(struct committer *committer); void committer_update_menu(struct committer *committer); void committer_commit(struct committer *committer); void diff_append_line(char *str, size_t len, bool flush); void diff_chunk_write(void); void diff_finish(void); void committer_init(struct browser *browser) { Str255 title, filename; struct committer *committer; struct focusable *focusable; Rect bounds = { 0 }, te_bounds = { 0 }; TextStyle style; short fh; committer = xmalloczero(sizeof(struct committer), "committer_init"); committer->browser = browser; browser->committer = committer; /* main window, centered in its browser */ bounds = (*(((WindowPeek)browser->win)->strucRgn))->rgnBBox; bounds.left += (PADDING / 2); bounds.top += ((PADDING / 2) + MBarHeight); bounds.right -= (PADDING / 2); bounds.bottom -= (PADDING / 2); memcpy(&filename, browser->repo->bile->filename, sizeof(filename)); PtoCstr(filename); snprintf((char *)&title, sizeof(title), "%s: %s: diff", PROGRAM_NAME, (browser->repo ? (char *)filename : "No repo open")); committer->win = NewWindow(0L, &bounds, CtoPstr(title), false, noGrowDocProc, (WindowPtr)-1L, true, 0); if (!committer) err(1, "Can't create committer window"); SetPort(committer->win); /* log message */ bounds.top = PADDING; bounds.left = 50; fh = FontHeight(monaco, 9); bounds.bottom = bounds.top + (fh * 5) + 2; bounds.right = committer->win->portRect.right - SCROLLBAR_WIDTH - PADDING; te_bounds = bounds; InsetRect(&te_bounds, 2, 2); TextFont(monaco); TextSize(9); committer->log_te = TEStylNew(&te_bounds, &bounds); style.tsFont = monaco; style.tsSize = 9; TESetStyle(doFont | doSize, &style, false, committer->log_te); TEAutoView(true, committer->log_te); TETabEnable(committer->log_te); TEActivate(committer->log_te); /* scrollbar for log message */ bounds.left = bounds.right; bounds.right += SCROLLBAR_WIDTH; bounds.bottom++; bounds.top--; committer->log_scroller = NewControl(committer->win, &bounds, "\p", true, 1, 1, 1, scrollBarProc, 0L); /* diff */ bounds.top = bounds.bottom + PADDING; bounds.left = PADDING; bounds.bottom = committer->win->portRect.bottom - 20 - PADDING - PADDING; bounds.right = committer->win->portRect.right - SCROLLBAR_WIDTH - PADDING; te_bounds = bounds; InsetRect(&te_bounds, 2, 2); committer->diff_te = TEStylNew(&te_bounds, &bounds); TEAutoView(true, committer->diff_te); TETabEnable(committer->diff_te); (*(committer->diff_te))->caretHook = NullCaretHook; TEActivate(committer->diff_te); /* scrollbar for diff */ bounds.left = bounds.right; bounds.right += SCROLLBAR_WIDTH; bounds.bottom++; bounds.top--; committer->diff_scroller = NewControl(committer->win, &bounds, "\p", true, 1, 1, 1, scrollBarProc, 0L); /* commit button */ bounds.left = committer->win->portRect.right - PADDING - 100; bounds.right = bounds.left + 100; bounds.bottom = committer->win->portRect.bottom - PADDING; bounds.top = bounds.bottom - 20; committer->commit_button = NewControl(committer->win, &bounds, "\pCommit", true, 1, 1, 1, pushButProc, 0L); committer->last_te = committer->log_te; focusable = xmalloczero(sizeof(struct focusable), "committer focusable"); focusable->cookie = committer; focusable->win = committer->win; focusable->modal = true; focusable->idle = committer_idle; focusable->update = committer_update; focusable->mouse_down = committer_mouse_down; focusable->key_down = committer_key_down; focusable->menu = committer_handle_menu; focusable->close = committer_close; focusable_add(focusable); committer->state = COMMITTER_STATE_DO_DIFF; } bool committer_close(struct focusable *focusable) { struct committer *committer = (struct committer *)focusable->cookie; committer->browser->committer = NULL; if (committer->diff_line != NULL) { DisposHandle(committer->diff_line); committer->diff_line = NULL; } if (committer->diffed_files != NULL) xfree(&committer->diffed_files); TEDispose(committer->log_te); TEDispose(committer->diff_te); DisposeWindow(committer->win); xfree(&committer); return true; } void committer_idle(struct focusable *focusable, EventRecord *event) { struct committer *committer = (struct committer *)focusable->cookie; switch (committer->state) { case COMMITTER_STATE_IDLE: if (committer->last_te == committer->log_te) TEIdle(committer->log_te); break; case COMMITTER_STATE_DO_DIFF: committer_generate_diff(committer); committer->state = COMMITTER_STATE_IDLE; break; case COMMITTER_STATE_DO_COMMIT: break; } } void committer_update(struct focusable *focusable, EventRecord *event) { Str255 buf; Rect r; short what = -1, len; struct committer *committer = (struct committer *)focusable->cookie; if (event != NULL) what = event->what; switch (what) { case -1: case updateEvt: r = (*(committer->log_te))->viewRect; MoveTo(r.top, r.top + FontHeight(LABEL_FONT, LABEL_FONT_SIZE) - 2); TextFont(LABEL_FONT); TextSize(LABEL_FONT_SIZE); DrawText("Log:", 0, 4); r = (*(committer->log_te))->viewRect; TEUpdate(&r, committer->log_te); InsetRect(&r, -1, -1); FrameRect(&r); r = (*(committer->diff_te))->viewRect; TEUpdate(&r, committer->diff_te); InsetRect(&r, -1, -1); FrameRect(&r); if ((*(committer->diff_te))->nLines > 0) { r = (*(committer->diff_te))->viewRect; MoveTo(r.left, r.bottom + FontHeight(monaco, 9) + PADDING); TextFont(monaco); TextSize(9); len = snprintf((char *)buf, sizeof(buf), "%d (+), %d (-)", committer->diff_adds, committer->diff_subs); DrawText(buf, 0, len); } committer_update_menu(committer); UpdtControl(committer->win, committer->win->visRgn); break; case activateEvt: if (event->modifiers & activeFlag) { TEActivate(committer->log_te); TEActivate(committer->diff_te); } else { TEDeactivate(committer->log_te); TEDeactivate(committer->diff_te); } break; } } void committer_suspend(struct focusable *focusable) { struct committer *committer = (struct committer *)focusable->cookie; TEDeactivate(committer->log_te); TEDeactivate(committer->diff_te); } void committer_resume(struct focusable *focusable) { struct committer *committer = (struct committer *)focusable->cookie; TEActivate(committer->log_te); TEActivate(committer->diff_te); } void committer_key_down(struct focusable *focusable, EventRecord *event) { struct committer *committer = (struct committer *)focusable->cookie; char k; k = event->message & charCodeMask; if ((event->modifiers & cmdKey) != 0) { if (k == 'w') focusable_close(focusable); return; } TEKey(k, committer->log_te); UpdateScrollbarForTE(committer->win, committer->log_scroller, committer->log_te, false); committer_update_menu(committer); } void committer_mouse_down(struct focusable *focusable, EventRecord *event) { struct committer *committer = (struct committer *)focusable->cookie; Point p; ControlHandle control; Rect r; short val, adj; p = event->where; GlobalToLocal(&p); r = (*(committer->diff_te))->viewRect; if (PtInRect(p, &r)) { TEClick(p, ((event->modifiers & shiftKey) != 0), committer->diff_te); committer->last_te = committer->diff_te; committer_update_menu(committer); return; } r = (*(committer->log_te))->viewRect; if (PtInRect(p, &r)) { TEClick(p, ((event->modifiers & shiftKey) != 0), committer->log_te); committer->last_te = committer->log_te; committer_update_menu(committer); return; } switch (FindControl(p, committer->win, &control)) { case inButton: if (TrackControl(control, p, 0L) && control == committer->commit_button) committer_commit(committer); break; case inUpButton: case inDownButton: case inPageUp: case inPageDown: if (control == committer->diff_scroller) SetTrackControlTE(committer->diff_te); else if (control == committer->log_scroller) SetTrackControlTE(committer->log_te); else break; TrackControl(control, p, TrackMouseDownInControl); break; case inThumb: val = GetCtlValue(control); if (TrackControl(control, p, 0L) == 0) break; adj = val - GetCtlValue(control); if (adj != 0) { val -= adj; if (control == committer->diff_scroller) TEScroll(0, adj * TEGetHeight(0, 0, committer->diff_te), committer->diff_te); else if (control == committer->log_scroller) TEScroll(0, adj * TEGetHeight(0, 0, committer->log_te), committer->log_te); SetCtlValue(control, val); } break; } } void committer_update_menu(struct committer *committer) { HLock(committer->diff_te); HLock(committer->log_te); if (committer->last_te == committer->diff_te) { DisableItem(edit_menu, EDIT_MENU_CUT_ID); if ((*(committer->diff_te))->selStart == (*(committer->diff_te))->selEnd) DisableItem(edit_menu, EDIT_MENU_COPY_ID); else EnableItem(edit_menu, EDIT_MENU_COPY_ID); if ((*(committer->diff_te))->nLines > 0) EnableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID); else DisableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID); DisableItem(edit_menu, EDIT_MENU_PASTE_ID); } else if (committer->last_te == committer->log_te) { if ((*(committer->log_te))->selStart == (*(committer->log_te))->selEnd) { DisableItem(edit_menu, EDIT_MENU_CUT_ID); DisableItem(edit_menu, EDIT_MENU_COPY_ID); } else { EnableItem(edit_menu, EDIT_MENU_CUT_ID); EnableItem(edit_menu, EDIT_MENU_COPY_ID); } if ((*(committer->log_te))->nLines > 0) EnableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID); else DisableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID); EnableItem(edit_menu, EDIT_MENU_PASTE_ID); } DisableItem(repo_menu, REPO_MENU_ADD_FILE_ID); DisableItem(repo_menu, REPO_MENU_DISCARD_CHANGES_ID); DisableItem(repo_menu, REPO_MENU_APPLY_DIFF_ID); DisableItem(amendment_menu, AMENDMENT_MENU_EDIT_ID); DisableItem(amendment_menu, AMENDMENT_MENU_EXPORT_ID); HUnlock(committer->log_te); HUnlock(committer->diff_te); EnableItem(repo_menu, 0); if ((*(committer->log_te))->nLines > 0 && committer->allow_commit) HiliteControl(committer->commit_button, 0); else HiliteControl(committer->commit_button, 255); } void committer_generate_diff(struct committer *committer) { struct repo_file *file; short i, all_files; short *selected_files = NULL; short nselected_files = 0; TextStyle style; SetCursor(*(GetCursor(watchCursor))); nselected_files = browser_selected_file_ids(committer->browser, &selected_files); /* default to unified diffs (should this be a setting?) */ diff_format = D_UNIFIED; diff_context = 3; committer->diff_adds = 0; committer->diff_subs = 0; committer->ndiffed_files = 0; committer->allow_commit = false; committer->diffed_files = xcalloc(sizeof(struct diffed_file), nselected_files, "committer diffed_files"); committer->diff_too_big = false; HLock(committer->diff_te); style.tsFont = monaco; style.tsSize = 9; TESetStyle(doFont | doSize, &style, false, committer->diff_te); all_files = browser_is_all_files_selected(committer->browser); committer_diffing = committer; for (i = 0; i < nselected_files; i++) { file = repo_file_with_id(committer->browser->repo, selected_files[i]); if (file == NULL) err(1, "Failed to find file in repo with id %d", selected_files[i]); if (all_files && !repo_file_changed(committer->browser->repo, file)) { progress("Skipping unchanged %s...", file->filename); continue; } committer->diffed_files[committer->ndiffed_files].file = file; committer->diffed_files[committer->ndiffed_files].flags = DIFFED_FILE_METADATA; progress("Diffing %s...", file->filename); if (repo_diff_file(committer->browser->repo, file)) { committer->diffed_files[committer->ndiffed_files].flags |= DIFFED_FILE_TEXT; committer->allow_commit = true; } diff_finish(); committer->ndiffed_files++; } committer_diffing = NULL; HUnlock(committer->diff_te); InvalRect(&committer->win->portRect); UpdateScrollbarForTE(committer->win, committer->diff_scroller, committer->diff_te, true); progress(NULL); done_diffing: if (selected_files != NULL) xfree(&selected_files); SetCursor(&arrow); if (!committer->allow_commit) { warnx("No changes detected"); browser_close_committer(committer->browser); } } void committer_commit(struct committer *committer) { struct browser *browser; short loglen; HLock(committer->log_te); HLock(committer->diff_te); loglen = (*(committer->log_te))->teLength; SetCursor(*(GetCursor(watchCursor))); progress("Committing changes..."); repo_amend(committer->browser->repo, committer->diffed_files, committer->ndiffed_files, committer->diff_adds, committer->diff_subs, settings.author, (*(committer->log_te))->hText, loglen, (*(committer->diff_te))->hText, committer->diff_te_len); HUnlock(committer->diff_te); HUnlock(committer->log_te); progress(NULL); SetCursor(&arrow); browser = committer->browser; browser_close_committer(committer->browser); browser->state = BROWSER_STATE_UPDATE_AMENDMENT_LIST; } bool committer_handle_menu(struct focusable *focusable, short menu, short item) { struct committer *committer = (struct committer *)focusable->cookie; switch (menu) { case EDIT_MENU_ID: switch (item) { case EDIT_MENU_CUT_ID: if (committer->last_te == committer->log_te) { TECut(committer->log_te); UpdateScrollbarForTE(committer->win, committer->log_scroller, committer->log_te, false); committer_update_menu(committer); } return true; case EDIT_MENU_COPY_ID: if (committer->last_te) TECopy(committer->last_te); committer_update_menu(committer); return true; case EDIT_MENU_PASTE_ID: if (committer->last_te == committer->log_te) { TEPaste(committer->log_te); UpdateScrollbarForTE(committer->win, committer->log_scroller, committer->log_te, false); committer_update_menu(committer); } return true; case EDIT_MENU_SELECT_ALL_ID: if (committer->last_te) TESetSelect(0, 1024 * 32, committer->last_te); committer_update_menu(committer); return true; } break; } return false; } size_t diff_output(const char *format, ...) { va_list argptr; size_t len, last_pos, last_line, i; if (committer_diffing == NULL) panic("diff_output without committer_diffing"); if (committer_diffing->diff_line == NULL) { committer_diffing->diff_line = xNewHandle(DIFF_LINE_SIZE); committer_diffing->diff_line_pos = 0; HLock(committer_diffing->diff_line); } last_pos = committer_diffing->diff_line_pos; last_line = 0; va_start(argptr, format); if (format[0] == '%' && format[1] == 'c' && format[2] == '\0') { /* avoid having to vsprintf just to append 1 character */ (*(committer_diffing->diff_line))[last_pos] = va_arg(argptr, int); len = 1; } else len = vsprintf(*(committer_diffing->diff_line) + last_pos, format, argptr); va_end(argptr); committer_diffing->diff_line_pos += len; if (committer_diffing->diff_line_pos >= DIFF_LINE_SIZE) err(1, "diff line overflow!"); if (len == 1 && (*(committer_diffing->diff_line))[last_pos] != '\r') return 1; for (i = last_pos; i < committer_diffing->diff_line_pos; i++) { if (((char *)*(committer_diffing->diff_line))[i] == '\r') { diff_append_line(*(committer_diffing->diff_line) + last_line, i - last_line + 1, false); last_line = i + 1; } } if (last_line == committer_diffing->diff_line_pos) { committer_diffing->diff_line_pos = 0; } else if (last_line > 0) { memmove(*(committer_diffing->diff_line), *(committer_diffing->diff_line) + last_line, committer_diffing->diff_line_pos - last_line); committer_diffing->diff_line_pos -= last_line; } return len; } void diff_append_line(char *str, size_t len, bool flush) { if (committer_diffing == NULL) panic("diff_append_line without committer_diffing"); if (committer_diffing->diff_chunk == NULL) { committer_diffing->diff_chunk = xNewHandle(DIFF_CHUNK_SIZE); committer_diffing->diff_chunk_pos = 0; } if (str[0] == '-' && str[1] != '-') committer_diffing->diff_subs++; else if (str[0] == '+' && str[1] != '+') committer_diffing->diff_adds++; if (committer_diffing->diff_chunk_pos + len >= DIFF_CHUNK_SIZE) diff_chunk_write(); HLock(committer_diffing->diff_chunk); memcpy(*(committer_diffing->diff_chunk) + committer_diffing->diff_chunk_pos, str, len); HUnlock(committer_diffing->diff_chunk); committer_diffing->diff_chunk_pos += len; if (flush) diff_chunk_write(); } void diff_chunk_write(void) { if (committer_diffing == NULL) panic("diff_chunk_write without committer_diffing"); HLock(committer_diffing->diff_chunk); if (committer_diffing->diff_te_len + committer_diffing->diff_chunk_pos > MAX_TEXTEDIT_SIZE) { HUnlock((*(committer_diffing->diff_te))->hText); SetHandleSize((*(committer_diffing->diff_te))->hText, committer_diffing->diff_te_len + committer_diffing->diff_chunk_pos); if (MemError()) err(1, "Out of memory! Can't expand diff TE by %lu bytes.", committer_diffing->diff_chunk_pos); HLock((*(committer_diffing->diff_te))->hText); memcpy(*(*(committer_diffing->diff_te))->hText + committer_diffing->diff_te_len, *(committer_diffing->diff_chunk), committer_diffing->diff_chunk_pos); HUnlock((*(committer_diffing->diff_te))->hText); } else { TEStylInsert(*(committer_diffing->diff_chunk), committer_diffing->diff_chunk_pos, 0, committer_diffing->diff_te); } HUnlock(committer_diffing->diff_chunk); committer_diffing->diff_te_len += committer_diffing->diff_chunk_pos; committer_diffing->diff_chunk_pos = 0; } void diff_finish(void) { if (committer_diffing == NULL) panic("diff_finish without committer_diffing"); if (committer_diffing->diff_line != NULL) { if (committer_diffing->diff_line_pos) diff_append_line(*(committer_diffing->diff_line), committer_diffing->diff_line_pos, true); DisposHandle(committer_diffing->diff_line); committer_diffing->diff_line = NULL; } if (committer_diffing->diff_chunk != NULL) { if (committer_diffing->diff_chunk_pos) diff_chunk_write(); DisposHandle(committer_diffing->diff_chunk); committer_diffing->diff_chunk = NULL; } HUnlock((*(committer_diffing->diff_te))->hText); SetHandleSize((*(committer_diffing->diff_te))->hText, committer_diffing->diff_te_len); TECalText(committer_diffing->diff_te); }