CSS : margin, flex et sélecteur :only-child

AJA qu’il existait un lien et des règles spécifiques d’interaction entre display: flex; et margin:auto; en CSS - alors qu’a priori on pourrait penser que ça ne va pas du tout fonctionner ensemble. Et puis JA aussi qu’il existait un selecteur css :only-child, ce qui est pratique mais plus anecdotique.

Donnons-nous un exemple concret pour mettre tout cela en pratique.

Supposons que vous voulions créer un footer de page contenant des boutons “Previous” et “Next”, par exemple pour une appli avec un wizard de création d’objet en plusieurs étapes.
Je veux afficher la bouton “Previous” complètement à gauche, et le bouton “Next” complètement à droite, comme ceci :

Pour cela, on utilise la structure HTML suivante (simplifiée).

1
2
3
4
5
6
7
<div class="container">
<div class="panel"></div>
<div class="panel footer">
<input type="button" value="Previous" />
<input type="button" value="Next" />
</div>
</div>

Et le css suivant pour le footer :

1
2
3
4
5
6
7
8
.footer {
display: flex;
justify-content: space-between;

border-bottom-right-radius: 10px;
border-bottom-left-radius: 10px;
border-top: 1px solid #255f7e;
}

Super !

Sauf qu’un problème se pose pour la première étape, dans laquelle il n’y a pas de bouton “Previous”.

1
2
3
4
5
6
7
<div class="container">
<div class="panel"></div>
<div class="panel footer">
<!-- <input type="button" value="Previous" /> -->
<input type="button" value="Next" />
</div>
</div>

Mon bouton “Next” se retrouve à gauche à cause du comportement par défaut de flex et du justify-content: space-between; :

Pour régler ce problème, on va utiliser la capacité de margin combinée avec flex.

Il faut savoir que l’utilisation d’une règle margin: auto; sur un item se trouvant dans un context “flex” va étendre la marge de cet item pour occuper toute la place disponible dans le conteneur, dans la direction spécifiée.
Cela veut dire que si on applique margin-left: auto; sur notre bouton “Next”, une marge de la largeur restante du conteneur va être placée à sa gauche, revenant à le placer sur la droite :

1
2
3
4
5
6
7
8
9
input[type=button] {
border: none;
box-shadow: 3px 3px 0px #255f7e;
border-radius: 3px;
background: #00a8ff;
color: white;

margin-left: auto;
}

C’est bien pratique ! Cela permet aussi de centrer des éléments, car si on ne donne pas de direction, la marge va être partagée dans les directions opposables et centrer l’élément naturellement. Plus d’infos ici.

Mais si on remet notre bouton “Previous”, on obtient un nouveau probleme - les deux boutons partagent l’espace horizontal disponible du conteneur pour leur marge de gauche :

Il suffit donc d’appliquer le selecteur CSS :only-child et de n’appliquer la marge que là :

1
2
3
input[type=button]:only-child {
margin-left: auto;
}

Et voilà !😃

Et ne me remerciez pas pour la beauté du design, c’est cadeau !

Le code-pen pour faire joujou

JS: Syntaxe cryptique pour supprimer une propriété d'un objet

AJA qu’il existait une manière assez intriguante de retirer une propriété d’un objet en JS :

1
2
3
4
5
let obj = { prop1: 'hey', prop2: 'ho' };
// ...
obj = (({ prop2, ...o }) => o)(obj);
// équivaut à
delete obj.prop2;

Si on regarde bien ce “one-liner”, on a une lambda qui pourrait être extraite comme ceci :

1
2
3
4
5
6
7
8
let obj = { prop1: 'hey', prop2: 'ho' };
// ...
obj = prop2Remover(obj);

function prop2Remover({ prop2, ...o }) {
return o;
}

On voit une utilisation plutôt élégante et combinée de l’object destructuring et de la syntaxe spread, que l’on pourrait encore décortiquer davantage :

1
2
3
4
5
6
7
8
9
10
11
let obj = { prop1: 'hey', prop2: 'ho' };
// ...
obj = prop2Remover(obj);

