I normally use assembler to supplement what the C compiler cannot do as well, or features of the assembly language it cant or wont implement when I want it too. Or places where I need performance either through knowledge of the data and instruction set or through knowledge of the hardware that the compiler does not know. Aligning something with a cache line, or knowledge of the width or nature of the data bus, a write buffer, etc. boot code for embedded, the main body of the code in C but some sections in assembler for above reasons. Sometimes the assembler is the abstraction layer for embedded and to re-compile the application on an operating system some other abstraction layer is used.
Some folks write in assembler for a whole project (today) and it seems to me more political than practical. To prove that it can be done, or a personal goal or scratch that needed to be itched. Like climbing a mountain because it is there, some do it for personal internal mental or physical gain and good for them, others do it strictly to brag and show off, shame on them. For big apps the compiler (today) is going to do a better job on average producing code than a human by hand, and at the end of the day that rtos or whatever you made can be compiled and run on many machines and not just the assembler for the one processor you chose.
I do a lot of embedded work and some of that is microcontrollers. I often choose (all) assembler for those programs for a number of reasons, the peripherals, registers and I/O on this microcontroller are not portable to some other microcontroller, so the non-portability of assembler is not a problem. Memory is often very limited and the instruction sets are often inefficient and not compiler friendly (doesnt mean there are no compilers). And most importantly the (open source/free) cross compilers are not written in such a way or maintained in such a way that they work year after year as the operating systems (Linux) change and improve, where the assemblers are often simpler and are more likely to continue to work year after year. Another reason is sometimes I bought that microcontroller simply to learn the instruction set and chip internals and how it might be better or worse than an other. And C calls to libraries in an SDK are not going to help me.
I was around back in the day when C was taking over from Pascal and the same question was asked and it could be shown that some could code and debug an application in assembler as well as another could code and debug in C in the about the same amount of time. Things were simpler then. In both worlds you are not creating everything from scratch, just like when you drive to and from work your feet and hands and arms have this memory and drive in autopilot, same boring road day after day, much of your programming is done in autopilot, you cut and paste or re-type or link to the same routines you have been polishing your whole career. In assembler you re-use the same favorite instructions and never use others, in C you use the same favorite variables or language features and never use others. It is the individual not the language.
So to answer your question. Macros and functions and reusing previously developed code or libraries, in the same way that you might use macros and functions or previously developed code or libraries today in a high level language. You were more attentive and careful of a programmer, high or low level, the resources (memory, disc, etc) were not what they are today, registers/variables were chosen carefully and sparingly. The C compilers were nowhere near as good, they barely compiled much less optimized. And just like any language or car or keyboard or mouse, the more you use it the more efficient you get at using it. An experienced assembler programmer can outproduce a beginner as a well as an experienced C programmer can outproduce a beginner. Whether the experienced assembler programmer can outproduce the C programmer depends as much on the individuals and the application, and is not all about the language.
Structs are a solution to save on typing for some languages, assembler lets you do the same base address + (index*struct size) + offset caluclation plus more. Some instruction sets make it that much easier, and some programmers might swap the multiply for a shift or a shift plus add. Or use a multiply accumulate.
Strings are where assembler really shines. The coolest assembler tricks are found in the string and copy functions in C libraries. If you are going all assembler you can choose to use the pascal length plus data or C go until you hit a zero or mix and match or make up your own. You may or may not be able to improve on the C library in this area, but non-optimized string code is equally trivial in C and assembler.
Call os/C calls from assembler. Bootup code often calls memcpy for .data and memset for .bss. If you are willing to call a C library you are probably in the "I use C for the whole application and assembler for a few rare instances" camp. The "I am writing this whole thing in assembler camp" is not going to take that shortcut, they wouldnt be programming in assembler if that was the case. Operating system calls that have to be made are made in either case, there is no way around it other than writing your operating system in assembler and doing what you want.
Unions are just re-use of the same memory space, you can do whatever and redefine any byte in memory as you see fit. Assebler does not tie your hands and make you walk and think a certain way like a high level language does.
Text editor or ide has nothing to do with the language, the individuals and their habits/preferences mostly. Sometimes the project or the company forces a tool to be used and that is the one you use or get another job or get promoted high enough you get to change the company policy. Text editors, tools, tabs, spaces, theses are very touchy subjects in a professional environment, it is like forcing everyone to drive the same brand and model of car or wear the same brand and model of shoe. It has nothing to do with the language. On the other hand there is the occasional processor that has only one tool in only one environment and if you choose to use that processor that is the one you use.
System engineering is important and often overlooked or done poorly. When it is done and done right then calling conventions between defined modules automatically fall out of the process. The decision to use the same convention everywhere or tune the convention to the modules on either side, is also part of that system engineering process. For assembler, today, you are more likely to find the C calling convention used or close to it with some modifications for performance (well I know that I dont need to preserve R4 for this function because I know everywhere I use this function I am never going to use R4) simply because it is well known and these days carefully designed and you never know when you may want to call this cool function you wrote from C or share with a C programmer buddy.
If you want to go way way back, you are talking about time shares or older, where the hardware either barely worked and you had tons of time to hand code your machine language bits before the machine was back up. And you had no terminal or keyboard anyway. Or a little newer you had a time share where you maybe got to run your program once a day or once every few days so you had tons of time to create more punch cards, and again you had no terminal or keyboard or text editor. Your high level language was probably fortran if anything. You were happy to have your program run for a bit before it crashed, and thoughts of how many thousands of lines of debugged code you could produce in a week had not been invented yet.