diff --git a/.eslintignore b/.eslintignore index 7e8d61e..c98486f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,3 +3,4 @@ install/ flatpak/ node_modules/ repo/ +src/wordwrap.js diff --git a/.prettierignore b/.prettierignore index 7e8d61e..c98486f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,3 +3,4 @@ install/ flatpak/ node_modules/ repo/ +src/wordwrap.js diff --git a/TODO.md b/TODO.md index 9d92bfd..778580b 100644 --- a/TODO.md +++ b/TODO.md @@ -15,3 +15,4 @@ 14. [ ] detect non imperative mood https://tekin.co.uk/2020/03/git-commit-verbose-mode +https://wiki.gnome.org/Git/CommitMessages diff --git a/data/re.sonny.Commit.gschema.xml b/data/re.sonny.Commit.gschema.xml index f4d5d6b..14d7712 100644 --- a/data/re.sonny.Commit.gschema.xml +++ b/data/re.sonny.Commit.gschema.xml @@ -6,6 +6,13 @@ Message title length hint + + + 75 + + Message body length wrap + + false Use dark mode diff --git a/re.sonny.Commit.json b/re.sonny.Commit.json index 951d60c..87e1d39 100644 --- a/re.sonny.Commit.json +++ b/re.sonny.Commit.json @@ -87,7 +87,7 @@ "sources": [ { "type": "dir", - "path": "./" + "path": "." } ] } diff --git a/src/Editor.js b/src/CommitEditor.js similarity index 89% rename from src/Editor.js rename to src/CommitEditor.js index 1f69811..9e8ecef 100644 --- a/src/Editor.js +++ b/src/CommitEditor.js @@ -6,7 +6,7 @@ import Adw from "gi://Adw"; import { relativePath } from "./util.js"; -const file = Gio.File.new_for_path(relativePath("./Editor.ui")); +const file = Gio.File.new_for_path(relativePath("./CommitEditor.ui")); const [, template] = file.load_contents(null); const scheme_manager = GtkSource.StyleSchemeManager.get_default(); @@ -19,7 +19,7 @@ language_manager.set_search_path([ export default GObject.registerClass( { - GTypeName: "Editor", + GTypeName: "CommitEditor", Properties: { language: GObject.ParamSpec.string( "language", @@ -35,7 +35,7 @@ export default GObject.registerClass( "style-updated": {}, }, }, - class Editor extends Gtk.ScrolledWindow { + class CommitEditor extends Gtk.ScrolledWindow { _init(params = {}) { super._init(params); diff --git a/src/CommitEditor.ui b/src/CommitEditor.ui new file mode 100644 index 0000000..9d2a9b0 --- /dev/null +++ b/src/CommitEditor.ui @@ -0,0 +1,24 @@ + + + + diff --git a/src/Editor.ui b/src/Editor.ui deleted file mode 100644 index e75ed21..0000000 --- a/src/Editor.ui +++ /dev/null @@ -1,41 +0,0 @@ - - - - diff --git a/src/application.js b/src/application.js index 3ef7103..9db3b7b 100644 --- a/src/application.js +++ b/src/application.js @@ -111,7 +111,7 @@ function openEditor({ file, application, readonly }) { try { [, commitMessage] = GLib.file_get_contents(filePath); } catch (err) { - printerr(err); + logError(err); application.quit(); return; } diff --git a/src/editor.js b/src/editor.js index acec278..2e41dfd 100644 --- a/src/editor.js +++ b/src/editor.js @@ -2,10 +2,10 @@ import Gtk from "gi://Gtk"; import GLib from "gi://GLib"; import GtkSource from "gi://GtkSource"; -import Editor from "./Editor.js"; +import CommitEditor from "./CommitEditor.js"; import { settings } from "./util.js"; -import { parse, hasCommitMessage } from "./scm.js"; +import { hasCommitMessage } from "./scm.js"; const HIGHLIGHT_BACKGROUND_TAG_NAME = "highlightBackground"; @@ -13,19 +13,9 @@ export default function editor({ builder, commitButton, type, - commitMessage, window, + parsed, }) { - let parsed = {}; - - try { - parsed = parse(commitMessage, type); - } catch (err) { - if (__DEV__) { - logError(err); - } - } - const { body, comment, @@ -51,7 +41,7 @@ export default function editor({ let previousNumberOfLinesInCommitMessage = 1; const main = builder.get_object("main"); - const widget = new Editor({ language }); + const widget = new CommitEditor({ language }); main.append(widget); const source_view = widget.view; diff --git a/src/main.js b/src/main.js index c8cfcab..129a0a2 100644 --- a/src/main.js +++ b/src/main.js @@ -52,4 +52,4 @@ export default function main(argv, { version, datadir }) { } return application.run(argv); -} +} diff --git a/src/scm.js b/src/scm.js index f2f7d6d..458d3df 100644 --- a/src/scm.js +++ b/src/scm.js @@ -68,6 +68,8 @@ export function parse(commit, type) { capitalize = true; } + log(type); + return { body, comment, @@ -78,6 +80,13 @@ export function parse(commit, type) { read_only_index, language, capitalize, + wrap: [ + "hg", + "commit", + "git-merge-squash", + "git-rebase-squash", + "merge", + ].includes(type), }; } diff --git a/src/welcome.js b/src/welcome.js index 65ccebd..da779d0 100644 --- a/src/welcome.js +++ b/src/welcome.js @@ -12,15 +12,26 @@ export default function Welcome({ application }) { loadStyleSheet(relativePath("./style.css")); - const spinButton = builder.get_object("spinButton"); - spinButton.set_range(50, 200); - spinButton.set_increments(1, 10); + const button_hint = builder.get_object("button_hint"); + button_hint.set_range(50, 200); + button_hint.set_increments(1, 10); settings.bind( "title-length-hint", - spinButton, + button_hint, "value", Gio.SettingsBindFlags.DEFAULT, ); + + const button_wrap = builder.get_object("button_wrap"); + button_wrap.set_range(72, 200); + button_wrap.set_increments(1, 10); + settings.bind( + "body-length-wrap", + button_wrap, + "value", + Gio.SettingsBindFlags.DEFAULT, + ); + const darkSwitch = builder.get_object("darkSwitch"); Adw.StyleManager.get_default().bind_property( "dark", diff --git a/src/welcome.ui b/src/welcome.ui index 9837954..1bbf227 100644 --- a/src/welcome.ui +++ b/src/welcome.ui @@ -30,11 +30,30 @@ Message title length hint + >Commit title max length - + 50 - True + true + center + + + + + + + + + + + Message body max length + + + 75 + true center diff --git a/src/window.js b/src/window.js index 381f000..bb06f35 100644 --- a/src/window.js +++ b/src/window.js @@ -5,6 +5,8 @@ import Gio from "gi://Gio"; import Editor from "./editor.js"; import { relativePath } from "./util.js"; +import { parse } from "./scm.js"; +import wordwrap from "./wordwrap.js"; export default function Window({ application, @@ -13,12 +15,28 @@ export default function Window({ type, readonly, }) { - const builder = Gtk.Builder.new_from_file(relativePath("./window.ui")); + let parsed = {}; + try { + parsed = parse(commitMessage, type); + } catch (err) { + if (__DEV__) { + logError(err); + } + } + const builder = Gtk.Builder.new_from_file(relativePath("./window.ui")); const window = builder.get_object("window"); const cancelButton = builder.get_object("cancelButton"); const commitButton = builder.get_object("commitButton"); + const { buffer, source_view } = Editor({ + builder, + commitButton, + type, + window, + parsed, + }); + window.set_application(application); const cancelAction = new Gio.SimpleAction({ @@ -26,7 +44,8 @@ export default function Window({ parameter_type: null, }); cancelAction.connect("activate", () => { - save({ file, application, value: "", readonly }); + log(parsed); + save({ file, window, value: "", readonly }); }); window.add_action(cancelAction); @@ -35,33 +54,31 @@ export default function Window({ parameter_type: null, }); commitAction.connect("activate", () => { - const value = buffer.text; - save({ file, application, value, readonly }); + const { text } = buffer; + const value = parsed.wrap ? wordwrap(0, 75, { mode: "hard" })(text) : text; + save({ + file, + window, + value, + readonly, + }); }); window.add_action(commitAction); - const { buffer, source_view } = Editor({ - builder, - commitButton, - type, - commitMessage, - window, - }); - // https://github.com/sonnyp/Commit/issues/33 window.set_focus(source_view); return { window, cancelButton, commitButton, buffer }; } -function save({ file, value, application, readonly }) { +function save({ file, value, window, readonly }) { if (!readonly) { try { GLib.file_set_contents(file.get_path(), value); } catch (err) { - printerr(err); + logError(err); } } - application.quit(); + window.close(); } diff --git a/src/wordwrap.js b/src/wordwrap.js new file mode 100644 index 0000000..65f1821 --- /dev/null +++ b/src/wordwrap.js @@ -0,0 +1,89 @@ +// This software is released under the MIT license: + +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +export default function wordwrap (start, stop, params) { + if (typeof start === 'object') { + params = start; + start = params.start; + stop = params.stop; + } + + if (typeof stop === 'object') { + params = stop; + start = start || params.start; + stop = undefined; + } + + if (!stop) { + stop = start; + start = 0; + } + + if (!params) params = {}; + var mode = params.mode || 'soft'; + var re = mode === 'hard' ? /\b/ : /(\S+\s+)/; + + return function (text) { + var chunks = text.toString() + .split(re) + .reduce(function (acc, x) { + if (mode === 'hard') { + for (var i = 0; i < x.length; i += stop - start) { + acc.push(x.slice(i, i + stop - start)); + } + } + else acc.push(x) + return acc; + }, []) + ; + + return chunks.reduce(function (lines, rawChunk) { + if (rawChunk === '') return lines; + + var chunk = rawChunk.replace(/\t/g, ' '); + + var i = lines.length - 1; + if (lines[i].length + chunk.length > stop) { + lines[i] = lines[i].replace(/\s+$/, ''); + + chunk.split(/\n/).forEach(function (c) { + lines.push( + new Array(start + 1).join(' ') + + c.replace(/^\s+/, '') + ); + }); + } + else if (chunk.match(/\n/)) { + var xs = chunk.split(/\n/); + lines[i] += xs.shift(); + xs.forEach(function (c) { + lines.push( + new Array(start + 1).join(' ') + + c.replace(/^\s+/, '') + ); + }); + } + else { + lines[i] += chunk; + } + + return lines; + }, [ new Array(start + 1).join(' ') ]).join('\n'); + }; +};