function prop2Remover(obj) {
// le pattern matching va chercher la propriété prop2 dans obj
// et le spread operator va assigner tout le reste de obj à o !
const { prop2, ...o } = obj;
return o;
}

Cette syntaxe peut paraître un peu cuistre par rapport au delete, mais on peut quand même y trouver un intérêt. Par exemple si l’on veut assigner l’objet “nettoyé” en une seule instruction. De plus, l’opérateur spread effectue une nouvelle copie ce qui n’est pas à négliger dans certains contextes par exemple vuex ou redux.

1
2
3
4
5
6
7
let obj = { prop1: 'hey', prop2: 'ho' };
// une seule instruction pour 'clean up' l'objet obj et l'assigner dans otherObj
const otherObj = {
otherProp1: 'lol',
otherProp2: (({ prop2, ...o }) => o)(obj)
}

Mais bon, autrement, c’est vraiment pour frimer ! 😃

C#: astuce avec foreach

AJA que l’on pouvait obtenir l’index dans une boucle foreach d’une façon assez pratique sans avoir à incrémenter un compteur à la main (ce qui est vulgaire 😏). La fonction Select de Linq possède en effet un overload qui prend une lambda prenant en charge l’index de boucle.

Avant C#7:

1
2
3
foreach(var item in inputList.Select((wheel, i) => new { wheel, i})) {
Console.WriteLine($"Wheel n°{item.i + 1} : {item.wheel.friction}");
}

Mais ce n’est pas super car on se trimballe un item dont on se passerait bien.

Heureusement, depuis C#7 (avec la déconstruction de Tuples), on peut directement assigner les variables :

1
2
3
foreach((Wheel wheel, int i) in inputList.Select((value, i) => (wheel, i))) {
Console.WriteLine($"Numero {i + 1} : {wheel.friction}");
}

Attention cependant, ça peut vite nuire à la lisibilité du code car on est forcé de spécifier les types et l’instruction foreach devient vite indigeste sur une ligne.

SQL - Window functions et aggregation functions

AJA que je n’avais rien écouté pendant le cours sur les bases de données 🤨 : je n’avais jamais entendu parler des “window functions”, qui sont un peu les cousines des aggregation functions. EN SQL il est courant d’utiliser des aggregation functions par exemple pour compter les lignes d’une table avec la méthode COUNT().

Les window function permettent de faire à peu près la même chose, sauf qu’elles agissent a posteriori et peuvent permettre d’éviter de tomber dans l’utilisation de requêtes imbriquées qui sont plutôt moins performantes.

Dans ce billet, pour introduire la notion de window function, je vais présenter un problème que j’ai rencontré dans un projet perso. Je vais tout d’abord décrire le jeu de données utilisé, mon approche initiale et l’approche qui m’a été soufflée par un chic type sur stackoverflow. Malgré toute la justesse de sa réponse, elle était quand même plutôt expédiée (note : insérer ici un commentaire sur la course à la réputation dans le site stackoverflow) ; cela m’a amené à mener ma petit enquête et ce billet est né.

Le contexte

Notre blog de vidéos rigolotes nécéssite trois tables SQL afin de représenter le fait que les posts créés sont associés à des tags.

Par exemple, pour créer un billet de blog “Chicken flying over a cucumber”, je vais créer une entrée dans la table posts, trois entrées dans la table tags (“chicken”, “funny”, “cucumber”) et trois entrées dans une table d’association poststags connectant le tout.

ℹ️ Une table d’association est ici nécéssaire pour représenter une relation de type “many-to-many” entre posts et tags car un post peut avoir plusieurs tags et un tag peut décorer plusieurs posts.

Pendant plusieurs mois, je remplis mon blog de supers vidéos, tout va bien même si mes choix éditoriaux ne m’amènent que peu de visites mais ce n’est pas le sujet de cet article.

Plus tard dans le développement de mon blog, je voudrais faire des statistiques, par exemple “Quel est le nombre d’articles de blog ne comportant aucun chat et n’étant pas drôle non plus ? “, autrement dit, il s’agit de compter le nombre de posts n’ayant aucune association avec les tags “cat” et “funny”.

