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

Support system/user default clone editors #22378

Closed
wants to merge 16 commits into from
16 changes: 11 additions & 5 deletions models/user/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/cache"
setting_module "code.gitea.io/gitea/modules/setting"

"xorm.io/builder"
)
Expand Down Expand Up @@ -149,16 +150,21 @@ func DeleteUserSetting(userID int64, key string) error {
}

// SetUserSetting updates a users' setting for a specific key
func SetUserSetting(userID int64, key, value string) error {
func SetUserSetting(uid int64, key, value string) error {
if err := validateUserSettingKey(key); err != nil {
return err
}

_, err := cache.GetString(genSettingCacheKey(userID, key), func() (string, error) {
return value, upsertUserSettingValue(userID, key, value)
})
if err := upsertUserSettingValue(uid, strings.ToLower(key), value); err != nil {
return err
}

return err
cc := cache.GetCache()
if cc != nil {
return cc.Put(genSettingCacheKey(uid, key), value, setting_module.CacheService.TTLSeconds())
}

return nil
}

func upsertUserSettingValue(userID int64, key, value string) error {
Expand Down
7 changes: 7 additions & 0 deletions modules/context/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
asymkey_service "code.gitea.io/gitea/services/asymkey"
"code.gitea.io/gitea/services/dev"

"github.com/editorconfig/editorconfig-core-go/v2"
)
Expand Down Expand Up @@ -587,6 +588,12 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
}
ctx.Data["CloneButtonShowHTTPS"] = cloneButtonShowHTTPS
ctx.Data["CloneButtonShowSSH"] = cloneButtonShowSSH
editors, err := dev.GetUserDefaultEditorsWithFallback(ctx.Doer)
if err != nil {
ctx.ServerError("dev.GetDefaultEditor", err)
return
}
ctx.Data["CloneEditors"] = editors
ctx.Data["CloneButtonOriginLink"] = ctx.Data["RepoCloneLink"] // it may be rewritten to the WikiCloneLink by the router middleware

ctx.Data["RepoSearchEnabled"] = setting.Indexer.RepoIndexerEnabled
Expand Down
6 changes: 6 additions & 0 deletions modules/templates/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ func NewFuncMap() []template.FuncMap {
},
"Safe": Safe,
"SafeJS": SafeJS,
"SafeURL": SafeURL,
"JSEscape": JSEscape,
"Str2html": Str2html,
"TimeSince": timeutil.TimeSince,
Expand Down Expand Up @@ -664,6 +665,11 @@ func Safe(raw string) template.HTML {
return template.HTML(raw)
}

// SafeURL render raw as URL
func SafeURL(raw string) template.URL {
return template.URL(raw)
}