Mon objectif est donc de pouvoir compter le nombre de posts ne contenant pas certains tags.

Les données

Posts

Id Title Content
1 Chicken flying over a cucumber
2 Cat eating a cucumber
3 Cucumber wants to take revenge
4 Snail sad because no cucumber left

Tags

Id Label
1 cat
2 funny
3 snail
4 cucumber
5 chicken

PostsTags

Id PostId TagId
1 1 2
2 1 4
3 1 5
4 2 1
5 2 2
6 2 4
7 3 1
8 3 4
9 4 3

On voit par exemple que “Cat eating a cucumber” est une vidéo avec les tags “cat”, “cucumber” et “funny” (oui, c’est rigolo).

Résolution du problème avec l’approche “SQL newbie” (requête imbriquée)

Je rappelle mon problème : je cherche à compter le nombre de posts n’ayant ni le tag “cat” ni le tag “funny”.

La première considération, c’est que l’ensemble de l’information nécéssaire pour répondre à cette question est contenue dans la table PostsTags. Je n’ai pas besoin de considérer les autres tables pour répondre.

Première étape, isoler les posts contenant soit un tag “cat” soit un tag “funny” (il s’agit bien d’un OR ici, car je veux à terme la négation de ce jeu de données, qui sera un NAND entre tous les tags à exclure). Pour cela on utilise l’opérateur in.

Voici la requête :

1
2
3
4
select postid 
from poststags
where TagId in (1, 2)
group by postid

Ce qui donne :

Id
1
2
3

En effet, tous les posts parlent de chat ou sont drôles, sauf le post 4.

Ensuite, c’est tout simple en utilisant not in (c’est à dire une requête imbriquée), je n’ai plus qu’à compter l’ensemble des posts ne se trouvant pas dans ce premier résultat :

1
2
3
4
5
6
7
select count(p.Id)
from posts p
where p.Id not in (
select postid
from poststags
where TagId in (1, 2)
group by postid)

Ça fonctionne, mais quand la requête imbriquée a beaucoup de résultats, la performance peut en prendre un coup.

Résolution du problème avec l’approche “SQL professional” (“aggregation function” et “window function”)

De la même manière, je vais procéder par étapes simples.

  1. Tout d’abord, on va chercher à obtenir pour chaque association dans posttags si le tag est “cat” ou “funny”. Pour cela on va encore utiliser l’opérateur in, mais cette fois-ci dans la clause select. L’idée est de préparer le terrain pour faire une somme des booléens obtenus.
1
2
select postId, TagId in (1, 2) as tagIsCatOrFunny
from poststags
PostId tagIsCatOrFunny
1 1
1 0
1 0
2 1
2 1
2 0
3 1
3 0
4 0
  1. On voit que chaque résultat précédent représente une association post-tag et qu’il va falloir grouper par postid pour aggréger intelligemment la valeur tagIsCatOrFunny : on va utiliser group by et la fonction d’aggrégation SUM pour obtenir pour chaque postid la somme du nombre de fois que le tag “chat” ou “funny” est représenté pour un post. Il est à noter ici que SUM pourrait être utilisé sans le group by mais cela nous retournerait une ligne unique avec le nombre total d’occurence d’associations dont le tag est “cat” ou “funny”, ce qui ne nous intéresse pas.
1
2
3
select postid, SUM(tagid IN (1, 2)) as occurencesOfCatOrFunny
from poststags
group by postid
PostId tagIsCatOrFunny
1 1
2 2
3 1
4 0

On constate bien que par exemple, le post 2 “Cat eating a cucumber” est bien à la fois “funny” et contenant un chat.

  1. On sait qu’à terme, on veut le nombre de fois que cette table contient la valeur 0 pour tagIsCatOrFunny, ajoutons donc simplement = 0 à notre colonne de somme :
1
2
3
select postid, SUM(tagid IN (1, 2)) = 0 as hasNoOccurencesOfCatOrFunny
from poststags
group by postid
PostId tagIsCatOrFunny
1 0
2 0
3 0
4 1
  1. Il ne “reste plus qu’à” faire la somme de l’ensemble des valeurs de cette colonne pour obtenir le nombre final. Ça se complique un peu car il n’est pas permis d’aggréger directement le résultat d’une aggrégation en SQL (c’est pour cela qu’on voit très souvent des fonctions imbriquées pour traiter ce genre de situation). Or les window functions peuvent venir à la rescousse, car elles sont calculées de façon indépendante et après la ou les fonctions d’aggrégation. Le marqueur d’une window function est l’utilisation du mot clef OVER :
1
2
3
select postId, SUM(SUM(tagid IN (1, 2)) = 0) OVER()
from poststags
group by postid

Sans rentrer trop dans les détails (voir les liens en fin d’article pour plus d’info), il faut savoir que les window functions sont assez proches des fonctions d’aggrégation, mais une grande différence est qu’elles maintiennent les lignes sur lesquelles elles agissent (les fonctions d’aggrégation vont au contraire combiner les lignes pour chaque groupe). Par ailleurs, la définition de la “window frame” dans le OVER() détermine sur quel set de lignes le fonction s’applique, en l’occurence toutes les lignes ici, car la clause OVER() est vide.

On obtient le résultat suivant :

PostId targetValue
1 1
2 1
3 1
4 1

Pour chaque ligne de la table intermédiaire précédente, on a calculé “la somme des sommes”. Et comme la window function maintient les lignes de la table sur laquelle elle est appliquée, on obtient le résultat autant de fois qu’il y a de posts.

  1. Sachant que chaque ligne aura la même valeur de somme, on peut simplement ajouter distinct pour récupérer une ligne unique (on en profite pour retirer le postId qui n’aura plus de sens) :
1
2
3
select distinct SUM(SUM(tagid IN (1, 2)) = 0)  OVER() as finalResult
from poststags
group by postid

Et voilà, on obtient notre résultat sans utiliser de requête imbriquée !

targetValue
1

Il ne me reste plus qu’à mesurer à quel point cette technique est plus performante que l’utilisation de requête imbriquées mais cet article est déjà long !


Voici une page expliquant très clairement la différence entre les aggregation functions et les window functions

Enfin, une vidéo de chat mangeant un concombre

C# : la base de l'asynchrone

AJA un peu plus précisément comment fonctionnait le système de gestion des tâches asynchrones en C#. J’ai découvert une page de documentation microsoft vraiment bien écrite pour découvrir (ou redécouvrir) le sujet en détail.

L’article est fondé sur l’analogie de la préparation d’un petit déjeuner : certaines des tâches ne nécessitent pas la présence constante d’un cuistot, par exemple pendant que les tartines cuisent dans le grille-pain. On comprend intuitivement que personne de sain d’esprit ne resterait planté devant le grille-pain alors qu’il est possible de cuire les oeufs et de verser le jus d’orange en attendant. On parle de tâche asynchrone.

En .net, ce concept de tâche asynchrone est implémenté à l’aide de l’objet Task. Il représente non pas l’action de faire quelque chose (comme le ferait une fonction quelconque) mais plutôt le fait qu’une action est en cours, il s’agit donc d’une représentation de son status. Cela correspond pratiquement à la notion de promesse en JS.

Concrètement, il peut s’agir d’un appel à un service externe. Voici un exemple particulièrement utile :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static async Task<ImportantData> GetDataAsync(int taskNumber)
{
return await Task.Run(() =>
{
Console.WriteLine("\tLong task start " + taskNumber);
Thread.Sleep(1000);
Console.WriteLine("\tTick " + taskNumber);
Thread.Sleep(1000);
Console.WriteLine("\tTick " + taskNumber);
Thread.Sleep(1000);
Console.WriteLine("\tLong task finish " + taskNumber);
return new ImportantData();
});
}

Un utilisateur de cette méthode asynchrone pourra adopter plusieurs stratégies, plus ou moins efficaces.