// SafeJS renders raw as JS
func SafeJS(raw string) template.JS {
return template.JS(raw)
Expand Down
16 changes: 16 additions & 0 deletions modules/util/slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

package util

import "strings"

// RemoveIDFromList removes the given ID from the slice, if found.
// It does not preserve order, and assumes the ID is unique.
func RemoveIDFromList(list []int64, id int64) ([]int64, bool) {
Expand All @@ -15,3 +17,17 @@ func RemoveIDFromList(list []int64, id int64) ([]int64, bool) {
}
return list, false
}

func SplitStringWithTrim(s, sep string) []string {
if len(s) == 0 {
return nil
}
var result []string
for _, word := range strings.Split(s, sep) {
word = strings.TrimSpace(word)
if len(word) > 0 {
result = append(result, word)
}
}
return result
}
6 changes: 5 additions & 1 deletion options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -856,7 +856,7 @@ already_forked = You've already forked %s
fork_to_different_account = Fork to a different account
fork_visibility_helper = The visibility of a forked repository cannot be changed.
use_template = Use this template
clone_in_vsc = Clone in VS Code
clone_in_editor = Clone in %s
download_zip = Download ZIP
download_tar = Download TAR.GZ
download_bundle = Download BUNDLE
Expand Down Expand Up @@ -2911,6 +2911,10 @@ config.xorm_log_sql = Log SQL
config.get_setting_failed = Get setting %s failed
config.set_setting_failed = Set setting %s failed

config.dev_config = Development
config.dev_default_editors = Default Editors
config.dev_default_editors_desc = Chose default editors
lunny marked this conversation as resolved.
Show resolved Hide resolved

monitor.cron = Cron Tasks
monitor.name = Name
monitor.schedule = Schedule
Expand Down
1 change: 1 addition & 0 deletions public/img/svg/gitea-idea.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions public/img/svg/gitea-vscodium.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions routers/web/admin/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/dev"
"code.gitea.io/gitea/services/mailer"

"gitea.com/go-chi/session"
Expand Down Expand Up @@ -187,6 +188,20 @@ func Config(ctx *context.Context) {
ctx.Data["EnableXORMLog"] = setting.EnableXORMLog
ctx.Data["LogSQL"] = setting.Database.LogSQL

editors := dev.GetEditors()

defaultEditorS := systemSettings.Get(dev.KeyDevDefaultEditors)
if defaultEditorS.SettingValue == "" {
defaultEditorS = system_model.Setting{
SettingKey: dev.KeyDevDefaultEditors,
SettingValue: dev.DefaultEditorsNames(),
}
}

ctx.Data["DevEditors"] = editors
ctx.Data["DevDefaultEditorNames"] = defaultEditorS.SettingValue
ctx.Data["DevDefaultEditorVersion"] = defaultEditorS.Version

ctx.HTML(http.StatusOK, tplConfig)
}

Expand Down
43 changes: 43 additions & 0 deletions routers/web/user/setting/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/services/agit"
"code.gitea.io/gitea/services/dev"
"code.gitea.io/gitea/services/forms"
container_service "code.gitea.io/gitea/services/packages/container"
user_service "code.gitea.io/gitea/services/user"
Expand Down Expand Up @@ -375,9 +376,51 @@ func Appearance(ctx *context.Context) {
return forms.IsUserHiddenCommentTypeGroupChecked(commentTypeGroup, hiddenCommentTypes)
}

editors := dev.GetEditors()

myDefaultEditors, err := dev.GetUserDefaultEditorsWithFallback(ctx.Doer)
if err != nil {
ctx.ServerError("dev.GetEditors", err)
return
}

var myEditorNames string
for i, editor := range myDefaultEditors {
if i > 0 {
myEditorNames += ","
}
myEditorNames += editor.Name
}

ctx.Data["DevEditors"] = editors
ctx.Data["DevDefaultEditorNames"] = myEditorNames

ctx.HTML(http.StatusOK, tplSettingsAppearance)
}

func ChangeConfig(ctx *context.Context) {
key := strings.TrimSpace(ctx.FormString("key"))
if key == "" {
ctx.JSON(http.StatusOK, map[string]string{
"redirect": ctx.Req.URL.String(),
})
return
}
value := ctx.FormString("value")

if err := user_model.SetUserSetting(ctx.Doer.ID, key, value); err != nil {
log.Error("set setting failed: %v", err)
ctx.JSON(http.StatusOK, map[string]string{
"err": ctx.Tr("admin.config.set_setting_failed", key),
})
return
}

ctx.JSON(http.StatusOK, map[string]interface{}{
"version": 1,
})
}

// UpdateUIThemePost is used to update users' specific theme
func UpdateUIThemePost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.UpdateThemeForm)
Expand Down
8 changes: 8 additions & 0 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"code.gitea.io/gitea/routers/web/user/setting/security"
auth_service "code.gitea.io/gitea/services/auth"
context_service "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/dev"
"code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/lfs"