Tout d’abord il est possible d’appeler cette méthode sans syntaxe particulière comme ceci :

1
2
3
4
5
6
7
static async Task Main(string[] args)
{
Console.WriteLine("Program start");
var gettingImportantData1 = SlowService.GetDataAsync(1);
Console.WriteLine("Synchronous task");
Console.WriteLine("Program end");
}

Ici, l’éxecution ne va pas se bloquer pour attendre GetDataAsync. Cela veut dire que le programme va se terminer avant même que la tâche ait effectué son premier “tick” :

1
2
3
4
Program start
Long task start 1
Synchronous task
Program end

Autre possibilité : attendre.

1
2
3
4
5
6
7
static async Task Main(string[] args)
{
Console.WriteLine("Program start");
var gettingImportantData1 = await SlowService.GetDataAsync(1);
Console.WriteLine("Synchronous task");
Console.WriteLine("Program end");
}

Dans ce cas, le programme va suspendre son execution et attendre la fin de la tâche longue avant de reprendre : on note cependant que la tâche synchrône est toujours effectuée après la tâche longue.

1
2
3
4
5
6
7
Program start
Long task start 1
Tick 1
Tick 1
Long task finish 1
Synchronous task
Program end

⚠️ Ici, il est important de noter que pendant l’exécution de la tâche longue (avec await), la CLR relâche le thread. Dans un contexte web ou client WPF par exemple, cela signifie que d’autres actions utilisateurs pourront être prises en charge par ce thread en attendant. Ce n’est pas du tout négligeable pour la scalabilité.

Toujours est-il que notre programme en tant que tel n’est pas très optimisé : en effet, je préfèrerais largement gagner du temps sur l’éxecution de ma tâche synchrone et l’effectuer en parallèle de la tâche longue. Et qui sait, peut-être voudrais-je appeler deux fois ma tâche longue avec d’autres paramètres !

C’est très simple, il suffit d’ordonnancer les appels de façon intuitive par rapport aux tâches :

1
2
3
4
5
6
7
8
static async Task Main(string[] args)
{
Console.WriteLine("Program start");
var gettingImportantData1 = SlowService.GetDataAsync(1);
Console.WriteLine("Synchronous task");
await gettingImportantData1;
Console.WriteLine("Program end");
}

On voit ici qu’il y a une différence entre démarrer une tâche asynchrône, et forcer l’attente de la fin de son exécution. L’objet Task permet de garder la main sur cela et d’avoir enfin un programme performant :

1
2
3
4
5
6
7
Program start
Long task start 1
Synchronous task
Tick 1
Tick 1
Long task finish 1
Program end

Et comme je l’avais envisagé, je peux appeler ma tâche longue plusieurs fois en parallèle :

1
2
3
4
5
6
7
8
9
10
static async Task Main(string[] args)
{
Console.WriteLine("Program start");
var gettingImportantData1 = SlowService.GetDataAsync(1);
var gettingImportantData2 = SlowService.GetDataAsync(2);
Console.WriteLine("Synchronous task");
await gettingImportantData1;
await gettingImportantData2;
Console.WriteLine("Program end");
}

Et ainsi obtenir le parallelisme attendu :

1
2
3
4
5
6
7
8
9
10
11
Program start
Synchronous task
Long task start 1
Long task start 2
Tick 1
Tick 2
Tick 1
Tick 2
Long task finish 1
Long task finish 2
Program end

HTML/JS : astuce pour générer un fichier téléchargeable depuis le browser

AJA que les hyperliens HTML pouvaient spécifier un attribut download permettant de forcer le téléchargement d’un fichier depuis un lien donné.

Certes, les navigateurs ont tendance à outrepasser ce comportement pour beaucoup de types de fichiers par défaut (ils vont par exemple afficher directement une image même si le lien a cet attribut).

Cependant, il est possible d’utiliser cette technique pour générer un téléchargement en créant un hyperlien à la volée (“sur la mouche”, comme on dit dans le milieu).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<html>
<button onclick="clickHandler()">Clique moi</button>
<script>
function clickHandler() {
const data =
"More than cleverness, we need kindness and gentleness. Charlie Chaplin";
const fileURL = window.URL.createObjectURL(new Blob([data]));

const fileLink = document.createElement("a");

fileLink.href = fileURL;
fileLink.setAttribute("download", "charlie.txt");
document.body.appendChild(fileLink);
fileLink.click();
document.body.removeChild(fileLink);
}
</script>
</html>

Si par exemple des données JSON sont retournées par une API, il est ainsi parfaitement possible de créer un fichier pour le rendre téléchargeable à partir de la chaîne de caractères.

Par ailleurs, on voit que la fonction utilise un Blob dont les mystères restent encore à découvrir 🙂.

En tout cas, c’est malin !

JS : une syntaxe marrante pour filtrer

AJA une syntaxe intéressante pour filtrer facilement un tableau en fonction de la “truthiness” de ses valeurs.

Imaginons le tableau suivant :

1
const myArray = ["mon", NaN, "beau", null, "tableau", ""];

Pour filtrer ce tableau, j’aurais utilisé le code suivant :

1
const cleanArray = myArray.filter((i) => i);

Car filter évalue la truthiness de la lambda pour chaque élément, cela fonctionne.

Or sur stackoverflow, j’ai lu la réponse suivante :

1
const cleanArray = myArray.filter(Boolean);

Comment ça ? 🤔 J’ai tout d’abord cru qu’il s’agissait d’une syntaxe bizarre que je ne connaissais pas.

Mais en fait, en JS, Boolean n’est pas seulement un type, c’est également un constructeur, ou encore une fonction. Et il se trouve que l’on peut construire un booléen en lui fournissant n’importe quoi en entrée, ce qui donnera à ce booléen la truthiness de la valeur fournie.

En somme, les trois syntaxes suivantes sont équivalentes :

1
2
3
const cleanArray1 = myArray.filter((i) => i);
const cleanArray2 = myArray.filter((i) => Boolean(i));
const cleanArray3 = myArray.filter(Boolean);

Après… Entre nous, à part pour briller en société, la syntaxe ne me parait pas beaucoup plus lisible, mais c’était marrant de découvrir cette manière de procéder.

CSS : comprendre les ellipses de texte

AJA comment fonctionnaient vraiment les ellipses de texte en CSS. A priori, on pourrait croire qu’il suffirait d’une ligne de CSS pour les mettre en place. En réalité, c’est un tout petit peu plus compliqué que ça.

En effet, il s’agit de spécifier au navigateur la chose suivante : “si ce texte est trop long, il ne doit pas dépasser de son conteneur et afficher trois petits points juste avant de dépasser”.

Tout d’abord, voyons comment le texte se comporte lorsqu’aucune règle particulière n’est appliquée :

On constate que, par défaut, le moteur CSS applique un “wrapping”, qui donne au texte la propriété d’aller automatiquement à la ligne lorsqu’il rencontre les bords du conteneur.

Or, notre objectif est que le texte n’aille pas à la ligne mais qu’il soit caché. Il faut donc tout d’abord désactiver ce “wrapping” :

1
white-space: nowrap;

Ensuite, il semble clair que tout ce qui dépasse doit être rabotté ! Cela n’est manifestement pas le cas par défaut, il y a donc une nouvelle règle CSS à ajouter à notre code :

1
2
white-space: nowrap;
overflow: hidden;

Apparement, le moteur CSS ne veut pas appliquer l’ellipse par défaut. En effet, son comportement par défaut est “clip”, ce qui signifie que le texte sera coupé sans autre forme de procès. C’est peu élégant. Appliquons notre ellipse ici :

1
2
3
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;

Et voilà ! 😅

Ce qu’il faut retenir de tout ça, c’est que cette dernière règle seule ne se “met en route” que dans le contexte où le texte va sortir de son conteneur (white-space: nowrap;) et où ce dépassement est caché (overflow: hidden).

Le code des exemples se trouve dans ce codepen.

Sinon, vous trouverez plus de détail sur text-overflow dans l’excellente documentation MDN Web Docs.