Expand Down Expand Up @@ -404,6 +405,7 @@ func RegisterRoutes(m *web.Route) {
m.Group("/user/settings", func() {
m.Get("", user_setting.Profile)
m.Post("", web.Bind(forms.UpdateProfileForm{}), user_setting.ProfilePost)
m.Post("/config", user_setting.ChangeConfig)
m.Get("/change_password", auth.MustChangePassword)
m.Post("/change_password", web.Bind(forms.MustChangePasswordForm{}), auth.MustChangePasswordPost)
m.Post("/avatar", web.Bind(forms.AvatarForm{}), user_setting.AvatarPost)
Expand Down Expand Up @@ -1203,6 +1205,12 @@ func RegisterRoutes(m *web.Route) {
m.Get("/commit/{sha:[a-f0-9]{7,40}}.{ext:patch|diff}", repo.RawDiff)
}, repo.MustEnableWiki, func(ctx *context.Context) {
ctx.Data["PageIsWiki"] = true
editors, err := dev.GetUserDefaultEditorsWithFallback(ctx.Doer)
if err != nil {
ctx.ServerError("dev.GetDefaultEditor", err)
return
}
ctx.Data["CloneEditors"] = editors
ctx.Data["CloneButtonOriginLink"] = ctx.Repo.Repository.WikiCloneLink()
})

Expand Down
142 changes: 142 additions & 0 deletions services/dev/editor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
lunny marked this conversation as resolved.
Show resolved Hide resolved
// SPDX-License-Identifier: MIT

package dev

import (
"html/template"
"strings"

"code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
)

const KeyDevDefaultEditors = "dev.default_editors"

type Editor struct {
Name string
URL string
Icon string
}

func (e *Editor) RenderURL(repoURL string) template.URL {
return template.URL(strings.ReplaceAll(e.URL, "${repo_url}", repoURL))
}

var defaultEditors = []Editor{
{
Name: "VS Code",
URL: "vscode://vscode.git/clone?url=${repo_url}",
Icon: `gitea-vscode`,
},
{
Name: "VSCodium",
URL: "vscodium://vscode.git/clone?url=${repo_url}",
Icon: `gitea-vscodium`,
},
{
Name: "IDEA",
URL: "jetbrains://idea/checkout/git?idea.required.plugins.id=Git4Idea&checkout.repo=${repo_url}",
Icon: `gitea-idea`,
},
}

func GetEditorByName(name string) *Editor {
for _, editor := range defaultEditors {
if editor.Name == name {
return &editor
}
}
return nil
}

func GetEditorsByNames(names []string) []*Editor {
editors := make([]*Editor, 0, len(names))
for _, name := range names {
if editor := GetEditorByName(name); editor != nil {
editors = append(editors, editor)
} else {
log.Error("Unknown editor: %s", name)
}
}
return editors
}

// GetEditors returns all editors
func GetEditors() []Editor {
return defaultEditors
}

func DefaultEditorsNames() string {
return defaultEditors[0].Name
}

func GetDefaultEditors() ([]*Editor, error) {
defaultNames, err := system.GetSetting(KeyDevDefaultEditors)
if err != nil && !system.IsErrSettingIsNotExist(err) {
return nil, err
}
names := strings.Split(defaultNames, ",")
return GetEditorsByNames(names), nil
}

func SetDefaultEditors(names []string) error {
var validateNames []string
for _, name := range names {
if editor := GetEditorByName(name); editor != nil {
validateNames = append(validateNames, name)
}
}

return system.SetSetting(&system.Setting{
SettingKey: KeyDevDefaultEditors,
SettingValue: strings.Join(validateNames, ","),
})
}

type ErrUnknownEditor struct {
editorName string
}

func (e ErrUnknownEditor) Error() string {
return "Unknown editor: " + e.editorName
}

func GetUserDefaultEditors(userID int64) ([]*Editor, error) {
defaultNames, err := user_model.GetSetting(userID, KeyDevDefaultEditors)
if err != nil {
return nil, err
}
names := strings.Split(defaultNames, ",")
return GetEditorsByNames(names), nil
}

func SetUserDefaultEditors(userID int64, names []string) error {
var validateNames []string
for _, name := range names {
if editor := GetEditorByName(name); editor != nil {
validateNames = append(validateNames, name)
}
}
return user_model.SetUserSetting(userID, KeyDevDefaultEditors, strings.Join(validateNames, ","))
}

func GetUserDefaultEditorsWithFallback(user *user_model.User) ([]*Editor, error) {
if user == nil || user.ID <= 0 {
return GetDefaultEditors()
}
editor, err := GetUserDefaultEditors(user.ID)
if err == nil {
return editor, nil
}

if theErr, ok := err.(ErrUnknownEditor); ok {
log.Error("Unknown editor for user %d: %s, fallback to system default", user.ID, theErr.editorName)
return GetDefaultEditors()
}
if user_model.IsErrUserSettingIsNotExist(err) {
return GetDefaultEditors()
}
return nil, err
}
Loading