Edition du 14 Mai 2022 : un super article est sorti sur codersblock.com qui rentre beaucoup plus dans le détail de comment couper du texte en css. Un doit-lire pour connaître le sujet sur le bout des doigts : https://codersblock.com/blog/deep-dive-into-text-wrapping-and-word-breaking/

Vim : plus de flexibilité avec vim-surround

AJA que le plugin Vim de VsCode proposait d’activer vim-surround, qui est à l’origine un plugin de Vim particulièrement pratique. Il fait économiser beaucoup de manipulations.

Voici un exemple de texte :

1
Texte à manipuler avec vim.

Sans vim-surround, si on veut mettre les mot manipuler avec vim entre guillemets, en supposant que le curseur se trouve au début de la ligne, la manipulation est la suivante :

1
wwi"<esc>3ea"

Avec vim-surround, cela devient plus idiomatique :

1
wwys3w"

Le principe est de n’écrire qu’une fois le caractère englobant, et de dire à vim-surround comment atteindre la fin de l’expression à entourer avec un déplacement (ici 3w)

1
ys3w"

pourrait être traduit en :

1
You Surround 3 Words with "

C’est déjà une belle économie. Mais je n’aime personnellement pas beaucoup compter les mots pour les mouvements car ce n’est pas très fiable dans du code source. J’utilise plutôt des sélections en mode visuel (potentiellement en utilisant la souris).

Heureusement, vim-surround permet d’être actionné à partir d’une sélection avec S (s majuscule) !

vim-surround fait selon moi vraiment la différence pour remplacer des caractères déjà existant.

Reprenons notre texte modifié :

1
Texte à "manipuler avec vim".

Si je veux remplacer les guillemets par des parenthèses, la manipulation sans vim-surround est encore une fois fastidieuse :

1
wwr(eeelr)

Avec vim-surround :

1
wwcs")

cs") pourrait être traduit en

1
Change Surround " to )

On obtient alors directement

1
Texte à (manipuler avec vim).

Plutôt pratique !

Le petit bonus sympa, c’est qu’en utilisant les caractères ouvrant ( ou [ dans l’expression de remplacement, vim-surround va automatiquement insérer des espaces à l’intérieur. Ainsi la commande suivante :

1
wwcs"(

produira le texte suivant :

1
Texte à ( manipuler avec vim ).

Vim, c’est la vie.

git add -N : marquer des fichiers pour ajout

AJA que l’on pouvait spécifier à git qu’on prévoyait d’ajouter des fichiers sans forcément les ajouter tout de suite à l’index !

Mais pourquoi voudrait-on faire ça ? Eh bien il peut être embêtant dans git de faire des différentiels lorsque certains fichiers viennent d’être rajoutés, car ceux-ci seront tout simplement ignorés par git tant qu’il n’aura pas été mis au courant que ces fichiers ont un rôle dans la base de code (pour autant que git sache, on pourrait très bien vouloir ajouter ces fichiers dans le .gitignore).

Jusqu’ici, je détournais ce comportement en ajoutant tous les fichiers avec

1
$ git add .

Ainsi, les nouveaux fichiers apparaissaient dans le diff-tool en utilisant le flag --staged :

1
$ git diff-tool --staged

Et je pouvais noter les fichiers que je voulais réellement inclure dans mon commit, avant de faire git reset… Bref, pas idéal. L’ennui avec cette approche, c’est que l’opération naturelle qui consiste à ajouter certains fichiers au fur à mesure de l’analyse du différentiel n’est plus possible car tous les fichiers sont déjà dans l’index…

Mais ça, c’était avant. Il est possible de spécifier à git que des fichiers vont être ajoutés, sans les ajouter concrètement tout de suite à l’index :

1
2
3
$ git add --intent-to-add .
ou
$ git add -N .

Les nouveaux fichiers ont alors bien une entrée dans l’index, sans contenu effectif : le diff-tool les verra sans qu’ils soient encore concrètement dans l’index. C’est du temps de gagné !

Pour plus d’info sur les options de git adddans la doc officielle ici.